Compare commits

...

1 Commits

Author SHA1 Message Date
Jose Luis Montañes Ojados
9610957f1b add turingcomplete cpu compiler 2026-03-03 01:18:47 +01:00
13 changed files with 1284 additions and 6 deletions

312
docs/todo.md Normal file
View File

@@ -0,0 +1,312 @@
# TODO: Backend mycpu_v2 para j-lang
Roadmap de implementación del generador de código para el CPU v2 de 16-bit.
El objetivo es que `gencode.h` tome el AST del frontend y produzca:
- **Texto ensamblador** legible (para debug)
- **Binario** (array de bytes para cargar en PROM)
---
## Referencia rápida del CPU v2
```
Instrucción: 8 bytes = [OPCODE:16][PARAM1:16][PARAM2:16][TARGET:16]
PC cuenta en words de 16-bit → instrucción N está en PC = N × 4
Registros libres: REG0-REG11 (12 registros, 16 bits cada uno)
Registros especiales: REG12(RAM_VAL), REG13(RAM_ADDR), REG14(PC), REG15(I/O)
Modos de direccionamiento (codificados en el opcode):
base + 0x00 = registro, registro
base + 0x40 = inmediato, registro
base + 0x80 = registro, inmediato
base + 0xC0 = inmediato, inmediato
RAM:
Leer: Escribir dirección en REG13 → REG12 se actualiza automáticamente
Escribir: REG12 = valor, REG13 = addr → RSTR (0x18)
```
### Pseudo-instrucciones útiles
```asm
MOV #valor, REGn → ADD #valor, #0, REGn (opcode 0xC0)
MOV REGa, REGb ADD REGa, #0, REGb (opcode 0x80)
JMP #addr → EQ #0, #0, addr (opcode 0xD0, siempre true)
NOP ADD #0, #0, REG0 (opcode 0xC0)
```
---
## Convención de registros
```
REG0-REG3 → Temporales para evaluación de expresiones (expression stack)
REG4-REG5 → Auxiliares (spill de expresiones profundas)
REG6-REG11 → Libres / reserva futura (frame pointer, etc.)
REG12 → RAM VALUE (especial, no tocar directamente)
REG13 → RAM ADDR (especial, no tocar directamente)
REG14 → PC (especial)
REG15 → I/O (especial)
```
## Almacenamiento de variables
Todas las variables en **RAM**. Tabla nombre→dirección en tiempo de compilación.
```asm
; Leer variable 'x' (dirección addr) → REGn
ADD #addr, #0, REG13 ; REG13 = addr
ADD REG12, #0, REGn ; REGn = RAM[addr] (lectura automática)
; Escribir variable 'x' (valor en REGn, dirección addr)
ADD #addr, #0, REG13 ; REG13 = addr
ADD REGn, #0, REG12 ; REG12 = valor
RSTR ; RAM[addr] = REG12
```
---
## Fase 0: Infraestructura del emisor
**Objetivo**: Estructuras y funciones base para emitir instrucciones.
- [ ] Struct `Instruction` (opcode, param1, param2, target)
- [ ] Buffer de instrucciones (array dinámico donde se acumulan)
- [ ] Función `emit(opcode, p1, p2, target)` — agrega instrucción al buffer
- [ ] Tabla de variables: mapeo nombre→dirección_RAM (compilación)
- [ ] Función `lookupOrCreateVar(name)` — busca o asigna dirección RAM
- [ ] Sistema de labels/backpatching:
- [ ] `emitPlaceholder()` → emite instrucción con target=0, retorna índice
- [ ] `patchTarget(index, target)` → rellena el target de instrucción emitida
- [ ] `currentAddr()` → posición actual (nº de instrucción)
- [ ] Output ASM: recorrer buffer → texto legible con mnemonicos
- [ ] Output binario: recorrer buffer → array de bytes (8 bytes/instrucción)
**Criterio**: Emitir instrucciones hardcoded, ver texto ASM y binario generados.
---
## Fase 1: Constantes, asignaciones y print
**Objetivo**: Compilar `x = 42` y `print x`.
- [ ] Compilar `NODE_INT_LIT` → cargar inmediato en REG[depth]
```asm
ADD #42, #0, REG0 ; MOV #42, REG0
```
- [ ] Compilar `NODE_ASSIGN` → evaluar expr → REG0, store en RAM
```asm
; (resultado ya en REG0)
ADD #addr, #0, REG13 ; REG13 = dirección de variable
ADD REG0, #0, REG12 ; REG12 = valor
RSTR ; RAM[addr] = valor
```
- [ ] Compilar `NODE_VAR` → leer de RAM a REG[depth]
```asm
ADD #addr, #0, REG13 ; REG13 = dirección
ADD REG12, #0, REG0 ; REG0 = RAM[addr]
```
- [ ] Compilar `NODE_PRINT` → evaluar expr → REG0, copiar a I/O
```asm
ADD REG0, #0, REG15 ; OUTPUT = REG0
```
- [ ] Compilar `NODE_BLOCK` → iterar y compilar cada statement
**Test**: `simple.j` (x = 10). `print 10` → escribe 10 en REG15.
---
## Fase 2: Expresiones aritméticas
**Objetivo**: Compilar `x = 10 + 20 * 3`.
**Estrategia**: Register depth counter. Cada sub-expresión deposita resultado en `REG[depth]`.
- [ ] Variable `int reg_depth = 0` para tracking
- [ ] Compilar `NODE_BINOP`:
```
compilar left → resultado en REG[depth]
depth++
compilar right → resultado en REG[depth]
depth--
emit OP REG[depth], REG[depth+1], REG[depth]
```
- [ ] Manejar profundidad > 4 → PUSH/POP al stack (spill)
- [ ] Mapeo de operadores:
- `+` → ADD (0x00)
- `-` → SUB (0x01)
- `*` → MUL (0x02)
- `/` → DIV (0x03)
**Test**: `sum.j`, `resta.j`. Verificar que `2 + 3 * 4` da 14.
---
## Fase 3: Comparaciones y control de flujo
**Objetivo**: Compilar `if` y `while`.
### if
- [ ] Compilar `NODE_IF`:
```
compilar condición left → REG0
compilar condición right → REG1
emit CONDICIONAL_INVERSO REG0, REG1, [placeholder]
compilar bloque then
patch placeholder → currentAddr() × 4
```
- [ ] Mapeo de condicionales **inversos** (saltar si la condición es FALSA):
- `==` en AST → emit `NEQ` (0x11)
- `!=` en AST → emit `EQ` (0x10)
- `<` en AST → emit `GRE` (0x15) — saltar si >=
- `>` en AST → emit `LSE` (0x13) — saltar si <=
### while
- [ ] Compilar `NODE_WHILE`:
```
loop_start = currentAddr()
compilar condición left → REG0
compilar condición right → REG1
emit CONDICIONAL_INVERSO REG0, REG1, [placeholder_exit]
compilar cuerpo
emit EQ #0, #0, (loop_start × 4) ; JMP incondicional
patch placeholder_exit → currentAddr() × 4
```
### Recordar
- **PC = instrucción_index × 4** (cada instrucción = 4 words de 16-bit)
- El salto incondicional es `EQ #0, #0, target` (0xD0, siempre true)
**Test**: `if.j`, `while.j`. While que cuenta de 0 a 10.
---
## Fase 4: Funciones (CALL/RET)
**Objetivo**: Compilar `fn` definitions y llamadas.
### Convención de llamada
```
1. Caller pushea argumentos al stack (derecha a izquierda)
2. Caller ejecuta CALL #dirección (pushea PC+1 al stack, salta)
3. Callee popea argumentos → variables locales en RAM
4. Callee ejecuta cuerpo
5. Callee deja resultado en REG0
6. Callee ejecuta RET (popea PC del stack, salta)
7. Caller usa REG0 como valor de retorno
```
### Tareas
- [ ] Compilar `NODE_FN_DEF`:
```
emit JMP [placeholder_skip] ; saltar sobre el cuerpo
fn_addr = currentAddr()
para cada param (de derecha a izq):
emit POP → REGn
store REGn → RAM[param_addr]
compilar cuerpo
emit RET
patch placeholder_skip → currentAddr() × 4
registrar fn_name → fn_addr en tabla de funciones
```
- [ ] Compilar `NODE_CALL`:
```
para cada argumento (de izq a der):
compilar argumento → REG0
emit PUSH REG0
emit CALL #(fn_addr × 4)
; resultado queda en REG0
```
- [ ] Compilar `NODE_RETURN`:
```
compilar expresión → REG0
emit RET
```
- [ ] Resolver scope de variables locales:
- **Opción simple**: cada función tiene su propio rango de RAM
- **Opción avanzada**: frame pointer (registro base + offset para locales)
**Test**: `functions.j`, `custom_fn.j`.
---
## Fase 5: Strings y objetos (avanzado)
**Objetivo**: Soportar strings, clases, campos e instancias.
- [ ] Strings en RAM — caracteres consecutivos, variable apunta a dirección base
- [ ] Print de strings — loop: leer cada char de RAM → escribir en REG15
- [ ] Instancias — bloque de RAM con campos, variable apunta a base
- [ ] Campos — offset fijo desde base de instancia
- [ ] Métodos — funciones con `self` (dirección de instancia) como primer arg
- [ ] Constructor — reservar espacio en RAM, llamar a `init`
**Nota**: Requiere un allocator en runtime para reservar memoria dinámica en RAM.
### Estrategia de allocator en runtime
Hay dos opciones, de menor a mayor complejidad:
**Opción A: Bump allocator (recomendado para empezar)**
La más simple. Una dirección de RAM fija (ej: `RAM[0x00FF]`) actúa como "heap pointer" que empieza al final de las variables estáticas. Cada asignación avanza el pointer. No tiene `free`.
```asm
; alloc(size) — size en REG1, retorna dirección en REG0
ADD #0x00FF, #0, REG13 ; REG13 = dirección del heap_ptr
ADD REG12, #0, REG0 ; REG0 = heap_ptr actual (dirección a retornar)
ADD REG12, REG1, REG12 ; REG12 = heap_ptr + size
RSTR ; guardar nuevo heap_ptr en RAM[0x00FF]
; REG0 = dirección del bloque asignado
```
~4 instrucciones. Suficiente para strings literales y concatenaciones simples.
**Opción B: Allocator con metadata (como `allocator.h`, pero en ASM del CPU v2)**
Mismo diseño conceptual que `src/memory/allocator.h` pero implementado como rutina en ensamblador del CPU v2:
- Cada bloque en RAM: `[size:16][in_use:16][payload...]`
- Loop que recorre bloques con comparaciones + saltos (first-fit)
- `free` marca `in_use = 0`
- ~30-50 instrucciones del CPU v2
Solo necesario si se van a liberar strings (reasignar variables string, concatenaciones temporales).
**Recomendación**: Empezar con bump allocator. Si más adelante se necesita `free`, implementar opción B usando el diseño de `allocator.h` como referencia conceptual.
**Test**: `str.j`, `classes.j`.
---
## Diagrama de dependencias
```
Fase 0 (infraestructura)
└── Fase 1 (constantes, asignación, print)
└── Fase 2 (aritmética)
└── Fase 3 (if/while)
└── Fase 4 (funciones)
└── Fase 5 (strings/objetos)
```
## Verificación por fase
| Fase | Archivos de test |
|------|-----------------|
| 1 | `simple.j` |
| 2 | `sum.j`, `resta.j` |
| 3 | `if.j`, `while.j` |
| 4 | `functions.j`, `custom_fn.j` |
| 5 | `str.j`, `classes.j` |
Comparar output generado (ASM + binario) con lo que la VM produce para la misma entrada.

103
mycpu.md Normal file
View File

@@ -0,0 +1,103 @@
# Motivación
A partir de completar el juego "Turing Complete" y tener un CPU de 8bit con arquitectura LEG funcional, me dispongo a crear un compilador para mi lenguaje "J-LANG".
# Estructura del CPU
Arquitectura: 8bit
PROM: 256 bytes
RAM: 256 bytes
STACK: 256 bytes
## Direcciones
0x00 REG0
0x01 REG1
0x02 REG2
0x03 REG3
0x04 REG4 | RAM VALUE
0x05 REG5 | RAM ADDR PTR
0x06 PROGRAM COUNTER
0x07 INPUT/OUTPUT
### Registros basicos
Desde: 0x00
Hasta: 0x03
Son registros que almacenan 1 byte.
### Registros conectados a la RAM
Estos registros hacen de "puente" con la ram.
0x05 REG5 es el counter de la ram.
0x04 REG4 es el valor que contiene RAM[counter(0x05)]
### input/output
Son 1 byte de entrada y otro de salida para interactuar con el juego "Turing Complete"
## OPCODES
Las instrucciones de este cpu tienen que tener un tamaño de 4 bytes cada una.
Se permiten dos modos de direccionamiento, desde un registro o modo inmediato.
- Desde registro: Se usa el byte para indicar el registro que guarda el valor
- Inmediato: Se usa el byte como valor directamente
La estructura de una instrucción es:
[OPCODE] [INPUT0] [INPUT1] [TARGET]
El ultimo byte "target" indica en que registro debe guardarse el resultado de la instruccion.
En los opcodes condicionales, el ultimo byte (TARGET) indica el valor que se escribirá en el PROGRAM_COUNTER si se cumple la condicion.
======== ALU ========
0x00 ADD r0 r1 t0
0x01 SUB r0 r1 t0
0x02 AND r0 r1 t0
0x03 OR r0 r1 t0
0x04 NOT r0 r1 t0
0x05 XOR r0 r1 t0
---------------------
0x40 ADD r0 #1 t0 ; # significa inmediato
0x41 SUB r0 #1 t0
0x42 AND r0 #1 t0
0x43 OR r0 #1 t0
0x44 NOT r0 #1 t0
0x45 XOR r0 #1 t0
--------------------
0x80 ADD #0 r1 t0
0x81 SUB #0 r1 t0
0x82 AND #0 r1 t0
0x83 OR #0 r1 t0
0x84 NOT #0 r1 t0
0x85 XOR #0 r1 t0
--------------------
0xC0 ADD #0 #1 t0
0xC1 SUB #0 #1 t0
0xC2 AND #0 #1 t0
0xC3 OR #0 #1 t0
0xC4 NOT #0 #1 t0
0xC5 XOR #0 #1 t0
====== CONDITIONAL ======
0x30 EQ r0 r1 pc ; equal
0x31 NEQ r0 r1 pc ; not_equal
0x32 LS r0 r1 pc ; less
0x33 LSE r0 r1 pc ; less_or_equal
0x34 GR r0 r1 pc ; greater
0x35 GRE r0 r1 pc ; greater_or_equal
======== RAM ========
0xE0 RAM_ST ?? ?? ?? ; store value in REG4 in REG5 position RAM[REG5] = REG4
0xE1 RAM_LD ?? ?? ?? ; no es usa en realidad, la ram siempre está haciendo output en la direccion 0x04
====== STACK ======
0x22 PUSH r0 ?? t0 ; ?? no se usa pero debe estar, t0 sobrescribe dicha direccion a 0
0x23 POP ?? ?? t0
0xE2 PUSH #0 ?? t0
0xE3 POP ?? ?? t0
====== FUNCTIONS ======
0x08 CALL r0 ?? ?? ; push de pc+1 en stack y setea pc al valor que contiene r0
0x09 RET ?? ?? ?? ; pop del stack y escribe el valor en el pc
0x88 CALL #0
0x89 RET ?? ?? ??

178
mycpu_v2.md Normal file
View File

@@ -0,0 +1,178 @@
# Especificaciones
Arquitectura: 16bit
Tamaño de instruccion: 16bit
PROM: Ilimitado
RAM: 1kB - 20kB
STACK: 256 - Ilimitado
# Registros
Cada registro puede almacenar 16 bits
| ADDR | NAME | NOTES |
| ---- | -------- | --------- |
| 0x00 | REG0 | |
| 0x01 | REG1 | |
| 0x02 | REG2 | |
| 0x03 | REG3 | |
| 0x04 | REG4 | |
| 0x05 | REG5 | |
| 0x06 | REG6 | |
| 0x07 | REG7 | |
| 0x08 | REG8 | |
| 0x09 | REG9 | |
| 0x0A | REG10 | |
| 0x0B | REG11 | |
| 0x0C | REG12 | RAM VALUE |
| 0x0D | REG13 | RAM ADDR |
| 0x0E | PC | |
| 0x0F | IN/OUT | |
# Opcodes
Las instrucciones en este CPU tienen un tamaño total de 8 bytes, es decir, 4 parametros de 16bit cada uno.
[OPCODE] [PARAM1] [PARAM2] [TARGET1]
PARAM1 y PARAM2 soportan 2 modos de direccionamiento:
- Modo registro
- Modo inmediato
TARGET1 indica el registro donde se guardará el resultado.
## ALU
| OPCODE | ADDR | PARAM1 | PARAM2 | TARGET1 | DESCRIPTION |
| ------ | ---- | ------ | ------ | ------- | ----------- |
| ADD | 0x00 | R0 | R1 | T1 | |
| SUB | 0x01 | R0 | R1 | T1 | |
| MUL | 0x02 | R0 | R1 | T1 | |
| DIV | 0x03 | R0 | R1 | T1 | |
| AND | 0x04 | R0 | R1 | T1 | |
| OR | 0x05 | R0 | R1 | T1 | |
| NOT | 0x06 | R0 | R1 | T1 | |
| NAND | 0x07 | R0 | R1 | T1 | |
| NOR | 0x08 | R0 | R1 | T1 | |
| XOR | 0x09 | R0 | R1 | T1 | |
| XNOR | 0x0A | R0 | R1 | T1 | |
| NEG | 0x0B | R0 | R1 | T1 | |
| - | 0x0C | | | | |
| - | 0x0D | | | | |
| - | 0x0E | | | | |
| - | 0x0F | | | | |
| ------ | ---- | ------ | ------ | ------- | ----------- |
| ADD | 0x40 | #0 | R1 | T1 | |
| SUB | 0x41 | #0 | R1 | T1 | |
| MUL | 0x42 | #0 | R1 | T1 | |
| DIV | 0x43 | #0 | R1 | T1 | |
| AND | 0x44 | #0 | R1 | T1 | |
| OR | 0x45 | #0 | R1 | T1 | |
| NOT | 0x46 | #0 | R1 | T1 | |
| NAND | 0x47 | #0 | R1 | T1 | |
| NOR | 0x48 | #0 | R1 | T1 | |
| XOR | 0x49 | #0 | R1 | T1 | |
| XNOR | 0x4A | #0 | R1 | T1 | |
| NEG | 0x4B | #0 | R1 | T1 | |
| - | 0x4C | | | | |
| - | 0x4D | | | | |
| - | 0x4E | | | | |
| - | 0x4F | | | | |
| ------ | ---- | ------ | ------ | ------- | ----------- |
| ADD | 0x80 | R0 | #1 | T1 | |
| SUB | 0x81 | R0 | #1 | T1 | |
| MUL | 0x82 | R0 | #1 | T1 | |
| DIV | 0x83 | R0 | #1 | T1 | |
| AND | 0x84 | R0 | #1 | T1 | |
| OR | 0x85 | R0 | #1 | T1 | |
| NOT | 0x86 | R0 | #1 | T1 | |
| NAND | 0x87 | R0 | #1 | T1 | |
| NOR | 0x88 | R0 | #1 | T1 | |
| XOR | 0x89 | R0 | #1 | T1 | |
| XNOR | 0x8A | R0 | #1 | T1 | |
| NEG | 0x8B | R0 | #1 | T1 | |
| - | 0x8C | | | | |
| - | 0x8D | | | | |
| - | 0x8E | | | | |
| - | 0x8F | | | | |
| ------ | ---- | ------ | ------ | ------- | ----------- |
| ADD | 0xC0 | #0 | #1 | T1 | |
| SUB | 0xC1 | #0 | #1 | T1 | |
| MUL | 0xC2 | #0 | #1 | T1 | |
| DIV | 0xC3 | #0 | #1 | T1 | |
| AND | 0xC4 | #0 | #1 | T1 | |
| OR | 0xC5 | #0 | #1 | T1 | |
| NOT | 0xC6 | #0 | #1 | T1 | |
| NAND | 0xC7 | #0 | #1 | T1 | |
| NOR | 0xC8 | #0 | #1 | T1 | |
| XOR | 0xC9 | #0 | #1 | T1 | |
| XNOR | 0xCA | #0 | #1 | T1 | |
| NEG | 0xCB | #0 | #1 | T1 | |
| - | 0xCC | | | | |
| - | 0xCD | | | | |
| - | 0xCE | | | | |
| - | 0xCF | | | | |
## CONDITIONALS
En los condicionales TARGET1 representa a la direccion del PC (Program Counter) que se saltará si se cumple la condicion.
| OPCODE | ADDR | PARAM1 | PARAM2 | TARGET1 | DESCRIPTION |
| ------ | ---- | ------ | ------ | ------- | ----------- |
| EQ | 0x10 | R0 | R1 | T1 | equal |
| NEQ | 0x11 | R0 | R1 | T1 | not equal |
| LS | 0x12 | R0 | R1 | T1 | less |
| LSE | 0x13 | R0 | R1 | T1 | less or eq |
| GR | 0x14 | R0 | R1 | T1 | greater |
| GRE | 0x15 | R0 | R1 | T1 |greater or eq|
| | 0x16 | R0 | R1 | T1 | |
| | 0x17 | R0 | R1 | T1 | |
| ------ | ---- | ------ | ------ | ------- | ----------- |
| EQ | 0x50 | #0 | R1 | T1 | equal |
| NEQ | 0x51 | #0 | R1 | T1 | not equal |
| LS | 0x52 | #0 | R1 | T1 | less |
| LSE | 0x53 | #0 | R1 | T1 | less or eq |
| GR | 0x54 | #0 | R1 | T1 | greater |
| GRE | 0x55 | #0 | R1 | T1 |greater or eq|
| | 0x56 | #0 | R1 | T1 | |
| | 0x57 | #0 | R1 | T1 | |
| ------ | ---- | ------ | ------ | ------- | ----------- |
| EQ | 0x90 | R0 | #1 | T1 | equal |
| NEQ | 0x91 | R0 | #1 | T1 | not equal |
| LS | 0x92 | R0 | #1 | T1 | less |
| LSE | 0x93 | R0 | #1 | T1 | less or eq |
| GR | 0x94 | R0 | #1 | T1 | greater |
| GRE | 0x95 | R0 | #1 | T1 |greater or eq|
| | 0x96 | R0 | #1 | T1 | |
| | 0x97 | R0 | #1 | T1 | |
| ------ | ---- | ------ | ------ | ------- | ----------- |
| EQ | 0xD0 | #0 | #1 | T1 | equal |
| NEQ | 0xD1 | #0 | #1 | T1 | not equal |
| LS | 0xD2 | #0 | #1 | T1 | less |
| LSE | 0xD3 | #0 | #1 | T1 | less or eq |
| GR | 0xD4 | #0 | #1 | T1 | greater |
| GRE | 0xD5 | #0 | #1 | T1 |greater or eq|
| | 0xD6 | #0 | #1 | T1 | |
| | 0xD7 | #0 | #1 | T1 | |
## CONTROL UNIT
| OPCODE | ADDR | PARAM1 | PARAM2 | TARGET1 | DESCRIPTION |
| ------ | ---- | ------ | ------ | ------- | ----------- |
| RSTR | 0x18 | -- | -- | - | |
| PUSH | 0x19 | R1 | -- | - | |
| POP | 0x1A | -- | -- | T1 | |
| ------ | ---- | ------ | ------ | ------- | ----------- |
| PUSH | 0x59 | #1 | -- | - | |
## FUNCTIONS
| OPCODE | ADDR | PARAM1 | PARAM2 | TARGET1 | DESCRIPTION |
| ------ | ---- | ------ | ------ | ------- | ----------- |
| CALL | 0x20 | R1 | -- | - | |
| RET | 0x21 | -- | -- | - | |
| HALT | 0x22 | -- | -- | - | |
| ------ | ---- | ------ | ------ | ------- | ----------- |
| CALL | 0x60 | #1 | -- | - | |

3
projects/mycpu/assign.j Normal file
View File

@@ -0,0 +1,3 @@
x = 10
y = 512
z = x + y

View File

@@ -0,0 +1,6 @@
g = 2
fn suma(x, y):
return x + y
x = suma(5, 2) - g + 1

12
projects/mycpu/ifs.j Normal file
View File

@@ -0,0 +1,12 @@
counter = 0
fn inc():
counter = counter + 1
fn main():
if counter < 30:
inc()
main()
main()

4
projects/mycpu/while.j Normal file
View File

@@ -0,0 +1,4 @@
counter = 0
while counter < 65000:
counter = counter + 1

BIN
run.exe

Binary file not shown.

504
src/backend/mycpu/gencode.h Normal file
View File

@@ -0,0 +1,504 @@
#ifndef JLANG_MYCPU_H
#define JLANG_MYCPU_H
#include "../../frontend/parser.h"
#include "opcodes.h"
#define CPU_NOT_FOUND 0xFFFF
typedef struct {
char *name;
short entry_point;
int param_count;
char **param_names;
} CPUFunctionEntry;
typedef struct {
CPUInstruction code[4096];
int code_count;
char *names[256];
unsigned short name_addr[256];
int name_count;
CPUFunctionEntry functions[64];
int func_count;
unsigned short ram_offset;
char *current_fn; // NULL si estamos en global
char **comments[4096];
int comments_per_code[4096];
} CPUChunk;
unsigned short cpu_malloc(CPUChunk *chunk, size_t size) {
// Save current offset
short current_addr = chunk->ram_offset;
// Increase offset
chunk->ram_offset += size;
return current_addr;
}
short cpu_emit(CPUChunk *chunk, CPUInstruction instr) {
chunk->code[chunk->code_count++] = instr;
return chunk->code_count - 1;
}
CPUInstruction cpu_make_instruction(CPUOpCode op) {
CPUInstruction instr = {0};
instr.op = op;
return instr;
}
unsigned short cpu_find_name(CPUChunk *chunk, char *name) {
for (int i = 0; i < chunk->name_count; i++) {
if (strcmp(chunk->names[i], name) == 0) {
return chunk->name_addr[i];
}
}
return CPU_NOT_FOUND;
}
unsigned short cpu_add_name(CPUChunk *chunk, char *name) {
for (int i = 0; i < chunk->name_count; i++) {
if (strcmp(chunk->names[i], name) == 0) {
return chunk->name_addr[i];
}
}
chunk->names[chunk->name_count++] = name;
// Asignar hueco en la ram
chunk->name_addr[chunk->name_count - 1] = cpu_malloc(chunk, 1);
return chunk->name_addr[chunk->name_count - 1];
}
void cpu_comment(CPUChunk *chunk, char *comment) {
unsigned short pc = chunk->code_count;
// Get comment count for pc
int total_comments = chunk->comments_per_code[pc];
chunk->comments_per_code[pc] += 1;
if (total_comments == 0) {
// Allocate space for comments
chunk->comments[pc] = malloc(sizeof(char *) * 64);
}
chunk->comments[pc][total_comments] = comment;
}
short cpu_compile_node(CPUChunk *chunk, ASTNode *node, int depth) {
if (depth > 12) {
printf("error: max register depth reached\n");
exit(1);
}
switch (node->type) {
case NODE_INT_LIT: {
// Mostrar comentario
char comment[64];
snprintf(comment, sizeof(comment), "NODE_INT_LIT %d", node->data.int_val);
cpu_comment(chunk, strdup(comment));
// Cargar inmediato en REG[depth]
CPUInstruction instr = cpu_make_instruction(II_ADD);
instr.param1 = node->data.int_val;
instr.param2 = 0;
instr.target = depth; // REG[depth]
cpu_emit(chunk, instr);
break;
}
case NODE_VAR: {
// Leer de ram a REG[depth]
// Obtener addr de la variable de la tabla o crearla
unsigned short var_addr;
if (chunk->current_fn) {
char mangled[128];
snprintf(mangled, sizeof(mangled), "%s.%s", chunk->current_fn,
node->data.string_val);
// Primero buscar variable local de la funcion
var_addr = cpu_find_name(chunk, strdup(mangled));
if (var_addr == CPU_NOT_FOUND) {
// Si no existe buscamos la variable global
var_addr = cpu_add_name(chunk, node->data.string_val);
}
} else {
var_addr = cpu_add_name(chunk, node->data.string_val);
}
// Add comment
cpu_comment(chunk, "REG13 = var_addr");
// REG13 = var_addr
CPUInstruction set_addr = cpu_make_instruction(II_ADD);
set_addr.param1 = var_addr;
set_addr.param2 = 0;
set_addr.target = 0x0D;
cpu_emit(chunk, set_addr);
// REG12 -> REG[DEPTH]
CPUInstruction mov_val = cpu_make_instruction(RI_ADD);
mov_val.param1 = 0x0C;
mov_val.param2 = 0;
mov_val.target = depth;
cpu_emit(chunk, mov_val);
break;
}
case NODE_ASSIGN: {
char comment[64];
snprintf(comment, sizeof(comment), "ASSIGN %s", node->data.assign.name);
cpu_comment(chunk, strdup(comment));
// 1. Compilar la expresion, el resultado va a REG[depth]
cpu_compile_node(chunk, node->data.assign.value, depth);
// 2. Obtener direccion ram de la variable
unsigned short addr;
if (chunk->current_fn) {
char mangled[128];
snprintf(mangled, sizeof(mangled), "%s.%s", chunk->current_fn,
node->data.assign.name);
addr = cpu_find_name(chunk, strdup(mangled));
if (addr == CPU_NOT_FOUND) {
addr = cpu_add_name(chunk, node->data.assign.name);
}
} else {
addr = cpu_add_name(chunk, node->data.assign.name);
}
// 3. REG13 = addr
CPUInstruction set_addr = cpu_make_instruction(II_ADD);
set_addr.param1 = addr;
set_addr.param2 = 0;
set_addr.target = 0x0D;
cpu_emit(chunk, set_addr);
// 4. REG12 = REG[depth]
CPUInstruction set_value = cpu_make_instruction(RI_ADD);
set_value.param1 = depth; // REG[depth]
set_value.param2 = 0;
set_value.target = 0x0C;
cpu_emit(chunk, set_value);
// 5. RSTR -> RAM[REG13] = REG12 -> STORE RAM
cpu_emit(chunk, cpu_make_instruction(RR_STR));
break;
}
case NODE_CALL: {
char comment[64];
snprintf(comment, sizeof(comment), "NODE_CALL %s", node->data.call.name);
cpu_comment(chunk, strdup(comment));
// Pushear argumentos al stack
for (int i = 0; i < node->data.call.arg_count; i++) {
cpu_compile_node(chunk, node->data.call.args[i], depth);
// push reg[depth]
cpu_comment(chunk, "RI_PUSH");
CPUInstruction push_arg = cpu_make_instruction(RI_PUSH);
push_arg.param1 = depth;
push_arg.target =
0xff; // si se deja en 0, el push sobrescribe ese registro a 0
cpu_emit(chunk, push_arg);
}
// Buscar funcion en la tabla de funciones
unsigned short fn_addr = CPU_NOT_FOUND;
for (int i = 0; i < chunk->func_count; i++) {
if (strcmp(chunk->functions[i].name, node->data.call.name) == 0) {
fn_addr = chunk->functions[i].entry_point;
}
}
if (fn_addr == CPU_NOT_FOUND) {
printf("error: funcion '%s' no definida\n", node->data.call.name);
exit(1);
}
cpu_comment(chunk, "IR_CALL");
CPUInstruction call_fn = cpu_make_instruction(IR_CALL);
call_fn.param1 = fn_addr * 4;
cpu_emit(chunk, call_fn);
if (depth != 0) {
CPUInstruction mov = cpu_make_instruction(RI_ADD);
mov.param1 = 0;
mov.param2 = 0;
mov.target = depth;
cpu_emit(chunk, mov);
}
break;
}
case NODE_BLOCK: {
// Iterar statments y compilar
for (int i = 0; i < node->data.block.count; i++) {
cpu_compile_node(chunk, node->data.block.stmts[i], depth);
}
break;
}
case NODE_RETURN: {
if (node->data.ret.value) {
// Por convencion el resultado va a REG0
cpu_compile_node(chunk, node->data.ret.value, 0);
}
CPUInstruction ret = cpu_make_instruction(RR_RET);
ret.target = 0xff;
cpu_emit(chunk, ret);
break;
}
case NODE_BINOP: {
cpu_compile_node(chunk, node->data.binop.left, depth);
cpu_compile_node(chunk, node->data.binop.right, depth + 1);
CPUOpCode op;
switch (node->data.binop.op) {
case '+': {
op = RR_ADD;
break;
}
case '-': {
op = RR_SUB;
break;
}
case '*': {
op = RR_MUL;
break;
}
case '/': {
op = RR_DIV;
break;
}
default: {
op = RR_ADD;
break;
}
}
CPUInstruction binop = cpu_make_instruction(op);
binop.param1 = depth;
binop.param2 = depth + 1;
binop.target = depth;
cpu_emit(chunk, binop);
break;
}
case NODE_FN_DEF: {
char comment[64];
snprintf(comment, sizeof(comment), "NODE_FN_DEF %s", node->data.fn_def.name);
cpu_comment(chunk, strdup(comment));
// emitir jmp para ignorar la funcion por defecto
CPUInstruction jump = cpu_make_instruction(II_ADD);
jump.param1 = 0x00;
jump.param2 = 0x00;
jump.target = 0x0E;
short jump_idx = cpu_emit(chunk, jump);
// Registrar entrypoint
short entry_point = chunk->code_count;
CPUFunctionEntry *fn = &chunk->functions[chunk->func_count++];
fn->name = node->data.fn_def.name;
fn->entry_point = entry_point;
fn->param_count = node->data.fn_def.param_count;
fn->param_names = node->data.fn_def.params;
CPUInstruction pop_ret = cpu_make_instruction(RR_POP);
pop_ret.target = 0x0B; // REG11 como temp
cpu_emit(chunk, pop_ret);
// Pop de los params del stack a RAM
for (int i = node->data.fn_def.param_count - 1; i >= 0; i--) {
// POP -> reg0
CPUInstruction pop = cpu_make_instruction(RR_POP);
pop.target = 0x00;
cpu_emit(chunk, pop);
// guardar en ram como variable local
char *param_name = node->data.fn_def.params[i];
char mangled[128];
snprintf(mangled, sizeof(mangled), "%s.%s", node->data.fn_def.name,
param_name);
short addr = cpu_add_name(chunk, strdup(mangled));
CPUInstruction set_addr = cpu_make_instruction(II_ADD);
set_addr.param1 = addr;
set_addr.target = 0x0D;
cpu_emit(chunk, set_addr);
CPUInstruction set_val = cpu_make_instruction(RI_ADD);
set_val.param1 = 0x00;
set_val.target = 0x0C;
cpu_emit(chunk, set_val);
cpu_emit(chunk, cpu_make_instruction(RR_STR));
}
CPUInstruction push_ret = cpu_make_instruction(RR_PUSH);
push_ret.param1 = 0x0B;
cpu_emit(chunk, push_ret);
chunk->current_fn = node->data.fn_def.name;
// Compilar body
cpu_compile_node(chunk, node->data.fn_def.body, 0);
chunk->current_fn = NULL;
// RET implicito
cpu_emit(chunk, cpu_make_instruction(RR_RET));
// Backpatch del jump
chunk->code[jump_idx].param1 = chunk->code_count * 4;
break;
}
case NODE_IF: {
cpu_comment(chunk, "NODE_IF");
ASTNode *cond = node->data.if_statement.cond;
// Compilar miembros de la expresion y mover a depth y depth+1
cpu_compile_node(chunk, cond->data.binop.left, depth);
cpu_compile_node(chunk, cond->data.binop.right, depth + 1);
// Opcode inverso (saltar si la conficion es falsa)
CPUOpCode skip_op;
switch (cond->data.binop.op) {
case '>': {
skip_op = RR_LSE;
break;
}
case '<': {
skip_op = RR_GRE;
break;
}
case 'e': {
skip_op = RR_NEQ;
break;
}
case 'n': {
skip_op = RR_EQ;
break;
}
default: {
printf("error: binop op not supported '%c'\n", cond->data.binop.op);
exit(1);
}
}
// Emitir salto con placeholder
CPUInstruction jmp = cpu_make_instruction(skip_op);
jmp.param1 = depth;
jmp.param2 = depth + 1;
jmp.target = 0;
short jmp_idx = cpu_emit(chunk, jmp);
// Compilar body
cpu_compile_node(chunk, node->data.if_statement.body, depth);
// Backpatch
chunk->code[jmp_idx].target = chunk->code_count * 4;
break;
}
case NODE_WHILE: {
cpu_comment(chunk, "NODE_WHILE");
// Guardar entrada al bucle
short entry = chunk->code_count * 4;
// chequear condicional, si no se cumple, salir del bucle
ASTNode *cond = node->data.while_loop.cond;
// Compilar miembros de la expresion y mover a depth y depth+1
cpu_compile_node(chunk, cond->data.binop.left, depth);
cpu_compile_node(chunk, cond->data.binop.right, depth + 1);
// Opcode inverso (saltar si la conficion es falsa)
CPUOpCode skip_op;
switch (cond->data.binop.op) {
case '>': {
skip_op = RR_LSE;
break;
}
case '<': {
skip_op = RR_GRE;
break;
}
case 'e': {
skip_op = RR_NEQ;
break;
}
case 'n': {
skip_op = RR_EQ;
break;
}
default: {
printf("error: binop op not supported '%c'\n", cond->data.binop.op);
exit(1);
}
}
// Emitir salto con placeholder
CPUInstruction jmp = cpu_make_instruction(skip_op);
jmp.param1 = depth;
jmp.param2 = depth + 1;
jmp.target = 0;
short jmp_idx = cpu_emit(chunk, jmp);
// Compilar body
cpu_compile_node(chunk, node->data.while_loop.body, depth);
// Saltar al principio
CPUInstruction entry_jmp = cpu_make_instruction(II_ADD);
entry_jmp.param1 = entry;
entry_jmp.target = 0x0E;
cpu_emit(chunk, entry_jmp);
// Backpatch
chunk->code[jmp_idx].target = chunk->code_count * 4;
break;
}
default:
break;
}
return -1;
}
CPUChunk *compileAST(ASTNode *root) {
CPUChunk *chunk = (CPUChunk *)malloc(sizeof(CPUChunk));
memset(chunk, 0, sizeof(CPUChunk));
cpu_compile_node(chunk, root, 0);
// Añadir HALT
cpu_comment(chunk, "HALT");
CPUInstruction halt = cpu_make_instruction(RR_HALT);
cpu_emit(chunk, halt);
return chunk;
}
void cpu_print_code(CPUChunk *chunk, int show_comments) {
printf("========= BINARY =========\n");
for (int i = 0; i < chunk->code_count; i++) {
if (show_comments) {
// Mostrar comentarios
for (int c = 0; c < chunk->comments_per_code[i]; c++) {
printf("# %s\n", chunk->comments[i][c]);
}
}
CPUInstruction instr = chunk->code[i];
printf("0x%04X 0x%04X 0x%04X 0x%04X\n", instr.op, instr.param1,
instr.param2, instr.target);
}
}
#endif

129
src/backend/mycpu/opcodes.h Normal file
View File

@@ -0,0 +1,129 @@
#ifndef JLANG_MYCPU_OOCODES_H
#define JLANG_MYCPU_OOCODES_H
typedef enum {
// Register, Register
RR_ADD,
RR_SUB,
RR_MUL,
RR_DIV,
RR_AND,
RR_OR,
RR_NOT,
RR_NAND,
RR_NOR,
RR_XOR,
RR_XNOR,
RR_NEG,
RR_EQ = 0x10,
RR_NEQ,
RR_LS,
RR_LSE,
RR_GR,
RR_GRE,
RR_STR = 0x18,
RR_PUSH,
RR_POP,
RR_CALL = 0x20,
RR_RET,
RR_HALT,
// Inmediate, Register
IR_ADD = 0x40,
IR_SUB,
IR_MUL,
IR_DIV,
IR_AND,
IR_OR,
IR_NOT,
IR_NAND,
IR_NOR,
IR_XOR,
IR_XNOR,
IR_NEG,
IR_EQ = 0x50,
IR_NEQ,
IR_LS,
IR_LSE,
IR_GR,
IR_GRE,
IR_STR = 0x58,
IR_PUSH,
IR_POP,
IR_CALL = 0x60,
IR_RET,
IR_HALT,
// Register, Inmediate
RI_ADD = 0x80,
RI_SUB,
RI_MUL,
RI_DIV,
RI_AND,
RI_OR,
RI_NOT,
RI_NAND,
RI_NOR,
RI_XOR,
RI_XNOR,
RI_NEG,
RI_EQ = 0x90,
RI_NEQ,
RI_LS,
RI_LSE,
RI_GR,
RI_GRE,
RI_STR = 0x98,
RI_PUSH,
RI_POP,
RI_CALL = 0xA0,
RI_RET,
RI_HALT,
// Inmediate, Inmediate
II_ADD = 0xC0,
II_SUB,
II_MUL,
II_DIV,
II_AND,
II_OR,
II_NOT,
II_NAND,
II_NOR,
II_XOR,
II_XNOR,
II_NEG,
II_EQ = 0xD0,
II_NEQ,
II_LS,
II_LSE,
II_GR,
II_GRE,
II_STR = 0xD8,
II_PUSH,
II_POP,
II_CALL = 0xE0,
II_RET,
II_HALT,
} CPUOpCode;
typedef struct {
CPUOpCode op;
unsigned short param1;
unsigned short param2;
unsigned short target;
} CPUInstruction;
#endif

View File

@@ -115,8 +115,21 @@ Token *tokenize(const char *source, int *token_count) {
tokens[count++] = make_token(TOK_SLASH, "/");
pos++;
} else if (c == '=') {
tokens[count++] = make_token(TOK_ASSIGN, "=");
pos++;
if (source[pos + 1] == '=') {
tokens[count++] = make_token(TOK_EQ, "==");
pos += 2;
} else {
tokens[count++] = make_token(TOK_ASSIGN, "=");
pos++;
}
} else if (c == '!') {
if (source[pos + 1] == '=') {
tokens[count++] = make_token(TOK_NEQ, "!=");
pos += 2;
} else {
printf("WARN: caracter no reconocido '!' en pos %d\n", pos);
exit(1);
}
} else if (c == '<') {
tokens[count++] = make_token(TOK_LT, "<");
pos++;

View File

@@ -206,8 +206,16 @@ ASTNode *parse_expr(Token *tokens) {
ASTNode *left = parse_term(tokens);
while (tokens[pos].type == TOK_PLUS || tokens[pos].type == TOK_MINUS ||
tokens[pos].type == TOK_LT || tokens[pos].type == TOK_GT) {
char op = tokens[pos].value[0]; // +,-,*,/
tokens[pos].type == TOK_LT || tokens[pos].type == TOK_GT ||
tokens[pos].type == TOK_EQ || tokens[pos].type == TOK_NEQ) {
char op;
if (tokens[pos].type == TOK_EQ) {
op = 'e';
} else if (tokens[pos].type == TOK_NEQ) {
op = 'n';
} else {
op = tokens[pos].value[0]; // +,-,*,/
}
pos++;
ASTNode *right = parse_term(tokens);

View File

@@ -1,10 +1,11 @@
#include "backend/eval/eval.h"
#include "backend/bytecode/compiler.h"
#include "backend/bytecode/vm.h"
#include "backend/mycpu/gencode.h"
int main(int argc, char **argv) {
if (argc != 3) {
printf("usage: %s eval|vm|asm <path to .j file>\n", argv[0]);
printf("usage: %s eval|vm|asm|mycpu <path to .j file>\n", argv[0]);
exit(1);
}
// Creamos un allocator
@@ -26,12 +27,13 @@ int main(int argc, char **argv) {
fclose(fptr);
printf("=== CODE ===\n");
printf("%s\n", buff);
// Lexer test
int totalTokens = 0;
Token *tokens = tokenize(buff, &totalTokens);
printf("totalTokens=%d\n", totalTokens);
printf("=== INFO ===\n");
ASTNode *block = parse(tokens, totalTokens);
ast_debug(block);
@@ -51,6 +53,10 @@ int main(int argc, char **argv) {
// printf("\n");
// JLANG_visualize(allocPtr);
} else if (strcmp(argv[1], "mycpu") == 0){
CPUChunk* chunk = compileAST(block);
cpu_print_code(chunk, 1);
} else {
printf("panic: WIP\n");
}