Merge development into main for v0.6.2 - #203
Merged
Merged
Conversation
locatedVars[] mixes two ownership classes: POU-local `VAR ... AT`, serviced by the owning IEC task around run(), and CONFIGURATION `VAR_GLOBAL ... AT`, owned by no task and therefore serviced by a host dispatcher at a quiescent frame boundary. The descriptors carry no scope, and a runtime cannot recover it -- globals are file-scope GlobalVar<V> singletons that appear nowhere in the configuration object graph reachable via ConfigurationInstance. So runtimes inferred the split from array position. The OpenPLC v4 runtime assumed [program-local ... ][config globals ... ] and took the tail not covered by any program's located_range() as the globals. We emit config globals FIRST (emitFileScopeGlobals runs before the program headers), so the program-local block is always the tail: one POU-local located variable made the computed count collapse to zero and silently stopped servicing EVERY located global -- %MX coils written over Modbus never reached the program, and %IX/%QX/%MW globals stopped being read and committed. Reported on the forum as "%MX locations now invalid - Runtime v4". State the scope instead of leaving it to be guessed. locatedGlobals[] holds the canonical storage pointer of each located VAR_GLOBAL -- the same raw_ptr() value written into locatedVars[].pointer -- so a runtime resolves the config-scope entries by pointer identity, with no dependence on array order. Located variables are always elementary types, so every entry is a scalar IECVar and raw_ptr() is available. Populated in the configuration constructor because raw_ptr() is not a constant expression, and to keep it beside the locatedVars[] population it must agree with. The extern "C" accessors are emitted here rather than left to a host runtime's shim. That is what makes the change backwards-compatible in both directions: the OpenPLC runtime rebuilds its shim against each upload's generated header, so a shim referencing these symbols would fail to COMPILE against an older project, whereas an accessor emitted beside its own array always matches. An older runtime never resolves the symbols and is bit-for-bit unaffected; a newer runtime probes with dlsym and skips located globals when they are absent. Guarded by STRUCPP_THREADED throughout: freestanding targets bind every located variable directly and have no dispatcher, so they pay nothing -- verified that a non-threaded build emits no locatedGlobals symbol at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
validateLocatedVariables() rejected duplicate addresses across POUs, but three configurations slipped through and each produced silent misbehaviour in the runtime rather than a diagnostic. 1. Configuration VAR_GLOBALs were invisible to every located-variable rule. They live in ast.configurations[].varBlocks, while buildVarBlockSymbols only walks POU scopes (program / function / functionBlock) via ast.programs et al, so a located global was never recorded. A POU-local `VAR ... AT %MX0.0` alongside a `VAR_GLOBAL ... AT %MX0.0` therefore compiled, and the two entries resolve to one image slot -- serviced by two different paths, the owning task and the dispatcher at the quiescent frame boundary. Collect them (deduped by name, since codegen emits one file-scope singleton per name and a global repeated across configurations is one canonical global) so they take part in the duplicate, type-compatibility and bit-range rules. 2. A located variable in a PROGRAM instantiated more than once was accepted. Codegen allocates one locatedVars[] slot per *declaration*, and the pointer is assigned in the POU constructor `this`-relative -- the class cannot name its own instance -- so the last instance constructed wins and the other instances' copies are never serviced. Verified: two instances of one POU report the same located_range() and share one slot, leaving one instance's %IX3.0 orphaned. Same reasoning as the existing FUNCTION_BLOCK restriction: a physical address belongs to one point of hardware. IEC 61131-3 permits multiple program instances and answers per-instance addressing with a partly specified location (`AT %I*`) resolved by VAR_CONFIG, which is not supported here, so the fully specified form must be rejected. 3. Only VAR and VAR_GLOBAL may now carry a location. Interface sections describe a call contract, not hardware, and a located interface declaration hands the runtime two owners for one image slot -- a hazard invisible from the generated code. VAR_EXTERNAL is the sharpest case: it references storage a VAR_GLOBAL owns, codegen emits it as `GlobalVar<T>*` and collects located vars from local declarations only, so the address is silently dropped while duplicating the global's claim. Matches the editor, which rejects the same set at edit and load time (DISALLOWED_LOCATION_CLASSES, GitHub issue #904). tests/fixtures/smart-traffic-light declared its located I/O in VAR_INPUT / VAR_OUTPUT; moved to VAR, which is where it belonged. addressKey() is deliberately left matching on exact (area, size, byte, bit). The image is not flat memory: each size class has its own array in the runtime (bool_memory[][], int_memory[], dint_memory[], lint_memory[]) and byte_index indexes that array, so %MW0 and %MD0 name unrelated storage rather than overlapping bytes. Documented on the function so it is not "fixed" into a range check later. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
feat(codegen): emit locatedGlobals[] and tighten located-address validation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Promotes
development→mainto release v0.6.2.Contents
Only the located-globals work — nothing else is pending on
development.fb7980fa3a5ae4fix/located-address-validationcf59629fix(semantic): close three gaps in located-address validationc5ccc7ffeat(codegen): emitlocatedGlobals[]so runtimes stop inferring scope5 files, +661/−11.
What v0.6.2 delivers
locatedGlobals[]+ C-linkage accessors. Codegen now states whichlocatedVars[]entries areCONFIGURATION VAR_GLOBAL ... ATby recording their canonical storage pointers, so a host runtime resolves config scope by pointer identity instead of inferring it from array position. The OpenPLC v4 runtime's inference was backwards — it assumed program-local entries came first — so one located variable declared in a POU made it conclude a project had zero located globals and silently stop syncing all of them. Reported on the forum as "%MX locations now invalid - Runtime v4".Tightened located-address validation — three configurations that previously compiled into programs that looked fine and did not work now produce errors:
VAR/VAR_GLOBALPROGRAMinstantiated more than onceCONFIGURATION VAR_GLOBALBehaviour change for release notes
The validation rejects projects that previously compiled. Editor-authored projects should be unaffected (the editor already blocks located interface classes), but hand-written or imported ST may be.
tests/fixtures/smart-traffic-lightneeded its located I/O moved fromVAR_INPUT/VAR_OUTPUTtoVAR.Consumers waiting on this tag
Both editors pin v0.6.2, so the release must be cut as exactly
v0.6.2or their download URL 404s:locatedGlobals[])Verification
%MX0.0global reaches program logic and%MD0is committed back; runtime reports2 config-scope shared globalswhere it previously reported0.After merge
Tag
v0.6.2on the resulting merge commit..github/workflows/release.ymltriggers onv*, injects the version from the tag (npm version $VERSION), runsnpm pack— whoseprepackbuildsdist/browser-server.js— and attachesstrucpp-0.6.2.tgzto the GitHub release, which is what the editors download.🤖 Generated with Claude Code