Skip to content

Commit b76c009

Browse files
committed
Xtensa ESP32-S3
1 parent a160ce6 commit b76c009

29 files changed

+2892
-840
lines changed

.gitignore

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
compile-exe
2-
execute-exe
1+
/compile-exe
2+
/execute-exe
33
/*.bin
4-
x86-32_backend_generated.c
5-
x86-32_backend_generated.h
6-
x86-32_engine_asm.o
7-
__pycache__
4+
/*_backend_generated.c
5+
/*_backend_generated.h
6+
/x86-32_engine_asm.o
7+
/__pycache__
8+
/tmp.*

Makefile

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@ NO_FLAGS =
44

55
all: compile-exe execute-exe
66

7-
x86-32_backend_generated.c: x86_32_backend_generator.py nasm.py
7+
x86_32_backend_generated.c: x86_32_backend_generator.py backend_generator.py
88
python3 x86_32_backend_generator.py
99

10-
compile-exe: mcp_forth.h mcp_forth.c compile.c compile-exe.c vm_backend.c x86-32_backend.c x86-32_backend_generated.c x86-32_backend_generated.h elf_linux.c
11-
gcc $(NO_FLAGS) -m32 -Wall -fsanitize=address -g mcp_forth.c compile.c compile-exe.c vm_backend.c x86-32_backend.c x86-32_backend_generated.c elf_linux.c -o compile-exe
10+
compile-exe: mcp_forth.h mcp_forth.c compile.c compile-exe.c vm_backend.c x86-32_backend.c x86_32_backend_generated.c x86_32_backend_generated.h elf_linux.c
11+
gcc $(NO_FLAGS) -m32 -Wall -fsanitize=address -g mcp_forth.c compile.c compile-exe.c vm_backend.c x86-32_backend.c x86_32_backend_generated.c elf_linux.c -o compile-exe
1212

1313
x86-32_engine_asm.o: x86-32_engine_asm.s
1414
nasm -felf32 -g -o x86-32_engine_asm.o x86-32_engine_asm.s
1515

1616
execute-exe: mcp_forth.h mcp_forth.c global.h execute-exe.c runtime_io.c runtime_time.c runtime_string.c runtime_process.c runtime_file.c runtime_assert.c vm_engine.c x86-32_engine.c x86-32_engine_asm.o
17-
gcc $(NO_FLAGS) -m32 -Wall -fsanitize=address -g mcp_forth.c execute-exe.c runtime_io.c runtime_time.c runtime_string.c runtime_process.c runtime_file.c runtime_assert.c runtime_threadutil.c vm_engine.c x86-32_engine.c x86-32_engine_asm.o -lpthread -ldl -o execute-exe
17+
gcc $(NO_FLAGS) -m32 -no-pie -Wall -fsanitize=address -g mcp_forth.c execute-exe.c runtime_io.c runtime_time.c runtime_string.c runtime_process.c runtime_file.c runtime_assert.c runtime_threadutil.c vm_engine.c x86-32_engine.c x86-32_engine_asm.o -lpthread -ldl -o execute-exe
1818

1919
test-simple: all
2020
find forth_programs/simple -maxdepth 1 -type f | xargs -I{} ./compile-and-run.sh vm {}
@@ -23,4 +23,4 @@ test-simple-x86: all
2323
find forth_programs/simple -maxdepth 1 -type f | xargs -I{} ./compile-and-run.sh x86 {}
2424

2525
clean:
26-
rm -f compile-exe execute-exe x86-32_backend_generated.c x86-32_backend_generated.h x86-32_engine_asm.o
26+
rm -f compile-exe execute-exe x86_32_backend_generated.c x86_32_backend_generated.h x86-32_engine_asm.o

README.md

Lines changed: 14 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# mcp_forth
22

3+
mcp_forth is an embeddable Forth compiler.
4+
5+
## Features
6+
7+
- Native machine code
8+
- No OS or library dependencies
9+
- Can call C functions
10+
- Can create C callbacks
11+
- Multithreading support
12+
- Small memory footprint
13+
14+
## Quickstart
15+
316
`gcc-multilib` needs to be installed (unless OS is 32 bit) to compile `execute-exe` as 32 bit.
417

518
```sh
@@ -51,11 +64,11 @@ Currently supported architectures:
5164

5265
- Interpreted bytecode VM (explained above)
5366
- x86-32
67+
- Xtensa (ESP32-S3, call8 ABI)
5468

5569
Planned:
5670

5771
- ARM Thumb (Cortex M0+)
58-
- Xtensa (LX6, i.e. ESP32)
5972

6073
## 32 Bits
6174

@@ -79,42 +92,3 @@ VM runtime because they have no support for 32 bit programs. An emulator such as
7992
the runtime.
8093
- Gforth's "compile time only words" can be used outside of functions in mcp-forth.
8194
- `UNLOOP` is not required and is a no-op
82-
83-
## Minutia
84-
85-
### Iterative "Fragment Solving"
86-
87-
Fragments aka snippets of machine code have variable sizes depending on their operands. If a jump
88-
instruction jumps somewhere nearby, it may only use 1 byte to encode the offset, otherwise 4 bytes.
89-
Literal values are similar. An immediate literal may be loaded into a register differently
90-
depending on its size. Some architectures require multiple instructions to load larger immediate
91-
literal values.
92-
93-
Given that the jump distance may not be known at the time of a jump fragment's creation, the
94-
collection of all fragments at the end of compilation must be solved in an iterative way to
95-
achieve optimal packing.
96-
97-
Question: will iterative solving ever cause the compiler to hang in an infinite loop that it can't solve?
98-
99-
### Optimizing Compiler Memory Usage
100-
101-
The compiler allocates a few arrays which it repeatedly appends elements to during compilation
102-
and resizes them when their capacities are exceeded. There are no small allocations since the overhead
103-
of N allocations of a small struct may be greater than an allocated contiguous array of N small structs.
104-
105-
Strings referring to source code tokens are not allocated arrays of bytes.
106-
They are pointers into the source code and a length.
107-
108-
Since struct references are always being invalidated due to array resizing, struct references
109-
are stored as array indices instead of pointers.
110-
111-
Question: is it a good or bad idea to reduce memory usage by:
112-
113-
- Storing strings as only a pointer with no length. The strings are whitespace-terminated since they
114-
point inside the source code. There is a special case for a string that is the last token in the
115-
source with no following whitespace.
116-
- Extending the previous point, should 16 bit offsets into the source be used instead of 32 bit pointers?
117-
- For indices into arrays of structs, should 16 bit indices be used instead of 32 bit ints?
118-
- For a case where less-than-32-bit integer types are used to store offsets, can all the members of
119-
an array of offsets be dynamically promoted as needed? The first offset that exceeds 65535 would
120-
cause the array to be converted from an array of 16 bit offsets to an array of 32 bit offsets.

backend_generator.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import sys
2+
import re
3+
from itertools import chain
4+
5+
templ_c = """\
6+
/* generated by {generated_by} */
7+
#include "{name}_backend_generated.h"
8+
9+
const uint8_t m4_{name}_backend_code[{code_array_size}] = {{
10+
{code_array_content}
11+
}};
12+
13+
const uint8_t m4_{name}_backend_chore_a[{a_size}] = {{
14+
{a_code} /* {a_src} */
15+
}};
16+
const uint8_t m4_{name}_backend_chore_b[{b_size}] = {{
17+
{b_code} /* {b_src} */
18+
}};
19+
const uint8_t m4_{name}_backend_chore_y[{y_size}] = {{
20+
{y_code} /* {y_src} */
21+
}};
22+
const uint8_t m4_{name}_backend_chore_z[{z_size}] = {{
23+
{z_code} /* {z_src} */
24+
}};
25+
26+
const m4_backend_table_entry_t m4_{name}_backend_table[{code_table_count}] = {{
27+
{code_table_content}
28+
}};
29+
"""
30+
31+
templ_h = """\
32+
/* generated by {generated_by} */
33+
#include "mcp_forth.h"
34+
35+
extern const uint8_t m4_{name}_backend_chore_a[{a_size}];
36+
extern const uint8_t m4_{name}_backend_chore_b[{b_size}];
37+
extern const uint8_t m4_{name}_backend_chore_y[{y_size}];
38+
extern const uint8_t m4_{name}_backend_chore_z[{z_size}];
39+
40+
extern const uint8_t m4_{name}_backend_code[{code_array_size}];
41+
extern const m4_backend_table_entry_t m4_{name}_backend_table[{code_table_count}];
42+
"""
43+
44+
def generate_builtins(backend_name, builtins, elidables, src_to_code_cb):
45+
a_code = src_to_code_cb(elidables[0][0] + "\n")
46+
b_code = src_to_code_cb(elidables[1][0] + "\n")
47+
y_code = src_to_code_cb(elidables[2][0] + "\n")
48+
z_code = src_to_code_cb(elidables[3][0] + "\n")
49+
50+
code_content = []
51+
table_content = []
52+
code_cumulative_len = 0
53+
for word, flags, src in builtins:
54+
src_lines = list(line for line in src.splitlines() if line.strip())
55+
flag_a = len(src_lines) > 0 and re.fullmatch(elidables[0][1], src_lines[0]) is not None
56+
flag_b = flag_a and len(src_lines) > 1 and re.fullmatch(elidables[1][1], src_lines[1]) is not None
57+
flag_clob = flag_b and "clobber" in flags
58+
if not flag_clob and "clobber" in flags:
59+
print("warning: clobber flag given but criteria not met")
60+
flag_z = len(src_lines) > 0 and re.fullmatch(elidables[3][1], src_lines[-1]) is not None
61+
flag_y = flag_z and len(src_lines) > 1 and re.fullmatch(elidables[2][1], src_lines[-2]) is not None
62+
if(flag_a): del src_lines[0]
63+
if(flag_b): del src_lines[0]
64+
if(flag_z): del src_lines[-1]
65+
if(flag_y): del src_lines[-1]
66+
src_reduced = "".join(line + '\n' for line in src_lines)
67+
code = src_to_code_cb(src_reduced)
68+
code_content.append(
69+
f" /* {word} */ "
70+
+ "".join(f"0x{b:02x}," for b in code)
71+
)
72+
pos_str = f"{code_cumulative_len},".ljust(7)
73+
len_str = f"{len(code)},".ljust(6)
74+
code_cumulative_len += len(code)
75+
flags_str = " | ".join(chain.from_iterable((
76+
("M4_BACKEND_FLAG_A",) if flag_a else (),
77+
("M4_BACKEND_FLAG_B",) if flag_b else (),
78+
("M4_BACKEND_FLAG_CLOB",) if flag_clob else (),
79+
("M4_BACKEND_FLAG_Y",) if flag_y else (),
80+
("M4_BACKEND_FLAG_Z",) if flag_z else (),
81+
)))
82+
table_content.append(
83+
f" {{{pos_str}{len_str}{flags_str}}},"
84+
)
85+
86+
c = templ_c.format(
87+
generated_by = sys.argv[0],
88+
name = backend_name,
89+
code_array_size = code_cumulative_len,
90+
code_array_content = "\n".join(code_content),
91+
a_size = len(a_code),
92+
a_code = ",".join(f"0x{b:02x}" for b in a_code),
93+
a_src = elidables[0][0],
94+
b_size = len(b_code),
95+
b_code = ",".join(f"0x{b:02x}" for b in b_code),
96+
b_src = elidables[1][0],
97+
y_size = len(y_code),
98+
y_code = ",".join(f"0x{b:02x}" for b in y_code),
99+
y_src = elidables[2][0],
100+
z_size = len(z_code),
101+
z_code = ",".join(f"0x{b:02x}" for b in z_code),
102+
z_src = elidables[3][0],
103+
code_table_count = len(table_content),
104+
code_table_content = "\n".join(table_content)
105+
)
106+
107+
h = templ_h.format(
108+
generated_by = sys.argv[0],
109+
name = backend_name,
110+
a_size = len(a_code),
111+
b_size = len(b_code),
112+
y_size = len(y_code),
113+
z_size = len(z_code),
114+
code_array_size = code_cumulative_len,
115+
code_table_count = len(table_content)
116+
)
117+
118+
with open(f"{backend_name}_backend_generated.c", "w") as f:
119+
f.write(c)
120+
121+
with open(f"{backend_name}_backend_generated.h", "w") as f:
122+
f.write(h)

compile-exe.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ int main(int argc, char ** argv)
4242

4343
uint8_t * bin;
4444
int error_near = -1;
45-
int bin_len = m4_compile(source, source_len, &bin, backend, &error_near);
45+
int bin_len = m4_compile(source, source_len, &bin, NULL, backend, &error_near);
4646
free(source);
4747
if(bin_len < 0) {
4848
printf("error %d near %d\n", bin_len, error_near);

0 commit comments

Comments
 (0)