diff --git a/blog-esp32.html b/blog-esp32.html
new file mode 100644
index 0000000..34ba8da
--- /dev/null
+++ b/blog-esp32.html
@@ -0,0 +1,442 @@
+
+
+
+
+
+
+
+script.spider
+ ↓ Spider Compiler
+bytecode.spd ← Spider's own format, not x86, not Xtensa
+
+Spider Runtime (C++)
+ ↓ xtensa-esp-elf-g++ ← ESP-IDF toolchain
+spider_esp32.bin
+ ↓ esptool.py
+ESP32 flash memory ← Runtime lives here permanently
+
+
+
+ Spider's bytecode is a neutral format that belongs to no specific processor.
+ The toolchain's job is not to convert bytecode — it compiles the
+ C++ source of the Runtime itself into Xtensa machine code.
+ Once flashed, the Runtime lives in the ESP32's flash memory and
+ interprets Spider bytecode at runtime.
+
+
+
+
+
+ Install ESP-IDF v5.x and add the Xtensa toolchain to your PATH:
+
+ # Add to ~/.bashrc to make it permanent
+export PATH=$PATH:/c/Espressif/tools/xtensa-esp-elf/esp-14.2.0_20251107/xtensa-esp-elf/bin
+
+
+
+
02
+
Compile flags for ESP32
+
+
+
+ The Runtime uses compile-time platform detection via distro_mcu.hpp.
+ These flags tell it which hardware it is targeting:
+
+
+
+
-DESP32
+
Activates ESP32 detection in distro_mcu.hpp
+
-DSPIDER_DISTRO_MICRO
+
Enables microcontroller mode (reduced memory footprint)
+
-DSPIDER_OS_NONE
+
Declares bare-metal environment, no OS
+
-mlongcalls
+
Required for Xtensa's memory layout and call distances
+
-fno-exceptions -fno-rtti
+
Disable C++ features unavailable on bare-metal
+
-O0
+
No optimizations — faster compilation during development
+
+
+
+
+
03
+
Build with ESP-IDF (CMake)
+
+
+
+ The bare-metal Makefile approach caused boot loops because the ESP32 bootloader
+ expects a specific binary format. The correct approach is to use ESP-IDF's
+ CMake build system, which generates the bootloader, partition table, and app
+ binary automatically.
+
+
+ # From spider-micro-buildtools/esp32/idf_project/
+idf.py set-target esp32
+idf.py build
+idf.py -p COM6 flash monitor
+
+ The CMakeLists.txt registers the Spider Runtime source files as a component:
+
+ set(SPIDER_RUNTIME_SRC
+ "${CMAKE_CURRENT_SOURCE_DIR}/../../../../spider-runtime/src"
+)
+
+idf_component_register(
+ SRCS
+ "main.cpp"
+ "${SPIDER_RUNTIME_SRC}/spider/runtime/Runtime.cpp"
+ "${SPIDER_RUNTIME_SRC}/spider/runtime/cpu/CPU.cpp"
+ ... (all Runtime source files)
+ PRIV_INCLUDE_DIRS "${SPIDER_RUNTIME_SRC}"
+)
+
+
+ Note: Relative paths using CMAKE_CURRENT_SOURCE_DIR
+ are used to avoid exposing personal directory structure in the repository.
+
+
+
+
+
04
+
app_main — The ESP32 entry point
+
+
+
+ ESP-IDF requires app_main()
+ instead of the standard main().
+ The Spider Runtime is initialized here with a fixed amount of RAM:
+
+
+ #include "esp_log.h"
+#include <spider/runtime/Runtime.hpp>
+
+static const char* TAG = "Spider";
+
+extern "C" void app_main(void) {
+ ESP_LOGI(TAG, "Spider Runtime starting...");
+ spider::Runtime runtime(32 * 1024); // 32 KB RAM
+ ESP_LOGI(TAG, "Spider Runtime initialized!");
+ ESP_LOGI(TAG, "RAM size: 32KB");
+ while(1) { vTaskDelay(pdMS_TO_TICKS(1000)); }
+}
+
+
+
+
+I (274) Spider: Spider Runtime starting...
+I (274) Spider: Spider Runtime initialized!
+I (274) Spider: RAM size: 32KB
+
+
+
+
+
+
05
+
Repository structure
+
+
+
+ The build tools live in spider-micro-buildtools,
+ separate from the Runtime source. Both repos must be cloned at the same level:
+
+
+
+Internship/
+├── spider-runtime/ ← Runtime C++ source
+└── spider-micro-buildtools/
+ └── esp32/
+ ├── Makefile ← bare-metal reference build
+ ├── gen_makefile.py
+ ├── main_esp32.cpp
+ ├── README.md
+ └── idf_project/ ← ESP-IDF project (use this)
+ ├── CMakeLists.txt
+ └── main/
+ ├── CMakeLists.txt
+ └── main.cpp
+
+
+
+
+
+
+
+
+ Context background
+
+ The Spider VM instruction set is organized in a 9-bit opcode space (512 slots),
+ divided among team members. My assigned range covers 15 instructions:
+ two type cast conversions and thirteen math functions.
+
+
+ Instructions are defined in Instr_060-07F.cpp,
+ which already contained the previous range (0x060–0x067) implemented by another team member.
+ My work begins at 0x068.
+
+
+
+
+
+ Assigned Instructions 0x068 → 0x079
+
+
+
Cast Conversions
+
+
+
0x068
+
D2I
+
Double (f64) → Integer (i32), truncates toward zero
+
+
+
0x069
+
D2L
+
Double (f64) → Long (i64), truncates toward zero
+
+
+
+
+
+
Trigonometric
+
+
+
0x06C
+
SIN
+
Sine — sin(Dst) → Dst
+
+
+
0x06D
+
COS
+
Cosine — cos(Dst) → Dst
+
+
+
0x06E
+
TAN
+
Tangent — tan(Dst) → Dst
+
+
+
0x06F
+
ASIN
+
Arc sine — asin(Dst) → Dst
+
+
+
0x070
+
ACOS
+
Arc cosine — acos(Dst) → Dst
+
+
+
0x071
+
ATAN
+
Arc tangent — atan(Dst) → Dst
+
+
+
0x072
+
ATAN2
+
Arc tangent 2-argument — atan2(Dst, Src) → Dst
+
+
+
+
+
+
Exponential
+
+
+
0x074
+
EXP
+
Natural exponential — eˣ → Dst
+
+
+
0x075
+
LOG
+
Natural logarithm — ln(Dst) → Dst
+
+
+
0x076
+
LOGAB
+
Logarithm base A of B — log_Src(Dst) → Dst
+
+
+
0x077
+
POW
+
Power — Dst^Src → Dst
+
+
+
0x078
+
SQRT
+
Square root — √Dst → Dst
+
+
+
0x079
+
ROOT
+
General root — Dst^(1/Src) → Dst
+
+
+
+
+
+
+
+ Implementation Pattern how it works
+
+
+ Every instruction in the Spider VM follows the same three-step pattern:
+ fetch the operand(s), perform the operation, and call the post-execution hook.
+
+
+
+
Single-operand pattern
+
fetchOperDst(); // points _dst to the destination register
+_dst->_f64 = std::sin(_dst->_f64);
+(this->*_post)(); // post-execution hook (write-back if needed)
+
+
+
+
Two-operand pattern (ATAN2, LOGAB, POW, ROOT)
+
fetchOperDst(); // _dst ← destination
+fetchOperSrc(); // _src ← source
+_dst->_f64 = std::atan2(_dst->_f64, _src->_f64);
+(this->*_post)();
+
+
+
+ The register_t union
+ allows each register to be read as any type (_f64,
+ _u32,
+ _u64, etc.)
+ without casting overhead. The type modifier in the instruction header determines
+ which field is active.
+
+
+ Notable special cases:
+
+ LOGAB uses the change-of-base formula:
+ loga(b) = ln(b) / ln(a), where Dst is b and Src is a.
+
+
+ ROOT uses the identity: ⁿ√x = x^(1/n),
+ implemented as std::pow(dst, 1.0 / src).
+
+
+ The full implementation in C++20:
+
+void CPU::D2I() {
+ fetchOperDst();
+ _dst->_u32 = static_cast<u32>(_dst->_f64);
+ (this->*_post)();
+}
+
+void CPU::SIN() {
+ fetchOperDst();
+ _dst->_f64 = std::sin(_dst->_f64);
+ (this->*_post)();
+}
+
+void CPU::LOGAB() {
+ fetchOperDst();
+ fetchOperSrc();
+ _dst->_f64 = std::log(_dst->_f64) / std::log(_src->_f64);
+ (this->*_post)();
+}
+
+void CPU::ROOT() {
+ fetchOperDst();
+ fetchOperSrc();
+ _dst->_f64 = std::pow(_dst->_f64, 1.0 / _src->_f64);
+ (this->*_post)();
+}
+
+
+
+
+ Verification test results
+
+
+ A test program was written to call each instruction directly on the CPU
+ and verify the output against mathematically known values.
+ Since fetchInstr()
+ and execute()
+ are not yet implemented in the Runtime, operands are set manually before each call.
+
+
+
+
+ -- Cast Instructions --
+ [PASS] D2I (3.9 -> 3) = 3 (expected 3)
+ [PASS] D2L (1e12) = 1e+12 (expected 1e+12)
+ -- Trigonometric Instructions --
+ [PASS] SIN(pi/2) = 1 (expected 1)
+ [PASS] COS(0) = 1 (expected 1)
+ [PASS] TAN(pi/4) = 1 (expected 1)
+ [PASS] ASIN(1.0) = 1.5708 (expected 1.5708)
+ [PASS] ACOS(1.0) = 0 (expected 0)
+ [PASS] ATAN(1.0) = 0.785398 (expected 0.785398)
+ [PASS] ATAN2(1,1) = 0.785398 (expected 0.785398)
+ -- Exponential Instructions --
+ [PASS] EXP(1) = 2.71828 (expected 2.71828)
+ [PASS] LOG(e) = 1 (expected 1)
+ [PASS] LOGAB(100,10) = 2 (expected 2)
+ [PASS] POW(2,10) = 1024 (expected 1024)
+ [PASS] SQRT(9) = 3 (expected 3)
+ [PASS] ROOT(27,3) = 3 (expected 3)
+
+
+
+
+ Note on testing approach: The test program uses stub implementations
+ of fetchOperDst(),
+ fetchOperSrc(),
+ and imp()
+ as no-ops, and sets _dst and
+ _src manually.
+ This isolates the instruction logic completely from the unimplemented CPU dispatch system.
+
+
+
+
+
+