These rules are hardware-derived and cross-checked against the TI C6000 PDFs. Follow them before porting a desktop plugin algorithm, especially Airwindows effects. See TI-PDF-NOTES.md for the manual-backed details. For Airwindows release work, also follow AIRWINDOWS-EXACT-PORTS.md: approximate DSP is an experiment, not a port.
audio_nop: true or a tiny dry/pass-through
DSP. Verify it appears on the pedal before adding audio code.
For complex effects, keep the final parameter count, descriptor shape, and
edit-handler strategy in this smoke test so load-time UI/linker problems are
caught before the DSP is involved.--mem_model:data=far. The TI compiler otherwise uses
DP/B14-relative near access for scalar globals/statics..fardata tiny and initialized. Large writable static state has frozen
real pedals.malloc, .bss, .usect, common symbols, or uninitialised
static storage. The custom ZDL path is not normal C runtime startup.sinf, cosf, tanf, logf, powf, and friends. The Zoom runtime
does not provide the normal desktop math library.__c6xabi_divf is linked by this
repo, but no-divide DSP is easier to trust while isolating loader crashes.double, integer division/modulo, long long, and implicit conversion
code in first probes. TI may lower them to __c6xabi_* helper calls that
this repo has not bundled or tested.__c6xabi_push_rts / __c6xabi_pop_rts from code-size
optimization. Prefer no high --opt_for_space for DSP builds unless the
helper symbols are explicitly bundled and hardware-tested.R_C6000_SBR_*, R_C6000_SBR_GOT_*, DSBT, TLS, exception, and C++
relocations as unsafe in plugin objects. They imply DP/GOT/runtime machinery
our loader path does not provide.B3, keep B15 aligned,
and do not rely on B14 being ours.ctx[11] / ctx[12] magic shuttle pattern used by the
existing effects.NAMLite-amptrec.ZDL + NAMLite-smokey.ZDL
(both NAMLite-) froze the pedal on boot; each alone was fine.
build/extract_effect_db.py now refuses to run over such a dist/.LinkerConfig.dsp_cost, descriptor self-entry
+0x28). The firmware sums it per patch and shows “DSP Full” above ~230
(community measurement, MS-70CDR 2.10: github.com/Leemuzhko/ZOOM_development,
NAM/sdk/docs/DSP_COST_230_HYPOTHESIS_RU.md). Stock effects declare 11.6-127.7
(median 25; Great Muff 30.43). The linker default 20.0 is a placeholder: a
heavy effect declaring 20 lets the pedal build a chain that overloads and
crackles instead of refusing it. NAM declares 194 (full) / 188 (Eco 7/8) /
177 (Eco 6/8), CabIR 10. THIS pedal’s limit, measured with pass-through
probes declaring a fixed cost next to Great Muff (30.43): 224.4 accepted,
228.4 refused – below the ~230 reported elsewhere. PE’s meter uses 228. The same research also confirmed our size cap (~30 KB
code+data, layout-dependent) and a 9-control maximum.build/zdl_size_guard.py enforces the proven sizes; the dist/ DB
build, the NAM template packager and the trial builder all call it.switch (or a dense if/else on an int) in .audio code.
The compiler lowers it to a jump table: a .switch:<func> section of
absolute code addresses reached by an INDIRECT branch (B An/B Bn). Those
addresses need relocation, but ZDLs link with zero relocations, so the
branch lands on garbage and the DSP freezes. Replace with straight-line
arithmetic (e.g. 2^n via (float)(1<<n); 2^-n by building the IEEE-754
exponent field ((uint32_t)(127-n))<<23 — no divide, no table). Verify with
dis6x: there must be no .switch:* section and no register-indirect branch
other than B B3 (the return). This freeze is insidious because if the
switch result is unused the compiler deletes it (so a pass-through smoke build
looks fine) — it only appears once the result is actually consumed.The general rule behind that one: never materialize a code address as an
absolute constant. A jump table is just the compiler doing it for you. The
same freeze is available by hand — MVKL/MVKH a link-time address into a
register and B through it is byte-for-byte the same defect, and the
hand-written _init in build/init_materialize.asm shipped exactly that for
four hardware attempts. TEXT_VA is 0x00000000 in the linker, so an
“absolute” target is really a section offset that is correct only while
text loads at zero. Use a PC-relative B to a symbol so call and return both
follow the load base.
This inverts the usual reading of the zero-relocation rule. Applied 0 .obj
relocations is normally the success line; here the missing relocation was
the bug. When you emit a branch target yourself, “there is no relocation for
it” means “this only works at link base”, not “this is clean”. Check
.rela.dyn for your own emitted calls, and test at a nonzero text base —
the emulator loads at zero, so it cannot see this class at all.
.fardata..bss or B14/SBR-relative addressing.switch statements / jump tables in .audio (indirect branch through an
unrelocated .switch section). This froze Mangle across ~6 rebuilds — a
mg_crush_scale(bits) switch was called every buffer and its jump table’s
indirect branch landed on garbage. The freeze presented as “turning the knob
freezes the pedal” because the knob changed the switch index. Every red
herring (granular reads, feedback, denormals) was ruled out only after
disassembly showed the .switch:Fx_DLY_Mangle section + BNOP.S2X A5.build/init_materialize.asm loaded a link-time handler address with
MVKL/MVKH and branched through it, with no .rela.dyn entry. Four
hardware attempts froze on boot. A zero-call variant of the same frame booted
fine, which is what eventually localised it: no calls, no absolute targets.
Fixed by branching PC-relative to the handler. See
PARAM-INIT-INVESTIGATION.md.__c6xabi_* helpers beyond the tiny set already handled by
the linker.ToTape9 cleared
ctx[3] lazy init but froze in the old derived-parameter/computeHDB path
before the 8-sample loop; the no-divide full build is the version that
hardware-reported as running.ZOOM_EDIT_HANDLER symbols for multi-page UIs.
T9NoAudio loads with the DSP NOPed, then freezes on knob/page interaction._init. InitProbe proved
the setup callback alone can load, but setup plus one cloned LineSel edit
handler froze on boot.gid=3 with ZDL_MOD_... or
gid=6 with ZDL_DRV_....TapeEcho4.ZDL can become TapeEcho.ZDL and collide with a stock
or custom TapeEcho install.For a new Airwindows port:
manifest.json.write_param_header(...)..fardata: 0 bytes, Applied 0 .obj relocations
for the DSP object, and no unexpected external symbols.ctx[3] for full persistent delay/reverb/chorus buffers, validate its
base/end/span fields before use, and initialize large memory lazily.