// Tricarbon Driver
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#ifdef _PLAN9_SOURCE
#include <mp.h>
#include <lib9.h>
#else
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#endif
#include "lib/z80e/cpu.h"
#include "package.h"
#include "context.h"
#include "parser.h"
#include "queue.h"
#include "c3/tags.h"
#include "c3/tree.h"
#include "c3/util.h"
#include "c3/stb.h"
struct context zyg_ctx;
static void
context_init(void)
{
zyg_ctx.root.name = "root";
zyg_ctx.root.path = NULL;
zyg_ctx.root.subpackages = NULL;
zyg_ctx.single_process = 0;
zyg_ctx.target = default_target;
zyg_ctx.trees = NULL;
zyg_ctx.dump_parsed = 0;
zyg_ctx.dump_analyzed = 0;
zyg_ctx.parse_only = 0;
zyg_ctx.usage_debug = 0;
zyg_ctx.asm_only = 0;
zyg_ctx.out_path = NULL;
zyg_ctx.tests = 0;
}
const char usage[] =
"Usage: %s [root source file] [-S] [-c] [--test] -o output_file\n"
;
static void
context_parse(int argc, char **argv)
{
#define arg(x) (strcmp(argv[i], x) == 0)
int i;
zyg_ctx.self = argv[0];
if(argc == 1){
fprintf(stderr, usage, argv[0]);
exit(1);
}
for(i = 1; i < argc; i += 1){
if(arg("-c")){
FATAL_EXIT("TODO: -c",0);
} else if(arg("-s")){
zyg_ctx.single_process = 1;
} else if(arg("-S")){
zyg_ctx.asm_only = 1;
} else if(arg("-p")){
zyg_ctx.parse_only = 1;
} else if(arg("--target")){
i += 1;
if(i == argc)
FATAL_EXIT("Expected argument to --target",0);
if(arg("arm64"))
zyg_ctx.target = arm64;
else if(arg("c"))
zyg_ctx.target = c;
else if(arg("z80"))
zyg_ctx.target = z80;
else if(arg("amd64"))
zyg_ctx.target = amd64;
else if(arg("detect"))
zyg_ctx.target = detect;
else if(arg("none"))
zyg_ctx.target = none;
else
FATAL_EXIT("Unrecognized target: %s", argv[i]);
} else if(arg("--debug")){
i += 1;
if(i == argc)
FATAL_EXIT("Expected argument to --debug",0);
if(arg("usage"))
zyg_ctx.usage_debug = 1;
else
FATAL_EXIT("Unrecognized debug string: %s", argv[i]);
} else if(arg("-o")){
i += 1;
if(i == argc)
FATAL_EXIT("Expected argument to --dump",0);
zyg_ctx.out_path = argv[i];
} else if(arg("--dump")){
i += 1;
if(i == argc)
FATAL_EXIT("Expected argument to --dump",0);
if(arg("parse"))
zyg_ctx.dump_parsed = 1;
else if(arg("anal"))
zyg_ctx.dump_analyzed = 1;
else
FATAL_EXIT("Unrecognized dump string: %s", argv[i]);
}else if(arg("--test")){
zyg_ctx.tests = 1;
} else if(argv[i][0] == '-'){
FATAL_EXIT("Unrecognized argument: %s", argv[i]);
} else if(zyg_ctx.root.path == NULL){
zyg_ctx.root.path = h_path_resolve(argv[i]);
}else{
FATAL_EXIT("Extra file found on command line: %s", h_path_resolve(argv[i]));
}
}
}
static void
context_validate(void)
{
if(zyg_ctx.root.path == NULL){
printf(usage, zyg_ctx.self);
exit(1);
}
#ifdef _PLAN9_arm64
if(zyg_ctx.single_process == 0){
ERROR("Parallel compilation is currently broken on plan9-arm64 hosts. Forcing single-process compilation...",0);
zyg_ctx.single_process = 1;
}
#endif
#ifndef _PLAN9_SOURCE
if(zyg_ctx.single_process == 0){
WARN("Parallel compilation is currently broken on POSIX hosts. Forcing single-process compilation...",0);
zyg_ctx.single_process = 1;
}
#endif
if(zyg_ctx.target == detect){
#if MACHINE == arm64 || MACHINE == aarch64
zyg_ctx.target = arm64;
#else
WARN("Host CPU unknown, defaulting to arm64", 0);
zyg_ctx.target = arm64;
#endif
}
}
int codegen_z80(struct c3tree tree, FILE *);
int codegen_c(struct c3tree tree, FILE *);
int codegen_arm64(struct c3tree tree, FILE *);
static void
context_free(void)
{
for(size_t i = 0; i < stb_sb_count(zyg_ctx.trees); i += 1){
c3free(zyg_ctx.trees[i].tree);
free(zyg_ctx.trees[i].path);
}
stb_sb_free(zyg_ctx.trees);
}
static void
c_compile(char *in, char *name)
{
char *target;
if(zyg_ctx.out_path != NULL)
target = strdup(zyg_ctx.out_path);
else
#ifdef _PLAN9_SOURCE
target = strdup("/tmp/zyg.bin");
#else
target = aprintf("%s/tmp.bin", name);
#endif
#ifdef _PLAN9_SOURCE
char *command = aprintf("/bin/pcc %s -o %s", in, target);
#else
char *command = aprintf("gcc %s -o %s", in, target);
#endif
if(command == NULL)
FATAL_EXIT("Out of memory", 0);
WARN("Executing: %s", command);
if(system(command) != 0)
FATAL_EXIT("C compilation failed!", 0);
free(command);
free(target);
}
static void
assemble(char *in, char *out)
{
#ifdef _PLAN9_SOURCE
char *command = aprintf("/bin/knightos/scas %s -o %s", in, out);
#else
char *command = aprintf("scas %s -o %s", in, out);
#endif
if(command == NULL)
FATAL_EXIT("Out of memory", 0);
if(system(command) != 0)
FATAL_EXIT("Assembly failed", 0);
free(command);
}
static void
codegen(void)
{
char *name = NULL;
FILE *f;
char *asm_path;
if(zyg_ctx.asm_only){
if(zyg_ctx.out_path == NULL){
f = stdout;
asm_path = "standard output";
}
else{
asm_path = zyg_ctx.out_path;
f = fopen(asm_path, "wb");
if(f == NULL)
FATAL_EXIT("failed to open file!", 0);
}
}
else{
#ifdef _PLAN9_SOURCE
asm_path = aprintf("/tmp/zyg.%s", zyg_ctx.target == c ? "c" : "asm");
#else
name = strdup("/tmp/zyg.XXXXXX");
if(name == NULL)
FATAL_EXIT("OOM",0);
if(mkdtemp(name) == NULL)
FATAL_EXIT("Failed to create temporary directory!", 0);
asm_path = aprintf("%s/tmp.%s", name, zyg_ctx.target == c ? "c" : "asm");
if(asm_path == NULL)
FATAL_EXIT("OOM", 0);
#endif
f = fopen(asm_path, "wb");
if(f == NULL)
FATAL_EXIT("failed to open file!", 0);
}
switch(zyg_ctx.target){
case amd64:
FATAL_EXIT("AMD64 codegen support is gone for now until after the analysis rework",0);
break;
case arm64:
// if(!codegen_arm64(*zyg_ctx.trees[0].tree, f))
FATAL_EXIT("Failed to codegen the root source file for arm64!", 0);
break;
case c:
// if(!codegen_c(*zyg_ctx.trees[0].tree, f))
FATAL_EXIT("Failed to codegen the root source file for C!", 0);
if(!zyg_ctx.asm_only){
zyg_ctx.asm_only = 1;
c_compile(asm_path, name);
}
break;
case z80:
// if(!codegen_z80(*zyg_ctx.trees[0].tree, f))
FATAL_EXIT("Failed to codegen the root source file for z80!", 0);
break;
case none:
case detect:
break;
default:
FATAL_EXIT("TODO: codegen %d", zyg_ctx.target);
}
fflush(f);
fclose(f);
if(!zyg_ctx.asm_only){
char *target;
if(zyg_ctx.out_path != NULL)
target = strdup(zyg_ctx.out_path);
else
#ifdef _PLAN9_SOURCE
target = strdup("/tmp/zyg.bin");
#else
target = aprintf("%s/tmp.bin", name);
#endif
assemble(asm_path, target);
free(zyg_ctx.out_path);
zyg_ctx.out_path = target;
}
}
static uint8_t
cpu_read(z80cpu_t *cpu, uint16_t addr, int opcode)
{
uint8_t *RAM = cpu->memory;
if(opcode && addr < 0x20){
if(addr != 0)
FATAL_EXIT("Segfault in test code!", 0);
if(cpu->registers.C == 0){
if(cpu->registers.D == 0)
puts("Tests passed!");
else
ERROR("Tests failed: error code %d", cpu->registers.D);
exit(cpu->registers.D);
}
if(cpu->registers.C == 1){
ERROR("Test failed! Panic: %s", &RAM[cpu->registers.HL]);
exit(1);
}
}
return RAM[addr];
}
static void
cpu_write(void *memory, uint16_t addr, uint8_t val)
{
uint8_t *RAM = memory;
RAM[addr] = val;
}
static void
tests(void)
{
// At this stage, the call graph should consist exclusively of tests and their needed functions
// Semantic analysis should additionally have generated a start function which calls all of the test functions
// and then invokes exit(0).
// As such, we just need to codegen to a temporary file, assemble the file, and execute it, exiting with its return code.
if(zyg_ctx.target != c)
zyg_ctx.asm_only = 0;
// TODO: codegen should track errors and pass them up. Currently, if codegen returns,
// it means that zyg_ctx.out_path contains the compiled test contents.
codegen();
if(zyg_ctx.target != z80)
FATAL_EXIT("TODO: non-z80 test execution", 0);
z80cpu_t *cpu = cpu_init(NULL);
FILE *f = fopen(zyg_ctx.out_path, "rb");
if(f == NULL)
FATAL_EXIT("Failed to open test binary", 0);
cpu->memory = calloc(65536, 1);
if(fread(&((char*)cpu->memory)[0x20], 1, 65536 - 0x20, f) < 0)
FATAL_EXIT("failed to read test binary", 0);
fclose(f);
cpu->read_byte = cpu_read;
cpu->write_byte = cpu_write;
cpu->registers.PC = 0x20;
cpu->registers.SP = 0xFFFF;
while(!cpu->halted)
cpu_execute(cpu, 100000);
cpu_free(cpu);
}
int
main(int argc, char **argv)
{
const char *eqn = "=======================";
context_init();
context_parse(argc, argv);
context_validate();
if(!queue_init(&zyg_ctx, QUEUE_PARSER))
FATAL_EXIT("Failed to initialize event queue", 0);
if(!queue_job(zyg_ctx.root.path))
FATAL_EXIT("Failed to add root source file to event queue", 0);
// Error will have been reported in the job queue, so just abort.
if(!queue_wait())
exit(1);
// Dump trees to ease parser debugging
if(zyg_ctx.dump_parsed){
printf("%s\nParsed:\n%s\n", eqn, eqn);
for(size_t i = 0; i < stb_sb_count(zyg_ctx.trees); i += 1){
if(zyg_ctx.trees[i].tree == NULL)
FATAL_EXIT("ICE: queue reported success, but parse tree %d is NULL!",i);
c3dump(*(zyg_ctx.trees[i].tree));
}
}
if(zyg_ctx.parse_only)
exit(0);
fflush(NULL);
queue_free();
if(queue_init(&zyg_ctx, QUEUE_ANALYSIS) == 0)
FATAL_EXIT("Failed to initialize analysis event queue", 0);
if(queue_job(zyg_ctx.root.path) == 0)
FATAL_EXIT("Failed to add root analysis to event queue", 0);
if(queue_wait() == 0)
exit(1);
queue_free();
if(zyg_ctx.dump_analyzed){
printf("%s\nAnalysed:\n%s\n", eqn, eqn);
for(size_t i = 0; i < stb_sb_count(zyg_ctx.trees); i += 1){
c3dump(*zyg_ctx.trees[i].tree);
printf("\n");
c3nodedump(*zyg_ctx.trees[i].tree, zyg_ctx.trees[i].tree->graphs.deps, 1);
printf("\nNote: the call graph is not dumped at present, as infinite recursion is not yet detected\n");
}
}
if(zyg_ctx.tests)
tests();
else
codegen();
context_free();
return 0;
}
/*
TODO:
• move this out of comment
• document recent design decisions (sticking to parscing)
• fill out usage info
• fix conformance with doc
• remove dead code
*/