Compare commits
1 Commits
d8b4f9b2ea
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9610957f1b |
312
docs/todo.md
Normal file
312
docs/todo.md
Normal 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
103
mycpu.md
Normal 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
178
mycpu_v2.md
Normal 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
3
projects/mycpu/assign.j
Normal file
@@ -0,0 +1,3 @@
|
||||
x = 10
|
||||
y = 512
|
||||
z = x + y
|
||||
6
projects/mycpu/functions.j
Normal file
6
projects/mycpu/functions.j
Normal 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
12
projects/mycpu/ifs.j
Normal 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
4
projects/mycpu/while.j
Normal file
@@ -0,0 +1,4 @@
|
||||
counter = 0
|
||||
|
||||
while counter < 65000:
|
||||
counter = counter + 1
|
||||
504
src/backend/mycpu/gencode.h
Normal file
504
src/backend/mycpu/gencode.h
Normal 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
129
src/backend/mycpu/opcodes.h
Normal 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
|
||||
@@ -115,8 +115,21 @@ Token *tokenize(const char *source, int *token_count) {
|
||||
tokens[count++] = make_token(TOK_SLASH, "/");
|
||||
pos++;
|
||||
} else if (c == '=') {
|
||||
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++;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
10
src/main.c
10
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 <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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user