Skip to content

Commit d19549e

Browse files
committed
feat: Implement core runtime with NaN-boxing values, virtual machine, and Python bindings.
1 parent a1fb34a commit d19549e

File tree

5 files changed

+312
-66
lines changed

5 files changed

+312
-66
lines changed

CMakeLists.txt

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,31 @@ link_directories(${LLVM_LIBRARY_DIRS})
2323
# Map generic components to specific libs
2424
llvm_map_components_to_libnames(llvm_libs core support executionengine native ipo)
2525

26+
# --- Project Source ---
27+
# Export symbols for Windows DLL
28+
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
29+
2630
# --- Project Source ---
2731
include_directories(include)
2832

29-
# Gather sources
30-
file(GLOB_RECURSE SOURCES
33+
# Gather sources (excluding main.c for library)
34+
file(GLOB_RECURSE LIB_SOURCES
3135
"src/*.c"
32-
"src/compiler/*.cpp" # Pickup the new backend
36+
"src/compiler/*.cpp"
3337
)
38+
list(FILTER LIB_SOURCES EXCLUDE REGEX ".*main\\.c$")
39+
40+
# --- Shared Library ---
41+
add_library(proxpl_lib SHARED ${LIB_SOURCES})
42+
target_link_libraries(proxpl_lib PRIVATE ${llvm_libs})
3443

3544
# --- Executable ---
36-
add_executable(proxpl ${SOURCES})
45+
add_executable(proxpl src/main.c ${LIB_SOURCES})
3746

3847
# Link LLVM and standard libraries
3948
target_link_libraries(proxpl PRIVATE ${llvm_libs})
4049

4150
if(UNIX)
4251
target_link_libraries(proxpl PRIVATE pthread dl z tinfo)
52+
target_link_libraries(proxpl_lib PRIVATE pthread dl z tinfo)
4353
endif()

include/value.h

Lines changed: 47 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,40 +8,58 @@
88
#define PROX_VALUE_H
99

1010
#include "common.h"
11+
#include <string.h>
1112

12-
// For now, simpler Tagged Union.
13-
// Phase 2 can introduce Nan-boxing.
13+
// NaN-Boxing:
14+
// IEEE 754 doubles have 52 bits of mantissa.
15+
// specific NaN patterns can encode pointers (48 bits on x64) and tags.
16+
//
17+
// Mask: 0xFFFF000000000000 (Top 16 bits are tag/sign/exp)
18+
//
19+
// QNaN : 0x7ff8000000000000
20+
// SIGN : 0x8000000000000000 (Sign bit implies object pointer)
21+
// TAGS : Lower 3 bits of the high 16 bits can be types.
22+
23+
typedef uint64_t Value;
24+
25+
// Masks
26+
#define SIGN_BIT ((uint64_t)0x8000000000000000)
27+
#define QNAN ((uint64_t)0x7ffc000000000000)
28+
29+
#define TAG_NULL 1 // 01
30+
#define TAG_FALSE 2 // 10
31+
#define TAG_TRUE 3 // 11
1432

1533
typedef struct Obj Obj;
16-
typedef struct ObjString ObjString;
1734

18-
typedef enum { VAL_BOOL, VAL_NULL, VAL_NUMBER, VAL_OBJ } ValueType;
35+
// Classification Macros
36+
#define IS_NUMBER(v) (((v) & QNAN) != QNAN)
37+
#define IS_NULL(v) ((v) == (QNAN | TAG_NULL))
38+
#define IS_BOOL(v) (((v) | 1) == (QNAN | TAG_TRUE))
39+
#define IS_OBJ(v) (((v) & (QNAN | SIGN_BIT)) == (QNAN | SIGN_BIT))
1940

20-
typedef struct {
21-
ValueType type;
22-
union {
23-
bool boolean;
24-
double number;
25-
Obj *obj;
26-
} as;
27-
} Value;
28-
29-
// Macros for checking type
30-
#define IS_BOOL(value) ((value).type == VAL_BOOL)
31-
#define IS_NULL(value) ((value).type == VAL_NULL)
32-
#define IS_NUMBER(value) ((value).type == VAL_NUMBER)
33-
#define IS_OBJ(value) ((value).type == VAL_OBJ)
34-
35-
// Macros for unwrapping
36-
#define AS_BOOL(value) ((value).as.boolean)
37-
#define AS_NUMBER(value) ((value).as.number)
38-
#define AS_OBJ(value) ((value).as.obj)
39-
40-
// Macros for creating values
41-
#define BOOL_VAL(value) ((Value){VAL_BOOL, {.boolean = value}})
42-
#define NULL_VAL ((Value){VAL_NULL, {.number = 0}})
43-
#define NUMBER_VAL(value) ((Value){VAL_NUMBER, {.number = value}})
44-
#define OBJ_VAL(object) ((Value){VAL_OBJ, {.obj = (Obj *)object}})
41+
// Conversion Macros
42+
#define AS_NUMBER(v) valueToNum(v)
43+
#define AS_BOOL(v) ((v) == (QNAN | TAG_TRUE))
44+
#define AS_OBJ(v) ((Obj*)(uintptr_t)((v) & ~(SIGN_BIT | QNAN)))
45+
46+
// Value Constructors
47+
static inline Value numToValue(double num) {
48+
Value value;
49+
memcpy(&value, &num, sizeof(double));
50+
return value;
51+
}
52+
53+
static inline double valueToNum(Value value) {
54+
double num;
55+
memcpy(&num, &value, sizeof(Value));
56+
return num;
57+
}
58+
59+
#define NUMBER_VAL(num) (numToValue(num))
60+
#define NULL_VAL ((Value)(QNAN | TAG_NULL))
61+
#define BOOL_VAL(b) ((b) ? (QNAN | TAG_TRUE) : (QNAN | TAG_FALSE))
62+
#define OBJ_VAL(obj) (Value)(SIGN_BIT | QNAN | (uint64_t)(uintptr_t)(obj))
4563

4664
// Value Array (for constants, stack etc.)
4765
typedef struct {

src/runtime/value.c

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,13 @@ void freeValueArray(ValueArray *array) {
3636
}
3737

3838
void printValue(Value value) {
39-
switch (value.type) {
40-
case VAL_BOOL:
39+
if (IS_BOOL(value)) {
4140
printf(AS_BOOL(value) ? "true" : "false");
42-
break;
43-
case VAL_NULL:
41+
} else if (IS_NULL(value)) {
4442
printf("null");
45-
break;
46-
case VAL_NUMBER:
43+
} else if (IS_NUMBER(value)) {
4744
printf("%g", AS_NUMBER(value));
48-
break;
49-
case VAL_OBJ:
50-
// Placeholder until object system is implemented
45+
} else if (IS_OBJ(value)) {
5146
printf("<obj>");
52-
break;
5347
}
5448
}

src/runtime/vm.c

Lines changed: 110 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,32 +29,117 @@ Value pop(VM *vm) {
2929
}
3030

3131
static InterpretResult run(VM *vm) {
32-
#define READ_BYTE() (*vm->ip++)
33-
#define READ_CONSTANT() (vm->chunk->constants.values[READ_BYTE()])
34-
#define BINARY_OP(valueType, op) \
35-
do { \
36-
if (!IS_NUMBER((*(vm->stackTop - 1))) || \
37-
!IS_NUMBER((*(vm->stackTop - 2)))) { \
38-
printf("Operands must be numbers.\n"); \
39-
return INTERPRET_RUNTIME_ERROR; \
40-
} \
41-
double b = AS_NUMBER(pop(vm)); \
42-
double a = AS_NUMBER(pop(vm)); \
43-
push(vm, valueType(a op b)); \
44-
} while (false)
32+
register u8* ip = vm->ip;
33+
register Value* sp = vm->stackTop;
4534

46-
for (;;) {
47-
#ifdef DEBUG_TRACE_EXECUTION
48-
printf(" ");
49-
for (Value *slot = vm->stack; slot < vm->stackTop; slot++) {
50-
printf("[ ");
51-
printValue(*slot);
52-
printf(" ]");
53-
}
35+
#ifdef __GNUC__
36+
#define READ_BYTE() (*ip++)
37+
#define READ_CONSTANT() (vm->chunk->constants.values[READ_BYTE()])
38+
#define DISPATCH() goto *dispatch_table[*ip++]
39+
40+
static void* dispatch_table[] = {
41+
&&DO_OP_CONSTANT, &&DO_OP_TRUE, &&DO_OP_FALSE, &&DO_OP_NULL,
42+
&&DO_OP_POP, &&DO_OP_ADD, &&DO_OP_SUBTRACT, &&DO_OP_MULTIPLY,
43+
&&DO_OP_DIVIDE, &&DO_OP_NOT, &&DO_OP_NEGATE, &&DO_OP_PRINT,
44+
&&DO_OP_RETURN
45+
};
46+
47+
DISPATCH();
48+
49+
DO_OP_CONSTANT: {
50+
Value constant = READ_CONSTANT();
51+
*sp++ = constant;
52+
DISPATCH();
53+
}
54+
DO_OP_TRUE: {
55+
*sp++ = BOOL_VAL(true);
56+
DISPATCH();
57+
}
58+
DO_OP_FALSE: {
59+
*sp++ = BOOL_VAL(false);
60+
DISPATCH();
61+
}
62+
DO_OP_NULL: {
63+
*sp++ = NULL_VAL;
64+
DISPATCH();
65+
}
66+
DO_OP_POP: {
67+
sp--;
68+
DISPATCH();
69+
}
70+
DO_OP_ADD: {
71+
double b = AS_NUMBER(*(sp - 1));
72+
double a = AS_NUMBER(*(sp - 2));
73+
sp -= 2;
74+
*sp++ = NUMBER_VAL(a + b);
75+
DISPATCH();
76+
}
77+
DO_OP_SUBTRACT: {
78+
double b = AS_NUMBER(*(sp - 1));
79+
double a = AS_NUMBER(*(sp - 2));
80+
sp -= 2;
81+
*sp++ = NUMBER_VAL(a - b);
82+
DISPATCH();
83+
}
84+
DO_OP_MULTIPLY: {
85+
double b = AS_NUMBER(*(sp - 1));
86+
double a = AS_NUMBER(*(sp - 2));
87+
sp -= 2;
88+
*sp++ = NUMBER_VAL(a * b);
89+
DISPATCH();
90+
}
91+
DO_OP_DIVIDE: {
92+
double b = AS_NUMBER(*(sp - 1));
93+
double a = AS_NUMBER(*(sp - 2));
94+
sp -= 2;
95+
*sp++ = NUMBER_VAL(a / b);
96+
DISPATCH();
97+
}
98+
DO_OP_NOT: {
99+
Value v = *(sp-1);
100+
*(sp-1) = BOOL_VAL(IS_NULL(v) || (IS_BOOL(v) && !AS_BOOL(v)));
101+
DISPATCH();
102+
}
103+
DO_OP_NEGATE: {
104+
double a = AS_NUMBER(*(sp-1));
105+
*(sp-1) = NUMBER_VAL(-a);
106+
DISPATCH();
107+
}
108+
DO_OP_PRINT: {
109+
// printValue expects Value, we need to sync slightly or safe call
110+
// printValue doesn't use VM, so it's safe.
111+
printValue(*(sp-1));
112+
sp--;
54113
printf("\n");
55-
disassembleInstruction(vm->chunk, (int)(vm->ip - vm->chunk->code));
56-
#endif
114+
DISPATCH();
115+
}
116+
DO_OP_RETURN: {
117+
vm->stackTop = sp;
118+
vm->ip = ip;
119+
return INTERPRET_OK;
120+
}
121+
122+
#else
123+
// Fallback for MSVC / Standard C
124+
// We use local variables for speed even in switch
125+
vm->ip = ip;
126+
vm->stackTop = sp;
127+
128+
#define READ_BYTE() (*vm->ip++)
129+
#define READ_CONSTANT() (vm->chunk->constants.values[READ_BYTE()])
130+
#define BINARY_OP(valueType, op) \
131+
do { \
132+
if (!IS_NUMBER((*(vm->stackTop - 1))) || \
133+
!IS_NUMBER((*(vm->stackTop - 2)))) { \
134+
printf("Operands must be numbers.\n"); \
135+
return INTERPRET_RUNTIME_ERROR; \
136+
} \
137+
double b = AS_NUMBER(pop(vm)); \
138+
double a = AS_NUMBER(pop(vm)); \
139+
push(vm, valueType(a op b)); \
140+
} while (false)
57141

142+
for (;;) {
58143
u8 instruction;
59144
switch (instruction = READ_BYTE()) {
60145
case OP_CONSTANT: {
@@ -108,10 +193,12 @@ static InterpretResult run(VM *vm) {
108193
}
109194
}
110195
}
196+
#endif
111197

112198
#undef READ_BYTE
113199
#undef READ_CONSTANT
114200
#undef BINARY_OP
201+
#undef DISPATCH
115202
}
116203

117204
InterpretResult interpret(VM *vm, Chunk *chunk) {

0 commit comments

Comments
 (0)