From bd71a2f2b5007e6bfb3ca2bee7cb6b4b2a834437 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Tue, 6 Oct 2026 18:00:24 +0200 Subject: [PATCH 1/3] Add xcc700 and rcc700 apps --- Apps/Rcc700/CMakeLists.txt | 13 + Apps/Rcc700/LICENSE | 21 + Apps/Rcc700/README.md | 29 ++ Apps/Rcc700/main/CMakeLists.txt | 18 + Apps/Rcc700/main/Source/rcc700.c | 753 ++++++++++++++++++++++++++++ Apps/Rcc700/main/Source/tactility.c | 61 +++ Apps/Rcc700/manifest.properties | 16 + Apps/Xcc700/CMakeLists.txt | 13 + Apps/Xcc700/LICENSE | 21 + Apps/Xcc700/README.md | 27 + Apps/Xcc700/main/CMakeLists.txt | 18 + Apps/Xcc700/main/Source/tactility.c | 61 +++ Apps/Xcc700/main/Source/xcc700.c | 700 ++++++++++++++++++++++++++ Apps/Xcc700/manifest.properties | 15 + 14 files changed, 1766 insertions(+) create mode 100644 Apps/Rcc700/CMakeLists.txt create mode 100644 Apps/Rcc700/LICENSE create mode 100644 Apps/Rcc700/README.md create mode 100644 Apps/Rcc700/main/CMakeLists.txt create mode 100644 Apps/Rcc700/main/Source/rcc700.c create mode 100644 Apps/Rcc700/main/Source/tactility.c create mode 100644 Apps/Rcc700/manifest.properties create mode 100644 Apps/Xcc700/CMakeLists.txt create mode 100644 Apps/Xcc700/LICENSE create mode 100644 Apps/Xcc700/README.md create mode 100644 Apps/Xcc700/main/CMakeLists.txt create mode 100644 Apps/Xcc700/main/Source/tactility.c create mode 100644 Apps/Xcc700/main/Source/xcc700.c create mode 100644 Apps/Xcc700/manifest.properties diff --git a/Apps/Rcc700/CMakeLists.txt b/Apps/Rcc700/CMakeLists.txt new file mode 100644 index 0000000..71bdb26 --- /dev/null +++ b/Apps/Rcc700/CMakeLists.txt @@ -0,0 +1,13 @@ +# tactility-cmakelists-version: 1 +cmake_minimum_required(VERSION 3.20) + +if (NOT DEFINED ENV{TACTILITY_SDK_PATH}) + message(FATAL_ERROR "TACTILITY_SDK_PATH environment variable is not set") +endif() + +get_filename_component(TACTILITY_SDK_PATH "$ENV{TACTILITY_SDK_PATH}" ABSOLUTE) +include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake") + +tactility_project_pre(rcc700) +project(rcc700) +tactility_project_post(rcc700) diff --git a/Apps/Rcc700/LICENSE b/Apps/Rcc700/LICENSE new file mode 100644 index 0000000..939345c --- /dev/null +++ b/Apps/Rcc700/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Valentyn Danylchuk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Apps/Rcc700/README.md b/Apps/Rcc700/README.md new file mode 100644 index 0000000..cd9a884 --- /dev/null +++ b/Apps/Rcc700/README.md @@ -0,0 +1,29 @@ +# rcc700 + +[rcc700](https://github.com/valdanylchuk/rcc700) is a self-hosting mini C compiler for ESP32 (RISC-V). +It is a port of [xcc700](https://github.com/valdanylchuk/xcc700). + +## Usage + +Run it from the Terminal app: + +``` +rcc700 FILE.c [-o OUTPUT] +``` + +Without `-o`, the output is named after the input: `something.c` becomes `something.elf`. +The output is a RV32 ELF for the [ESP-IDF elf_loader](https://components.espressif.com/components/espressif/elf_loader/). + +It supports a small subset of C: see the [xcc700 README](https://github.com/valdanylchuk/xcc700#what-is-missing). +rcc700 allocates a 256 KiB code buffer, so it requires PSRAM. + +## Source + +`main/Source/rcc700.c` is rcc700 (upstream commit `8cc0639`), unmodified. +`main/Source/tactility.c` replaces: +- `main()`, to require an input file and name the default output after it +- `open()`, to translate the newlib flags that rcc700 hardcodes + +## License + +rcc700 is licensed under the MIT license, see [LICENSE](LICENSE). diff --git a/Apps/Rcc700/main/CMakeLists.txt b/Apps/Rcc700/main/CMakeLists.txt new file mode 100644 index 0000000..901f414 --- /dev/null +++ b/Apps/Rcc700/main/CMakeLists.txt @@ -0,0 +1,18 @@ +include("$ENV{TACTILITY_SDK_PATH}/TactilitySDK.cmake") + +file(GLOB_RECURSE SOURCE_FILES Source/*.c) +tactility_component_register(SRCS ${SOURCE_FILES}) + +# rcc700.c is kept unmodified: tactility.c provides main() and open() in its place. +# It declares its own libc prototypes (with int sizes), which don't match the builtin ones. +if (DEFINED COMPONENT_LIB) + set(RCC700_TARGET ${COMPONENT_LIB}) +else () + set(RCC700_TARGET ${PROJECT_NAME}) +endif () +set_source_files_properties(Source/rcc700.c + TARGET_DIRECTORY ${RCC700_TARGET} + PROPERTIES + COMPILE_DEFINITIONS "open=rcc700_open" + COMPILE_OPTIONS "-Umain;-Dmain=rcc700_main;-fno-builtin;-Wno-builtin-declaration-mismatch;-Wno-misleading-indentation" +) diff --git a/Apps/Rcc700/main/Source/rcc700.c b/Apps/Rcc700/main/Source/rcc700.c new file mode 100644 index 0000000..e50c829 --- /dev/null +++ b/Apps/Rcc700/main/Source/rcc700.c @@ -0,0 +1,753 @@ +// rcc700.c: A self-hosting mini C compiler for RISC-V / RV32 +// Tested on ESP32-P4 with ESP-IDF 5.5 elf_loader. +// Ported from my earlier xcc700 Xtensa variant. + +// --- Libc Shims --- +int printf(char *fmt, ...); void exit(int status); +void *malloc(int size); void free(void *ptr); +void *memcpy(void *dest, void *src, int n); void *memset(void *s, int c, int n); +int strcmp(char *s1, char *s2); char *strcpy(char *dest, char *src); +int strlen(char *s); int strtol(char *nptr, char **endptr, int base); +int open(char *pathname, int flags, int mode); int close(int fd); +int read(int fd, void *buf, int count); int write(int fd, void *buf, int count); +int lseek(int fd, int offset, int whence); + +// --- Constants & Globals --- +enum { + // Capacities sized so rcc700 can compile its own source (every global + // access, call and string is one pool patch; every declaration one name). + MAX_FUNCS=128, MAX_VARS=256, MAX_LITS=512, MAX_PATCHES=1200, NAMES=8192, + MAXFRAME=2000, MAX_EPI=64, // frame cap (sp adjust + offsets must fit 12 bits); max returns/func + EHDR_SZ=52, PHDR_SZ=32, N_PHDRS=1, N_SECS=7, SHDR_SZ=280, // N_SECS * 40 + R_RISCV_RELATIVE=3, R_RISCV_JUMP_SLOT=5, + ZERO=0, RA=1, SP=2, T0=5, A0=10, A1=11, A2=12, A7=17, + L_FUNC=0, L_STR=1, L_BSS=2, + // open() flags are newlib's (the libc on both the device and in elfrun) + O_RDONLY=0, O_WRONLY=1, O_CREAT=512, O_TRUNC=1024, SEEK_SET=0, SEEK_END=2 +}; + +enum { + T_EOF=0, T_INT=256, T_CHAR, T_VOID, T_IDENT, T_NUM, T_STR, T_RETURN, + T_EQ, T_NE, T_LE, T_GE, T_IF, T_ELSE, T_WHILE, T_ENUM, T_ELLIPSIS, + T_SHL, T_SHR, T_LAND, T_LOR, T_INC, T_DEC +}; + +// Variable/expression types: TY_* values, TF_* the bits they are made of. +enum { + TF_BYTE=1, TF_PTR=2, TF_ARR=4, TF_GLOBAL=8, TF_CONST=16, + TY_INT=0, TY_BYTE=1, TY_INTPTR=2, TY_BYTEPTR=3, TY_INTARR=4, TY_BYTEARR=5 +}; + +char *src; +int token; int num_val; int line; int token_cnt; +char str_val[256]; int str_len; +char *code_data; int code_size; int code_cap; +char *rodata; int rodata_sz; int rodata_cap; +char name_buf[NAMES]; int name_sz; + +int func_name_off[MAX_FUNCS]; int func_addrs[MAX_FUNCS]; int n_funcs; +int var_name_off[MAX_VARS]; int var_offsets[MAX_VARS]; int var_types[MAX_VARS]; +int n_vars; // globals + locals of the current function +int n_globals; int bss_size; // globals (incl. enum constants) / .bss bytes +int locals; int esp; // bytes of locals so far / expression stack depth, both sp-relative +int expr_type; // TY_* of the last parsed factor/expression +int lit_vals[MAX_LITS]; int lit_types[MAX_LITS]; int n_lits; +int patch_offs[MAX_PATCHES]; int patch_lits[MAX_PATCHES]; int n_patches; +// Per-function frame sizing: high-water mark of (locals+spills), the prologue's +// sp-adjust offset, and every epilogue's sp-adjust offset (all back-patched once +// the body is parsed and the real frame size is known). +int frame_max; int prologue_off; int n_epi; int epi_offs[MAX_EPI]; + +// Must put main() first. Forward-declare the functions it uses. +void next(); void parse_func(); void write_elf(char *out); +void print_stats(char *outfile); + +int main(int argc, char **argv) { + line = 1; + char *input_fname = argc > 1 ? argv[1] : "input.c"; + char *output_fname = "a.out"; + if (argc > 3) if (!strcmp(argv[2], "-o")) output_fname = argv[3]; // double if: no short-circuit && in self-hosted + int f = open(input_fname, O_RDONLY, 0); + if (f < 0) { printf("rcc700: cannot open input file: %s\n", input_fname); return 1; } + int f_size = lseek(f, 0, SEEK_END); lseek(f, 0, SEEK_SET); + char *src_buf = malloc(f_size+1); src = src_buf; + code_cap = 262144; code_data = malloc(code_cap); + rodata_cap = 8192; rodata = malloc(rodata_cap); + if (!src_buf || !code_data || !rodata) { printf("rcc700: out of memory\n"); return 1; } + read(f, src, f_size); src[f_size] = 0; close(f); + next(); while (token != T_EOF) parse_func(); + free(src_buf); write_elf(output_fname); + print_stats(output_fname); + free(code_data); free(rodata); + return 0; +} + +// Replace macros to avoid ABI issues +int isdigit(int c) { return c >= '0' && c <= '9'; } +int isalpha(int c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } +int isspace(int c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r'; } +int isalnum(int c) { return isdigit(c) || isalpha(c); } + +// Helpers +void put32(char *b, int v) { b[0]=v; b[1]=v>>8; b[2]=v>>16; b[3]=v>>24; } +void put16(char *b, int v) { b[0]=v; b[1]=v>>8; } +int align4(int x) { return (x + 3) & ~3; } + +// Not-yet-ported features bail out with exit code 42 -> run_tests.sh reports SKIP. +void unsupported(char *what) { + printf("rcc700: line %d: not implemented: %s (token %d)\n", line, what, token); + exit(42); +} + +// --- Lexer --- +void next() { + while (isspace(*src) || (src[0]=='/' && src[1]=='/')) { + if (*src == '\n') ++line; + if (*src == '/') while (*src && *src != '\n') ++src; else ++src; + } + if (!*src) { token = T_EOF; return; } + if (src[0]=='+' && src[1]=='+') { token=T_INC; src=src+2; return; } + if (src[0]=='-' && src[1]=='-') { token=T_DEC; src=src+2; return; } + if (src[0]=='=' && src[1]=='=') { token=T_EQ; src=src+2; return; } + if (src[0]=='!' && src[1]=='=') { token=T_NE; src=src+2; return; } + if (src[0]=='<' && src[1]=='=') { token=T_LE; src=src+2; return; } + if (src[0]=='>' && src[1]=='=') { token=T_GE; src=src+2; return; } + if (src[0]=='<' && src[1]=='<') { token=T_SHL; src=src+2; return; } + if (src[0]=='>' && src[1]=='>') { token=T_SHR; src=src+2; return; } + if (src[0]=='&' && src[1]=='&') { token=T_LAND; src=src+2; return; } + if (src[0]=='|' && src[1]=='|') { token=T_LOR; src=src+2; return; } + if (src[0]=='.' && src[1]=='.' && src[2]=='.') { token=T_ELLIPSIS; src=src+3; return; } + if (isalpha(*src) || *src == '_') { + char *p = str_val; + while (isalnum(*src) || *src == '_') { *p = *src; ++p; ++src; } + *p = 0; + token = !strcmp(str_val,"int") ? T_INT : !strcmp(str_val,"char") ? T_CHAR : + !strcmp(str_val,"void") ? T_VOID : !strcmp(str_val,"if") ? T_IF : + !strcmp(str_val,"else") ? T_ELSE : !strcmp(str_val,"while") ? T_WHILE : + !strcmp(str_val,"enum") ? T_ENUM : + !strcmp(str_val,"return") ? T_RETURN : T_IDENT; + } else if (isdigit(*src)) { + num_val = strtol(src, &src, 0); token = T_NUM; + } else if (*src == '\'') { + ++src; + if (*src == '\\') { + ++src; + if (*src == 'n') num_val = '\n'; else if (*src == 't') num_val = '\t'; + else if (*src == 'r') num_val = '\r'; else if (*src == '0') num_val = 0; else num_val = *src; + } else num_val = *src; + ++src; if (*src == '\'') ++src; + token = T_NUM; + } else if (*src == '"') { + char *p = str_val; ++src; + while (*src && *src != '"') { + if (*src == '\\') { + ++src; + if (*src == 'n') *p='\n'; else if (*src == 't') *p='\t'; + else if (*src == 'r') *p='\r'; else if (*src == '0') *p='\0'; else *p=*src; + } else *p=*src; + ++src; ++p; + } + *p = 0; if (*src) ++src; + str_len = p - str_val; + token = T_STR; + } else { token = *src; ++src; } + ++token_cnt; +} + +void expect(int tok) { + if (token != tok) { + printf("rcc700: line %d: expected token %d, got %d (%s)\n", line, tok, token, str_val); + exit(1); + } + next(); +} + +// --- Code Emitter (RV32I) --- +void emit32(int v) { + if (code_size + 4 > code_cap) { printf("rcc700: out of memory (code)\n"); exit(1); } + put32(code_data + code_size, v); code_size = code_size + 4; +} +void emit16(int v) { + if (code_size + 2 > code_cap) { printf("rcc700: out of memory (code)\n"); exit(1); } + put16(code_data + code_size, v); code_size = code_size + 2; +} +void emit_r(int f7, int rs2, int rs1, int f3, int rd, int op) { + emit32((f7<<25)|(rs2<<20)|(rs1<<15)|(f3<<12)|(rd<<7)|op); +} +void emit_i(int imm, int rs1, int f3, int rd, int op) { + emit32(((imm&0xfff)<<20)|(rs1<<15)|(f3<<12)|(rd<<7)|op); +} +int enc_j(int off, int rd) { // J-type: imm[20|10:1|11|19:12] + return (((off>>20)&1)<<31)|(((off>>1)&0x3ff)<<21)|(((off>>11)&1)<<20)|(((off>>12)&0xff)<<12)|(rd<<7)|0x6f; +} +int enc_b(int off, int rs2, int rs1, int f3) { // B-type: imm[12|10:5] ... imm[4:1|11] + return (((off>>12)&1)<<31)|(((off>>5)&0x3f)<<25)|(rs2<<20)|(rs1<<15)|(f3<<12)|(((off>>1)&0xf)<<8)|(((off>>11)&1)<<7)|0x63; +} +void emit_addi(int rd, int rs1, int imm) { emit_i(imm, rs1, 0, rd, 0x13); } +// A word access fits c.lwsp/c.swsp when it is sp-relative with off 0..252, /4 +// (rd is always a real reg here, so c.lwsp's rd != x0 holds for free). +int sp_word(int base, int off) { return base == SP && (off & 3) == 0 && off >= 0 && off < 256; } +void emit_lw(int rd, int base, int off) { + if (sp_word(base, off)) emit16(0x4002 | (rd<<7) | (((off>>5)&1)<<12) | (((off>>2)&7)<<4) | (((off>>6)&3)<<2)); // c.lwsp + else emit_i(off, base, 2, rd, 3); +} +void emit_lbu(int rd, int base, int off) { emit_i(off, base, 4, rd, 3); } +void emit_s(int rs2, int base, int off, int f3) { + emit32((((off>>5)&0x7f)<<25)|(rs2<<20)|(base<<15)|(f3<<12)|((off&0x1f)<<7)|0x23); +} +void emit_sw(int rs2, int base, int off) { + if (sp_word(base, off)) emit16(0xC002 | (rs2<<2) | (((off>>2)&0xf)<<9) | (((off>>6)&3)<<7)); // c.swsp + else emit_s(rs2, base, off, 2); +} +void emit_sb(int rs2, int base, int off) { emit_s(rs2, base, off, 0); } +void emit_lui(int rd, int hi20) { emit32(((hi20&0xfffff)<<12)|(rd<<7)|0x37); } +void emit_li(int rd, int v) { + if (v >= -2048 && v < 2048) emit_addi(rd, ZERO, v); + else { int hi = (v + 0x800) >> 12; emit_lui(rd, hi); emit_addi(rd, rd, v - (hi<<12)); } +} +void emit_ecall() { emit32(0x73); } +void emit_ret() { emit_i(0, RA, 0, ZERO, 0x67); } // jalr zero, ra, 0 +void emit_beqz(int rs, int off) { emit32(enc_b(off, ZERO, rs, 0)); } +void emit_j(int off) { emit32(enc_j(off, ZERO)); } + +// Resolve a forward branch/jump emitted with offset 0: OR the now-known +// offset (current position - branch position) into the instruction. +void patch(int addr, int is_j) { + int off = code_size - addr; int w = 0; + memcpy(&w, code_data + addr, 4); + put32(code_data + addr, w | (is_j ? enc_j(off, ZERO) : enc_b(off, 0, 0, 0))); +} + +void patch_frame(int addr, int imm) { + int w = 0; memcpy(&w, code_data + addr, 4); + put32(code_data + addr, w | ((imm & 0xfff) << 20)); +} + +// Spill a0 into the frame above the locals; sp itself never moves inside a +// function body, so local var offsets stay valid. +void push() { + if (locals + esp >= MAXFRAME) { printf("rcc700: line %d: expression too deep\n", line); exit(1); } + emit_sw(A0, SP, locals + esp); esp = esp + 4; + if (locals + esp > frame_max) frame_max = locals + esp; +} +void pop(int r) { esp = esp - 4; emit_lw(r, SP, locals + esp); } + +// --- Symbols & patches --- +int add_name(char *s) { + int len = strlen(s) + 1; + if (name_sz + len > NAMES) { printf("rcc700: out of memory (names)\n"); exit(1); } + int off = name_sz; strcpy(name_buf + off, s); + name_sz = name_sz + len; return off; +} +int get_func(char *name) { + int i = 0; + while (i < n_funcs) { if (!strcmp(name_buf + func_name_off[i], name)) return i; ++i; } + if (n_funcs >= MAX_FUNCS) { printf("rcc700: too many functions\n"); exit(1); } + func_name_off[n_funcs] = add_name(name); + func_addrs[n_funcs] = -1; + ++n_funcs; + return n_funcs - 1; +} +int get_lit(int val, int type) { + int i = 0; + while (i < n_lits && (lit_vals[i] != val || lit_types[i] != type)) ++i; + if (i == n_lits) { + if (n_lits >= MAX_LITS) { printf("rcc700: too many literals\n"); exit(1); } + lit_vals[i] = val; lit_types[i] = type; ++n_lits; + } + return i; +} + +// rd = pool[lit], loaded pc-relative: auipc rd, hi; lw rd, lo(rd). +// Immediates stay 0 here; write_elf patches them once the pool size (and so +// the slot's distance from the auipc) is known. +void emit_pool_lw(int rd, int lit) { + if (n_patches >= MAX_PATCHES) { printf("rcc700: too many patches\n"); exit(1); } + patch_offs[n_patches] = code_size; patch_lits[n_patches] = lit; ++n_patches; + emit32((rd << 7) | 0x17); // auipc rd, 0 + emit_lw(rd, rd, 0); // lw rd, 0(rd) +} + +void emit_call(char *name) { + emit_pool_lw(T0, get_lit(get_func(name), L_FUNC)); + emit_i(0, T0, 0, RA, 0x67); // jalr ra, t0 +} + +void emit_load_str() { // a0 = &.rodata[str] + if (rodata_sz + str_len + 1 > rodata_cap) { printf("rcc700: out of memory (rodata)\n"); exit(1); } + emit_pool_lw(A0, get_lit(rodata_sz, L_STR)); + memcpy(rodata + rodata_sz, str_val, str_len + 1); + rodata_sz = rodata_sz + str_len + 1; +} + +// --- Parser --- +void parse_expr(int limit); + +int find_var(char *name) { + int i = n_vars; + while (i > 0) { --i; if (!strcmp(name_buf + var_name_off[i], name)) return i; } + return -1; +} + +int get_prec(int t) { + if (t == '?') return 1; + if (t == T_LOR) return 2; + if (t == T_LAND) return 3; + if (t == '|') return 4; + if (t == '^') return 5; + if (t == '&') return 6; + if (t == T_EQ || t == T_NE) return 7; + if (t == '<' || t == '>' || t == T_LE || t == T_GE) return 8; + if (t == T_SHL || t == T_SHR) return 9; + if (t == '+' || t == '-') return 10; + if (t == '*' || t == '/' || t == '%') return 11; + return 0; +} + +void emit_binop(int op) { // a0 = t0 op a0 (t0 = lhs, a0 = rhs) + if (op == '+') emit_r(0, A0, T0, 0, A0, 0x33); + else if (op == '-') emit_r(0x20, A0, T0, 0, A0, 0x33); + else if (op == '*') emit_r(1, A0, T0, 0, A0, 0x33); + else if (op == '/') emit_r(1, A0, T0, 4, A0, 0x33); + else if (op == '%') emit_r(1, A0, T0, 6, A0, 0x33); + else if (op == '&') emit_r(0, A0, T0, 7, A0, 0x33); + else if (op == '|') emit_r(0, A0, T0, 6, A0, 0x33); + else if (op == '^') emit_r(0, A0, T0, 4, A0, 0x33); + else if (op == T_SHL) emit_r(0, A0, T0, 1, A0, 0x33); // sll + else if (op == T_SHR) emit_r(0x20, A0, T0, 5, A0, 0x33); // sra + else if (op == '<') emit_r(0, A0, T0, 2, A0, 0x33); // slt a0, t0, a0 + else if (op == '>') emit_r(0, T0, A0, 2, A0, 0x33); // slt a0, a0, t0 + else if (op == T_GE) { emit_r(0, A0, T0, 2, A0, 0x33); emit_i(1, A0, 4, A0, 0x13); } // !(t0 < a0) + else if (op == T_LE) { emit_r(0, T0, A0, 2, A0, 0x33); emit_i(1, A0, 4, A0, 0x13); } // !(a0 < t0) + else if (op == T_EQ) { emit_r(0, A0, T0, 4, A0, 0x33); emit_i(1, A0, 3, A0, 0x13); } // xor; sltiu 1 + else if (op == T_NE) { emit_r(0, A0, T0, 4, A0, 0x33); emit_r(0, A0, ZERO, 3, A0, 0x33); } // xor; sltu 0< + else if (op == T_LAND || op == T_LOR) { // normalize to 0/1; like xcc700, not short-circuit + emit_r(0, T0, ZERO, 3, T0, 0x33); // sltu t0, zero, t0 + emit_r(0, A0, ZERO, 3, A0, 0x33); // sltu a0, zero, a0 + emit_r(0, A0, T0, op == T_LAND ? 7 : 6, A0, 0x33); // and/or + } + else unsupported("operator"); + expr_type = TY_INT; +} + +void load_var_address(int i) { // a0 = &var + if (var_types[i] & TF_GLOBAL) emit_pool_lw(A0, get_lit(var_offsets[i], L_BSS)); + else emit_addi(A0, SP, var_offsets[i]); +} +void load_var(int i) { // a0 = var (arrays decay to a pointer) + int ty = var_types[i]; int is_byte = ((ty & ~TF_GLOBAL) == TY_BYTE); + if (ty & TF_CONST) { emit_li(A0, var_offsets[i]); expr_type = TY_INT; } + else if (ty & TF_ARR) { load_var_address(i); expr_type = (ty & TF_BYTE) ? TY_BYTEPTR : TY_INTPTR; } + else if (ty & TF_GLOBAL) { + load_var_address(i); + if (is_byte) emit_lbu(A0, A0, 0); else emit_lw(A0, A0, 0); + expr_type = ty & ~TF_GLOBAL; + } else { + if (is_byte) emit_lbu(A0, SP, var_offsets[i]); else emit_lw(A0, SP, var_offsets[i]); + expr_type = ty; + } +} + +void parse_index(int base_type) { // a0 = a0 + index (scaled); '[' already current + next(); push(); + parse_expr(1); expect(']'); + if (!(base_type & TF_BYTE)) emit_i(2, A0, 1, A0, 0x13); // slli a0, a0, 2 + pop(T0); emit_r(0, A0, T0, 0, A0, 0x33); // add a0, t0, a0 +} + +void parse_call(char *name) { + int arg_cnt = 0; next(); + if (token != ')') { + parse_expr(1); push(); ++arg_cnt; + while (token == ',') { next(); parse_expr(1); push(); ++arg_cnt; } + if (arg_cnt > 6) { printf("rcc700: line %d: more than 6 args\n", line); exit(1); } + while (arg_cnt > 0) { --arg_cnt; pop(A0 + arg_cnt); } + } + expect(')'); emit_call(name); +} + +void parse_factor() { // Result in a0 + if (token == T_INC || token == T_DEC) { // prefix ++/--: returns the new value + int diff = (token == T_INC) ? 1 : -1; next(); + char name[64]; strcpy(name, str_val); expect(T_IDENT); + int i = find_var(name); + if (i < 0) unsupported("identifier"); + load_var(i); emit_addi(A0, A0, diff); + int is_byte = ((var_types[i] & ~TF_GLOBAL) == TY_BYTE); + if (var_types[i] & TF_GLOBAL) { + emit_pool_lw(T0, get_lit(var_offsets[i], L_BSS)); + if (is_byte) emit_sb(A0, T0, 0); else emit_sw(A0, T0, 0); + } else if (is_byte) emit_sb(A0, SP, var_offsets[i]); + else emit_sw(A0, SP, var_offsets[i]); + expr_type = TY_INT; + } + else if (token == '!' || token == '~' || token == '-') { + int op = token; next(); parse_factor(); + if (op == '-') emit_r(0x20, A0, ZERO, 0, A0, 0x33); // neg: sub a0, zero, a0 + else if (op == '~') emit_i(-1, A0, 4, A0, 0x13); // xori a0, a0, -1 + else emit_i(1, A0, 3, A0, 0x13); // seqz: sltiu a0, a0, 1 + expr_type = TY_INT; + } + else if (token == T_NUM) { emit_li(A0, num_val); expr_type = TY_INT; next(); } + else if (token == T_STR) { emit_load_str(); expr_type = TY_BYTEPTR; next(); } + else if (token == '*') { + next(); parse_factor(); int pt = expr_type; + if (pt & TF_BYTE) { emit_lbu(A0, A0, 0); expr_type = TY_BYTE; } + else { emit_lw(A0, A0, 0); expr_type = TY_INT; } + } + else if (token == '&') { + next(); char name[64]; strcpy(name, str_val); expect(T_IDENT); + int i = find_var(name); + if (i < 0) unsupported("identifier"); + load_var_address(i); + expr_type = (var_types[i] & TF_BYTE) ? TY_BYTEPTR : TY_INTPTR; + } + else if (token == T_IDENT) { + char name[64]; strcpy(name, str_val); next(); + if (token == '(') { parse_call(name); expr_type = TY_INT; } + else { + int i = find_var(name); + if (i < 0) unsupported("identifier"); + load_var(i); + if (token == '[') { + int bt = expr_type; + parse_index(bt); + if (bt & TF_BYTE) { emit_lbu(A0, A0, 0); expr_type = TY_BYTE; } + else { emit_lw(A0, A0, 0); expr_type = TY_INT; } + } + } + } + else if (token == '(') { next(); parse_expr(1); expect(')'); } + else unsupported("expression"); +} + +void parse_expr(int limit) { // Precedence climbing; result in a0 + parse_factor(); + while (get_prec(token) >= limit) { + int op = token; next(); + if (op == '?') { // ternary; the false branch recurses, so right-assoc + int p1 = code_size; emit_beqz(A0, 0); + parse_expr(2); + int p2 = code_size; emit_j(0); + expect(':'); patch(p1, 0); + parse_expr(1); + patch(p2, 1); + } else { + push(); parse_expr(get_prec(op) + 1); + pop(T0); emit_binop(op); + } + } +} + +// ra lives at sp+0 (fixed); the frame size isn't known until the body is parsed, +// so the sp-restore offset is recorded here and patched in parse_func. +void emit_epilogue() { + emit_lw(RA, SP, 0); + if (n_epi >= MAX_EPI) { printf("rcc700: line %d: too many returns\n", line); exit(1); } + epi_offs[n_epi] = code_size; ++n_epi; + emit_addi(SP, SP, 0); emit_ret(); +} + +void parse_stmt() { + esp = 0; + if (token == T_WHILE) { + next(); int loop_start = code_size; + expect('('); parse_expr(1); expect(')'); + int exit_patch = code_size; emit_beqz(A0, 0); + parse_stmt(); + emit_j(loop_start - code_size); + patch(exit_patch, 0); + } else if (token == T_IF) { + next(); expect('('); parse_expr(1); expect(')'); + int p1 = code_size; emit_beqz(A0, 0); + parse_stmt(); + if (token == T_ELSE) { + int p2 = code_size; emit_j(0); + patch(p1, 0); next(); parse_stmt(); patch(p2, 1); + } else patch(p1, 0); + } else if (token == T_INT || token == T_CHAR) { // local: int x = expr; / int x[N]; + int is_byte = (token == T_CHAR); next(); + int is_ptr = 0; while (token == '*') { is_ptr = 1; next(); } + if (n_vars >= MAX_VARS) { printf("rcc700: line %d: too many locals\n", line); exit(1); } + var_name_off[n_vars] = add_name(str_val); + var_offsets[n_vars] = locals; ++n_vars; + expect(T_IDENT); + if (token == '[') { + next(); int sz = num_val; expect(T_NUM); expect(']'); + var_types[n_vars-1] = is_byte ? TY_BYTEARR : TY_INTARR; + locals = locals + (is_byte ? align4(sz) : sz * 4); + } else { + var_types[n_vars-1] = is_ptr ? (is_byte ? TY_BYTEPTR : TY_INTPTR) : (is_byte ? TY_BYTE : TY_INT); + locals = locals + 4; + expect('='); parse_expr(1); + if (is_byte && !is_ptr) emit_sb(A0, SP, var_offsets[n_vars-1]); + else emit_sw(A0, SP, var_offsets[n_vars-1]); + } + if (locals >= MAXFRAME) { printf("rcc700: line %d: stack frame overflow\n", line); exit(1); } + if (locals > frame_max) frame_max = locals; + expect(';'); + } else if (token == T_RETURN) { + next(); + if (token != ';') parse_expr(1); else emit_li(A0, 0); + emit_epilogue(); expect(';'); + } else if (token == '{') { + next(); while (token != '}' && token != T_EOF) parse_stmt(); expect('}'); + } else if (token == T_IDENT) { + char name[64]; strcpy(name, str_val); next(); + if (token == '(') { parse_call(name); expect(';'); } + else { + int i = find_var(name); + if (i < 0) unsupported("identifier"); + if (token == '[') { // arr[idx] = expr; + load_var(i); + int bt = expr_type; + parse_index(bt); push(); + expect('='); parse_expr(1); + pop(T0); + if (bt & TF_BYTE) emit_sb(A0, T0, 0); else emit_sw(A0, T0, 0); + } else { // var = expr; + expect('='); parse_expr(1); + if (var_types[i] & TF_GLOBAL) { + emit_pool_lw(T0, get_lit(var_offsets[i], L_BSS)); + emit_sw(A0, T0, 0); + } else emit_sw(A0, SP, var_offsets[i]); + } + expect(';'); + } + } else if (token == '*') { // *ptr = expr; + next(); parse_factor(); + int pt = expr_type; + push(); expect('='); parse_expr(1); + pop(T0); + if (pt & TF_BYTE) emit_sb(A0, T0, 0); else emit_sw(A0, T0, 0); + expect(';'); + } else { parse_expr(1); expect(';'); } +} + +void parse_func() { + if (token == T_ENUM) { // enum [Name] { A, B = 2, ... }; members become int constants + next(); if (token == T_IDENT) next(); + expect('{'); int val = 0; + while (token == T_IDENT) { + if (n_globals >= MAX_VARS) { printf("rcc700: too many globals\n"); exit(1); } + var_name_off[n_globals] = add_name(str_val); + var_offsets[n_globals] = val; + var_types[n_globals] = TF_CONST; + ++n_globals; n_vars = n_globals; next(); + if (token == '=') { next(); val = num_val; var_offsets[n_globals-1] = val; next(); } + ++val; if (token == ',') next(); + } + expect('}'); expect(';'); return; + } + + int is_byte = (token == T_CHAR); + if (token == T_INT || token == T_CHAR || token == T_VOID) next(); + else unsupported("declaration"); + int is_ptr = 0; while (token == '*') { is_ptr = 1; next(); } + char name[64]; strcpy(name, str_val); expect(T_IDENT); + + if (token == ';' || token == '[') { // global variable: zero-initialized bss + if (n_globals >= MAX_VARS) { printf("rcc700: too many globals\n"); exit(1); } + int ty = TF_GLOBAL | (is_ptr ? (is_byte ? TY_BYTEPTR : TY_INTPTR) : (is_byte ? TY_BYTE : TY_INT)); + var_offsets[n_globals] = bss_size; + if (token == '[') { + next(); int sz = 0; + if (token == T_NUM) { sz = num_val; next(); } + else if (token == T_IDENT) { // size given as an enum constant + int i = find_var(str_val); + if (i < 0 || !(var_types[i] & TF_CONST)) { + printf("rcc700: line %d: undefined constant: %s\n", line, str_val); exit(1); + } + sz = var_offsets[i]; next(); + } else { printf("rcc700: line %d: array size expected\n", line); exit(1); } + expect(']'); + ty = TF_GLOBAL | (is_byte ? TY_BYTEARR : TY_INTARR); + bss_size = bss_size + (is_byte ? align4(sz) : sz * 4); + } else bss_size = bss_size + 4; + var_name_off[n_globals] = add_name(name); + var_types[n_globals] = ty; + ++n_globals; n_vars = n_globals; + expect(';'); return; + } + + expect('('); n_vars = n_globals; locals = 4; int n_args = 0; // locals start above ra (sp+0) + frame_max = 4; n_epi = 0; + while (token != ')' && token != T_EOF) { // parameters + is_byte = 0; int ptr_count = 0; char pname[64]; pname[0] = 0; + // Skip type specifiers/qualifiers (const, int, ...); last ident is the name. + while (token != ',' && token != ')' && token != T_EOF) { + if (token == T_CHAR) is_byte = 1; + else if (token == '*') ++ptr_count; + else if (token == T_IDENT) strcpy(pname, str_val); + else if (token != T_INT && token != T_VOID && token != T_ELLIPSIS) unsupported("parameter"); + next(); + } + if (pname[0]) { + if (n_vars >= MAX_VARS) { printf("rcc700: too many locals\n"); exit(1); } + var_name_off[n_vars] = add_name(pname); + var_offsets[n_vars] = locals; + var_types[n_vars] = ptr_count >= 2 ? TY_INTPTR : ptr_count ? (is_byte ? TY_BYTEPTR : TY_INTPTR) : (is_byte ? TY_BYTE : TY_INT); + locals = locals + 4; ++n_vars; ++n_args; + } + if (token == ',') next(); + } + expect(')'); + if (token == ';') { next(); return; } // prototype + if (n_args > 6) { printf("rcc700: line %d: more than 6 args\n", line); exit(1); } + if (locals > frame_max) frame_max = locals; // params live in the frame too + func_addrs[get_func(name)] = code_size; + prologue_off = code_size; emit_addi(SP, SP, 0); emit_sw(RA, SP, 0); // prologue; sp adjust patched at end + int j = 0; while (j < n_args) { emit_sw(A0 + j, SP, var_offsets[n_globals + j]); ++j; } // spill args + expect('{'); + while (token != '}' && token != T_EOF) parse_stmt(); + expect('}'); + emit_li(A0, 0); emit_epilogue(); // implicit return 0 + // Back-patch the frame to what the body actually used + int fsz = (frame_max + 15) & ~15; + patch_frame(prologue_off, -fsz); + int k = 0; while (k < n_epi) { patch_frame(epi_offs[k], fsz); ++k; } +} + +// --- ELF Writer --- +// Layout: ehdr | phdrs | .text (pool + code) | .rodata | .rela.dyn | .symtab | +// .strtab | .shstrtab | section headers. Alloc vaddrs equal file offsets (like +// a gcc -shared image), so one PT_LOAD at vaddr 0 covers them; the output loads +// on both Xtensa (via sections) and RISC-V (via PT_LOAD). +void write_elf(char *out) { + int pool_sz = n_lits * 4; + int phoff = EHDR_SZ; // phdr table right after ehdr; .text follows + int text_addr = EHDR_SZ + N_PHDRS*PHDR_SZ; int text_size = pool_sz + code_size; + int rodata_addr = align4(text_addr + text_size); + // bss is a vaddr range only (p_memsz zero-fill), so no file bytes. + // The trailing non-alloc sections sit right after .rodata + int bss_addr = align4(rodata_addr + rodata_sz); + int rela_off = bss_addr; + int n_syms = n_funcs + 1; + int symtab_off = rela_off + n_lits * 12; + int strtab_off = symtab_off + n_syms * 16; + + // Symbols: one per function; defined ones carry their .text address, + // undefined ones are imports for the loader to resolve by name. + char *syms = malloc(n_syms * 16); memset(syms, 0, n_syms * 16); + char *strtab = malloc(NAMES + 1); strtab[0] = 0; int str_off = 1; + int main_addr = -1; + int i = 0; + while (i < n_funcs) { + char *s = syms + (i + 1) * 16; + char *name = name_buf + func_name_off[i]; + put32(s, str_off); + strcpy(strtab + str_off, name); str_off = str_off + strlen(name) + 1; + if (func_addrs[i] < 0) { + s[12] = 16; // GLOBAL | NOTYPE, UNDEF + } else { + put32(s + 4, text_addr + pool_sz + func_addrs[i]); + s[12] = 18; put16(s + 14, 1); // GLOBAL | FUNC, .text + if (!strcmp(name, "main")) main_addr = text_addr + pool_sz + func_addrs[i]; + } + ++i; + } + if (main_addr < 0) { printf("rcc700: main() is not defined\n"); exit(1); } + + // Literal pool + one relocation per slot. + char *pool = malloc(pool_sz + 4); + char *rels = malloc(n_lits * 12 + 4); + i = 0; + while (i < n_lits) { + int val = 0; int info = 0; + if (lit_types[i] == L_STR) { + val = rodata_addr + lit_vals[i]; info = R_RISCV_RELATIVE; + } else if (lit_types[i] == L_BSS) { + val = bss_addr + lit_vals[i]; info = R_RISCV_RELATIVE; + } else if (func_addrs[lit_vals[i]] >= 0) { + val = text_addr + pool_sz + func_addrs[lit_vals[i]]; info = R_RISCV_RELATIVE; + } else { + info = ((lit_vals[i] + 1) << 8) | R_RISCV_JUMP_SLOT; // import, by symbol + } + put32(pool + i*4, val); + put32(rels + i*12, text_addr + i*4); put32(rels + i*12 + 4, info); + put32(rels + i*12 + 8, val); // addend (RELATIVE: base + A) + ++i; + } + + // Point each auipc+lw pair at its pool slot, now that distances are known. + i = 0; + while (i < n_patches) { + int off = patch_offs[i]; + int delta = patch_lits[i] * 4 - (pool_sz + off); + int hi = (delta + 0x800) >> 12; int w = 0; + memcpy(&w, code_data + off, 4); + put32(code_data + off, w | ((hi & 0xfffff) << 12)); + memcpy(&w, code_data + off + 4, 4); + put32(code_data + off + 4, w | (((delta - (hi << 12)) & 0xfff) << 20)); + ++i; + } + + // Section headers: 0:null 1:.text 2:.rodata 3:.rela.dyn 4:.symtab + // 5:.strtab 6:.shstrtab. shstrtab name offsets are fixed. + char *shstr = "\0.text\0.rodata\0.rela.dyn\0.symtab\0.strtab\0.shstrtab\0"; + int shstr_sz = 51; int shstr_off = strtab_off + str_off; + int shoff = align4(shstr_off + shstr_sz); + char *shdr = malloc(SHDR_SZ); memset(shdr, 0, SHDR_SZ); + // 1: .text (PROGBITS, ALLOC|EXEC) + put32(shdr+40, 1); put32(shdr+44, 1); put32(shdr+48, 6); put32(shdr+52, text_addr); + put32(shdr+56, text_addr); put32(shdr+60, text_size); put32(shdr+72, 4); + // 2: .rodata (PROGBITS, ALLOC: literals only; globals live in the bss tail) + put32(shdr+80, 7); put32(shdr+84, 1); put32(shdr+88, 2); put32(shdr+92, rodata_addr); + put32(shdr+96, rodata_addr); put32(shdr+100, rodata_sz); put32(shdr+112, 4); + // 3: .rela.dyn (RELA, link=.symtab) + put32(shdr+120, 15); put32(shdr+124, 4); put32(shdr+136, rela_off); + put32(shdr+140, n_lits*12); put32(shdr+144, 4); put32(shdr+148, 1); + put32(shdr+152, 4); put32(shdr+156, 12); + // 4: .symtab (link=.strtab, info=first global) + put32(shdr+160, 25); put32(shdr+164, 2); put32(shdr+176, symtab_off); + put32(shdr+180, n_syms*16); put32(shdr+184, 5); put32(shdr+188, 1); + put32(shdr+192, 4); put32(shdr+196, 16); + // 5: .strtab + put32(shdr+200, 33); put32(shdr+204, 3); put32(shdr+216, strtab_off); + put32(shdr+220, str_off); put32(shdr+232, 1); + // 6: .shstrtab + put32(shdr+240, 41); put32(shdr+244, 3); put32(shdr+256, shstr_off); + put32(shdr+260, shstr_sz); put32(shdr+272, 1); + + // Single PT_LOAD: text+rodata file-backed, .bss tail is memsz-filesz zero fill. + // The RISC-V loader pins svaddr to 0, so this segment must be vaddr 0. + char phdr[32]; memset(phdr, 0, PHDR_SZ); // PHDR_SZ; literal size for self-host + put32(phdr+0, 1); // p_type = PT_LOAD + put32(phdr+4, 0); // p_offset = 0 + put32(phdr+8, 0); // p_vaddr = 0 (== p_offset) + put32(phdr+16, rodata_addr + rodata_sz); // p_filesz: through .rodata + put32(phdr+20, bss_addr + bss_size); // p_memsz: + zero-fill bss + put32(phdr+24, 7); put32(phdr+28, 4); // p_flags = R|W|X, p_align + + char ehdr[52]; memset(ehdr, 0, 52); + ehdr[0]=0x7f; ehdr[1]='E'; ehdr[2]='L'; ehdr[3]='F'; ehdr[4]=1; ehdr[5]=1; ehdr[6]=1; + put16(ehdr+16, 3); put16(ehdr+18, 243); // e_type=ET_DYN, e_machine=EM_RISCV + put32(ehdr+20, 1); put32(ehdr+24, main_addr); // e_version, e_entry + put32(ehdr+28, phoff); put32(ehdr+32, shoff); // e_phoff, e_shoff + put16(ehdr+40, 52); put16(ehdr+46, 40); // ehsize, shentsize + put16(ehdr+42, PHDR_SZ); put16(ehdr+44, N_PHDRS); // phentsize, phnum + put16(ehdr+48, N_SECS); put16(ehdr+50, 6); // shnum, shstrndx + + int f = open(out, O_WRONLY | O_CREAT | O_TRUNC, 420); // 0644 + if (f < 0) { printf("rcc700: cannot open output file: %s\n", out); exit(1); } + write(f, ehdr, 52); + write(f, phdr, PHDR_SZ); + write(f, pool, pool_sz); write(f, code_data, code_size); + lseek(f, rodata_addr, SEEK_SET); write(f, rodata, rodata_sz); + lseek(f, rela_off, SEEK_SET); write(f, rels, n_lits * 12); + write(f, syms, n_syms * 16); + write(f, strtab, str_off); + write(f, shstr, shstr_sz); + lseek(f, shoff, SEEK_SET); write(f, shdr, SHDR_SZ); + close(f); + free(pool); free(rels); free(syms); free(strtab); free(shdr); +} + +void print_stats(char *outfile) { + int fsz = 0; int f = open(outfile, O_RDONLY, 0); + if (f >= 0) { fsz = lseek(f, 0, SEEK_END); close(f); } + printf("\n[ rcc700 ] BUILD COMPLETED > OK\n"); + printf("> IN : %d Lines / %d Tokens\n", line, token_cnt); + printf("> SYM : %d Funcs / %d Globals / %d Literals / %d Patches\n", n_funcs, n_globals, n_lits, n_patches); + printf("> OUT : %d B .text / %d B .rodata / %d B bss / %d B ELF\n", code_size, rodata_sz, bss_size, fsz); +} diff --git a/Apps/Rcc700/main/Source/tactility.c b/Apps/Rcc700/main/Source/tactility.c new file mode 100644 index 0000000..f04efbc --- /dev/null +++ b/Apps/Rcc700/main/Source/tactility.c @@ -0,0 +1,61 @@ +/* + * rcc700.c is built with main() and open() renamed (see main/CMakeLists.txt): + * - main() here requires an input file, and names the output after it when there's no "-o". + * - open() translates the newlib flags that rcc700 hardcodes to the platform's own. + */ +#include +#include +#include +#include + +#define RCC700_O_CREAT 512 +#define RCC700_O_TRUNC 1024 + +int rcc700_main(int argc, char** argv); + +int rcc700_open(char* path, int flags, int mode) { + int platform_flags = flags & (O_RDONLY | O_WRONLY | O_RDWR); + if (flags & RCC700_O_CREAT) { + platform_flags |= O_CREAT; + } + if (flags & RCC700_O_TRUNC) { + platform_flags |= O_TRUNC; + } + return open(path, platform_flags, mode); +} + +/** @return the input path with its extension replaced by ".elf", NULL when out of memory */ +static char* get_default_output_path(const char* input_path) { + const char* name = strrchr(input_path, '/'); + name = (name != NULL) ? name + 1 : input_path; + const char* extension = strrchr(name, '.'); + // A leading dot is part of the name (e.g. ".c" becomes ".c.elf") + const size_t base_length = (extension != NULL && extension != name) ? (size_t)(extension - input_path) : strlen(input_path); + char* output_path = malloc(base_length + sizeof(".elf")); + if (output_path != NULL) { + memcpy(output_path, input_path, base_length); + memcpy(output_path + base_length, ".elf", sizeof(".elf")); + } + return output_path; +} + +int main(int argc, char* argv[]) { + // rcc700 only takes the "-o" option after the input file + if (argc == 4 && strcmp(argv[2], "-o") == 0) { + return rcc700_main(argc, argv); + } + if (argc != 2 || argv[1][0] == '-') { + fprintf(stderr, "Usage: rcc700 FILE.c [-o OUTPUT]\n"); + return EXIT_FAILURE; + } + + char* output_path = get_default_output_path(argv[1]); + if (output_path == NULL) { + fprintf(stderr, "rcc700: out of memory\n"); + return EXIT_FAILURE; + } + char* forwarded_argv[] = { argv[0], argv[1], "-o", output_path, NULL }; + const int result = rcc700_main(4, forwarded_argv); + free(output_path); + return result; +} diff --git a/Apps/Rcc700/manifest.properties b/Apps/Rcc700/manifest.properties new file mode 100644 index 0000000..1c8fc0a --- /dev/null +++ b/Apps/Rcc700/manifest.properties @@ -0,0 +1,16 @@ +manifest.version=0.3 +target.sdk=0.8.0-dev +target.platforms=esp32,esp32s3,esp32c6,esp32p4,posix-x86_64 +id=tactility.rcc700 +version.name=0.1.0 +version.code=1 +requires.ram=2M +name=rcc700 +description=Mini C compiler for ESP32 RISC-V +app.0.id=tactility.rcc700 +app.0.name=rcc700 +app.0.binary=rcc700 +app.0.hidden=true +app.0.headless=true +app.0.stack.depth=16384 +app.0.cleanup=true diff --git a/Apps/Xcc700/CMakeLists.txt b/Apps/Xcc700/CMakeLists.txt new file mode 100644 index 0000000..449a540 --- /dev/null +++ b/Apps/Xcc700/CMakeLists.txt @@ -0,0 +1,13 @@ +# tactility-cmakelists-version: 1 +cmake_minimum_required(VERSION 3.20) + +if (NOT DEFINED ENV{TACTILITY_SDK_PATH}) + message(FATAL_ERROR "TACTILITY_SDK_PATH environment variable is not set") +endif() + +get_filename_component(TACTILITY_SDK_PATH "$ENV{TACTILITY_SDK_PATH}" ABSOLUTE) +include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake") + +tactility_project_pre(xcc700) +project(xcc700) +tactility_project_post(xcc700) diff --git a/Apps/Xcc700/LICENSE b/Apps/Xcc700/LICENSE new file mode 100644 index 0000000..bf9a44b --- /dev/null +++ b/Apps/Xcc700/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Valentyn Danylchuk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Apps/Xcc700/README.md b/Apps/Xcc700/README.md new file mode 100644 index 0000000..4d2c0e9 --- /dev/null +++ b/Apps/Xcc700/README.md @@ -0,0 +1,27 @@ +# xcc700 + +[xcc700](https://github.com/valdanylchuk/xcc700) is a self-hosting mini C compiler for ESP32 (Xtensa). + +## Usage + +Run it from the Terminal app: + +``` +xcc700 FILE.c [-o OUTPUT] +``` + +Without `-o`, the output is named after the input: `something.c` becomes `something.elf`. +The output is a relocatable Xtensa ELF for the [ESP-IDF elf_loader](https://components.espressif.com/components/espressif/elf_loader/). + +It supports a small subset of C: see the [xcc700 README](https://github.com/valdanylchuk/xcc700#what-is-missing). + +## Source + +`main/Source/xcc700.c` is xcc700 (upstream commit `a6179d2`), unmodified. +`main/Source/tactility.c` replaces: +- `main()`, to require an input file and name the default output after it +- `open()`, to translate the Linux flags that xcc700 hardcodes + +## License + +xcc700 is licensed under the MIT license, see [LICENSE](LICENSE). diff --git a/Apps/Xcc700/main/CMakeLists.txt b/Apps/Xcc700/main/CMakeLists.txt new file mode 100644 index 0000000..70979ed --- /dev/null +++ b/Apps/Xcc700/main/CMakeLists.txt @@ -0,0 +1,18 @@ +include("$ENV{TACTILITY_SDK_PATH}/TactilitySDK.cmake") + +file(GLOB_RECURSE SOURCE_FILES Source/*.c) +tactility_component_register(SRCS ${SOURCE_FILES}) + +# xcc700.c is kept unmodified: tactility.c provides main() and open() in its place. +# It declares its own libc prototypes (with int sizes), which don't match the builtin ones. +if (DEFINED COMPONENT_LIB) + set(XCC700_TARGET ${COMPONENT_LIB}) +else () + set(XCC700_TARGET ${PROJECT_NAME}) +endif () +set_source_files_properties(Source/xcc700.c + TARGET_DIRECTORY ${XCC700_TARGET} + PROPERTIES + COMPILE_DEFINITIONS "open=xcc700_open" + COMPILE_OPTIONS "-Umain;-Dmain=xcc700_main;-fno-builtin;-Wno-builtin-declaration-mismatch;-Wno-misleading-indentation" +) diff --git a/Apps/Xcc700/main/Source/tactility.c b/Apps/Xcc700/main/Source/tactility.c new file mode 100644 index 0000000..2b3290c --- /dev/null +++ b/Apps/Xcc700/main/Source/tactility.c @@ -0,0 +1,61 @@ +/* + * xcc700.c is built with main() and open() renamed (see main/CMakeLists.txt): + * - main() here requires an input file, and names the output after it when there's no "-o". + * - open() translates the Linux flags that xcc700 hardcodes to the platform's own. + */ +#include +#include +#include +#include + +#define XCC700_O_CREAT 64 +#define XCC700_O_TRUNC 512 + +int xcc700_main(int argc, char** argv); + +int xcc700_open(char* path, int flags, int mode) { + int platform_flags = flags & (O_RDONLY | O_WRONLY | O_RDWR); + if (flags & XCC700_O_CREAT) { + platform_flags |= O_CREAT; + } + if (flags & XCC700_O_TRUNC) { + platform_flags |= O_TRUNC; + } + return open(path, platform_flags, mode); +} + +/** @return the input path with its extension replaced by ".elf", NULL when out of memory */ +static char* get_default_output_path(const char* input_path) { + const char* name = strrchr(input_path, '/'); + name = (name != NULL) ? name + 1 : input_path; + const char* extension = strrchr(name, '.'); + // A leading dot is part of the name (e.g. ".c" becomes ".c.elf") + const size_t base_length = (extension != NULL && extension != name) ? (size_t)(extension - input_path) : strlen(input_path); + char* output_path = malloc(base_length + sizeof(".elf")); + if (output_path != NULL) { + memcpy(output_path, input_path, base_length); + memcpy(output_path + base_length, ".elf", sizeof(".elf")); + } + return output_path; +} + +int main(int argc, char* argv[]) { + // xcc700 only takes the "-o" option after the input file + if (argc == 4 && strcmp(argv[2], "-o") == 0) { + return xcc700_main(argc, argv); + } + if (argc != 2 || argv[1][0] == '-') { + fprintf(stderr, "Usage: xcc700 FILE.c [-o OUTPUT]\n"); + return EXIT_FAILURE; + } + + char* output_path = get_default_output_path(argv[1]); + if (output_path == NULL) { + fprintf(stderr, "xcc700: out of memory\n"); + return EXIT_FAILURE; + } + char* forwarded_argv[] = { argv[0], argv[1], "-o", output_path, NULL }; + const int result = xcc700_main(4, forwarded_argv); + free(output_path); + return result; +} diff --git a/Apps/Xcc700/main/Source/xcc700.c b/Apps/Xcc700/main/Source/xcc700.c new file mode 100644 index 0000000..a9f4cc2 --- /dev/null +++ b/Apps/Xcc700/main/Source/xcc700.c @@ -0,0 +1,700 @@ +// xcc700.c - A mini C compiler for esp32 / Xtensa + +// --- Libc Shims --- +int printf(char *fmt, ...); void exit(int status); int clock(); +void *malloc(int size); void *calloc(int nmemb, int size); void free(void *ptr); +void *memcpy(void *dest, void *src, int n); void *memset(void *s, int c, int n); +int strcmp(char *s1, char *s2); char *strcpy(char *dest, char *src); +int strlen(char *s); int strtol(char *nptr, char **endptr, int base); +int open(char *pathname, int flags, int mode); int close(int fd); +int read(int fd, void *buf, int count); int write(int fd, void *buf, int count); +int lseek(int fd, int offset, int whence); + +// --- Constants & Globals --- +enum { + MAX_VARS=256, MAX_LOCAL_VARS=128, MAX_LITS=256, MAX_PATCHES=1200, STRTAB=2048, + R_XTENSA_RELATIVE=5, R_XTENSA_JMP_SLOT=4, + SP_REG=1, RES_REG=8, TMP_REG=9, ARG1_REG=10, ARG2_REG=11, + O_RDONLY=0, O_WRONLY=1, O_RDWR=2, O_CREAT=64, O_TRUNC=512, + SEEK_SET=0, SEEK_END=2 +}; + +enum { + T_EOF=0, T_INT=256, T_CHAR, T_VOID, T_IDENT, T_NUM, T_STR, T_EQ, T_NE, T_LE, T_GE, + T_SHL, T_SHR, T_LAND, T_LOR, T_RETURN, T_ELLIPSIS, T_IF, T_ELSE, T_WHILE, T_ENUM, T_INC, T_DEC +}; + +enum { L_INT, L_STR, L_FUNC, L_BSS }; +enum { TF_BYTE=1, TF_PTR=2, TF_ARR=4, TF_GLOBAL=8, TF_CONST=16 }; +enum { TY_INT=0, TY_BYTE=1, TY_INTPTR=2, TY_BYTEPTR=3, TY_INTARR=4, TY_BYTEARR=5 }; + +// Global Context +char *src; +char *rodata; int rodata_sz; int rodata_cap; +int token; int num_val; int line; int token_cnt; +char str_val[256]; int str_len; +char *code_data; int code_size; int code_cap; +char *name_buf; int name_sz; int name_cap; + +// Vars +int var_name_off[MAX_VARS]; +int var_offsets[MAX_VARS]; +int var_types[MAX_VARS]; +int n_vars; int locals; int esp; int expr_type; int n_globals; int bss_size; + +// Funcs +int func_name_off[MAX_VARS]; +int func_addrs[MAX_VARS]; +int n_funcs; + +// Literals +int lit_vals[MAX_LITS]; +int lit_types[MAX_LITS]; +int n_lits; + +// Patches +int patch_offs[MAX_PATCHES]; +int patch_lits[MAX_PATCHES]; +int n_patches; + +// Must put main() first. Forward-declare the functions it uses. +void next(); void parse_func(); void write_elf(char *out); void print_stats(char *outfile, int t_ms); + +int main(int argc, char **argv) { + line = 1; name_buf = 0; + char *input_fname = argc > 1 ? argv[1] : "input.c"; + char *output_fname = (argc > 3 && !strcmp(argv[2], "-o")) ? argv[3] : "output.elf"; + int f = open(input_fname, O_RDONLY, 0); + if (f < 0) { printf("Cannot open input file: %s\n", input_fname); return 1; } + int f_size = lseek(f, 0, SEEK_END); lseek(f, 0, SEEK_SET); + char *src_buf = malloc(f_size+1); src = src_buf; + code_cap = 32768; code_data = malloc(code_cap); + rodata_cap = 2048; rodata = malloc(rodata_cap); + name_cap = 4096; name_buf = malloc(name_cap); name_sz = 0; + if (!src_buf || !code_data || !rodata || !name_buf) { printf("Error: Not enough memory!\n"); return 1; } + + read(f, src, f_size); src[f_size]=0; close(f); + int start_clk = clock(); + next(); while(token!=T_EOF) parse_func(); + int duration_ms = clock() - start_clk; // In espressif newlib, CLOCKS_PER_SEC=1000 + free(src_buf); write_elf(output_fname); + print_stats(output_fname, duration_ms); + free(rodata); free(name_buf); + return 0; +} + +// Replace macros to avoid ABI issues +int isdigit(int c) { return c >= '0' && c <= '9'; } +int isalpha(int c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } +int isspace(int c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r'; } +int isalnum(int c) { return isdigit(c) || isalpha(c); } + +// Helpers +void put32(char *b, int v) { b[0]=v; b[1]=v>>8; b[2]=v>>16; b[3]=v>>24; } +void put16(char *b, int v) { b[0]=v; b[1]=v>>8; } + +// --- Lexer --- +void next() { + while (isspace(*src) || (src[0]=='/' && src[1]=='/')) { + if (*src == '\n') ++line; + if (*src == '/') while (*src && *src != '\n') ++src; else ++src; + } + if (!*src) { token = T_EOF; return; } + if (src[0]=='+' && src[1]=='+') { token=T_INC; src=src+2; return; } + if (src[0]=='-' && src[1]=='-') { token=T_DEC; src=src+2; return; } + if (src[0]=='=' && src[1]=='=') { token=T_EQ; src=src+2; return; } + if (src[0]=='!' && src[1]=='=') { token=T_NE; src=src+2; return; } + if (src[0]=='<' && src[1]=='=') { token=T_LE; src=src+2; return; } + if (src[0]=='>' && src[1]=='=') { token=T_GE; src=src+2; return; } + if (src[0]=='<' && src[1]=='<') { token=T_SHL; src=src+2; return; } + if (src[0]=='>' && src[1]=='>') { token=T_SHR; src=src+2; return; } + if (src[0]=='&' && src[1]=='&') { token=T_LAND; src=src+2; return; } + if (src[0]=='|' && src[1]=='|') { token=T_LOR; src=src+2; return; } + if (src[0]=='.' && src[1]=='.' && src[2]=='.') { token=T_ELLIPSIS; src=src+3; return; } + + if (isalpha(*src) || *src == '_') { + char *p = str_val; + while (isalnum(*src) || *src == '_') { *p = *src; ++p; ++src; } + *p = 0; + token = !strcmp(str_val,"int") ? T_INT : !strcmp(str_val,"char") ? T_CHAR : + !strcmp(str_val,"void") ? T_VOID : !strcmp(str_val, "enum") ? T_ENUM : + !strcmp(str_val,"if") ? T_IF : !strcmp(str_val,"else") ? T_ELSE : + !strcmp(str_val,"while") ? T_WHILE : !strcmp(str_val,"return") ? T_RETURN : T_IDENT; + } else if (isdigit(*src)) { + num_val = strtol(src, &src, 0); token = T_NUM; + } else if (*src == '\'') { + ++src; + if (*src == '\\') { + ++src; + if (*src == 'n') num_val = '\n'; else if (*src == 't') num_val = '\t'; + else if (*src == 'r') num_val = '\r'; else if (*src == '0') num_val = 0; else num_val = *src; + } else num_val = *src; + ++src; if (*src == '\'') ++src; + token = T_NUM; + } else if (*src == '"') { + char *p = str_val; ++src; + while (*src && *src != '"') { + if (*src == '\\') { + ++src; + if (*src == 'n') *p='\n'; else if (*src == 't') *p='\t'; + else if (*src == 'r') *p='\r'; else if (*src == '0') *p = '\0'; else *p=*src; + } else *p=*src; + ++src; ++p; + } + *p = 0; if (*src) ++src; + str_len = p - str_val; + token = T_STR; + } else { token = *src; ++src; } + ++token_cnt; +} + +void expect(int tok) { + if (token != tok) { + if (tok < 256) printf("Error: Line %d: expected '%c'", line, tok); + else printf("Error: Line %d: expected token %d", line, tok); + if (token < 256) printf(", got '%c'\n", token); + else printf(", got token %d (%s)\n", token, str_val); + exit(1); + } + next(); +} + +// --- Code Emitter --- +void ensure_code_capacity(int n) { + if (code_size + n > code_cap) { printf("Error: Out of memory (code)!\n"); exit(1); } +} +void emit3(int b0, int b1, int b2) { + ensure_code_capacity(3); + code_data[code_size] = b0; code_data[code_size+1] = b1; code_data[code_size+2] = b2; + code_size = code_size + 3; +} +void emit2(int b0, int b1) { + ensure_code_capacity(2); + code_data[code_size] = b0; ++code_size; + code_data[code_size] = b1; ++code_size; +} + +void emit_rrr(int op, int op0, int r, int s, int t) { emit3((t<<4)|op0, (r<<4)|s, op); } +void emit_l32i(int d, int b, int off) { emit3((d<<4)|2, (2<<4)|b, off/4); } +void emit_s32i(int s, int b, int off) { emit3((s<<4)|2, (6<<4)|b, off/4); } +void emit_l8ui(int d, int b, int off) { emit3((d<<4)|2, (0<<4)|b, off); } +void emit_s8i(int s, int b, int off) { emit3((s<<4)|2, (4<<4)|b, off); } +void emit_l32r(int r) { emit3((r<<4)|1, 0, 0); } +void emit_add_n(int d, int s1, int s2) { emit2((s2<<4)|0xa, (d<<4)|s1); } +void emit_mov_n(int d, int s) { emit2((d<<4)|0xd, s); } +void emit_movi_n(int d, int imm) { + int i=(((imm&0xf)<<12)|(d<<8)|(((imm&0x70)>>4)<<4)|0xc); + emit2(i&0xff, i>>8); +} +void emit_load_lit(int val, int type); +void emit_movi(int d, int imm) { + if (imm >= -2048 && imm < 2048) emit3((d<<4)|2, 0xa0|((imm>>8)&0xf), imm&0xff); + else { emit_load_lit(imm, L_INT); if (d != RES_REG) emit_mov_n(d, RES_REG); } +} +void emit_addi(int d, int s, int imm) { + if (imm >= -128 && imm < 128) emit3((d<<4)|2, (0xc<<4)|s, imm&0xff); + else { emit_movi(TMP_REG, imm); emit_add_n(d, s, TMP_REG); } +} +void emit_op(int op, int d, int s1, int s2) { emit_rrr(op, 0, d, s1, s2); } +void emit_neg(int d, int s1) { emit_op(0x60, d, 0, s1); } +void emit_xor(int d, int s1, int s2) { emit_op(0x30, d, s1, s2); } +void emit_br(int op, int s, int t) { emit3((t<<4)|7, (op<<4)|s, 1); } +void emit_j(int off) { int i=0x06|((off&0x3ffff)<<6); emit3(i, i>>8, i>>16); } +void emit_beqz(int s, int off) { int i=((off&0xfff)<<12)|(s<<8)|0x16; emit3(i, i>>8, i>>16); } +void emit_callx8() { emit3(0xe0, 0x08, 0x00); } +void emit_retw_n() { emit2(0x1d, 0xf0); } +void emit_entry(int sz) { int imm12 = sz/8; emit3(0x36, (imm12<<4)|1, imm12>>4); } + +void patch(int addr, int is_j) { + int off = code_size - addr - 4; + int i = 0; memcpy(&i, code_data + addr, 3); + if (is_j) i = i | ((off & 0x3ffff) << 6); + else i = i | ((off & 0xfff) << 12); + memcpy(code_data + addr, &i, 3); +} + +// --- Literals/symbols handling --- +int add_name(char *s) { + int len = strlen(s) + 1; + if (name_sz + len > name_cap) { printf("Out of memory (names)\n"); exit(1); } + int off = name_sz; strcpy(name_buf + off, s); + name_sz = name_sz + len; return off; +} +int get_func(char *name) { + int i=0; + while (i= MAX_LITS) { printf("Error: Too many literals\n"); exit(1); } + lit_vals[n_lits] = val; lit_types[n_lits] = type; ++n_lits; + } + if (n_patches >= MAX_PATCHES) { printf("Error: Too many patches\n"); exit(1); } + patch_offs[n_patches] = code_size; patch_lits[n_patches] = i; ++n_patches; + emit_l32r(RES_REG); +} + +void push(int r) { emit_s32i(r, SP_REG, locals + esp); esp = esp + 4; } +void pop(int r) { esp = esp - 4; emit_l32i(r, SP_REG, locals + esp); } + +// --- Parser --- +int get_prec(int t) { + if(t=='?') return 1; if(t==T_LOR) return 2; if(t==T_LAND) return 3; + if(t=='|') return 4; if(t=='^') return 5; if(t=='&') return 6; + if(t==T_EQ||t==T_NE) return 7; + if(t=='<'||t=='>'||t==T_LE||t==T_GE) return 8; + if(t==T_SHL||t==T_SHR) return 9; + if(t=='+'||t=='-') return 10; if(t=='*'||t=='/'||t=='%') return 11; + return 0; +} + +void emit_binop(int op) { + if (op == T_LAND || op == T_LOR) { + emit_movi_n(ARG1_REG, 0); emit_beqz(TMP_REG, 1); emit_movi_n(ARG1_REG, 1); + emit_movi_n(ARG2_REG, 0); emit_beqz(RES_REG, 1); emit_movi_n(ARG2_REG, 1); + emit_op(op==T_LAND ? 0x10 : 0x20, RES_REG, ARG2_REG, ARG1_REG); + } else if (get_prec(op) >= 7 && get_prec(op) <= 8) { + emit_mov_n(ARG1_REG, RES_REG); emit_movi_n(RES_REG, 0); + if(op=='<') emit_br(0xa, TMP_REG, ARG1_REG); else if(op==T_LE) emit_br(0x2, ARG1_REG, TMP_REG); + else if(op=='>') emit_br(0xa, ARG1_REG, TMP_REG); else if(op==T_GE) emit_br(0x2, TMP_REG, ARG1_REG); + else if(op==T_EQ) emit_br(0x9, TMP_REG, ARG1_REG); else if(op==T_NE) emit_br(0x1, TMP_REG, ARG1_REG); + emit_movi_n(RES_REG, 1); + } else if(op=='+') emit_add_n(RES_REG, TMP_REG, RES_REG); + else if(op=='-') emit_op(0xc0, RES_REG, TMP_REG, RES_REG); + else if(op=='*') emit_op(0x82, RES_REG, TMP_REG, RES_REG); + else if(op=='/') emit_op(0xd2, RES_REG, TMP_REG, RES_REG); + else if(op=='%') emit_op(0xf2, RES_REG, TMP_REG, RES_REG); + else if(op=='&') emit_op(0x10, RES_REG, TMP_REG, RES_REG); + else if(op=='|') emit_op(0x20, RES_REG, TMP_REG, RES_REG); + else if(op=='^') emit_op(0x30, RES_REG, TMP_REG, RES_REG); + else if(op==T_SHL) { emit3(0,0x10|RES_REG,0x40); emit3(0,(RES_REG<<4)|TMP_REG,0xA1); } + else if(op==T_SHR) { emit3(0,RES_REG,0x40); emit3(TMP_REG<<4,RES_REG<<4,0xB1); } + expr_type = TY_INT; +} + +void parse_expr(int limit); + +void parse_call(char *name) { + int arg_cnt = 0; next(); + if (token != ')') { + parse_expr(1); push(RES_REG); ++arg_cnt; + if (arg_cnt > 5) { printf("Error: L%d: Arg count exceeds the supported maximum of 5\n", line); exit(1); } + while (token == ',') { next(); parse_expr(1); push(RES_REG); ++arg_cnt; } + while (arg_cnt > 0) { --arg_cnt; pop(arg_cnt <= 4 ? ARG1_REG + arg_cnt : TMP_REG); } + } + expect(')'); emit_load_lit(get_func(name), L_FUNC); emit_callx8(); +} + +int find_var(char *name) { + int i = n_vars; + while (i > 0) { --i; if (!strcmp(name_buf + var_name_off[i], name)) return i; } + return -1; +} +void load_var_address(int i) { + if (var_types[i] & TF_GLOBAL) emit_load_lit(var_offsets[i], L_BSS); + else emit_addi(RES_REG, SP_REG, var_offsets[i]); +} +void load_var(int i) { + int ty = var_types[i]; int is_byte = ((ty & ~TF_GLOBAL) == TY_BYTE); + if (ty & TF_CONST) { + emit_movi(RES_REG, var_offsets[i]); expr_type = TY_INT; + } else if (ty & TF_ARR) { + load_var_address(i); expr_type = (ty & TF_BYTE) ? TY_BYTEPTR : TY_INTPTR; + } else if (ty & TF_GLOBAL) { + load_var_address(i); + if (is_byte) emit_l8ui(RES_REG, RES_REG, 0); else emit_l32i(RES_REG, RES_REG, 0); + expr_type = ty & ~TF_GLOBAL; + } else { + if (is_byte) emit_l8ui(RES_REG, SP_REG, var_offsets[i]); + else emit_l32i(RES_REG, SP_REG, var_offsets[i]); + expr_type = ty & ~TF_GLOBAL; + } +} + +void parse_index(int base_type) { + next(); push(RES_REG); + parse_expr(1); expect(']'); + if (!(base_type & TF_BYTE)) { emit_add_n(RES_REG, RES_REG, RES_REG); emit_add_n(RES_REG, RES_REG, RES_REG); } + pop(TMP_REG); emit_add_n(RES_REG, TMP_REG, RES_REG); +} + +void parse_factor() { + if (token == T_INC || token == T_DEC) { + int diff = (token == T_INC) ? 1 : -1; next(); + char name[64]; strcpy(name, str_val); expect(T_IDENT); + int i = find_var(name); + if (i < 0) { printf("Undef: %s\n", name); exit(1); } + load_var(i); emit_addi(RES_REG, RES_REG, diff); + int is_byte = ((var_types[i] & ~TF_GLOBAL) == TY_BYTE); + if (var_types[i] & TF_GLOBAL) { + emit_mov_n(ARG1_REG, RES_REG); load_var_address(i); + if (is_byte) emit_s8i(ARG1_REG, RES_REG, 0); else emit_s32i(ARG1_REG, RES_REG, 0); + emit_mov_n(RES_REG, ARG1_REG); + } else { + if (is_byte) emit_s8i(RES_REG, SP_REG, var_offsets[i]); + else emit_s32i(RES_REG, SP_REG, var_offsets[i]); + } + expr_type = TY_INT; + } else if (token == '!' || token == '~' || token == '-') { + int op = token; next(); parse_factor(); + if (op == '-') emit_neg(RES_REG, RES_REG); + else if (op == '~') { emit_movi(TMP_REG, -1); emit_xor(RES_REG, RES_REG, TMP_REG); } + else { emit_movi_n(TMP_REG, 1); emit_beqz(RES_REG, 1); emit_movi_n(TMP_REG, 0); emit_mov_n(RES_REG, TMP_REG); } + expr_type = TY_INT; + } else if (token == '*') { + next(); parse_factor(); int pt = expr_type; + if (pt & TF_BYTE) { emit_l8ui(RES_REG, RES_REG, 0); expr_type = TY_BYTE; } + else { emit_l32i(RES_REG, RES_REG, 0); expr_type = TY_INT; } + } else if (token == '&') { + next(); char name[64]; strcpy(name, str_val); expect(T_IDENT); + int i = find_var(name); + if (i < 0) { printf("Undef: %s\n", name); exit(1); } + load_var_address(i); + expr_type = (var_types[i] & TF_BYTE) ? TY_BYTEPTR : TY_INTPTR; + } else if (token == T_NUM) { + emit_movi(RES_REG, num_val); expr_type = TY_INT; next(); + } else if (token == T_STR) { + if (rodata_sz + str_len + 1 > rodata_cap) { printf("Out of memory (rodata)\n"); exit(1); } + emit_load_lit(rodata_sz, L_STR); + memcpy(rodata + rodata_sz, str_val, str_len + 1); + rodata_sz = rodata_sz + str_len + 1; + expr_type = TY_BYTEPTR; next(); + } else if (token == T_IDENT) { + char name[64]; strcpy(name, str_val); next(); + if (token == '(') { + parse_call(name); emit_mov_n(RES_REG, ARG1_REG); expr_type = TY_INT; + } else { + int i = find_var(name); + if (i < 0) { printf("Undef: %s\n", name); exit(1); } + load_var(i); + if (token == '[') { + int bt = expr_type; + parse_index(bt); + if (bt & TF_BYTE) { emit_l8ui(RES_REG, RES_REG, 0); expr_type = TY_BYTE; } + else { emit_l32i(RES_REG, RES_REG, 0); expr_type = TY_INT; } + } + } + } else if (token == '(') { next(); parse_expr(1); expect(')'); } + else { printf("Error: Line %d: unexpected token %d\n", line, token); exit(1); } +} + +void parse_expr(int limit) { + parse_factor(); + while (get_prec(token) >= limit) { + int op = token; next(); + if (op == '?') { + int patch_to_false = code_size; emit_beqz(RES_REG, 0); + parse_expr(2); + int patch_to_end = code_size; emit_j(0); + expect(':'); patch(patch_to_false, 0); + parse_expr(1); + patch(patch_to_end, 1); + } else { + push(RES_REG); parse_expr(get_prec(op) + 1); + pop(TMP_REG); emit_binop(op); + } + } +} + +int align4(int x) { return (x + 3) & ~3; } + +void parse_stmt() { + esp = 0; + if (token == T_WHILE) { + next(); int loop_start = code_size; + expect('('); parse_expr(1); expect(')'); + int exit_patch = code_size; emit_beqz(RES_REG, 0); + parse_stmt(); + emit_j(loop_start - code_size - 4); + patch(exit_patch, 0); + } else if (token == T_IF) { + next(); expect('('); parse_expr(1); expect(')'); + int p1 = code_size; emit_beqz(RES_REG, 0); + parse_stmt(); + if (token == T_ELSE) { + int p2 = code_size; emit_j(0); + patch(p1, 0); next(); parse_stmt(); patch(p2, 1); + } else patch(p1, 0); + } else if (token == '{') { + next(); while (token != '}' && token != T_EOF) parse_stmt(); expect('}'); + } else if (token == T_INT || token == T_CHAR) { + int is_byte = (token == T_CHAR); next(); + int is_ptr = 0; while (token == '*') { is_ptr = 1; next(); } + var_name_off[n_vars] = add_name(str_val); + var_offsets[n_vars] = locals; + if (++n_vars >= MAX_VARS) { printf("MAX_VARS exceeded\n"); exit(1); } + expect(T_IDENT); + if (token == '[') { + next(); int sz = num_val; expect(T_NUM); expect(']'); + var_types[n_vars-1] = is_byte ? TY_BYTEARR : TY_INTARR; + locals = locals + (is_byte ? align4(sz) : sz * 4); + } else { + var_types[n_vars-1] = is_ptr ? (is_byte ? TY_BYTEPTR : TY_INTPTR) : (is_byte ? TY_BYTE : TY_INT); + locals = locals + 4; + expect('='); parse_expr(1); + if (is_byte && !is_ptr) emit_s8i(RES_REG, SP_REG, var_offsets[n_vars-1]); + else emit_s32i(RES_REG, SP_REG, var_offsets[n_vars-1]); + } + if (locals >= MAX_LOCAL_VARS * 4) { + printf("Error: Line %d: Function stack frame exceeded MAX_LOCAL_VARS\n", line); exit(1); + } + expect(';'); + } else if (token == T_RETURN) { + next(); if (token != ';') parse_expr(1); else emit_movi_n(RES_REG, 0); + emit_mov_n(2, RES_REG); emit_retw_n(); expect(';'); + } else if (token == T_IDENT) { + char name[64]; strcpy(name, str_val); next(); + if (token == '(') { + parse_call(name); expect(';'); + } else { + int i = find_var(name); + if (i < 0) { printf("Undef: %s\n", name); exit(1); } + if (token == '[') { + load_var(i); + int bt = expr_type; + parse_index(bt); push(RES_REG); + expect('='); parse_expr(1); + pop(ARG1_REG); + if (bt & TF_BYTE) emit_s8i(RES_REG, ARG1_REG, 0); + else emit_s32i(RES_REG, ARG1_REG, 0); + } else { + expect('='); parse_expr(1); + if (var_types[i] & TF_GLOBAL) { + emit_mov_n(ARG1_REG, RES_REG); + emit_load_lit(var_offsets[i], L_BSS); + emit_s32i(ARG1_REG, RES_REG, 0); + emit_mov_n(RES_REG, ARG1_REG); + } else emit_s32i(RES_REG, SP_REG, var_offsets[i]); + } + expect(';'); + } + } else if (token == '*') { + next(); parse_factor(); + int pt = expr_type; + push(RES_REG); expect('='); parse_expr(1); + pop(ARG1_REG); + if (pt & TF_BYTE) emit_s8i(RES_REG, ARG1_REG, 0); + else emit_s32i(RES_REG, ARG1_REG, 0); + expect(';'); + } else { parse_expr(1); expect(';'); } +} + +void parse_func() { + if (token == T_ENUM) { + next(); if (token == T_IDENT) next(); + expect('{'); int val = 0; + while (token == T_IDENT) { + var_name_off[n_globals] = add_name(str_val); + var_offsets[n_globals] = val; + var_types[n_globals] = TF_CONST | TY_INT; + ++n_globals; n_vars = n_globals; next(); + if (token == '=') { next(); val = num_val; var_offsets[n_globals - 1] = val; next(); } + ++val; if (token == ',') next(); + } + expect('}'); expect(';'); return; + } + + int is_byte = (token == T_CHAR); + if (token == T_INT || token == T_CHAR || token == T_VOID) next(); + int is_ptr = 0; while(token=='*') { is_ptr = 1; next(); } + char name[64]; strcpy(name, str_val); expect(T_IDENT); + + if (token == ';' || token == '[') { + int ty = TF_GLOBAL | (is_ptr ? (is_byte ? TY_BYTEPTR : TY_INTPTR) : (is_byte ? TY_BYTE : TY_INT)); + if (token == '[') { + next(); int sz=0; + if (token == T_NUM) { sz = num_val; next(); } + else if (token == T_IDENT) { + int i = find_var(str_val); + if (i < 0 || !(var_types[i] & TF_CONST)) { + printf("Error: Line %d: Undefined constant: %s\n", line, str_val); exit(1); + } + sz = var_offsets[i]; next(); + } else { printf("Error: Line %d: Array size expected\n", line); exit(1); } + expect(']'); + ty = TF_GLOBAL | (is_byte ? TY_BYTEARR : TY_INTARR); + var_offsets[n_globals] = bss_size; + bss_size = bss_size + ((ty & TF_BYTE) ? align4(sz) : sz * 4); + } else { + var_offsets[n_globals] = bss_size; bss_size = bss_size + 4; + } + var_name_off[n_globals] = add_name(name); + var_types[n_globals] = ty; + ++n_globals; n_vars = n_globals; + expect(';'); return; + } + + expect('('); n_vars = n_globals; locals = 32; int n_args = 0; // Reserve 32 bytes for base save area + while (token != ')') { + is_byte = 0; int ptr_count = 0; + if (token == T_CHAR) { is_byte = 1; next(); } + else if (token == T_INT || token == T_VOID || token == T_IDENT || token == T_ELLIPSIS) next(); + while (token == '*') { ++ptr_count; next(); } + if (token == T_IDENT) { + var_name_off[n_vars] = add_name(str_val); + var_offsets[n_vars] = locals; + var_types[n_vars] = ptr_count >= 2 ? TY_INTPTR : ptr_count ? (is_byte ? TY_BYTEPTR : TY_INTPTR) : (is_byte ? TY_BYTE : TY_INT); + locals = locals + 4; ++n_vars; ++n_args; + if (n_vars >= MAX_VARS) { printf("MAX_VARS exceeded\n"); exit(1); } + next(); + } + if (token == ',') next(); + } + expect(')'); + if (token == ';') { next(); return; } // Prototype + + int i = get_func(name); int entry_addr = code_size; func_addrs[i] = entry_addr; + expect('{'); + int stack_sz = (MAX_LOCAL_VARS*4 + 32 + 15) & ~15; + emit_entry(stack_sz); + int j=0; while(j> 4; + emit_retw_n(); expect('}'); +} + +void write_elf(char *out) { + int code_start = n_lits * 4; + int off = 52; // Ehdr + int text_off = off; int text_addr = off; + off = off + align4(code_start + code_size); + int rodata_off = off; int rodata_addr = off; + off = off + align4(rodata_sz); + int bss_off = off; int bss_addr = off; + + int n_syms = n_funcs + 1; + char *syms = calloc(n_funcs+1, 16); + char *strtab = calloc(1, STRTAB); int str_off = 1; + char *lits = calloc(1, code_start); + char *rels = calloc(n_lits, 12); + char *shdr=calloc(1, 320); // 8 sections * 40 bytes + + int fname_len = 0; + int i = 0; while (i < n_funcs) { + put32(syms + (i+1)*16, str_off); + fname_len = strlen(name_buf + func_name_off[i]); + if (str_off + fname_len >= STRTAB) { printf("strtab overflow on func %d len=%d name=%s\n", i, fname_len, name_buf + func_name_off[i]); exit(1); } + strcpy(strtab + str_off, name_buf + func_name_off[i]); str_off = str_off + fname_len+1; + int is_ext = (func_addrs[i] == -1); + syms[(i+1)*16 + 12] = (1<<4)|(is_ext ? 0 : 2); // ST_INFO + put16(syms + (i+1)*16 + 14, is_ext ? 0 : 1); // shndx + if(!is_ext) put32(syms + (i+1)*16 + 4, code_start + func_addrs[i]); + ++i; + } + + int n_rels = 0; + i=0; while(i> 2; + code_data[patch_offs[i] + 1] = imm; + code_data[patch_offs[i] + 2] = imm>>8; + ++i; + } + + // Section Headers + int rela_off=off; off = off + align4(n_rels * 12); + int symtab_off=off; off = off + n_syms * 16; + int strtab_off=off; off = off + str_off; + char *shstrtab = "\0.text\0.rodata\0.bss\0.rela\0.symtab\0.strtab\0.shstrtab\0"; + int shstrtab_off = off; off = off + 53; // sizeof(shstrtab) + + // 1: .text + put32(shdr+40, 1); put32(shdr+44, 1); put32(shdr+48, 6); put32(shdr+52, text_addr); + put32(shdr+56, text_off); put32(shdr+60, code_start+code_size); put32(shdr+72, 4); + // 2: .rodata + put32(shdr+80, 7); put32(shdr+84, 1); put32(shdr+88, 2); put32(shdr+92, rodata_addr); + put32(shdr+96, rodata_off); put32(shdr+100, align4(rodata_sz)); put32(shdr+112, 4); + // 3: .bss + put32(shdr+120, 15); put32(shdr+124, 8); put32(shdr+128, 3); put32(shdr+132, bss_addr); + put32(shdr+136, bss_off); put32(shdr+140, bss_size); put32(shdr+152, 4); + // 4: .rela + put32(shdr+160, 20); put32(shdr+164, 4); put32(shdr+168, 2); put32(shdr+176, rela_off); + put32(shdr+180, n_rels*12); put32(shdr+184, 5); put32(shdr+188, 1); put32(shdr+192, 4); put32(shdr+196, 12); + // 5: .symtab + put32(shdr+200, 26); put32(shdr+204, 2); put32(shdr+216, symtab_off); + put32(shdr+220, n_syms*16); put32(shdr+224, 6); put32(shdr+228, 1); put32(shdr+232, 4); put32(shdr+236, 16); + // 6: .strtab + put32(shdr+240, 34); put32(shdr+244, 3); put32(shdr+256, strtab_off); + put32(shdr+260, str_off); put32(shdr+272, 1); + // 7: .shstrtab + put32(shdr+280, 42); put32(shdr+284, 3); put32(shdr+296, shstrtab_off); + put32(shdr+300, 53); put32(shdr+312, 1); // 53 == sizeof(shstrtab) + + int entry_func = get_func("main"); + int entry_vaddr = text_addr + code_start + func_addrs[entry_func]; + + char ehdr[52]; memset(ehdr, 0, 52); + ehdr[0]=0x7f; ehdr[1]='E'; ehdr[2]='L'; ehdr[3]='F'; ehdr[4]=1; ehdr[5]=1; ehdr[6]=1; + put16(ehdr+16, 1); put16(ehdr+18, 94); // e_type=ET_REL, e_machine=XTENSA + put32(ehdr+20, 1); put32(ehdr+24, entry_vaddr); + put32(ehdr+32, align4(off)); put32(ehdr+36, 0x300); put16(ehdr+40, 52); + put16(ehdr+42, 0); put16(ehdr+44, 0); put16(ehdr+46, 40); put16(ehdr+48, 8); put16(ehdr+50, 7); + + int f = open(out, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (f < 0) { printf("Cannot open output file: %s\n", out); exit(1); } + + write(f, ehdr, 52); write(f, lits, code_start); + write(f, code_data, code_size); free(code_data); + + lseek(f, rodata_off, SEEK_SET); write(f, rodata, rodata_sz); + lseek(f, rela_off, SEEK_SET); write(f, rels, n_rels * 12); + lseek(f, symtab_off, SEEK_SET); write(f, syms, n_syms * 16); + lseek(f, strtab_off, SEEK_SET); write(f, strtab, str_off); + lseek(f, shstrtab_off, SEEK_SET); write(f, shstrtab, 53); // 53 == sizeof(shstrtab) + lseek(f, align4(off), SEEK_SET); write(f, shdr, 320); + + close(f); free(lits); free(rels); free(syms); free(strtab); free(shdr); +} + +void print_stats(char *outfile, int t_ms) { + int fsz = 0; int f = open(outfile, O_RDONLY, 0); + if (f >= 0) { fsz = lseek(f, 0, SEEK_END); close(f); } + else printf("open() failed for stats: %s\n", outfile); + int speed = t_ms > 0 ? (line * 1000) / t_ms : 0; + printf("\n[ xcc700 ] BUILD COMPLETED > OK\n"); + printf("> IN : %d Lines / %d Tokens\n", line, token_cnt); + printf("> SYM : %d Funcs / %d Globals\n", n_funcs, n_globals); + printf("> REL : %d Literals / %d Patches\n", n_lits, n_patches); + printf("> MEM : %d B .rodata / %d B .bss\n", rodata_sz, bss_size); + printf("> OUT : %d B .text / %d B ELF\n", code_size, fsz); + printf("[ %d ms ] >> %d Lines/sec <<\n", t_ms, speed); +} \ No newline at end of file diff --git a/Apps/Xcc700/manifest.properties b/Apps/Xcc700/manifest.properties new file mode 100644 index 0000000..fcf9e1e --- /dev/null +++ b/Apps/Xcc700/manifest.properties @@ -0,0 +1,15 @@ +manifest.version=0.3 +target.sdk=0.8.0-dev +target.platforms=esp32,esp32s3,esp32c6,esp32p4,posix-x86_64 +id=tactility.xcc700 +version.name=0.1.0 +version.code=1 +name=xcc700 +description=Mini C compiler for ESP32 Xtensa +app.0.id=tactility.xcc700 +app.0.name=xcc700 +app.0.binary=xcc700 +app.0.hidden=true +app.0.headless=true +app.0.stack.depth=16384 +app.0.cleanup=true From b403768cb810c37e2e1252c1c3e2717e21f6e9e8 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Tue, 6 Oct 2026 18:08:59 +0200 Subject: [PATCH 2/3] Update LICENSE.md --- LICENSE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 5b68cf1..6f08245 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,7 +1,7 @@ # Tactility Apps -The Tactility Apps project is available under the [GNU General Public License v3](Documentation/license-apps.md). -Distributions and forks must adhere to the license terms. +Unless otherwise specified, the subprojects in the TactilityApps project are available under the [GNU General Public License v3](Documentation/license-apps.md). +Some applications have a different license, which is specified by a `LICENSE` or `LICENSE.md` file in their folder. # Libraries From e0aeb6219fcda2cffdc69c4f095e6008a69b7c93 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Tue, 6 Oct 2026 19:01:44 +0200 Subject: [PATCH 3/3] Fix build issue --- Apps/M5UnitTest/main/Source/TestListView.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Apps/M5UnitTest/main/Source/TestListView.cpp b/Apps/M5UnitTest/main/Source/TestListView.cpp index 70ba2af..ccafc00 100644 --- a/Apps/M5UnitTest/main/Source/TestListView.cpp +++ b/Apps/M5UnitTest/main/Source/TestListView.cpp @@ -67,6 +67,6 @@ void testListViewCreate(lv_obj_t* parent, Context* app) { lv_obj_t* textLbl = lv_obj_get_child(btn, 1); if (textLbl) lv_obj_set_style_text_font(textLbl, font, 0); lv_obj_t* iconLbl = lv_obj_get_child(btn, 0); - if (iconLbl) lv_obj_set_style_text_font(iconLbl, lvgl_get_shared_icon_font(), 0); + if (iconLbl) lv_obj_set_style_text_font(iconLbl, lvgl_get_shared_icon_default_font(), 0); } }