Adds what the folder was missing for someone picking it up cold.
as-built/ the STruC++ output copied out of the live container - the C++
the PLC is actually executing, plus program.st and the Modbus
buffer config. The running image was made with docker commit
and exists in no registry, so this is the only other copy of
the compiled form.
Verified while copying: the deployed program.st matches
build/wrps.st generated from src/. Identical POU structure,
ZERO differences in non-declaration lines. src/ is genuinely
canonical and the running PLC agrees with it.
VERSIONS.md every version read from the running system, not from
documentation: runtime v4.1.10, STruC++ 0.6.2, Editor
4.2.11, Debian 12, g++ 12.2, pymodbus 3.11.2, CI
Server R1.03. Plus which plugins are enabled -
ethercat is on for no reason - and a v3-vs-v4 table,
since most OpenPLC guidance online is for v3 and the
%MW HR1024 change silently produces wrong data.
GETTING-STARTED.md three questions answered in a page: how to modify the
program, how to stand up a new PLC container from
scratch, how to move this one. Includes the compose
file, the Editor steps that are not generated and are
always missed, and a symptom-to-document table.
261 lines
10 KiB
C++
261 lines
10 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception
|
|
// Copyright (C) 2025 Autonomy / OpenPLC Project
|
|
// This file is part of the STruC++ Runtime Library and is covered by the
|
|
// STruC++ Runtime Library Exception. See COPYING.RUNTIME for details.
|
|
/**
|
|
* STruC++ Runtime - IEC Pointer Type
|
|
*
|
|
* Provides IEC_Ptr<T>, a smart pointer wrapper that handles the type-punning
|
|
* semantics of IEC 61131-3 POINTER TO. In CODESYS, POINTER TO BYTE is commonly
|
|
* used as a universal memory pointer (like void*) and ADR() can return any
|
|
* address that is freely assigned to any pointer type.
|
|
*
|
|
* IEC_Ptr<T> stores a void* internally and provides:
|
|
* - Construction/assignment from any pointer type (cross-type OK)
|
|
* - Construction/assignment from other IEC_Ptr<U> (cross-type OK)
|
|
* - Dereference as T& / T*
|
|
* - Pointer arithmetic in units of sizeof(T)
|
|
* - Conversion to integral types (for CODESYS-style address math)
|
|
* - Comparison operators (same-type and cross-type)
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <cstddef>
|
|
#include <type_traits>
|
|
|
|
#include "iec_fault.hpp" // STRUCPP_HAS_EXCEPTIONS, IecFault, iec_runtime_fault
|
|
#if STRUCPP_HAS_EXCEPTIONS
|
|
#include <stdexcept>
|
|
#endif
|
|
|
|
namespace strucpp {
|
|
|
|
// Forward declare IECVar for arithmetic overloads
|
|
template<typename T> class IECVar;
|
|
|
|
template<typename T>
|
|
class IEC_Ptr {
|
|
void* ptr_;
|
|
public:
|
|
using element_type = T;
|
|
|
|
// Constructors
|
|
IEC_Ptr() noexcept : ptr_(nullptr) {}
|
|
IEC_Ptr(std::nullptr_t) noexcept : ptr_(nullptr) {}
|
|
|
|
// Construct from any raw pointer
|
|
template<typename U>
|
|
IEC_Ptr(U* p) noexcept : ptr_(const_cast<void*>(static_cast<const volatile void*>(p))) {}
|
|
|
|
// Construct from another IEC_Ptr (cross-type)
|
|
template<typename U>
|
|
IEC_Ptr(IEC_Ptr<U> other) noexcept : ptr_(other.get_void()) {}
|
|
|
|
// Construct from an integer address (e.g. __XWORD/ULINT from ADR()).
|
|
// `IEC_XWORD` implicitly converts to its underlying integer, which
|
|
// converts to uintptr_t here — the typed pointer round-trips the address.
|
|
explicit IEC_Ptr(std::uintptr_t addr) noexcept
|
|
: ptr_(reinterpret_cast<void*>(addr)) {}
|
|
|
|
// Assignment from any raw pointer
|
|
template<typename U>
|
|
IEC_Ptr& operator=(U* p) noexcept {
|
|
ptr_ = const_cast<void*>(static_cast<const volatile void*>(p));
|
|
return *this;
|
|
}
|
|
|
|
// Assignment from another IEC_Ptr (cross-type)
|
|
template<typename U>
|
|
IEC_Ptr& operator=(IEC_Ptr<U> other) noexcept {
|
|
ptr_ = other.get_void();
|
|
return *this;
|
|
}
|
|
|
|
IEC_Ptr& operator=(std::nullptr_t) noexcept {
|
|
ptr_ = nullptr;
|
|
return *this;
|
|
}
|
|
|
|
// Assignment from an integer address (e.g. `ptr := _TMP` where the temp is
|
|
// __XWORD/ULINT from ADR()). The address is reinterpreted to the typed
|
|
// pointer; the source integer carries a pointer-width value.
|
|
IEC_Ptr& operator=(std::uintptr_t addr) noexcept {
|
|
ptr_ = reinterpret_cast<void*>(addr);
|
|
return *this;
|
|
}
|
|
|
|
// Null-fault helper: a dereference of an unassigned (NULL) POINTER TO
|
|
// raises a clean fault instead of an undefined access — throw on exception
|
|
// targets (the runtime catches it and stops just the faulting task), or
|
|
// iec_runtime_fault(IecFault::NullReference) on -fno-exceptions MCU targets
|
|
// (where there is no MMU to trap a null deref, so the unchecked access would
|
|
// silently read/write address 0). Mirrors REF_TO's NullReferenceException.
|
|
void __fault_if_null() const {
|
|
if (!ptr_) {
|
|
#if STRUCPP_HAS_EXCEPTIONS
|
|
throw std::runtime_error("Null pointer dereference");
|
|
#else
|
|
iec_runtime_fault(IecFault::NullReference);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// Dereference (null-checked)
|
|
T& operator*() const { __fault_if_null(); return *static_cast<T*>(ptr_); }
|
|
T* operator->() const { __fault_if_null(); return static_cast<T*>(ptr_); }
|
|
|
|
// Subscript (pointer[n]) — null-checked base (the offset itself is
|
|
// unbounded raw pointer arithmetic, as in C).
|
|
T& operator[](std::ptrdiff_t n) const {
|
|
__fault_if_null();
|
|
return *(static_cast<T*>(ptr_) + n);
|
|
}
|
|
|
|
// Bounds-checked accessor used by codegen's subscript path. A POINTER TO has
|
|
// no bounds metadata, so this matches operator[]: null-checked base with
|
|
// unbounded raw pointer arithmetic (as in C).
|
|
T& at(std::ptrdiff_t n) const {
|
|
__fault_if_null();
|
|
return *(static_cast<T*>(ptr_) + n);
|
|
}
|
|
|
|
// Raw access
|
|
T* get() const noexcept { return static_cast<T*>(ptr_); }
|
|
void* get_void() const noexcept { return ptr_; }
|
|
|
|
// Pointer arithmetic (in units of sizeof(T))
|
|
// Use templates to directly match any arithmetic type, avoiding
|
|
// ambiguity with the integral conversion operator.
|
|
template<typename N, std::enable_if_t<std::is_arithmetic_v<N>, int> = 0>
|
|
IEC_Ptr operator+(N n) const noexcept {
|
|
return IEC_Ptr(static_cast<T*>(ptr_) + static_cast<std::ptrdiff_t>(n));
|
|
}
|
|
|
|
template<typename N, std::enable_if_t<std::is_arithmetic_v<N>, int> = 0>
|
|
IEC_Ptr operator-(N n) const noexcept {
|
|
return IEC_Ptr(static_cast<T*>(ptr_) - static_cast<std::ptrdiff_t>(n));
|
|
}
|
|
|
|
// Arithmetic with IECVar-wrapped types (unwrap the value)
|
|
template<typename U>
|
|
IEC_Ptr operator+(const IECVar<U>& n) const noexcept {
|
|
return IEC_Ptr(static_cast<T*>(ptr_) + static_cast<std::ptrdiff_t>(n.get()));
|
|
}
|
|
|
|
template<typename U>
|
|
IEC_Ptr operator-(const IECVar<U>& n) const noexcept {
|
|
return IEC_Ptr(static_cast<T*>(ptr_) - static_cast<std::ptrdiff_t>(n.get()));
|
|
}
|
|
|
|
template<typename N, std::enable_if_t<std::is_arithmetic_v<N>, int> = 0>
|
|
IEC_Ptr& operator+=(N n) noexcept {
|
|
ptr_ = static_cast<T*>(ptr_) + static_cast<std::ptrdiff_t>(n);
|
|
return *this;
|
|
}
|
|
|
|
template<typename N, std::enable_if_t<std::is_arithmetic_v<N>, int> = 0>
|
|
IEC_Ptr& operator-=(N n) noexcept {
|
|
ptr_ = static_cast<T*>(ptr_) - static_cast<std::ptrdiff_t>(n);
|
|
return *this;
|
|
}
|
|
|
|
// Pre/post increment/decrement
|
|
IEC_Ptr& operator++() noexcept {
|
|
ptr_ = static_cast<T*>(ptr_) + 1;
|
|
return *this;
|
|
}
|
|
|
|
IEC_Ptr operator++(int) noexcept {
|
|
IEC_Ptr tmp = *this;
|
|
ptr_ = static_cast<T*>(ptr_) + 1;
|
|
return tmp;
|
|
}
|
|
|
|
IEC_Ptr& operator--() noexcept {
|
|
ptr_ = static_cast<T*>(ptr_) - 1;
|
|
return *this;
|
|
}
|
|
|
|
IEC_Ptr operator--(int) noexcept {
|
|
IEC_Ptr tmp = *this;
|
|
ptr_ = static_cast<T*>(ptr_) - 1;
|
|
return tmp;
|
|
}
|
|
|
|
// Conversion to integral types (for CODESYS pointer-to-DWORD patterns)
|
|
// This enables: DWORD_VAR := PT; (store address as integer)
|
|
// Must NOT be implicit to avoid ambiguity with operator+ etc.
|
|
// Use to_addr() for explicit conversion instead.
|
|
uintptr_t to_addr() const noexcept {
|
|
return reinterpret_cast<uintptr_t>(ptr_);
|
|
}
|
|
|
|
// Conversion to bool (for null checks)
|
|
explicit operator bool() const noexcept { return ptr_ != nullptr; }
|
|
|
|
// Same-type comparison
|
|
bool operator==(IEC_Ptr other) const noexcept { return ptr_ == other.ptr_; }
|
|
bool operator!=(IEC_Ptr other) const noexcept { return ptr_ != other.ptr_; }
|
|
bool operator<(IEC_Ptr other) const noexcept { return ptr_ < other.ptr_; }
|
|
bool operator>(IEC_Ptr other) const noexcept { return ptr_ > other.ptr_; }
|
|
bool operator<=(IEC_Ptr other) const noexcept { return ptr_ <= other.ptr_; }
|
|
bool operator>=(IEC_Ptr other) const noexcept { return ptr_ >= other.ptr_; }
|
|
|
|
// Cross-type IEC_Ptr comparison
|
|
template<typename U>
|
|
bool operator==(IEC_Ptr<U> other) const noexcept { return ptr_ == other.get_void(); }
|
|
template<typename U>
|
|
bool operator!=(IEC_Ptr<U> other) const noexcept { return ptr_ != other.get_void(); }
|
|
template<typename U>
|
|
bool operator<(IEC_Ptr<U> other) const noexcept { return ptr_ < other.get_void(); }
|
|
template<typename U>
|
|
bool operator>(IEC_Ptr<U> other) const noexcept { return ptr_ > other.get_void(); }
|
|
template<typename U>
|
|
bool operator<=(IEC_Ptr<U> other) const noexcept { return ptr_ <= other.get_void(); }
|
|
template<typename U>
|
|
bool operator>=(IEC_Ptr<U> other) const noexcept { return ptr_ >= other.get_void(); }
|
|
|
|
// Comparison with nullptr
|
|
bool operator==(std::nullptr_t) const noexcept { return ptr_ == nullptr; }
|
|
bool operator!=(std::nullptr_t) const noexcept { return ptr_ != nullptr; }
|
|
|
|
// Comparison with IECVar<integer> (for CODESYS: PT < DWORD_END)
|
|
template<typename U, std::enable_if_t<std::is_integral_v<U>, int> = 0>
|
|
bool operator<(IECVar<U> other) const noexcept {
|
|
return reinterpret_cast<uintptr_t>(ptr_) < static_cast<uintptr_t>(other.get());
|
|
}
|
|
template<typename U, std::enable_if_t<std::is_integral_v<U>, int> = 0>
|
|
bool operator>(IECVar<U> other) const noexcept {
|
|
return reinterpret_cast<uintptr_t>(ptr_) > static_cast<uintptr_t>(other.get());
|
|
}
|
|
template<typename U, std::enable_if_t<std::is_integral_v<U>, int> = 0>
|
|
bool operator<=(IECVar<U> other) const noexcept {
|
|
return reinterpret_cast<uintptr_t>(ptr_) <= static_cast<uintptr_t>(other.get());
|
|
}
|
|
template<typename U, std::enable_if_t<std::is_integral_v<U>, int> = 0>
|
|
bool operator>=(IECVar<U> other) const noexcept {
|
|
return reinterpret_cast<uintptr_t>(ptr_) >= static_cast<uintptr_t>(other.get());
|
|
}
|
|
template<typename U, std::enable_if_t<std::is_integral_v<U>, int> = 0>
|
|
bool operator==(IECVar<U> other) const noexcept {
|
|
return reinterpret_cast<uintptr_t>(ptr_) == static_cast<uintptr_t>(other.get());
|
|
}
|
|
template<typename U, std::enable_if_t<std::is_integral_v<U>, int> = 0>
|
|
bool operator!=(IECVar<U> other) const noexcept {
|
|
return reinterpret_cast<uintptr_t>(ptr_) != static_cast<uintptr_t>(other.get());
|
|
}
|
|
};
|
|
|
|
// Reverse comparison: nullptr == IEC_Ptr
|
|
template<typename T>
|
|
bool operator==(std::nullptr_t, IEC_Ptr<T> p) noexcept { return p == nullptr; }
|
|
template<typename T>
|
|
bool operator!=(std::nullptr_t, IEC_Ptr<T> p) noexcept { return p != nullptr; }
|
|
|
|
// Arithmetic: n + ptr (reverse order)
|
|
template<typename T>
|
|
IEC_Ptr<T> operator+(std::ptrdiff_t n, IEC_Ptr<T> p) noexcept { return p + n; }
|
|
|
|
} // namespace strucpp
|