This commit is contained in:
TopchetoEU 2025-01-17 00:48:30 +02:00
parent c8d43d6ad5
commit 92d9c68d78
Signed by: topchetoeu
GPG Key ID: 6531B8583E5F6ED4
17 changed files with 654 additions and 2417 deletions

View File

@ -7,6 +7,8 @@ While reading this, you are strongly advised to have a JS console opened, so you
- Undefined and null - basically the same - Undefined and null - basically the same
- Booleans (true, false) - Booleans (true, false)
- Numbers (64 bit floats) - Numbers (64 bit floats)
- Symbols - unique and special "marker" GC-able values that contain nothing
- BigInts - integers with no size restrictions
- Strings - characters are of a 16-bit width, and may contain \0 - Strings - characters are of a 16-bit width, and may contain \0
- Objects - a string to value dictionary, with a prototype (later on) - Objects - a string to value dictionary, with a prototype (later on)
- Functions - an object that can be called, basically an object with a func ptr appended to it - Functions - an object that can be called, basically an object with a func ptr appended to it
@ -168,4 +170,89 @@ In short, arrays are nothing more than special-case objects. All arrays have the
Most sane engines of course define a special case object for arrays, which are usually backed by a linear buffer. When you try to grow the array by setting members out of its bounds, it will grow its buffer to accommodate its new members. When deleting a member (which will make the array sparse), engines usually take one of two routes: they either enter panic mode and convert the whole array to a normal object, or just set a special "flag" value in place of the empty value. Most sane engines of course define a special case object for arrays, which are usually backed by a linear buffer. When you try to grow the array by setting members out of its bounds, it will grow its buffer to accommodate its new members. When deleting a member (which will make the array sparse), engines usually take one of two routes: they either enter panic mode and convert the whole array to a normal object, or just set a special "flag" value in place of the empty value.
Other nasty things the user can do are: define a normal member (for example "pe6o") in the array, defining a property with the name "0" in an array and defining the member "0.5". The first two are handled by just having an underlying object on speed dial. The last example however is more interesting - in such cases, most engines will either, again, enter panic mode and revert to object mode, or just overlay the array with a backing object. Other nasty things the user can do are: define a normal member (for example "pe6o" or "0.5") in the array, defining a property (getter-setter member) with the name "0" in an array. The first two are handled by just having an underlying object on speed dial. The last example however is more interesting - in such cases, most engines will either, again, enter panic mode and revert to object mode, or just overlay the array with a backing object.
## 4. Functions
Functions are at the core of what makes the JS clock tick. As in any language, they are the storage of JavaScript code that gets executed when the function value gets invoked. But JS functions are objects as well. This means that you can work with a function as you would with any other regular object - define properties of it, freeze it, list its keys, etc.
However, a function wouldn't be a function without its ability to be called. In JS, functions can be called in two distinct ways:
- apply - a normal call, aka `my_func(a, b, c)`
- construct - a call with new, aka `new my_func(a, b, c)`
### Applying functions
When applying a function, you will pass all the arguments you put in the parens, but an implicit `this` argument will get passed, too. In most cases, `this` will be passed as `undefined`. After that, the function will consume the arguments, execute its body and return the result. Then the result will become the evaluated value of the call expression.
Now, the JS syntax allows for one special way of calling - a member call. This is achieved by calling a member expression: `a.b(c)`. In this case, the value of `a` will be passed as the implicit `this` argument of the call. This here leads to a lot of JS gotchas, mostly when trying to pass a method of an object as a value to somewhere else, when you get these `this is undefined` exceptions. This is easily resolved by calling `a.member.bind(a)`, which will produce a new function that when invoked will replace the implicit this argument with the passed value instead.
### Constructing functions
In JS, we achieve OOP by "simulating" a class with a function. When we call a function with the `new` prefix, we effectively call it in the special "construct" mode. In this mode, as the `this` argument, a special object is passed in that the function will modify. After the function evaluates, if its return value is a primitive, that special object is returned. Otherwise, the return value of the function becomes the evaluated value of the expression. In pseudocode, this is how a high level "construct" would look:
```js
function construct(func, ...args) {
const obj = {};
const res = func.apply(obj, args);
if (is_primitive(res)) return obj;
else return res;
}
```
### The Function.prototype member
This member is a special member of each function. It contains the object that will become the prototype (`__proto__`) of the newly created `this` for the constructed function. In it we define the instance methods, getters and setters of the class.
### The post ES6 construct method
ES6 introduced classes and inheritance, which led to the necessity of reworking the function model a little bit. In essence, the following was changed:
- In a constructor of a derived class, the `this` "variable" remains uninitialized until the `super` constructor has been called. When it is called, its value will become `this`'s value.
- The function is given the function that is being instantiated, instead of the `this` object. This is because the function may be the super class of another, and it needs to make the prototype of `this` equal to the `prototype` field of the derived class, instead of its own
Implementing this change in the runtime however is somewhat trivial.
## 5. Variables
Variables in JS are simple: each function has a single repository of variables that are accessible from the inception of the function. When you declare a variable, it is not visible from the point of declaration, but instead from the point of the function start. This can lead to some confusion, but also makes the runtime implementation 10 times easier.
### Capturing variables
Since in JS, a function may be defined in another, we need a mechanism via which the variables of the parent function can be made accessible by the child function. This can happen via variable capturing. Basically, during compilation, if it's determined that JS tries to access a variable from its parent function, that variable is marked as "capturable", aka a variable that is kept as a pointer to a value, instead of just a value, so that other functions can share that pointer, and the child function keeps track of which variables it has captured and to which parent variables it corresponds.
The runtime however, as far as its concerned, needs to only keep track of the captured, capturable and regular variables and provide a mechanism of constructing a function by supplying the raw function body (aka the instructions, name and other metadata) and a list of the captured variable instances.
### ES6 variables
Since the addition of the new `let` and `const` variables, the runtime model has been complicated a little bit - a function still has one repository of variables, but the runtime now needs to keep track of whether or not the variable has been initialized yet. This is because we can have code like this:
```js
const a = () => console.log(b);
a();
const b = 10;
```
For which static analysis of the scope is impossible. Of course, the compiler can feel free to omit these checks whenever it determines that the variable is definitely assigned, but the defined-ness of the variable must be guaranteed for it to be accessible.
Another consequence of the new variable model is that we can have code like this:
```js
const funcs = [];
for (let i = 0; i < 100; i++) {
funcs[i] = () => i;
}
console.log(funcs[69]()); // prints 69
```
In this case, for each iteration of the loop a new capturable instance needs to be created. This requires the runtime to have a mechanism of dynamically creating (and destroying) capturables.
### The global scope
When the compiler tries to resolve a variable, but doesn't find it anywhere along the scope chain, the variable is converted to a global variable access. In essence, this is just a more fancy way to get a field from an implicit "globals" object. However, unlike the traditional `my_obj.test`, where if `test` is not a member of `my_obj`, `undefined` is returned, instead, an error that reports that no such variable exists is thrown instead. The same mechanic follows for trying to assign to a nonexistent property of the global scope.
The only way of defining a new value of the global scope is by using `var name = value` in the top-level of the file. This statement will get 1 to 1 converted to `globalThis.name = value` (`globalThis` for the confused is just a reference to the global object. Alternatives are `self`, `window` and just `global`).

2115
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,32 +0,0 @@
{
"name": "server",
"version": "1.0.0",
"type": "module",
"scripts": {
"ts": "NODE_OPTIONS='--loader ts-node/esm --no-warnings --enable-source-maps'",
"watch:dev": "nodemon --exec npm run start",
"start:dev": "npm run ts node src/main.ts",
"start:prod": "node dst/main.js",
"build:dev": "NODE_ENV=dev npm run ts npx rollup -- -c",
"build:prod": "NODE_ENV=prod npm run ts npx rollup -- -c"
},
"dependencies": {
},
"devDependencies": {
"@babel/plugin-proposal-decorators": "^7.25.9",
"@babel/plugin-transform-class-properties": "^7.25.9",
"@babel/plugin-transform-typescript": "^7.25.9",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-commonjs": "^28.0.1",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.1.1",
"@types/node": "^22.7.9",
"nodemon": "^3.1.7",
"rollup": "^4.24.0",
"rollup-plugin-node-externals": "^7.1.3",
"ts-node": "^10.9.2",
"tslib": "^2.8.0"
}
}

View File

@ -1,57 +0,0 @@
import { resolve } from "path";
import { defineConfig } from "rollup";
import terser from "@rollup/plugin-terser";
import typescript from "@rollup/plugin-typescript";
import babel from "@rollup/plugin-babel";
import commonjs from "@rollup/plugin-commonjs";
import json from "@rollup/plugin-json";
import nodeResolve from "@rollup/plugin-node-resolve";
import nodeExternals from "rollup-plugin-node-externals";
const mode = process.env.NODE_ENV ?? "dev";
const shouldMinify = () => mode === "prod";
function overlay(path, src) {
path = resolve(path);
return { load: id => path === id ? src : null };
}
export default defineConfig({
input: "./src/main.ts",
plugins: [
typescript({
compilerOptions: {
allowImportingTsExtensions: true,
noEmit: true,
},
noForceEmit: true,
noEmitOnError: true,
}),
babel({
extensions: [".js", ".ts"],
exclude: "node_modules/**",
babelHelpers: "bundled",
plugins: [
["@babel/plugin-transform-typescript", {
onlyRemoveTypeImports: true,
optimizeConstEnums: true,
}],
["@babel/plugin-proposal-decorators", { legacy: true }],
["@babel/plugin-transform-class-properties"],
]
}),
nodeResolve(),
commonjs(),
json(),
nodeExternals(),
shouldMinify() && terser({ sourceMap: true })
],
output: {
dir: "dst",
format: "esm",
sourcemap: true,
inlineDynamicImports: true,
},
});

3
runtime/Makefile Normal file
View File

@ -0,0 +1,3 @@
SOURCES += $(wildcard )

70
runtime/include/gc.h Normal file
View File

@ -0,0 +1,70 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
// typedef struct {
// size_t cap, n;
// js_gc_task_node_t *tasks;
// } js_gc_task_stack_t;
typedef struct js_gc *js_gc_t;
typedef void (*js_gc_task_consumer_t)(void *data, js_gc_task_searcher_t searcher);
typedef void (*js_gc_task_searcher_t)(void *data, js_gc_task_consumer_t consumer);
typedef size_t js_gc_data_t;
void js_gc_run(size_t iterations);
// void js_gc_task_add(js_gc_task_stack_t *tasks, void *data, int type);
// typedef enum {
// GC_BLACK,
// GC_GREY,
// GC_WHITE,
// } js_gc_state_t;
// typedef struct {
// // Whether the node is a root
// bool root;
// } js_gc_data_t;
// typedef enum {
// JS_GC_OBJECT,
// JS_GC_STRING,
// JS_GC_CAPTURE,
// JS_GC_FRAME,
// } js_gc_node_type_t;
// typedef struct {
// js_gc_data_t *data;
// void *ptr;
// js_gc_node_type_t type;
// } js_gc_node_t;
// typedef struct js_gc_table_node {
// struct js_gc_table_node *next;
// js_gc_node_t node;
// } js_gc_table_node_t;
// typedef struct {
// size_t cap, n;
// js_gc_table_node_t **buckets;
// } js_gc_table_t;
// typedef struct {
// void **prev_ref;
// js_gc_node_t node;
// } js_gc_task_node_t;
// typedef struct {
// size_t cap, n;
// js_gc_task_node_t *tasks;
// } js_gc_task_stack_t;
// void js_gc_stack_push(js_gc_task_stack_t *stack, js_gc_task_node_t task);
// bool js_gc_stack_pop(js_gc_task_stack_t *stack, js_gc_task_node_t *pout);
// static void js_gc_dryrun_table(js_gc_task_stack_t *stack, void **ref, js_ctx_t obj);
// static void js_gc_reclaim_table(js_gc_task_stack_t *stack, void **ref, js_ctx_t obj);

55
runtime/include/gc_impl.h Normal file
View File

@ -0,0 +1,55 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
typedef enum {
GC_BLACK,
GC_GREY,
GC_WHITE,
} js_gc_state_t;
typedef struct {
// Whether the node is a root
bool root;
} js_gc_data_t;
typedef enum {
JS_GC_OBJECT,
JS_GC_STRING,
JS_GC_CAPTURE,
JS_GC_FRAME,
} js_gc_node_type_t;
typedef struct {
js_gc_data_t *data;
void *ptr;
js_gc_node_type_t type;
} js_gc_node_t;
typedef struct js_gc_table_node {
struct js_gc_table_node *next;
js_gc_node_t node;
} js_gc_table_node_t;
typedef struct {
size_t cap, n;
js_gc_table_node_t **buckets;
} js_gc_table_t;
typedef struct {
void **prev_ref;
js_gc_node_t node;
} js_gc_task_node_t;
typedef struct {
size_t cap, n;
js_gc_task_node_t *tasks;
} js_gc_task_stack_t;
void js_gc_stack_push(js_gc_task_stack_t *stack, js_gc_task_node_t task);
bool js_gc_stack_pop(js_gc_task_stack_t *stack, js_gc_task_node_t *pout);
static void js_gc_dryrun_table(js_gc_task_stack_t *stack, void **ref, js_ctx_t obj);
static void js_gc_reclaim_table(js_gc_task_stack_t *stack, void **ref, js_ctx_t obj);

25
runtime/include/impl.h Normal file
View File

@ -0,0 +1,25 @@
#pragma once
#include "./runtime.h"
#include "./gc.h"
struct js_ctx {
// js_gc_table_t gc;
};
typedef struct {
js_string_t str;
js_gc_data_t gc;
} *js_gc_str_t;
typedef struct {
js_gc_str_t str;
js_gc_data_t gc;
} *js_symbol_t;
struct js_capture {
js_value_t *ref;
js_gc_data_t gc;
};
// void js_gc_reclaim_str(js_gc_task_stack_t *stack, void **ref, js_obj_t obj);

26
runtime/include/obj.h Normal file
View File

@ -0,0 +1,26 @@
#pragma once
#include "./runtime.h"
#include "./impl.h"
typedef struct {
bool is_symbol;
js_gc_str_t name;
} js_obj_member_t;
typedef struct js_obj_node {
size_t hash;
js_obj_member_t *member;
struct js_obj_node *next;
} js_obj_node_t;
typedef struct {
js_gc_data_t gc;
size_t buckets_n;
js_obj_node_t *buckets;
js_obj_node_t *first;
} *js_obj_t;
void js_gc_dryrun_obj(js_gc_task_stack_t *stack, void **ref, js_obj_t obj);
void js_gc_reclaim_obj(js_gc_task_stack_t *stack, void **ref, js_obj_t obj);

115
runtime/include/runtime.h Normal file
View File

@ -0,0 +1,115 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
typedef struct js_value {
void *data;
enum {
JS_T_UNDEFINED,
JS_T_NULL,
JS_T_BOOL,
JS_T_STR,
JS_T_NUM,
JS_T_FUNC,
JS_T_OBJ,
JS_T_ARR,
} type;
} js_value_t;
typedef struct js_capture *js_capture_t;
typedef struct js_ctx *js_ctx_t;
typedef js_value_t (*js_func_body_t)(js_ctx_t ctx, js_value_t self, size_t argc, js_value_t argv);
typedef struct {
uint16_t *data;
size_t size: sizeof(size_t) - 1;
size_t dynamic: 1;
} js_string_t;
const js_string_t EMPTY = { .data = &(uint16_t[]){'t'}, .size = -1, .dynamic = false };
#define JS_STRING_LITERAL(literal) ((js_string_t) { .data = (literal), .size = sizeof (literal) / sizeof (literal)[0] - 1, .dynamic = false })
js_value_t JS_UNDEFINED = { .data = NULL, .type = JS_T_UNDEFINED };
js_value_t JS_NULL = { .data = NULL, .type = JS_T_NULL };
js_value_t JS_TRUE = { .data = (void*)1, .type = JS_T_BOOL };
js_value_t JS_FALSE = { .data = (void*)0, .type = JS_T_BOOL };
const js_string_t JS_TYPES[] = {
JS_STRING_LITERAL(u"undefined"),
JS_STRING_LITERAL(u"object"),
JS_STRING_LITERAL(u"boolean"),
JS_STRING_LITERAL(u"string"),
JS_STRING_LITERAL(u"number"),
JS_STRING_LITERAL(u"function"),
JS_STRING_LITERAL(u"object"),
JS_STRING_LITERAL(u"object"),
};
extern bool JS_ERR_FLAG;
js_value_t js_mk_num(double val);
js_value_t js_mk_bool(bool val);
js_value_t js_mk_str(js_ctx_t ctx, js_string_t str);
js_value_t js_mk_sym(js_ctx_t ctx, js_string_t name);
js_value_t js_mk_obj(js_ctx_t ctx);
js_value_t js_mk_arr(js_ctx_t ctx, size_t cap);
js_value_t js_mk_args(js_ctx_t ctx, size_t argc, js_value_t *argv);
js_value_t js_mk_func(js_ctx_t ctx, bool apply, bool construct, js_string_t name, js_func_body_t body, size_t capn, js_capture_t *capv);
int32_t js_to_int32(js_ctx_t ctx, js_value_t val);
int64_t js_to_int64(js_ctx_t ctx, js_value_t val);
bool js_to_bool(js_ctx_t ctx, js_value_t val);
js_string_t js_to_string(js_ctx_t ctx, js_value_t val);
js_value_t js_op_add(js_ctx_t ctx, js_value_t a, js_value_t b);
js_value_t js_op_sub(js_ctx_t ctx, js_value_t a, js_value_t b);
js_value_t js_op_mod(js_ctx_t ctx, js_value_t a, js_value_t b);
js_value_t js_op_div(js_ctx_t ctx, js_value_t a, js_value_t b);
js_value_t js_op_neg(js_ctx_t ctx, js_value_t val);
js_value_t js_op_b_and(js_ctx_t ctx, js_value_t a, js_value_t b);
js_value_t js_op_b_or(js_ctx_t ctx, js_value_t a, js_value_t b);
js_value_t js_op_b_xor(js_ctx_t ctx, js_value_t a, js_value_t b);
js_value_t js_op_b_rsh(js_ctx_t ctx, js_value_t a, js_value_t b);
js_value_t js_op_b_lsh(js_ctx_t ctx, js_value_t a, js_value_t b);
js_value_t js_op_b_ursh(js_ctx_t ctx, js_value_t a, js_value_t b);
bool js_op_b_not(js_ctx_t ctx, js_value_t val);
bool js_op_less(js_ctx_t ctx, js_value_t a, js_value_t b);
bool js_op_gr(js_ctx_t ctx, js_value_t a, js_value_t b);
bool js_op_leq(js_ctx_t ctx, js_value_t a, js_value_t b);
bool js_op_geq(js_ctx_t ctx, js_value_t a, js_value_t b);
bool js_op_eq_loose(js_ctx_t ctx, js_value_t a, js_value_t b);
bool js_op_neq_loose(js_ctx_t ctx, js_value_t a, js_value_t b);
bool js_op_eq(js_ctx_t ctx, js_value_t a, js_value_t b);
bool js_op_neq(js_ctx_t ctx, js_value_t a, js_value_t b);
js_value_t js_obj_set_proto(js_ctx_t ctx, js_value_t obj, js_value_t proto);
js_value_t js_obj_get_proto(js_ctx_t ctx, js_value_t obj);
js_value_t js_obj_get_own(js_ctx_t ctx, js_value_t obj, js_value_t key);
js_value_t js_obj_set_own(js_ctx_t ctx, js_value_t obj, js_value_t key, js_value_t value);
js_value_t js_obj_has_own(js_ctx_t ctx, js_value_t obj, js_value_t key);
js_value_t js_obj_del_own(js_ctx_t ctx, js_value_t obj, js_value_t key);
js_value_t js_obj_get(js_ctx_t ctx, js_value_t obj, js_value_t key);
bool js_obj_set(js_ctx_t ctx, js_value_t obj, js_value_t key, js_value_t value);
bool js_obj_has(js_ctx_t ctx, js_value_t obj, js_value_t key);
js_value_t js_obj_def_field(js_ctx_t ctx, js_value_t obj, js_value_t key, bool w, bool c, bool e);
js_value_t js_obj_def_prop(js_ctx_t ctx, js_value_t obj, js_value_t key, js_value_t get, js_value_t set, bool c, bool e);
js_value_t js_func_apply(js_ctx_t ctx, js_value_t func, js_value_t self, size_t argc, js_value_t *argv);
js_value_t js_func_construct(js_ctx_t ctx, js_value_t func, js_value_t self, size_t argc, js_value_t *argv);
js_value_t js_get_error(js_ctx_t ctx);
js_value_t js_return(js_ctx_t ctx, js_value_t val);
void js_gc_();
int js_runtime_bootstrap(int argc, const char **argv, void (*callee)(js_ctx_t ctx));

View File

@ -1,71 +0,0 @@
#include <stdint.h>
#include <stddef.h>
const int8_t JS_UNDEFINED = 0x00;
const int8_t JS_NULL = 0x01;
const int8_t JS_TRUE = 0x02;
const int8_t JS_FALSE = 0x03;
const int8_t JS_STRING = 0x05;
// const int8_t JS_INT = 0x06;
const int8_t JS_FLOAT = 0x07;
const int8_t JS_SYMBOL = 0x09;
const int8_t JS_OBJECT = 0x0A;
const int8_t JS_ARRAY = 0x0B;
const int8_t JS_FUNCTION = 0x0C;
typedef struct js_string {
int8_t mark;
size_t n;
const uint16_t *val;
} js_string_t;
typedef struct js_symbol {
int8_t mark;
js_string_t *description;
} js_symbol_t;
typedef struct js_object {
int8_t mark;
} js_object_t;
typedef struct js_function {
js_object_t object;
js_function_body_t body;
} js_function_t;
typedef struct js_val {
int8_t type;
union {
double number;
js_string_t *string;
js_symbol_t *symbol;
js_object_t *object;
js_function_t *function;
} value;
} js_val_t;
typedef struct js_weak_ref {
int8_t type;
union {
js_string_t *string;
js_symbol_t *symbol;
js_object_t *object;
js_function_t *function;
} value;
} js_weak_ref_t;
typedef js_val (*js_function_body_t)();
bool js_is_undefined(js_val_t);
js_val_t js_undefined();
js_val_t js_null();
js_val_t js_number_new(double val);
js_val_t js_string_new_c(const char *val);
js_val_t js_string_new_cn(size_t n, const char *val);
js_val_t js_string_new_cp(size_t n, const uint16_t *val);
js_val_t js_string_new_br(size_t n, const uint16_t *val);
js_val_t js_boolean_new(bool val);
js_val_t js_object_new(size_t alloc);
js_val_t js_array_new(size_t alloc);
js_val_t js_function_new(js_function_body_t val);

View File

@ -1,6 +0,0 @@
#include "runtime.h"
struct js_val {
};

View File

@ -1,117 +0,0 @@
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
typedef struct js_context *js_context_t;
typedef struct js_value *js_value_t;
extern js_value_t JS_UNDEFINED, JS_NULL, JS_TRUE, JS_FALSE;
typedef struct {
js_value_t *value;
int ref_count;
} js_capture_t;
// typedef struct {
// js_value_t
// } js_member_t;
js_value_t js_op_add(js_context_t ctx, js_value_t a, js_value_t b);
js_value_t js_op_sub(js_context_t ctx, js_value_t a, js_value_t b);
js_value_t js_op_mod(js_context_t ctx, js_value_t a, js_value_t b);
js_value_t js_op_div(js_context_t ctx, js_value_t a, js_value_t b);
js_value_t js_op_neg(js_context_t ctx, js_value_t val);
js_value_t js_op_b_and(js_context_t ctx, js_value_t a, js_value_t b);
js_value_t js_op_b_or(js_context_t ctx, js_value_t a, js_value_t b);
js_value_t js_op_b_xor(js_context_t ctx, js_value_t a, js_value_t b);
js_value_t js_op_b_rsh(js_context_t ctx, js_value_t a, js_value_t b);
js_value_t js_op_b_lsh(js_context_t ctx, js_value_t a, js_value_t b);
js_value_t js_op_b_ursh(js_context_t ctx, js_value_t a, js_value_t b);
js_value_t js_op_b_not(js_context_t ctx, js_value_t val);
js_value_t js_op_less(js_context_t ctx, js_value_t a, js_value_t b);
js_value_t js_op_gr(js_context_t ctx, js_value_t a, js_value_t b);
js_value_t js_op_leq(js_context_t ctx, js_value_t a, js_value_t b);
js_value_t js_op_geq(js_context_t ctx, js_value_t a, js_value_t b);
js_value_t js_op_eq_loose(js_context_t ctx, js_value_t a, js_value_t b);
js_value_t js_op_neq_loose(js_context_t ctx, js_value_t a, js_value_t b);
js_value_t js_op_eq(js_context_t ctx, js_value_t a, js_value_t b);
js_value_t js_op_neq(js_context_t ctx, js_value_t a, js_value_t b);
js_value_t js_obj_set_proto(js_context_t ctx, js_value_t obj, js_value_t key);
js_value_t js_obj_get_proto(js_context_t ctx, js_value_t obj, js_value_t key);
js_value_t js_obj_get_own(js_context_t ctx, js_value_t obj, js_value_t key);
js_value_t js_obj_set_own(js_context_t ctx, js_value_t obj, js_value_t key, js_value_t value);
js_value_t js_obj_has_own(js_context_t ctx, js_value_t obj, js_value_t key);
js_value_t js_obj_del_own(js_context_t ctx, js_value_t obj, js_value_t key);
js_value_t js_obj_get(js_context_t ctx, js_value_t obj, js_value_t key);
js_value_t js_obj_set(js_context_t ctx, js_value_t obj, js_value_t key, js_value_t value);
js_value_t js_obj_has(js_context_t ctx, js_value_t obj, js_value_t key);
js_value_t js_obj_def_field(js_context_t ctx, js_value_t obj, js_value_t key, bool writable, bool configurable, bool enumerable);
js_value_t js_obj_def_prop(js_context_t ctx, js_value_t obj, js_value_t key, js_value_t get, js_value_t set, bool configurable, bool enumerable);
js_value_t js_func_create(js_context_t ctx, bool apply, bool construct, js_string_t name, js_func_body_t body, size_t capture_n, js_capture_t *captures);
// void *ref = malloc(1);
typedef struct js_string {
int8_t mark;
size_t n;
const uint16_t *val;
} js_string_t;
typedef struct js_symbol {
int8_t mark;
js_string_t *description;
} js_symbol_t;
typedef struct js_object {
int8_t mark;
} js_object_t;
typedef struct js_function {
js_object_t object;
js_function_body_t body;
} js_function_t;
typedef struct js_val {
int8_t type;
union {
double number;
js_string_t *string;
js_symbol_t *symbol;
js_object_t *object;
js_function_t *function;
} value;
} js_val_t;
typedef struct js_weak_ref {
int8_t type;
union {
js_string_t *string;
js_symbol_t *symbol;
js_object_t *object;
js_function_t *function;
} value;
} js_weak_ref_t;
typedef js_val (*js_function_body_t)();
bool js_is_undefined(js_val_t);
js_val_t js_undefined();
js_val_t js_null();
js_val_t js_number_new(double val);
js_val_t js_string_new_c(const char *val);
js_val_t js_string_new_cn(size_t n, const char *val);
js_val_t js_string_new_cp(size_t n, const uint16_t *val);
js_val_t js_string_new_br(size_t n, const uint16_t *val);
js_val_t js_boolean_new(bool val);
js_val_t js_object_new(size_t alloc);
js_val_t js_array_new(size_t alloc);
js_val_t js_function_new(js_function_body_t val);

208
runtime/src/gc.c Normal file
View File

@ -0,0 +1,208 @@
#include <stdlib.h>
#include "./include/gc.h"
// #include "./internal/obj.h"
static uint64_t bucket_i(size_t cap, void *ptr) {
return (((size_t)ptr / 256) * 4567) % cap;
}
static void table_enlarge(js_gc_table_t *table) {
js_gc_table_t new_table = {
.cap = table->cap * 2,
.n = table->n,
.buckets = malloc(sizeof table->buckets * table->cap * 2),
};
js_gc_table_node_t **tasks[new_table.cap];
for (size_t i = 0; i < sizeof table->buckets * new_table.cap; i++) {
new_table.buckets[i] = NULL;
tasks[i] = &new_table.buckets[i];
}
for (size_t i = 0; i < table->cap; i++) {
for (js_gc_table_node_t *node = table->buckets[i]; node != NULL; ) {
js_gc_table_node_t *curr = node;
node = curr->next;
curr->next = NULL;
size_t index = bucket_i(new_table.cap, curr->node.ptr);
*tasks[index] = curr;
tasks[index] = &curr->next;
}
}
free(table->buckets);
*table = new_table;
}
static bool table_add_stat(js_gc_table_t *table, js_gc_node_t node, bool enlarge) {
if (enlarge && table->n >= table->cap) table_enlarge(table);
js_gc_table_node_t **target = &table->buckets[bucket_i(table, &node)];
while (*target) {
if (
(*target)->node.type == node.type &&
(*target)->node.ptr == node.ptr
) return false;
target = (*target)->next;
}
*target = malloc(sizeof *target);
(*target)->next = NULL;
(*target)->node = node;
table->n++;
return true;
}
static bool table_del(js_gc_table_t *table, js_gc_node_t node) {
js_gc_table_node_t **target = &table->buckets[bucket_i(table, &node)];
while (*target) {
if (
(*target)->node.type == node.type &&
(*target)->node.ptr == node.ptr
) {
js_gc_table_node_t *curr = *target;
*target = (*target)->next;
free(curr);
table->n--;
return true;
}
target = (*target)->next;
}
return false;
}
static void table_free(js_gc_table_t table) {
for (size_t i = 0; i < table.cap; i++) {
for (js_gc_table_node_t *node = table.buckets[i]; node != NULL; ) {
js_gc_table_node_t *curr = node;
node = curr->next;
free(curr);
}
}
free(table.buckets);
}
static void pin_obj(js_gc_table_t *table, js_obj_t obj) {
bool added = table_add_stat(table, (js_gc_node_t){ .ptr = obj, .type = JS_GC_OBJECT }, true);
if (added) obj->gc.refs++;
}
static void pin_str(js_gc_table_t *table, js_gc_str_t str) {
bool added = table_add_stat(table, (js_gc_node_t){ .ptr = str, .type = JS_GC_STRING }, true);
if (added) str->gc.refs++;
}
static bool unpin_obj(js_gc_table_t *table, js_obj_t obj) {
if (obj->gc.refs == 0) return true;
bool removed = table_del(table, (js_gc_node_t){ .ptr = obj, .type = JS_GC_OBJECT });
if (removed) obj->gc.refs--;
return obj->gc.refs == 0;
}
static bool unpin_str(js_gc_table_t *table, js_gc_str_t str) {
if (str->gc.refs == 0) return true;
bool removed = table_del(table, (js_gc_node_t){ .ptr = str, .type = JS_GC_STRING });
if (removed) str->gc.refs--;
return str->gc.refs == 0;
}
static js_gc_task_stack_t stack_new(const js_gc_table_t *table) {
size_t n = table->cap > 16 ? table->cap : 16;
size_t j = 0;
js_gc_task_stack_t stack;
stack.cap = n;
stack.n = table->n;
stack.tasks = malloc(sizeof(js_gc_task_node_t) * n);
for (size_t i = 0; i < table->cap; i++) {
for (js_gc_table_node_t *node = table->buckets[i]; node; node = node->next) {
stack.tasks[j++] = (js_gc_task_node_t){
.node = node->node,
.prev_ref = NULL,
};
}
}
return stack;
}
static void stack_free(const js_gc_task_stack_t *stack) {
free(stack->tasks);
}
static void table_dryrun(const js_gc_table_t *table) {
if (table->n == 0) return;
js_gc_task_stack_t stack = stack_new(table);
while(stack.n > 0) {
js_gc_task_node_t task = stack.tasks[--stack.n];
switch (task.node.type) {
case JS_GC_OBJECT:
js_gc_dryrun_obj(&stack, task.prev_ref, task.node.ptr);
break;
}
}
stack_free(&stack);
}
static void table_reclaim(const js_gc_table_t *table) {
js_gc_task_stack_t stack = stack_new(table);
while(stack.n > 0) {
js_gc_task_node_t task = stack.tasks[--stack.n];
switch (task.node.type) {
case JS_GC_OBJECT:
js_gc_reclaim_obj(&stack, task.prev_ref, task.node.ptr);
break;
case JS_GC_STRING:
js_gc_reclaim_str(&stack, task.prev_ref, task.node.ptr);
break;
}
}
stack_free(&stack);
}
void js_gc_stack_push(js_gc_task_stack_t *stack, js_gc_task_node_t task) {
while (stack->n >= stack->cap) {
stack->cap *= 2;
stack->tasks = realloc(stack->tasks, sizeof *stack->tasks * stack->cap);
}
stack->tasks[stack->n++] = task;
}
bool js_gc_stack_pop(js_gc_task_stack_t *stack, js_gc_task_node_t *pout) {
if (stack->n == 0) return false;
*pout = stack->tasks[--stack->n];
return true;
}
void js_gc_pin_val(js_gc_table_t *table, js_value_t val) {
if (val.type == JS_T_OBJ) pin_obj(table, val.data);
if (val.type == JS_T_STR && ((js_gc_str_t)val.data)->str.dynamic) pin_str(table, val.data);
}
void js_gc_unpin_val(js_gc_table_t *table, js_value_t val) {
if (val.type == JS_T_OBJ) unpin_obj(table, val.data);
if (val.type == JS_T_STR && ((js_gc_str_t)val.data)->str.dynamic) unpin_str(table, val.data);
}
void js_gc_reclaim_frame(js_ctx_t ctx) {
table_dryrun(&ctx->gc);
table_reclaim(&ctx->gc);
table_free(ctx->gc);
}

64
runtime/src/runtime.c Normal file
View File

@ -0,0 +1,64 @@
#include "./include/runtime.h"
#include "./include/obj.h"
extern bool JS_ERR_FLAG = false;
js_value_t js_mk_num(double val) {
return (js_value_t){ .type = JS_T_NUM, .data = *(void**)&val };
}
js_value_t js_mk_bool(bool val) {
return val ? JS_TRUE : JS_FALSE;
}
js_value_t js_mk_str(js_string_t str) {
js_gc_str_t res = malloc(sizeof *res);
res->str = str;
res->gc = 0;
return (js_value_t){ .data = res, .type = JS_T_STR };
}
void js_str_free(js_gc_str_t str) {
if (!str->str.dynamic || str->gc.refs == 0 || str == NULL) return;
str->gc.refs--;
if (str->gc.refs == 0) {
free(str->str.data);
free(str);
}
}
js_value_t js_mk_symbol(js_string_t str) {
js_gc_str_t res = malloc(sizeof *res);
res->str = str;
res->gc.refs = 0;
return (js_value_t){ .data = res, .type = JS_T_STR };
}
js_value_t js_mk_obj(js_ctx_t ctx) {
js_obj_t obj = malloc(sizeof *obj);
obj->buckets_n = 16;
obj->buckets = malloc(sizeof *obj->buckets * 16);
obj->first = NULL;
obj->gc.refs = 0;
return (js_value_t){ .type = JS_T_OBJ, .data = obj };
}
void js_obj_free(js_gc_str_t str) {
if (!str->str.dynamic || str->gc.refs == 0 || str == NULL) return;
str->gc.refs--;
if (str->gc.refs == 0) {
free(str->str.data);
free(str);
}
}
void js_bootstrap(int argc, const char **argv) {
}
typedef struct {
js_gc_table_node_t next;
} js_gc_table_node_t;

View File

@ -1 +0,0 @@
console.log("tset");

View File

@ -1,17 +0,0 @@
{
"include": ["**/*.ts"],
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"module": "ESNext",
"target": "ESNext",
"lib": ["ESNext"],
"forceConsistentCasingInFileNames": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"outDir": "dst",
"allowImportingTsExtensions": true,
"noEmit": true
}
}