Exception Handling in Programmable Controllers with Denotational Model

The paper introduces a customized approach to handle failures in IEC 61131–3 programmable controllers. The solution assumes the utilization of a virtual machine as a runtime environment to execute control code in an isolated manner. A formal model of the runtime is presented, employing denotational semantics. Subsequently, the model is expanded by incorporating new procedures that enable the handling of runtime exceptions using ST code constructs. This formal model serves as the foundation for implementing the exception infrastructure in the CPDev development environment. The research presented in the paper, driven by industry demands, aims to facilitate the development of more reliable and resilient control systems, capable of effectively dealing with failures.


I. INTRODUCTION
P ROGRAMMABLE Logic Controllers (PLC) and Pro- grammable Automation Controllers (PAC) have established their position in the modern world.Their applications are wide-ranging, including industrial production, energy, transportation, and Internet of Things solutions.They control processes, often using advanced algorithms, and are expected to be reliable and operate in real-time mode.
One characteristic that allows for the flexibility of these devices is the ability to program them, where an engineer creates their own control algorithm and places it in the controller.From this perspective, such a controller is versatile as it can be programmatically tailored to specific applications and its functionality can be extended or modified when changes are required in the controlled object.
In some controllers, programming is done using typical languages, most commonly C/C++.However, there are standardized mechanisms and programming solutions specifically designed for control devices.The most significant of these are the international standards IEC 61131-3 [1] and IEC 61499 [2].Particularly, the former has become a recognized standard adopted by many manufacturers.It allows, among other things, the transfer of program code between devices from different vendors and introduces specialized programming languages such as structured text (ST), instruction list (IL), graphical block diagram (FBD), ladder diagram (LD), and sequential function chart (SFC).The language structures align well with control system programming paradigms.Hence, this article considers a system that complies with the IEC 61131-3 standard.
The stable and predictable operation of PLC and PAC controllers is a feature resulting from their applications, where errors and unexpected reactions can have serious consequences.To minimize the risk of such situations, developers employ various solutions.The mere use of the standard's languages can reduce potential problems, particularly by avoiding programming mechanisms related to manipulating pointers and dynamic memory allocation.Another solution involves constructing an isolated runtime environment for user programs.In such cases, the effects of programmer errors will not propagate beyond that runtime, allowing the device to remain operational and enabling controlled handling of exceptional situations.
One concept for creating such an isolated environment is a virtual machine [3].In general terms, a virtual machine (referred to as VM) is understood here as a type of processor with its own instruction set and data types, implemented through software on specific hardware platforms.This means that when processing code designed for a VM, appropriate software mechanisms execute it using the native resources of the target platform, such as a specific CPU and memory.The VM processes code, typically referred to as intermediate code, which is generated by a compiler from a source program.The concept of virtual machines has gained prominence in information technology due to the widespread use of platforms such as the Java Runtime Environment [4] and the .NET Framework [5], [6].
Solutions based on virtual machines offer several important advantages.Firstly, the source program and intermediate code are independent of the target hardware platforms.This means that only one compiler for the source language is required, rather than separate cross-compilers for different platforms.Additionally, programs are executed within secure environments with memory protection, preventing potential errors from propagating beyond the designated boundaries.
However, there are also disadvantages to consider.Execution of intermediate code tends to be slower compared to executing native code on the target processor.This is because the instructions and operands of the intermediate language need to be decoded by software, whereas a standard CPU utilizes hardware decoders and pipelining.Consequently, implementing even a simple intermediate instruction requires multiple native instructions.
When designing a virtual machine as a runtime environment prepared to handle error and exceptional situations, several aspects need to be taken into account.The first aspect is the compatibility of the machine's operation with its specification.For this purpose, the authors have proposed a formal model of operations performed by the virtual machine using denotational semantics [7].This model enables the implementation of these operations in accordance with the assumptions, for example, in languages like C/C++.In this article, the model has been expanded to include functions related to exception handling.
Another task is to supplement the languages of the IEC 61131-3 standard with additional constructs related to exception handling.This is necessary due to the lack of dedicated solutions in the standard.Hence, there is a need to introduce them.The authors take into account the extensions to the ST language available in the CODESYS package but propose their own implementation of these extensions.

II. PROGRAMMING AND RUNTIME ENVIRONMENTS
The engineering environment CPDev (Control Program Developer) allows to program controllers according to the IEC standard [8].It consists of ST/IL/FBD/LD/SFC editors, a compiler translating programs to the intermediate code [9] and a VM-based runtime system written in C/C++ [10].
The architecture of the VM is shown in Fig. 1.It includes the following components: • code and data memories, • code and data stacks, • registers and pointers, • instruction processing module.The Instruction processing module fetches successive instructions from Code memory and executes them acquiring values of operands either from Data or Code memory.Results are stored directly in Data memory.
The machine does not utilize an accumulator, however it maintains other registers.The instruction pointer, also known as the program counter, is stored in the CodeReg register.VM increments the CodeReg register every time after fetching an instruction code or an operand address.The DataReg register is used for managing the data base addresses and is set during subprogram calls and returns, including function blocks and functions.This allows the executed code to access variables in different areas of the Data memory and handle multiple instances of subprograms.When entering a subprogram, the current values of CodeReg and DataReg are pushed onto the Code stack and Data stack respectively.Upon returning, the contents of these registers are popped from the stacks.This stack mechanism enables nested function blocks.Additionally, the machine includes the Flags register, which contains status flags that signal errors or unusual situations such as an array The virtual machine, as designed specifically for execution of control programs, can handle all IEC 6131-3 data types.The number of bytes required to store each such type in the data memory is given in Table I.
There are two kinds of virtual machine instructions: • functions, • system procedures.Examples of some functions are shown in Table II with decreasing priority.The functions return one value each to be written into the variable being the first operand (as said, an accumulator does not exist in this VM) and may have up to 15 other operands.Note that such order is different than in Static Single Assignment of dataflow graphs used in typical compilers [11].Arithmetic operations are executed in limited ranges, depending on the type.In case of integers the ranges are (−128, 127) for SINT, (−32768, 32767) for INT, etc.
Contrary to functions, system procedures do not return values or return more than one.Table III   subprograms, etc. From the programmers' viewpoint there is no major difference in using functions or procedures.To accommodate exception handling, the architecture of the virtual machine needed to be expanded.The modifications mainly revolved around the protected code stack, which will be thoroughly explained in Section IV.

III. RUNTIME FORMAL MODEL
A formal model has been developed to specify the operation of the virtual machine using denotational semantics [12], [13].The fundamental aspects of the model were outlined in the previous publication [14].In this context, we now expand upon the description of the model components.The model consists of various domains that define the states, memory functions, value interpreters, limited range operators, and a universal semantic function that invokes specific functions representing individual instructions.
The domains within the model encompass abstract data types that represent the values processed by different components of the virtual machine (Fig. 1).One such domain, denoted as BasicT ypes, comprises four sets that correspond to the memory sizes of the basic data types outlined in Table I  In the context of the virtual machine, the state is represented as a Cartesian product of various domains including memory, stacks, registers, and flags.More specifically, the domain denoted as State can be understood as a collection of tuples (cm, dm, cs, ds, cr, dr, f lg), where each element corresponds to a value within its respective domain.
The functions presented below model low-level operations executed on memory, stacks and flags.
• Get address from memory GetAddress = (Address × M emory) → Address The function returns the value stored at the given Address in M emory which is another Address.Since the VM has no accumulator, it operates directly on addresses, and the function GetAddress is essential for the model.Address domain means T woBytes or F ourBytes.
• Memory move The source and target Addresses of code or data M emory should be provided.The number of bytes being moved ranges from 0 to 255 (OneByte).
• Stack functions The functions execute stack operations needed by subprograms.Note that P op returns a pair, viz.Address and new Stack.

→ T woBytes
The F lags domain is an alias to T woBytes.The successive T woBytes above denote actual flags, bits to be set or reset, and new flags.

• Value conversions
ByteT oW ord = OneByte → T woBytes W ordT oByte = T woBytes → OneByte For a value without a sign, ByteT oW ord places zero bits into the more significant byte of T woBytes, otherwise the byte is filled with the sign bit.W ordT oByte reduces the value by removing the most significant bits.The following sample functions provide numerical interpretations of OneByte, T woBytes and two other memory chunks.
Other types are interpreted analogously.The numeric identifiers of VM instructions consist of the identifier of a group ig and the identifier it of a particular data type or procedure.In this way type-specific instructions or procedures may be selected.For some functions it also indicates the number of inputs.
To collectively represent the concept of decoding a group and type, followed by the execution of a specific instruction, a universal function U has been defined.The algorithm of the function U is presented in Fig. 2. It is assumed that the code register cr initially points to the group identifier ig in code memory cm.The algorithm starts by fetching ig (one byte) and incrementing cr to cr 1 .Then it is acquired and the code register incremented to cr 2 .At this moment the state of the VM is described by the tuple (cm, dm, cs, ds, cr 2 , dr, f lg) involving memories, stacks, registers and flags.For instance, for ig=04 and it=02 the DIV function is called with the two operands op1, op2 of type INT, whereas it=09 means operands of type REAL.The last group ig=1C consists of system procedures, including JMP and CALB.The semantics of the DIV function for INT-type operands is shown in Listing 1.The function divides two operands of type INT, sv denotes the value of the division.The result of a function execution is stored in the location labeled by the first operand, here denoted by r.The updated data memory is the second element of s 1 as the result of invoking U pd2BM em.
The number stored at raddr is given by F romInt(sv).
The procedure GAWR presented in Listing 2 is used in algorithms involving arrays.This procedure copies elements of an array in local memory to an array in global memory.The arrays may contain elements of any type, including arrays and structures.There are four operands, source src and destination dst labels, size of the elements, and array index idx.The values size and idx are addresses to data of type WORD.Since the operand dst refers to global memory, its It is important to note that the equations provided for the DIV and GAWR procedures do not account for erroneous operands, such as a divisor equal to zero or an array index out of bounds.Consequently, to address these failures and prevent unpredictable behavior, the model had to be extended with an exception mechanism.

IV. ADDING EXCEPTION HANDLING
Exceptions have been introduced to replace a sequence of nested if-else instructions when performing compound operations that may fail under certain circumstances.The complex branching of algorithm paths, depicted in Figure 3, can be challenging to analyze and distinguish between the normal execution path and the path taken to handle failures.To address this issue, some programming languages have introduced a try-catch construct, which separates the algorithm's main path (protected code) from the failure handling path.This approach allows programmers to focus on the operations that the algorithm needs to perform, while storing the failure handling logic in a separate section of the code.
When a failure occurs, it is reported through an exception, which can take various forms, ranging from a simple value like a number or string to a specifically designed object.The presence of an exception terminates the execution of the remaining instructions within the protected code.Subsequently, the processing of the first catch clause begins, but only if it matches the type of the exception object.If the exception does not match the type of the first catch clause, the subsequent catch clauses will be checked for a match.If no further catch clauses are found, the execution switches to the surrounding try-catch construct.However, if such a construct is not present, the execution is terminated with an unhandled exception state, preventing further execution.
Therefore, the revised code based on Figure 3 would resemble the examples provided in the code snippet shown in Listing 3.However, it is important to note that such code may overlook certain critical tasks that must be performed even if an exception occurs.To address this concern, the try-catch construct has been enhanced with an additional clause called finally.The finally clause contains the code that is always executed when the control exits the protected section of code, irrespective of whether a matched exception occurs, an unhandled exception is encountered, or no exception occurs at all.
The IEC 61131-3:2013 standard does not include constructs for writing code in an exception-style manner.However, certain manufacturers offer their own extensions to the ST language to support such functionality.For instance, the CODESYS development environment provides the following keywords to indicate protected code: • __TRY -beginning of the protected code, • __CATCH -point where failure path of code begins, • __FINALLY -beginning of mandatory code executed always, • __ENDTRY -end of the protected code.In our solution, it was decided to use keywords from CODESYS for exception handling for greater compatibility.In addition to the above keywords, __THROW keyword for throwing user-defined exceptions as in the general purpose programming languages (e.g.C++, Java, C#) is added.
To accommodate these language constructs, several changes need to be made to the denotational model of the virtual machine.Firstly, the State tuple needs to be expanded to include two additional components: P rotStack and ExcObj.P rotStack represents a stack (Kleene closure) of P rotEntry tuples, which consist of four addresses.To modify the P rotStack, the following functions are introduced: P ushP rot, P opP rot, and P eekP rot.The P ushP rot function adds an item to the stack, P opP rot removes an item from the stack, and P eekP rot returns a copy of the topmost item without modifying the stack.ExcObj is an Address that indicates the location where the exception object has been stored.Since an Address always refers to a memory location, a new flag, EXCOBJ, needs to be introduced in the F lags mask to indicate the presence of the exception object.In order to handle the __TRY, __CATCH, __FINALLY, __ENDTRY keywords, as well as the additional __THROW, new system procedures are required.These procedures are listed in Table IV.
When encountering the __TRY instruction, the ST language compiler should generate the system procedure PHPRS.The purpose of this procedure is to store the current DPTR context, the address of the first __CATCH instruction, the address of the __FINALLY instruction, and the address of the __ENDTRY instruction on the P rotStack.The corresponding denotational model of PHPRS is presented below.When the ST compiler encounters the __CATCH statement, it should generate the system procedure MEXCT.The purpose of this procedure is to detect whether the exception type (excTyp operand) matches the current exception.If the exception type matches, the following instructions will be processed and the exception state will be cleared with a jump to the __FINALLY statement.If the exception type does not match, a jump to the next __CATCH statement is performed.If there are no further __CATCH statements, a jump to the __FINALLY keyword is executed.This behavior can be represented using the following denotational equation: After the last statement of __CATCH, the compiler should generate the system procedure CEXCF, which marks the end of exception handling.The denotational model of CEXCF can be defined as follows: C〚CEXCF〛 = λs.
(cm, dm, cs, ds, cr, dr, f lg, ps, eo) := s stk := P eekP rot(ps) (dct, ctch, f in, ef n) := stk f lg 1 := ClearF lag(f lg, F _EXCP T ) s 1 := (cm, dm, cs, ds, f in, dr, f lg 1 , ps, 0) No special action is needed when the compiler encounters the __FINALLY statement.However, an action is required when the compiler encounters __ENDTRY in the ST input.In this case, the compiler should emit the POPRS system procedure, which can be represented by the following denotational model:

V. IMPLEMENTATION IN CPDEV
An exception is reported in the CPDev virtual machine either automatically or manually.The call is invoked internally, when a system exception condition is met during the program

Fig. 1 .
Fig. 1.Architecture of the virtual machine BasicT ypes = OneByte + T woBytes + + F ourBytes + EightBytes Address = F ourBytes M emory = Address → OneByte CodeM emory = M emory DataM emory = M emory Stack = Address * CodeStack = Stack DataStack = Stack CodeReg = Address DataReg = Address F lags = T woBytes Broadly speaking, the goal of program execution is to transition the current state of the computer into a new state.

Fig. 2 .
Fig. 2. Algorithm of the universal function U