-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.cpp
More file actions
2125 lines (1984 loc) · 107 KB
/
main.cpp
File metadata and controls
2125 lines (1984 loc) · 107 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* VestaVM - Máquina Virtual Distribuida
*
* Copyright © 2026 David López.T (DesmonHak) (Castilla y León, ES)
* Licencia VMProject
*
* USO LIBRE NO COMERCIAL con atribución obligatoria.
* PROHIBIDO lucro sin permiso escrito.
*
* Descargo: Autor no responsable por modificaciones.
*/
#include <cstdlib>
#include <iostream>
#include <iomanip>
#include <cstring>
#include <fstream>
#include <sstream>
#include <thread>
#include <atomic>
#include <csignal>
#include <openssl/sha.h>
#include "cxxopts.hpp"
#include "cli/cli.h"
#include "cli/vsh.h"
#include "ir/ir_emitter.h"
#include "jit/auto_jit.h"
#include "runtime/proceso_runtime.h"
#include "cli/runtime_api_commands.h"
#include "util/assembler_multiprocess.h"
#include "vex/compiler.h"
#include "vex/comptime_vm.h" /* Phase MC.4 probe del ComptimeRuntime */
#include "util/sqlite_singleton.h"
#include "util/fs_utils.h"
#include "runtime/manager_runtime.h"
#include "gc/gc_heap.h"
#include "loader/loader.h"
#include "profiler/timer.h"
#include "distrib/dist_runtime.h"
#include "distrib/dist_debug.h"
#include "distrib/node_registry.h"
#include "debug/debugger.h"
#include "debug/auth.h"
#include "install/install.h"
#include <filesystem>
#include <map> // Phase MC.16: per-macro manifest map
#include <set> // Phase MC.16: manifest diff seen-set
#include <cctype> // Phase MC.16: isalnum en find_macro_ranges_with_names
#include <openssl/rand.h>
#ifdef VESTA_HAS_PREPROCESSOR
#include "preprocessor/preprocessor.h"
#endif
// flag global para el modo --dist-server; SIGINT lo pone a false
static std::atomic<bool> g_server_running{true};
static void on_dist_sigint(int) { g_server_running.store(false); }
/**
* @brief Construye la NodeAuthConfig a partir de los flags de autenticacion.
*
* Calcula SHA-256 del token en texto plano si se proporciono --dist-token.
* Copia las rutas TLS si se activo --dist-tls.
*
* @param token Token en texto plano (puede estar vacio).
* @param use_tls true si se activo --dist-tls.
* @param cert Ruta al certificado PEM del cliente.
* @param key Ruta a la clave privada PEM del cliente.
* @param ca Ruta al CA bundle PEM para verificar pares.
* @return NodeAuthConfig relleno.
*/
static distrib::NodeAuthConfig build_node_auth(
const std::string &token,
bool use_tls,
const std::string &cert,
const std::string &key,
const std::string &ca)
{
distrib::NodeAuthConfig auth{};
if (!token.empty()) {
auth.use_token = true;
SHA256(reinterpret_cast<const unsigned char *>(token.c_str()),
token.size(), auth.token_hash);
}
if (use_tls) {
auth.use_tls = true;
std::snprintf(auth.cert_path, sizeof(auth.cert_path), "%s", cert.c_str());
std::snprintf(auth.key_path, sizeof(auth.key_path), "%s", key.c_str());
std::snprintf(auth.ca_path, sizeof(auth.ca_path), "%s", ca.c_str());
}
return auth;
}
/**
* @brief Aplica la configuracion distribuida a una instancia VM.
*
* Reemplaza el dist_runtime minimo creado en VM::VM() por uno completamente
* configurado segun los flags --dist-* de la linea de comandos.
* Si --dist-port > 0 o --dist-discover esta activo, llama a start() para
* abrir el servidor VDP y/o el hilo de descubrimiento UDP.
* Registra cualquier nodo estatico indicado con --dist-add-node (formato IP:PUERTO).
*
* @param vm Instancia VM sobre la que se aplica la configuracion.
* @param result Resultado del parseo de cxxopts con todos los flags.
*/
static void apply_dist_config(runtime::VM *vm, const cxxopts::ParseResult &result)
{
const std::string token = result["dist-token"].as<std::string>();
const bool use_tls = result.count("dist-tls") > 0;
const std::string cert = result["dist-cert"].as<std::string>();
const std::string key = result["dist-key"].as<std::string>();
const std::string ca = result["dist-ca"].as<std::string>();
// construir configuracion del DistRuntime
distrib::DistRuntimeConfig cfg{};
cfg.local_node_id = result["dist-node-id"].as<uint64_t>();
cfg.vdp_listen_port = result["dist-port"].as<uint16_t>();
cfg.discover_port = result["dist-discover-port"].as<uint16_t>();
cfg.enable_discovery = result.count("dist-discover") > 0;
std::string name = result["dist-name"].as<std::string>();
if (!name.empty())
std::snprintf(cfg.local_node_name, sizeof(cfg.local_node_name), "%s", name.c_str());
else
std::snprintf(cfg.local_node_name, sizeof(cfg.local_node_name), "vm-%llu",
static_cast<unsigned long long>(vm->id));
cfg.server_auth = build_node_auth(token, use_tls, cert, key, ca);
// reemplazar el dist_runtime minimal por uno completamente configurado
vm->dist_runtime = std::make_unique<distrib::DistRuntime>(*vm, cfg);
// arrancar el servidor VDP y/o el descubrimiento si alguno esta habilitado
if (cfg.vdp_listen_port > 0 || cfg.enable_discovery) {
if (vm->dist_runtime->start()) {
std::string msg = "[dist] Servidor VDP iniciado";
if (cfg.vdp_listen_port)
msg += " en puerto " + std::to_string(cfg.vdp_listen_port);
if (cfg.enable_discovery)
msg += " con descubrimiento UDP (puerto " + std::to_string(cfg.discover_port) + ")";
vesta::scout() << msg << "\n";
} else {
std::cerr << "[dist] Error al iniciar el servidor VDP\n";
}
}
// registrar nodos estaticos proporcionados con --dist-add-node IP:PUERTO
if (result.count("dist-add-node")) {
distrib::NodeAuthConfig node_auth = build_node_auth(token, use_tls, cert, key, ca);
for (auto &spec : result["dist-add-node"].as<std::vector<std::string>>()) {
auto colon = spec.rfind(':');
if (colon == std::string::npos) {
std::cerr << "[dist] Formato invalido (esperado IP:PUERTO): " << spec << "\n";
continue;
}
std::string node_ip = spec.substr(0, colon);
uint16_t node_port = 0;
try { node_port = static_cast<uint16_t>(std::stoul(spec.substr(colon + 1))); }
catch (...) { std::cerr << "[dist] Puerto invalido en: " << spec << "\n"; continue; }
uint32_t idx = vm->dist_runtime->add_node(
node_ip.c_str(), node_port, node_auth, node_ip.c_str());
vesta::scout() << "[dist] Nodo registrado: " << node_ip << ":"
<< node_port << " (idx=" << idx << ")\n";
}
}
}
int main(int argc, char *argv[]) {
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__)
asm_multi_process::run_and_capture("chcp 65001");
#endif
/* registrar el hook auto-JIT en el runtime.
* El runtime (vesta_rt) tiene un function pointer nulo por defecto;
* el main aqui lo apunta al JIT trigger. Si el env var
* VESTA_JIT_THRESHOLD esta presente, el trigger compila funciones
* hot a codigo nativo. Sin env var, el
* threshold queda en UINT32_MAX y el hook es no-op aunque se llame. */
runtime::g_callvirt_post_hook = &jit::maybe_compile_method;
cxxopts::Options options("VMProject", "Virtual Machine Example");
options.add_options()
("h,help", "Mostrar ayuda")
("o,output", "Archivo de salida (sin extensión o completo)", cxxopts::value<std::string>())
("driver", "Compilar un directorio completo en paralelo", cxxopts::value<std::string>())
("worker", "Compilar un único archivo (modo interno)", cxxopts::value<std::string>())
("j,threads", "Número de hilos para el driver", cxxopts::value<int>()->default_value("0"))
("v,version", "Mostrar versión")
("m,mode", "Modo de ejecución (vm/jit)", cxxopts::value<std::string>()->default_value("vm"))
("list-arch", "Imprimir arquitecturas soportadas")
("asm-file", "Archivo ASM a ensamblar", cxxopts::value<std::string>())
("disasm-file", "Archivo binario a desensamblar", cxxopts::value<std::string>())
("arch", "Arquitectura para ensamblar/desensamblar", cxxopts::value<std::string>())
("save-output", "Guardar código ensamblado/desensamblado en archivo")
("output-prefix", "Prefijo/nombre base para archivos de salida",
cxxopts::value<std::string>()->default_value("out"))
("run", "Ejecutar un archivo .velb en la VM", cxxopts::value<std::string>())
("build", "Compilar un archivo .vel a .velb", cxxopts::value<std::string>())
("schedulers", "Número de schedulers para el comando run", cxxopts::value<size_t>()->default_value("1"))
("install", "Ejecutar el instalador interactivo (o con flags adicionales)")
("uninstall", "Desinstalar Vesta usando el manifest")
("repair", "Reparar la instalacion existente")
("silent", "Modo silencioso para install/uninstall")
("per-user", "Forzar instalacion per-user")
("system-wide","Forzar instalacion system-wide")
("prefix", "Directorio destino", cxxopts::value<std::string>())
("manifest", "Ruta a install_manifest.json", cxxopts::value<std::string>())
("stats", "Mostrar estadísticas de ejecución al finalizar (tiempo, MIPS)")
// ---- opciones de JIT ----
("jit-threshold", "Umbral de invocaciones de un metodo para disparar JIT (default: UINT32_MAX = JIT off; sugerido para test: 1)",
cxxopts::value<uint32_t>())
("jit-warn", "Imprimir warnings cada vez que el Selector encuentra una IR op no soportada (dedup por op+linea)")
("jit-stats", "Imprimir snapshot final de counters del JIT: compiled/unsupported/no_ir + threshold")
("jit-disasm", "Volcar hex bytes + disasm (Capstone) de cada funcion JIT-compilada a stderr")
// ---- opciones de runtime distribuido ----
("dist-port", "Puerto VDP del servidor distribuido (0 = sin servidor TCP)",
cxxopts::value<uint16_t>()->default_value("0"))
("dist-discover", "Activar descubrimiento UDP de nodos en la LAN")
("dist-discover-port","Puerto UDP para descubrimiento de nodos",
cxxopts::value<uint16_t>()->default_value("7790"))
("dist-name", "Nombre del nodo local (cadena identificativa)",
cxxopts::value<std::string>()->default_value(""))
("dist-node-id", "ID de 64 bits del nodo (0 = generar automaticamente)",
cxxopts::value<uint64_t>()->default_value("0"))
("dist-add-node", "Nodo estatico a registrar y conectar (formato IP:PUERTO, repetible)",
cxxopts::value<std::vector<std::string>>())
("dist-token", "Token de autenticacion en texto plano (se almacena como SHA-256)",
cxxopts::value<std::string>()->default_value(""))
("dist-tls", "Usar TLS en las conexiones VDP salientes y entrantes")
("dist-cert", "Ruta al certificado TLS del nodo local (PEM)",
cxxopts::value<std::string>()->default_value(""))
("dist-key", "Ruta a la clave privada TLS del nodo local (PEM)",
cxxopts::value<std::string>()->default_value(""))
("dist-ca", "Ruta al CA bundle TLS para verificar pares (PEM)",
cxxopts::value<std::string>()->default_value(""))
("dist-server", "Modo servidor distribuido puro: espera conexiones VDP sin ejecutar bytecode")
("dist-debug", "Activar trazas de depuracion del subsistema distribuido (RSPAWN, HALT, FUTURE_FULFILL)")
("script", "Ejecutar un fichero VestaShell (.vsh) y salir", cxxopts::value<std::string>())
("interprete", "Abrir el interprete interactivo VestaShell (REPL .vsh)")
("ir-file", "Compilar archivo .ir (SSA IR) a .vel y opcionalmente a .velb",
cxxopts::value<std::string>())
("ir-opt", "Nivel de optimizacion IR: 0=O0, 1=O1, 2=O2, 3=O3 (defecto: 1)",
cxxopts::value<int>()->default_value("1"))
("ir-emit-only", "Solo emitir el texto .vel; no compilar a .velb")
("vex", "Compilar archivo .vex (lenguaje Vex) a .velb",
cxxopts::value<std::string>())
("vex-emit-only", "Solo emitir el .vel intermedio del .vex; no compilar a .velb")
("vex-emit-ir", "Emitir el SSA IR del .vex (pre y post optimizacion) en <output>.ir; util para debug del frontend")
("vex-base", "VA base address para el modulo (hex, e.g. 0x10000000). Usado para plugins cargados via loadmodule, evita solapamiento con el caller (default 0x0).",
cxxopts::value<std::string>()->default_value("0x0"))
// Diagramas para debug y traceo del pipeline Vex. Soportan dos
// formatos seleccionables via --diagram-format:
// mermaid (default): escribe .mmd con bloque ```mermaid```;
// listo para VS Code / GitHub / mermaid.live.
// graphviz: escribe .dot con `digraph G { ... }`;
// listo para `dot -Tsvg foo.dot -o foo.svg`.
// both: escribe AMBOS formatos.
// El contenido es paralelo entre formatos: misma topologia, misma
// info por nodo (Graphviz lleva extra via tooltips/atributos DOT).
// --diagram-all genera los 4 diagramas (AST, IR pre, IR post, VEL)
// en el formato escogido.
("diagram-vex", "Generar diagrama del AST Vex post type-check (.ast.<ext>)")
("diagram-ir", "Generar diagrama del SSA IR pre-optimizacion (.ir.pre.<ext>)")
("diagram-ir-opt", "Generar diagrama del SSA IR post-optimizacion (.ir.post.<ext>)")
("diagram-vel", "Generar diagrama del bytecode .vel final (.vel.<ext>)")
("diagram-all", "Generar los 4 diagramas (vex, ir pre, ir post, vel) con sufijos correspondientes")
("diagram-format", "Formato de salida: mermaid | graphviz | both (default: mermaid). graphviz produce .dot listo para Graphviz; mermaid produce .mmd.",
cxxopts::value<std::string>()->default_value("mermaid"))
("gc-debug", "Activar trazas de debug del Garbage Collector a stderr (minor_gc, major_gc, sweep, release_handle, evacuate). Util para diagnosticar use-after-free o objetos colectados prematuramente. Alt: env VESTA_GC_DEBUG=1.")
("gc-debug-buffered","Activar modo BUFFERED del GC debug: ~100x mas rapido (buffer thread-local de 64KB) pero pierde las ultimas trazas en crash. Implica --gc-debug. Alt: env VESTA_GC_DEBUG_BUFFERED=1.")
("debug-port", "Activar el servidor de depuracion TCP en el puerto N (default 9229 si N=0). Soporta multiples clientes simultaneos y JSON via length-prefix framing. El servidor permanece disponible mientras la VM ejecuta; los clientes (e.g. tools/dbg_client.vsh) se conectan via tcp_connect, envian comandos JSON y reciben eventos asincronos (break/exit/exception/spawned). Sin este flag, el debugger NO se instancia y el coste runtime es exactamente cero. Cuando esta presente, el proceso main arranca PAUSADO en su primera instruccion para dar tiempo al cliente a conectarse y poner breakpoints; el cliente debe enviar 'continue 0' para arrancar la ejecucion.",
cxxopts::value<uint16_t>()->default_value("0"))
("server-mode", "Iniciar la VM como un servidor persistente de depuracion. Implica --debug-port (default 9229). No requiere --run: la VM se queda viva esperando comandos del debugger para cargar/ejecutar/matar procesos remotamente. Compatible con --schedulers, --dist-port, etc. Termina con SIGINT (Ctrl+C) o con el comando 'server_shutdown' del cliente. Comandos disponibles: load_velb, load_velb_bytes, kill_proc, server_info, server_shutdown, auth_login/logout/whoami, auth_create_user/delete_user/list_users/change_pass, fs_read/write/list/stat/delete/mkdir/rename.")
("server-port", "Puerto del servidor persistente (sinonimo de --debug-port en modo --server-mode). Default 9229.",
cxxopts::value<uint16_t>()->default_value("0"))
("server-root", "Directorio raiz del sandbox de filesystem en modo --server-mode. Todas las rutas usadas por fs_* y load_velb se resuelven contra este directorio; cualquier intento de salir (con '..' o rutas absolutas) se rechaza. Sin este flag NO hay sandbox (modo back-compat: cualquier ruta absoluta del servidor es accesible). Recomendado en despliegues compartidos.",
cxxopts::value<std::string>()->default_value(""))
("auth-db", "Ruta al fichero SQLite con la tabla vm_users (usuarios, hashes PBKDF2 y roles) en modo --server-mode. Sin este flag la autenticacion esta desactivada y cualquier cliente puede invocar cualquier comando (modo desarrollo).",
cxxopts::value<std::string>()->default_value(""))
("admin-user", "Nombre del usuario admin a crear automaticamente cuando la base de datos --auth-db esta vacia. Default 'admin'.",
cxxopts::value<std::string>()->default_value("admin"))
("admin-password", "Contrasenya del usuario admin a crear automaticamente. Si se omite y --auth-db apunta a una BD vacia, se genera una contrasenya aleatoria de 24 caracteres y se imprime en stdout UNA UNICA VEZ.",
cxxopts::value<std::string>()->default_value(""))
("no-repl", "Desactivar el REPL interactivo local en modo --server-mode (modo headless). Sin este flag, el operador local mantiene un prompt vesta> que comparte el mismo runtime con los clientes remotos; con el, el proceso solo escucha el socket TCP.")
("vex-debug", "Emitir comentarios `// @line N` en el .vel intermedio del compilador Vex y, cuando se integre la pipeline completa de debug section (Phase 2), embeber la tabla bytecode_offset -> (file, line) en el .velb final. Sin este flag, el .vel/.velb no contienen info de debug -> el ejecutable es mas pequeno y el frontend NO genera datos extra. Con el flag, el cliente del debugger puede setear breakpoints por linea Vex (`b file.vex:42`) en lugar de solo por addr.")
("port", "Transpilar el IR a codigo fuente del lenguaje destino y escribir a <output>.<ext> (e.g. .c). Valores actuales: 'c'. Futuro: 'java', 'js', 'rust'. Con --port=c se genera codigo C99 portable listo para compilar con gcc/clang -O3 -std=c11 SIN dependencias de VestaVM (a menos que --port-gc=vesta). Implica --vex (se aplica al pipeline Vex post-optimizacion).",
cxxopts::value<std::string>())
("port-gc", "Modelo de memoria del codigo portado: none|vesta|boehm. none (default): malloc/free + sin GC. Las IR ops de objetos GC (NEWOBJ/strings) emiten stub. vesta: enlazar contra vesta_rt.lib. boehm: enlazar contra libgc (placeholder).",
cxxopts::value<std::string>()->default_value("none"))
("port-exc", "Manejo de excepciones del codigo portado: none|setjmp|returncode. none: throw -> abort. setjmp (default): longjmp portable. returncode: codigo de retorno + propagacion explicita (no impl v1).",
cxxopts::value<std::string>()->default_value("setjmp"))
("port-types", "Estilo de tipos del codigo portado: stdint (default) usa int8_t/uint64_t/etc de stdint.h. builtin usa char/int/long del C clasico (menos portable).",
cxxopts::value<std::string>()->default_value("stdint"))
("port-strings", "Modelo de strings: raw (const char* solo literales) | managed (default: VexString con tracking per-thread).",
cxxopts::value<std::string>()->default_value("managed"))
("port-freestanding", "Generar C freestanding (sin includes automaticos de stdio/stdlib/math). El usuario debe proveer vex_throw + vex_panic_with_str. Para bootloaders, kernels, firmware.")
("port-stdlib-dir", "Path al directorio stdlib/port/c con los snippets .v.c (autodetect si vacio).",
cxxopts::value<std::string>()->default_value(""))
("port-arch", "Target CPU: '' (portable, default) | native | x86-64-v2 | x86-64-v3 | x86-64-v4. Emite #pragma GCC target con flags agresivas (AVX2/FMA/BMI2 segun nivel).",
cxxopts::value<std::string>()->default_value(""))
("port-no-aggressive", "Desactivar atributos agresivos (const/cold/restrict/always_inline + __builtin_expect/unreachable). Default: activado.")
("instrument", "Instrumentacion en el IR Vex (heredada por bytecode VM, JIT, port C, port futuros): none (default) | trace (calls a vex_trace:enter/exit por funcion) | profile (timing per-funcion).",
cxxopts::value<std::string>()->default_value("none"))
#ifdef VESTA_HAS_PREPROCESSOR
("preprocess-only", "Solo preprocesar un .vel y mostrar/guardar el resultado (debug)", cxxopts::value<std::string>())
#endif
("keep-labels", "Mantener los nombres de label en el .velb (por defecto se eliminan: el loader no los usa y reducen ~9% el tamano)")
("emit-map", "Generar archivo .velb-map con info de simbolos y secciones (debug). Off por defecto: cuesta ~60% del tiempo del linker")
;
// BUG FIX: Args posicionales y allow_unrecognised DEBEN configurarse
// ANTES de @c options.parse(...). El bug anterior registraba el
// option `positional` y @c parse_positional DESPUES del parse, asi
// que cualquier `vm --script foo.vsh arg1 arg2` perdia arg1/arg2 y
// ARGV del script quedaba con solo el path del script. Se mueve
// toda la configuracion arriba; esto es prerequisito de @c parse.
options.add_options()("positional", "Argumentos posicionales",
cxxopts::value<std::vector<std::string>>());
options.parse_positional({"positional"});
options.positional_help("[args...]");
options.allow_unrecognised_options(); // para flags sin que aun hay que parsear
auto result = options.parse(argc, argv);
// activar trazas de depuracion del subsistema distribuido si se paso --dist-debug
if (result.count("dist-debug"))
distrib::set_dist_debug(true);
// activar trazas del GC si se paso --gc-debug. Tambien respeta env
// VESTA_GC_DEBUG=1 (la inicializacion estatica corre antes de main,
// no se pierde si el flag CLI esta o no).
if (result.count("gc-debug"))
gc::set_gc_debug(true);
// --gc-debug-buffered implica --gc-debug ademas de buffered mode.
if (result.count("gc-debug-buffered")) {
gc::set_gc_debug(true);
gc::set_gc_debug_buffered(true);
}
// ---- Configuracion del JIT ----
//
// Orden de prioridad (mayor primero):
// 1. --jit-threshold N (CLI explicito)
// 2. -m jit (preset = threshold 1, util para tests)
// 3. VESTA_JIT_THRESHOLD env var (default ya lazy-leida en auto_jit)
//
// --jit-warn activa el output detallado de IR ops no soportadas.
// --jit-stats imprime el snapshot final al RET de main (independiente).
if (result.count("jit-threshold")) {
jit::set_jit_threshold(result["jit-threshold"].as<uint32_t>());
} else if (result.count("mode")) {
const std::string m = result["mode"].as<std::string>();
if (m == "jit") {
/* Preset: threshold=1 => cada metodo se JIT-compila a la
* primera invocacion. Equivalente a la opcion explicita
* --jit-threshold 1. Si el usuario quiere otro threshold,
* que use --jit-threshold N directamente. */
jit::set_jit_threshold(1);
} else if (m == "vm" || m == "interp") {
/* Desactiva el JIT explicitamente sobreescribiendo cualquier
* env var. UINT32_MAX = JIT off, hook devuelve sin compilar
* en el fast path. */
jit::set_jit_threshold(UINT32_MAX);
}
}
if (result.count("jit-warn")) {
jit::g_jit_warn_unsupported = true;
}
if (result.count("jit-disasm")) {
jit::g_jit_disasm = true;
}
const bool jit_stats_requested = result.count("jit-stats") > 0;
if (result.count("help")) {
vesta::scout() << options.help() << std::endl;
return 0;
}
std::string out_prefix;
if (result.count("output")) {
out_prefix = result["output"].as<std::string>();
} else {
out_prefix = result["output-prefix"].as<std::string>();
}
if (result.count("version")) {
vesta::scout() << "Vesta v0.1.0" << std::endl;
return 0;
}
if (result.count("list-arch")) {
const ArchSupport &archs = get_available_architectures();
vesta::scout() << "Capstone supported architectures:\n";
for (auto &a: archs.capstone) vesta::scout() << " " << a << "\n";
vesta::scout() << "Keystone supported architectures:\n";
for (auto &a: archs.keystone) vesta::scout() << " " << a << "\n";
return 0;
}
#ifdef VESTA_HAS_PREPROCESSOR
// Solo preprocesar: expandir macros y directivas sin ensamblar
// vm.exe --preprocess-only src/main.vel [-o salida.vel]
if (result.count("preprocess-only")) {
const std::string& src_path = result["preprocess-only"].as<std::string>();
// leer el archivo fuente
std::ifstream ifs(src_path, std::ios::binary);
if (!ifs) {
std::cerr << "error: no se puede abrir: " << src_path << "\n";
return EXIT_FAILURE;
}
std::string source((std::istreambuf_iterator<char>(ifs)),
std::istreambuf_iterator<char>());
// configurar el preprocesador igual que run_worker
vpp::Preprocessor pp;
std::string source_dir =
std::filesystem::path(src_path).parent_path().string();
if (source_dir.empty()) source_dir = ".";
pp.options().include_paths.push_back(source_dir);
std::string exe_dir =
std::filesystem::path(fs::get_executable_path()).parent_path().string();
pp.options().import_paths.push_back(exe_dir + "/preprocessor/include_lib");
pp.options().import_paths.push_back(exe_dir + "/include_lib");
pp.options().import_paths.push_back(source_dir);
#ifdef _WIN32
pp.options().predefines.push_back("__VPP_WINDOWS__");
#elif defined(__linux__)
pp.options().predefines.push_back("__VPP_LINUX__");
#elif defined(__APPLE__)
pp.options().predefines.push_back("__VPP_MACOS__");
#endif
#if defined(__x86_64__) || defined(_M_X64)
pp.options().predefines.push_back("__VPP_X86_64__");
#elif defined(__i386__) || defined(_M_IX86)
pp.options().predefines.push_back("__VPP_X86_32__");
#elif defined(__aarch64__) || defined(_M_ARM64)
pp.options().predefines.push_back("__VPP_AARCH64__");
#endif
std::string processed = pp.process(source, src_path);
// imprimir todos los diagnosticos
for (const auto& d : pp.diagnostics().diagnostics()) {
std::cerr << d.loc.file << ":" << d.loc.line << ":"
<< d.loc.col << ": "
<< (d.level >= vpp::DiagLevel::ERR ? "error: " : "warning: ")
<< d.message << "\n";
}
if (pp.diagnostics().has_errors()) return EXIT_FAILURE;
// escribir a archivo si se especifico -o, si no a stdout
if (!out_prefix.empty() && out_prefix != "out") {
// determinar nombre del archivo de salida
std::string out_file = out_prefix;
if (out_file.find('.') == std::string::npos) out_file += ".vel";
std::ofstream ofs(out_file);
if (!ofs) {
std::cerr << "error: no se puede escribir: " << out_file << "\n";
return EXIT_FAILURE;
}
ofs << processed;
vesta::scout() << "[preprocess-only] -> " << out_file << "\n";
} else {
// sin -o: imprimir a stdout para inspeccion rapida
std::cout << processed;
}
return EXIT_SUCCESS;
}
#endif
// -----------------------------------------------------------------------
// Modo servidor distribuido puro (sin ejecutar bytecode)
// vm.exe --dist-server --dist-port 7789 [--dist-discover] [--dist-name nodo1]
// [--dist-add-node 192.168.1.100:7789] [--dist-token secreto]
// [--dist-tls --dist-cert cert.pem --dist-key key.pem --dist-ca ca.pem]
// -----------------------------------------------------------------------
if (result.count("dist-server")) {
try {
runtime::ManageVM dist_mgr(nullptr, 0);
runtime::VM *vm = dist_mgr.loader.create_vm_instance(1);
if (!vm) {
std::cerr << "[dist-server] Error: no se pudo crear la instancia VM\n";
return EXIT_FAILURE;
}
apply_dist_config(vm, result);
// activar modo persistente para que el scheduler no termine al no haber procesos;
// los procesos remotos llegan via rspawn despues del arranque
vm->vm_persistent = true;
vm->start();
vesta::scout() << "[dist-server] Nodo distribuido activo. "
"Pulse Ctrl+C para detener.\n";
// esperar hasta Ctrl+C
std::signal(SIGINT, on_dist_sigint);
while (g_server_running.load())
std::this_thread::sleep_for(std::chrono::milliseconds(100));
vm->dist_runtime->stop();
vm->stop();
vesta::scout() << "[dist-server] Nodo detenido.\n";
} catch (const std::exception &e) {
std::cerr << "[dist-server] Error: " << e.what() << "\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
// -----------------------------------------------------------------------
// Modo servidor persistente de depuracion (sin ejecutar un .velb inicial).
//
// vm.exe --server-mode [--server-port N | --debug-port N] [--schedulers M]
// [--dist-port ...] [--vex-debug]
//
// La VM se queda viva indefinidamente atendiendo comandos del debugger
// (load_velb, kill_proc, server_info, server_shutdown, etc.). Los
// clientes (tools/dbg_client.vsh o el IDE Electron) pueden conectarse,
// cargar .velb desde el filesystem de la VM, lanzarlos como procesos y
// gestionarlos via los comandos normales del protocolo de depuracion.
//
// Termina con SIGINT (Ctrl+C) o con el comando server_shutdown del cliente.
// -----------------------------------------------------------------------
if (result.count("server-mode")) {
try {
size_t num_schedulers = result["schedulers"].as<size_t>();
runtime::ManageVM mgr(nullptr, 0);
runtime::VM *vm = mgr.loader.create_vm_instance(num_schedulers);
if (!vm) {
std::cerr << "[server-mode] Error: no se pudo crear la instancia VM\n";
return EXIT_FAILURE;
}
// Aplicar configuracion distribuida si el usuario paso flags --dist-*
bool has_dist = result.count("dist-port") > 0 ||
result.count("dist-discover") > 0 ||
result.count("dist-add-node") > 0 ||
result.count("dist-tls") > 0 ||
result.count("dist-name") > 0 ||
result.count("dist-token") > 0 ||
result.count("dist-node-id") > 0;
if (has_dist) apply_dist_config(vm, result);
// Resolver puerto: --server-port > --debug-port > default 9229
uint16_t srv_port = 0;
if (result.count("server-port"))
srv_port = result["server-port"].as<uint16_t>();
if (srv_port == 0 && result.count("debug-port"))
srv_port = result["debug-port"].as<uint16_t>();
if (srv_port == 0) srv_port = debug::DBG_DEFAULT_PORT;
// Configuracion del sandbox de filesystem.
std::string srv_root = result["server-root"].as<std::string>();
if (!srv_root.empty()) {
std::error_code rec;
std::filesystem::create_directories(srv_root, rec);
}
// Configuracion del AuthManager.
std::string auth_db = result["auth-db"].as<std::string>();
bool auth_enabled = !auth_db.empty();
if (auth_enabled) {
if (!debug::AuthManager::instance().init(auth_db)) {
std::cerr << "[server-mode] Error: "
<< debug::AuthManager::instance().last_init_error()
<< "\n";
std::cerr << "[server-mode] Sugerencia: usa una ruta "
"absoluta a un directorio escribible, "
"p.ej. --auth-db \"C:\\Users\\TuUsuario\\"
"VestaVM\\users.db\".\n";
return EXIT_FAILURE;
}
// Bootstrap del admin si la BD esta vacia.
if (!debug::AuthManager::instance().has_any_user()) {
std::string admin_user = result["admin-user"].as<std::string>();
std::string admin_pass = result["admin-password"].as<std::string>();
if (admin_pass.empty()) {
// Generar contrasenya aleatoria de 24 chars base64.
uint8_t raw[18];
if (RAND_bytes(raw, sizeof(raw)) != 1) {
std::cerr << "[server-mode] Error: RAND_bytes "
"fallo al generar la contrasenya admin\n";
return EXIT_FAILURE;
}
static const char *alphabet =
"ABCDEFGHJKLMNPQRSTUVWXYZ"
"abcdefghijkmnpqrstuvwxyz"
"23456789";
admin_pass.reserve(24);
for (size_t i = 0; i < 24; ++i)
admin_pass.push_back(alphabet[raw[i % 18] % 62]);
}
std::string e;
if (!debug::AuthManager::instance().create_user(
admin_user, admin_pass, debug::Role::ADMIN, &e)) {
std::cerr << "[server-mode] Error: no se pudo crear "
"el admin: " << e << "\n";
return EXIT_FAILURE;
}
vesta::scout() << "[server-mode] Usuario admin creado: '"
<< admin_user << "'\n";
vesta::scout() << "[server-mode] Contrasenya admin (SOLO se "
"muestra una vez): " << admin_pass << "\n";
}
}
// Arrancar el servidor de depuracion ANTES de iniciar la VM.
auto dbg = std::make_unique<debug::Debugger>(*vm);
dbg->set_server_root(srv_root);
dbg->set_auth_required(auth_enabled);
if (!dbg->start(srv_port)) {
std::cerr << "[server-mode] Error: no se pudo iniciar el "
"servidor de depuracion en puerto " << srv_port
<< "\n";
return EXIT_FAILURE;
}
vm->debugger = dbg.get();
// NO activamos `sched->has_hooks=true` aqui: el debugger lo
// hara automaticamente via @c refresh_scheduler_hooks cuando
// el primer breakpoint / step / watch llegue. Si no hay
// depuracion activa, los schedulers se quedan en el fast
// path threaded computed-goto y el coste runtime es 0%.
// Modo persistente: el scheduler espera nuevos procesos en
// lugar de terminar al quedarse sin trabajo. Cuando un cliente
// hace load_velb, el proceso resultante entra en READY y el
// scheduler lo recoge sin necesidad de reiniciar nada.
vm->vm_persistent = true;
vm->start();
vesta::scout() << "[server-mode] VM persistente activa.\n";
vesta::scout() << "[server-mode] Debugger TCP escuchando en "
"puerto " << srv_port << ".\n";
vesta::scout() << "[server-mode] Schedulers: " << num_schedulers
<< ".\n";
if (!srv_root.empty()) {
vesta::scout() << "[server-mode] Sandbox de filesystem: "
<< srv_root << "\n";
} else {
vesta::scout() << "[server-mode] Sin sandbox de filesystem "
"(use --server-root para restringir).\n";
}
if (auth_enabled) {
vesta::scout() << "[server-mode] Auth: ACTIVO (BD: "
<< auth_db << "). Los clientes deben "
"invocar auth_login.\n";
} else {
vesta::scout() << "[server-mode] Auth: DESACTIVADO (modo "
"desarrollo; use --auth-db <ruta> para "
"exigir login).\n";
}
// Conectar el flag global con el debugger para que el comando
// server_shutdown remoto lo pueda mover.
std::signal(SIGINT, on_dist_sigint);
debug::set_server_shutdown_flag(&g_server_running);
bool no_repl = result.count("no-repl") > 0;
if (no_repl) {
// -- Modo headless (sin REPL local) --
vesta::scout() << "[server-mode] Pulse Ctrl+C o envie "
"'server_shutdown' para detener.\n";
while (g_server_running.load())
std::this_thread::sleep_for(std::chrono::milliseconds(100));
} else {
// -- REPL local compartiendo runtime con clientes remotos --
//
// El REPL (VestaViewManager) corre en ESTE thread porque
// posee stdin (readline necesita TTY). Un thread watcher
// observa g_server_running: si el flag pasa a false desde
// fuera (SIGINT o server_shutdown remoto), invoca
// VestaViewManager::stop() para cerrar el bucle del REPL
// -- pero como readline bloquea en stdin, el operador
// debera pulsar Enter UNA vez para liberarlo. Se imprime
// un mensaje para que sepa que hacer.
//
// Cuando el operador local teclea 'exit' o EOF en el
// REPL, vm.run() retorna; pasamos g_server_running a
// false aqui mismo para que el watcher salga.
vesta::scout() << "[server-mode] REPL local activo "
"(teclea 'exit' o pulsa Ctrl+D para "
"salir). Tambien aceptamos "
"'server_shutdown' del cliente.\n";
cli::Config cfg_srv;
cfg_srv.history_file = "my_vm_history.txt";
cfg_srv.history_max = 1000;
cfg_srv.prompt = "vesta(server)> ";
cfg_srv.multiline_end = ";;";
cli::VestaViewManager view_mgr(cfg_srv);
view_mgr.set_execute_callback([](const std::string &cmd) {
// mismo comportamiento del REPL normal: ejecucion
// asincrona via run_command_async; cuando el future
// resuelve, se imprime el output.
auto fut = runtime::run_command_async(cmd);
std::thread([f = std::move(fut)]() mutable {
try {
auto out = f.get();
if (!out.empty())
vesta::scout() << out << std::endl;
} catch (const std::exception &e) {
std::cerr << "Runtime error: " << e.what()
<< std::endl;
}
}).detach();
});
std::atomic<bool> watcher_done{false};
std::thread watcher([&]() {
while (g_server_running.load() && !watcher_done.load())
std::this_thread::sleep_for(
std::chrono::milliseconds(100));
if (watcher_done.load()) return;
// shutdown remoto / SIGINT: detener el REPL. El
// readline poll-loop chequea I.running cada ~50 ms,
// asi que view_mgr.stop() liberara el prompt en
// ~50 ms sin requerir entrada del operador.
vesta::scout()
<< "\n[server-mode] Apagado recibido; "
"cerrando REPL local.\n";
view_mgr.stop();
});
// Bucle del REPL (bloqueante en stdin).
view_mgr.run();
// El REPL salio (exit/EOF) o fue parado por el watcher;
// marcar global y joinar el thread.
g_server_running.store(false);
watcher_done.store(true);
if (watcher.joinable()) watcher.join();
}
vesta::scout() << "[server-mode] Senial de parada recibida; "
"deteniendo VM...\n";
if (vm->dist_runtime) vm->dist_runtime->stop();
dbg->stop();
// Desconectar el hook ANTES de destruir la VM para que un
// comando server_shutdown tardio no escriba a memoria liberada.
debug::set_server_shutdown_flag(nullptr);
vm->stop();
vesta::scout() << "[server-mode] Servidor detenido.\n";
} catch (const std::exception &e) {
std::cerr << "[server-mode] Error: " << e.what() << "\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
if (result.count("install") ||
result.count("uninstall") ||
result.count("repair"))
{
// Reconstruir argv "limpio" para el parser interno del instalador
std::vector<std::string> args;
if (result.count("uninstall")) args.push_back("uninstall");
else if (result.count("repair")) args.push_back("repair");
else args.push_back("install");
if (result.count("silent")) args.push_back("--silent");
if (result.count("per-user")) args.push_back("--per-user");
if (result.count("system-wide")) args.push_back("--system-wide");
if (result.count("prefix")) args.push_back("--prefix=" + result["prefix"].as<std::string>());
if (result.count("manifest")) args.push_back("--manifest=" + result["manifest"].as<std::string>());
std::vector<char*> argv2;
argv2.push_back(argv[0]);
for (auto& s : args) argv2.push_back(s.data());
return install::run_install_cli((int)argv2.size(), argv2.data());
}
// Compilar un archivo como worker
// vm.exe --worker src/main.vel -o main.velb
if (result.count("worker")) {
return asm_multi_process::run_worker(
result["worker"].as<std::string>(),
out_prefix,
/*skip_preprocessor=*/false,
/*keep_labels=*/(result.count("keep-labels") > 0),
/*ir_section_bytes=*/nullptr,
/*emit_map=*/(result.count("emit-map") > 0)
);
}
// Compilar un proyecto entero en paralelo
// vm.exe --driver src/ -j 8 -o program.velb
// Compilar con número automático de hilos
// vm.exe --driver src/ -j 0 -o program.velb
if (result.count("driver")) {
int threads = result["threads"].as<int>();
return asm_multi_process::run_driver(
result["driver"].as<std::string>(),
threads,
out_prefix
);
}
bool save_output = result.count("save-output") > 0;
// ensamblar un solo archivo (modo clásico)
// vm.exe --asm-file src/main.asm --arch x86_64
if (result.count("asm-file")) {
return assemble_file(
result["asm-file"].as<std::string>(),
result["arch"].as<std::string>(),
save_output,
out_prefix
)
? EXIT_SUCCESS
: EXIT_FAILURE;
}
if (result.count("disasm-file")) {
if (!result.count("arch")) {
std::cerr << "--arch es requerido para desensamblar\n";
return EXIT_FAILURE;
}
return disassemble_file(
result["disasm-file"].as<std::string>(),
result["arch"].as<std::string>(),
save_output,
out_prefix
)
? EXIT_SUCCESS
: EXIT_FAILURE;
}
// Compilar un archivo .ir (SSA IR) a .vel y opcionalmente a .velb
// vm.exe --ir-file program.ir --ir-opt 2 -o program.velb
if (result.count("ir-file")) {
const std::string &ir_path = result["ir-file"].as<std::string>();
int opt_n = result["ir-opt"].as<int>();
bool emit_only = result.count("ir-emit-only") > 0;
// Leer el archivo .ir
std::ifstream ifs(ir_path);
if (!ifs.is_open()) {
std::cerr << "[ir] No se puede abrir: " << ir_path << "\n";
return EXIT_FAILURE;
}
std::string ir_text((std::istreambuf_iterator<char>(ifs)),
std::istreambuf_iterator<char>());
// Emitir .vel
ir::EmitOptions eopts;
eopts.opt_level = ir::opt_level_from_int(opt_n);
eopts.emit_comments = true;
eopts.export_all = true;
ir::EmitResult er = ir::ir_emit_text(ir_text, eopts);
if (!er.ok) {
std::cerr << "[ir] Error de emision: " << er.error << "\n";
return EXIT_FAILURE;
}
// Determinar nombre del archivo .vel de salida
std::string vel_path = out_prefix.empty() ? "out.vel" : out_prefix + ".vel";
{
std::ofstream ofs(vel_path);
if (!ofs.is_open()) {
std::cerr << "[ir] No se puede escribir: " << vel_path << "\n";
return EXIT_FAILURE;
}
ofs << er.vel_text;
}
vesta::scout() << "[ir] .vel generado: " << vel_path << "\n";
if (emit_only) return EXIT_SUCCESS;
// Compilar el .vel generado a .velb usando el pipeline existente
return asm_multi_process::run_worker(
vel_path, out_prefix,
/*skip_preprocessor=*/false,
/*keep_labels=*/(result.count("keep-labels") > 0),
/*ir_section_bytes=*/nullptr,
/*emit_map=*/(result.count("emit-map") > 0));
}
// Compilar un archivo .vex (lenguaje Vex) a .velb.
// Pipeline:
// .vex source
// -> [VPP opcional] (metaprogramacion compartida con .vel)
// -> Vex frontend (lex + parse + tipos + lowering)
// -> ir::IrModule
// -> ir_emit_module (texto .vel)
// -> run_worker(.vel, skip_preprocessor=true)
// -> .velb
//
// Ejemplo: vm.exe --vex src/main.vex -o main.velb
if (result.count("vex")) {
const std::string &vex_path = result["vex"].as<std::string>();
bool emit_only = result.count("vex-emit-only") > 0;
bool emit_ir = result.count("vex-emit-ir") > 0;
// Flags de diagramas (Mermaid y/o Graphviz). --diagram-all activa
// los 4 diagramas; --diagram-format elige el formato de salida.
const bool diag_all = result.count("diagram-all") > 0;
const bool diag_vex = diag_all || result.count("diagram-vex") > 0;
const bool diag_ir_pre = diag_all || result.count("diagram-ir") > 0;
const bool diag_ir_post = diag_all || result.count("diagram-ir-opt") > 0;
const bool diag_vel = diag_all || result.count("diagram-vel") > 0;
// Parsear --diagram-format: mermaid | graphviz | both.
// Lower-case para tolerar "Mermaid", "GraphViz", "BOTH", etc.
std::string diag_fmt = result.count("diagram-format") > 0
? result["diagram-format"].as<std::string>()
: std::string("mermaid");
for (auto &c : diag_fmt) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
bool emit_mermaid = (diag_fmt == "mermaid" || diag_fmt == "both");
bool emit_graphviz = (diag_fmt == "graphviz" || diag_fmt == "dot" || diag_fmt == "both");
if (!emit_mermaid && !emit_graphviz) {
std::cerr << "[diagram] Formato desconocido: '" << diag_fmt
<< "' (usar mermaid | graphviz | both). Defaulting a mermaid.\n";
emit_mermaid = true;
}
// parsear --vex-base (VA base en hex). 0x0 = comportamiento
// por defecto (caller). Para plugins cargados via loadmodule usar
// un valor distinto (ej. 0x10000000) para evitar solapamiento con
// el caller cuyo code section vive en 0x0..N.
uint64_t vex_base_addr = 0;
if (result.count("vex-base")) {
const std::string &s = result["vex-base"].as<std::string>();
try {
vex_base_addr = std::stoull(s, nullptr, 0); // base 0 = autodetect 0x prefix
} catch (...) {
std::cerr << "[vex] --vex-base invalido: " << s << "\n";
return EXIT_FAILURE;
}
}
// 1 Leer el .vex.
std::ifstream ifs(vex_path);
if (!ifs.is_open()) {
std::cerr << "[vex] No se puede abrir: " << vex_path << "\n";
return EXIT_FAILURE;
}
std::string vex_source((std::istreambuf_iterator<char>(ifs)),
std::istreambuf_iterator<char>());
// 2 Aplicar VPP (mismo pipeline que run_worker). Esto es
// best-effort: si una macro genera sintaxis no soportada por Vex,
// el frontend reportara el error con la posicion preprocesada.
#ifdef VESTA_HAS_PREPROCESSOR
{
vpp::Preprocessor pp;
std::string source_dir =
std::filesystem::path(vex_path).parent_path().string();
pp.options().include_paths.push_back(source_dir);
std::string exe_dir =
std::filesystem::path(fs::get_executable_path()).parent_path().string();
pp.options().import_paths.push_back(exe_dir + "/preprocessor/include_lib");
pp.options().import_paths.push_back(exe_dir + "/include_lib");
pp.options().import_paths.push_back(source_dir);
#ifdef _WIN32
pp.options().predefines.push_back("__VPP_WINDOWS__");
#elif defined(__linux__)
pp.options().predefines.push_back("__VPP_LINUX__");
#elif defined(__APPLE__)
pp.options().predefines.push_back("__VPP_MACOS__");
#endif
std::string processed = pp.process(vex_source, vex_path);
if (pp.diagnostics().has_errors()) {
for (const auto &d : pp.diagnostics().diagnostics()) {
std::cerr << d.loc.file << ":" << d.loc.line << ": "
<< (d.level == vpp::DiagLevel::ERR ? "error: " : "warning: ")
<< d.message << "\n";
}
return EXIT_FAILURE;
}
vex_source = std::move(processed);
}
#endif
// 3 Frontend Vex: source -> IR -> .vel.
// Sanitizar el nombre del modulo: el parser .vel rechaza identificadores
// que empiezan con digito o que contienen caracteres no [A-Za-z0-9_],
// pero los nombres de fichero pueden tener cualquier cosa. Aplicamos
// dos transformaciones: (a) si empieza por digito, anteponer "m_";
// (b) sustituir cualquier byte no alfanumerico por '_'.
std::string raw_name = std::filesystem::path(vex_path).stem().string();
std::string mod_name; mod_name.reserve(raw_name.size() + 2);
if (!raw_name.empty()
&& (raw_name[0] >= '0' && raw_name[0] <= '9')) {
mod_name = "m_";
}
for (char c : raw_name) {