ESP32 build fails at link: dangerous relocation: call8: call target out of range in precompiled libOpenPLCUserLib.a (missing -mlongcalls)
Summary
Any OpenPLC Editor v4 program that targets an ESP32 board fails at the final link step with a large batch of Xtensa dangerous relocation: call8: call target out of range errors coming from the precompiled libOpenPLCUserLib.a (both arduino_runtime_glue.o and pou_*.o). The user's Structured Text and C/C++ POU code compile fine — the failure is purely in linking the precompiled OpenPLC user library.
Root cause: the objects inside libOpenPLCUserLib.a are built without -mlongcalls. On Xtensa, a direct call8 only reaches ±512 KB. Once the firmware .text grows past that (which it always does on ESP32 because WiFi + Network + Modbus are pulled in), the linker cannot relax the direct call8 into an indirect l32r/callx8 — because the compiler never reserved the literal — so it aborts with "dangerous relocation".
The normal Arduino sketch compilation for the same board does use -mlongcalls (it is present in esp32/tools/esp32-libs/<ver>/flags/cpp_flags, alongside -fno-builtin-memcpy -fno-builtin-memset). Only the OpenPLC precompile step ([precompile] Compiling N TU(s) ... → Archiving ... into libOpenPLCUserLib.a) omits it.
Environment
- OpenPLC Editor: 4.2.10
- STruC++: 0.6.1
- Arduino CLI: 1.4.1
- ESP32 Arduino core: esp32:esp32 3.3.11
- Toolchain: xtensa-esp32-elf-g++ (esp-x32 2601)
- Host: Windows 11 x64
- Board targets tested: "ESP32 Generic" and "ESP32 WROOM" — both fail identically.
Representative linker errors
libOpenPLCUserLib.a(arduino_runtime_glue.o): in function `runtime_discover_tasks':
arduino_runtime_glue.cpp:(.text+0x5f): dangerous relocation: call8: call target out of range: __umoddi3
arduino_runtime_glue.cpp:(.text+0xf6): dangerous relocation: call8: call target out of range: __udivdi3
... in function `runtime_plc_cycle':
arduino_runtime_glue.cpp:(.text+0x163): dangerous relocation: call8: call target out of range: updateInputBuffers
arduino_runtime_glue.cpp:(.text+0x1aa): dangerous relocation: call8: call target out of range: updateOutputBuffers
... strucpp::debug::write_string(...): dangerous relocation: call8: call target out of range: memcpy
... strucpp::debug::read_wstring(...): dangerous relocation: call8: call target out of range: memset
libOpenPLCUserLib.a(pou_A8_OUTPUT.o): in function `strucpp::A8_OUTPUT::operator()()':
pou_A8_OUTPUT.cpp:(.text+0xa5): dangerous relocation: call8: call target out of range: a8_output_setup
pou_A8_OUTPUT.cpp:(.text+0xb4): dangerous relocation: call8: call target out of range: a8_output_loop
collect2.exe: error: ld returned 1 exit status
The out-of-range targets are libgcc (__udivdi3, __umoddi3), libc (memcpy, memset), OpenPLC runtime (updateInputBuffers, updateOutputBuffers) and the user C-block entry points (a8_output_setup, a8_output_loop) — i.e. ordinary symbols that are simply too far for a non-relaxable call8.
Steps to reproduce
- Create any project with a program targeting an ESP32 board (with or without a C/C++ POU —
arduino_runtime_glue.o is always in the archive).
- Compile. Link fails with the errors above once total
.text exceeds the call8 range.
Suggested fix
Add -mlongcalls to the compiler flags used by the precompile step that builds libOpenPLCUserLib.a for Xtensa targets (esp32/esp32s2/esp32s3). Ideally mirror the core's cpp_flags for that architecture, which already includes -mlongcalls -fno-builtin-memcpy -fno-builtin-memset. Without longcalls the archive can never be safely linked into a full-size ESP32 firmware.
Notes
- Not board-specific (reproduced on ESP32 Generic and ESP32 WROOM).
- Not related to any third-party library — a plain program without user C code hits the same glue-object relocations.
- No link-time flag can fix this after the fact; the
call8 opcodes are already emitted in the archived objects, so the archive must be recompiled with -mlongcalls.
ESP32 build fails at link:
dangerous relocation: call8: call target out of rangein precompiledlibOpenPLCUserLib.a(missing-mlongcalls)Summary
Any OpenPLC Editor v4 program that targets an ESP32 board fails at the final link step with a large batch of Xtensa
dangerous relocation: call8: call target out of rangeerrors coming from the precompiledlibOpenPLCUserLib.a(botharduino_runtime_glue.oandpou_*.o). The user's Structured Text and C/C++ POU code compile fine — the failure is purely in linking the precompiled OpenPLC user library.Root cause: the objects inside
libOpenPLCUserLib.aare built without-mlongcalls. On Xtensa, a directcall8only reaches ±512 KB. Once the firmware.textgrows past that (which it always does on ESP32 because WiFi + Network + Modbus are pulled in), the linker cannot relax the directcall8into an indirectl32r/callx8— because the compiler never reserved the literal — so it aborts with "dangerous relocation".The normal Arduino sketch compilation for the same board does use
-mlongcalls(it is present inesp32/tools/esp32-libs/<ver>/flags/cpp_flags, alongside-fno-builtin-memcpy -fno-builtin-memset). Only the OpenPLC precompile step ([precompile] Compiling N TU(s) ...→Archiving ... into libOpenPLCUserLib.a) omits it.Environment
Representative linker errors
The out-of-range targets are libgcc (
__udivdi3,__umoddi3), libc (memcpy,memset), OpenPLC runtime (updateInputBuffers,updateOutputBuffers) and the user C-block entry points (a8_output_setup,a8_output_loop) — i.e. ordinary symbols that are simply too far for a non-relaxablecall8.Steps to reproduce
arduino_runtime_glue.ois always in the archive)..textexceeds thecall8range.Suggested fix
Add
-mlongcallsto the compiler flags used by the precompile step that buildslibOpenPLCUserLib.afor Xtensa targets (esp32/esp32s2/esp32s3). Ideally mirror the core'scpp_flagsfor that architecture, which already includes-mlongcalls -fno-builtin-memcpy -fno-builtin-memset. Without longcalls the archive can never be safely linked into a full-size ESP32 firmware.Notes
call8opcodes are already emitted in the archived objects, so the archive must be recompiled with-mlongcalls.