// 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, 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 stores a void* internally and provides: * - Construction/assignment from any pointer type (cross-type OK) * - Construction/assignment from other IEC_Ptr (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 #include #include #include "iec_fault.hpp" // STRUCPP_HAS_EXCEPTIONS, IecFault, iec_runtime_fault #if STRUCPP_HAS_EXCEPTIONS #include #endif namespace strucpp { // Forward declare IECVar for arithmetic overloads template class IECVar; template 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 IEC_Ptr(U* p) noexcept : ptr_(const_cast(static_cast(p))) {} // Construct from another IEC_Ptr (cross-type) template IEC_Ptr(IEC_Ptr 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(addr)) {} // Assignment from any raw pointer template IEC_Ptr& operator=(U* p) noexcept { ptr_ = const_cast(static_cast(p)); return *this; } // Assignment from another IEC_Ptr (cross-type) template IEC_Ptr& operator=(IEC_Ptr 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(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(ptr_); } T* operator->() const { __fault_if_null(); return static_cast(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(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(ptr_) + n); } // Raw access T* get() const noexcept { return static_cast(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, int> = 0> IEC_Ptr operator+(N n) const noexcept { return IEC_Ptr(static_cast(ptr_) + static_cast(n)); } template, int> = 0> IEC_Ptr operator-(N n) const noexcept { return IEC_Ptr(static_cast(ptr_) - static_cast(n)); } // Arithmetic with IECVar-wrapped types (unwrap the value) template IEC_Ptr operator+(const IECVar& n) const noexcept { return IEC_Ptr(static_cast(ptr_) + static_cast(n.get())); } template IEC_Ptr operator-(const IECVar& n) const noexcept { return IEC_Ptr(static_cast(ptr_) - static_cast(n.get())); } template, int> = 0> IEC_Ptr& operator+=(N n) noexcept { ptr_ = static_cast(ptr_) + static_cast(n); return *this; } template, int> = 0> IEC_Ptr& operator-=(N n) noexcept { ptr_ = static_cast(ptr_) - static_cast(n); return *this; } // Pre/post increment/decrement IEC_Ptr& operator++() noexcept { ptr_ = static_cast(ptr_) + 1; return *this; } IEC_Ptr operator++(int) noexcept { IEC_Ptr tmp = *this; ptr_ = static_cast(ptr_) + 1; return tmp; } IEC_Ptr& operator--() noexcept { ptr_ = static_cast(ptr_) - 1; return *this; } IEC_Ptr operator--(int) noexcept { IEC_Ptr tmp = *this; ptr_ = static_cast(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(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 bool operator==(IEC_Ptr other) const noexcept { return ptr_ == other.get_void(); } template bool operator!=(IEC_Ptr other) const noexcept { return ptr_ != other.get_void(); } template bool operator<(IEC_Ptr other) const noexcept { return ptr_ < other.get_void(); } template bool operator>(IEC_Ptr other) const noexcept { return ptr_ > other.get_void(); } template bool operator<=(IEC_Ptr other) const noexcept { return ptr_ <= other.get_void(); } template bool operator>=(IEC_Ptr 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 (for CODESYS: PT < DWORD_END) template, int> = 0> bool operator<(IECVar other) const noexcept { return reinterpret_cast(ptr_) < static_cast(other.get()); } template, int> = 0> bool operator>(IECVar other) const noexcept { return reinterpret_cast(ptr_) > static_cast(other.get()); } template, int> = 0> bool operator<=(IECVar other) const noexcept { return reinterpret_cast(ptr_) <= static_cast(other.get()); } template, int> = 0> bool operator>=(IECVar other) const noexcept { return reinterpret_cast(ptr_) >= static_cast(other.get()); } template, int> = 0> bool operator==(IECVar other) const noexcept { return reinterpret_cast(ptr_) == static_cast(other.get()); } template, int> = 0> bool operator!=(IECVar other) const noexcept { return reinterpret_cast(ptr_) != static_cast(other.get()); } }; // Reverse comparison: nullptr == IEC_Ptr template bool operator==(std::nullptr_t, IEC_Ptr p) noexcept { return p == nullptr; } template bool operator!=(std::nullptr_t, IEC_Ptr p) noexcept { return p != nullptr; } // Arithmetic: n + ptr (reverse order) template IEC_Ptr operator+(std::ptrdiff_t n, IEC_Ptr p) noexcept { return p + n; } } // namespace strucpp