diff --git a/docs/todo.md b/docs/todo.md new file mode 100644 index 0000000..0069912 --- /dev/null +++ b/docs/todo.md @@ -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. diff --git a/mycpu.md b/mycpu.md new file mode 100644 index 0000000..7f69618 --- /dev/null +++ b/mycpu.md @@ -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 ?? ?? ?? diff --git a/mycpu_v2.md b/mycpu_v2.md new file mode 100644 index 0000000..b397f5d --- /dev/null +++ b/mycpu_v2.md @@ -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 | -- | - | | \ No newline at end of file diff --git a/projects/mycpu/assign.j b/projects/mycpu/assign.j new file mode 100644 index 0000000..005e579 --- /dev/null +++ b/projects/mycpu/assign.j @@ -0,0 +1,3 @@ +x = 10 +y = 512 +z = x + y \ No newline at end of file diff --git a/projects/mycpu/functions.j b/projects/mycpu/functions.j new file mode 100644 index 0000000..7e33c25 --- /dev/null +++ b/projects/mycpu/functions.j @@ -0,0 +1,6 @@ +g = 2 + +fn suma(x, y): + return x + y + +x = suma(5, 2) - g + 1 \ No newline at end of file diff --git a/projects/mycpu/ifs.j b/projects/mycpu/ifs.j new file mode 100644 index 0000000..0966c10 --- /dev/null +++ b/projects/mycpu/ifs.j @@ -0,0 +1,12 @@ +counter = 0 + +fn inc(): + counter = counter + 1 + +fn main(): + if counter < 30: + inc() + main() + + +main() \ No newline at end of file diff --git a/projects/mycpu/while.j b/projects/mycpu/while.j new file mode 100644 index 0000000..ca8988f --- /dev/null +++ b/projects/mycpu/while.j @@ -0,0 +1,4 @@ +counter = 0 + +while counter < 65000: + counter = counter + 1 diff --git a/run.exe b/run.exe index cb1ae37..6c86f8a 100644 Binary files a/run.exe and b/run.exe differ diff --git a/src/backend/mycpu/gencode.h b/src/backend/mycpu/gencode.h new file mode 100644 index 0000000..5853586 --- /dev/null +++ b/src/backend/mycpu/gencode.h @@ -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 \ No newline at end of file diff --git a/src/backend/mycpu/opcodes.h b/src/backend/mycpu/opcodes.h new file mode 100644 index 0000000..d37ac23 --- /dev/null +++ b/src/backend/mycpu/opcodes.h @@ -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 \ No newline at end of file diff --git a/src/frontend/lexer.h b/src/frontend/lexer.h index 1ba9e4c..fe53811 100644 --- a/src/frontend/lexer.h +++ b/src/frontend/lexer.h @@ -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++; diff --git a/src/frontend/parser.h b/src/frontend/parser.h index cada036..ba4aeff 100644 --- a/src/frontend/parser.h +++ b/src/frontend/parser.h @@ -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); diff --git a/src/main.c b/src/main.c index 31025e5..b86f6b1 100644 --- a/src/main.c +++ b/src/main.c @@ -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 \n", argv[0]); + printf("usage: %s eval|vm|asm|mycpu \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"); }