Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The harness: matiec’s C and iecf’s, run side by side

(It also runs any program on both compilers, side by side, and compares every elementary variable of its symbol table tick by tick, not only the Q and M bytes: iecf-harness compare --reference DIR FILE.st..., on pseudo-random inputs, the same for both and at every run (src/compare.rs). A host given vars 0 0 00 as the script’s first line prints the variables after each line: iecf’s from the symbol table, matiec’s through a vars.c the harness writes from its VARIABLES.csv (src/vars.rs), for the same leaves: every array element (the first 64 of a big array), structure field and function block member, named from iecf’s <config>.symbols.json. CI runs it with --expect-same on the cases compare-cases lists: random inputs reach where iecf decided differently in the others, and that file says where.

It also checks the book’s examples: iecf-harness book builds and runs every row of each table marked <!-- checked --> under docs/, and compares the result and the flag with the row; src/book.rs.)

PROPOSAL.md §5 step 2: iecf’s C reaches matiec’s parity, compared in CI. This directory is that comparison. matiec is the reference compiler: the harness’s files, commands, options and CI steps say “reference” (reference_host.c, reference-skip, iecf-harness reference --reference DIR), and only these documents name it.

A case

cases/<name>.st is a program and its CONFIGURATION, plain IEC 61131-3, the same file for both compilers. cases/<name>.cycles says how many ticks to run and what the inputs are:

extension reference-to  # optional: an extension iecf builds it with
ticks 6                 # one tick is the CONFIGURATION's base interval
0 %IB0 16#03            # at tick 0, before it runs
3 %IX1.2 TRUE
4 %IW2 -5
5 warm                  # a warm restart before tick 5: iecf's <p>_warm,
                        # matiec's config_init__ (it has no warm one)

An extension NAME line switches on an extension of docs/extensions.md for iecf; the case is then not IEC 61131-3, and in reference-skip.

A case can be more than one file: each .st in cases/<name>.d/ is built with <name>.st, in name order, as iecf build takes a project of several files. Only iecf builds those (namespaces: a namespace two files add to); such a case is in reference-skip.

The program shows its results by writing them to %Q and %M. cases/<name>.trace is the trace: one line after initialisation and one per tick, the Q and M bytes in hex, as far as the program addresses them. Addresses count bytes and are little-endian (docs/c-output.md §4).

The trace is matiec’s, written by the harness and checked by hand. A case where iecf decides differently from matiec on purpose - overflow, the counters’ limits (PROPOSAL.md §1b) - has cases/<name>.iecf.trace beside it, and the reason in the case’s comment: overflow and narrow (an integer overflow or conversion out of range flagged; the bytes are matiec’s), edges (the counters count over their type’s range), infinity (an infinite or NaN real flagged) and unconverted (a real that does not fit an integer leaves its statement undone, where matiec’s C is undefined), subrange (a value outside a subrange clamped and flagged, where matiec stores it), strings (a string position past the end flagged), dates (a TOD wrapped at midnight, where matiec’s runs past 24 hours), durations (a TIME in whole milliseconds, where matiec’s holds nanoseconds), sfcdemo (SFC: actions before transitions and a final scan, where matiec tests transitions first), enable (EN = FALSE leaves the statement undone, where matiec assigns 0; ENO FALSE on the function’s own overflow), fbenable (a block with EN = FALSE leaves its outputs untaken, where matiec takes them; ENO FALSE on an overflow in its body), enobody (a body’s ENO := TRUE cannot hide an overflow, where matiec has no flag), and retain and fbretain (RETAIN kept across a warm restart, which matiec’s host does as a cold one). A flag ends the line with flag CODE POU LINE. A fault ends the line with fault CODE POU LINE (docs/c-output.md §5).

The cases cover the Ed.2 core a slice at a time: image (each size of address), arith, bits, control (every ST statement), timers, edges (counters, edges, bistables), pous (user FUNCTIONs and FUNCTION_BLOCKs, VAR_IN_OUT, globals), types (enumerations, subranges, arrays, structures), real, functions (the operators written as functions, SEL, EXPT), strings (STRING and Table 34’s functions), dates (DATE, TOD and DT, Table 30), parts (CONCAT_DATE, CONCAT_TOD, CONCAT_DT and the SPLITs, Table 36; reference-skip: matiec has none of them), splitwrap, splitindex, splitclamp (a SPLIT’s part too big for its output, wrapped and flagged; an output element out of bounds, nothing stored; a part clamped into a subrange; reference-skip too), fbenable (EN and ENO on a function block call; matiec takes the outputs with EN FALSE), fbskip (EN FALSE evaluates no input; reference-skip: matiec cannot compile NOT ENO =>), progio (a program’s VAR_IN_OUT bound to a global in the CONFIGURATION; reference-skip, as connections), enobody (ENO set in a FUNCTION’s and a FUNCTION_BLOCK’s body), splitenable (EN and ENO on a SPLIT; reference-skip, as parts), ldates (LDATE, LTOD, LDT and their conversions; reference-skip: matiec has no L dates), located (time and date types at an address, each its own width, a BOOL at a byte; reference-skip: matiec refuses an LTIME there, and its TIME is a structure), locdata (arrays, structures and a STRING at an address, packed; reference-skip: matiec refuses an array there), refs (REF_TO, REF(), ^, NULL and comparisons, on both compilers: matiec’s iec2c is given -r, which switches Ed.3’s references on), refimage (references into the image, to function blocks and to references, REF() as an initial value, a NULL reference dereferenced; reference-skip: matiec’s C for r^ := ... does not compile and REF_TO a function block crashes it), refto (CODESYS’s REFERENCE TO: REF= to a variable, another REFERENCE TO, an element, an address, a structure and a block, a block’s REFERENCE TO input, __ISVALIDREF, an unbound one used; the extension reference-to, reference-skip), widen (implicit conversions in an assignment, to a block’s input and from its output, to a function’s input, between operands; INT * INT overflowing as an INT into a DINT; reference-skip), fnout (a FUNCTION’s outputs into variables, at an address, their initial values at each call, RETURN; on both compilers), fnoutx (negated, widened, in order, into an element, a call that faults, a FUNCTION without a result; reference-skip), fninout (a FUNCTION’s in-outs, passed on to another; on both compilers), fnorder (a FUNCTION’s externals, and the order of evaluation once calls have effects: left operand first, a fault before a call stops it; reference-skip), chars (CHAR: literals, at an address, in an array and a structure, in calls, to and from STRING and BYTE; reference-skip: Ed.3’s), implicit (the implicit-externals extension in a PROGRAM, a FUNCTION_BLOCK and a FUNCTION; reference-skip), fneffen (EN and ENO on a FUNCTION with an in-out, an output, a VAR_EXTERNAL, and without a result; reference-skip, as fnorder), inoutcopy (an in-out given a variable at an address or of a subrange, a copy stored back, clamped; reference-skip), stringchars (s[i] read and written, past the end and below 1, in an array of strings; reference-skip: Ed.3’s), wstrings (WSTRING and WCHAR: a surrogate pair, Table 34’s functions, ws[i], in an array, a structure and a block, to and from STRING, CHAR, WORD and INT, a character a STRING cannot hold skipped and flagged; reference-skip: Ed.3’s), wlocated (a WSTRING at an address, at an odd byte, in a structure and an array there; reference-skip), methods (CLASS and METHOD: Ed.3’s COUNTER, THIS, a PUBLIC variable, a block with methods and a body, an in-out, an input set from outside, a method of an instance in an instance, EN; reference-skip), inherit (EXTENDS, OVERRIDE, SUPER: Ed.3’s CIRCLE and LIGHT2ROOM, Figure 21’s SUPER() chain, a derived block behind a base-typed in-out and reference, an ABSTRACT class as a FUNCTION’s in-out, a method through a NULL reference; reference-skip), interfaces (INTERFACE, IMPLEMENTS, ?=: Ed.3’s ROOM and ROOM_CTRL, THIS given to an interface, an interface extending another, an ABSTRACT block leaving a method to its derived type, an interface as a FUNCTION’s input, ?= into an interface and a reference and between references, a method through a NULL interface; reference-skip), itfplaces (interfaces wherever a variable can be: array elements and structure fields, a VAR_GLOBAL, initial values that are a local’s and a global’s instance, a FUNCTION’s result, outputs and an input left to its initial value, a block’s output taken into an element and read from outside, a method’s result through an interface’s table and a method’s output, ?= into an element, = of elements, NULL in a field faulting; reference-skip), itfprog (a PROGRAM’s interface inputs and outputs connected in the CONFIGURATION: from a global instance, a global interface and NULL, into a global interface of a base type; reference-skip), flexarrays (ARRAY[] in-outs of a FUNCTION, a method and a FUNCTION_BLOCK, Ed.3’s SUM and a two-dimensional one, passed on, LOWER_BOUND and UPPER_BOUND, a dimension out of range faulting; a block’s in-outs of an array, a structure and a STRING; bounds, a STRING’s length, a subrange and a CASE’s labels naming constants; a row of an array of arrays and of an array of two dimensions given to an ARRAY[], copied, at an address, and taken of an ARRAY[, ]; reference-skip), flexio (ARRAY[] inputs and outputs of a FUNCTION and a method, the caller’s array itself: read, passed on, an output’s elements set to their initial value first, given by name and in order, a constant array and a row given, an index out of range faulting; reference-skip), instvalues (a function block instance as a value: assigned, in a structure and an array copied, an input of a FUNCTION and of a block, a block’s output, a standard block copied, an input set apart from a call, a FUNCTION’s result, output and own instance; reference-skip: matiec refuses an instance in a structure, as a FUNCTION’s result and as a FUNCTION’s variable), callparts (a part of a FUNCTION’s result read at the call: a field, an element by a constant and a computed index, a variable of an instance, in an expression and a condition, a call with effects made once, an index out of range faulting; reference-skip: matiec refuses a part of a call), holders (a structure and an array that hold instances as a block’s and a FUNCTION’s input, output, in-out and result, an ARRAY[] of instances, a PROGRAM’s connections of an array, a structure and an instance; reference-skip), flexcopy (a whole ARRAY[] assigned from another and from an array of fixed bounds, in two dimensions, onto itself, sizes that differ faulting; reference-skip), inoutdata (an array and a structure at an address, a row of one, and one through a reference, given to an in-out and to an ARRAY[] input, output and in-out of a FUNCTION and a block; reference-skip), fbargs (a block’s in-out given and its output taken into an element by a constant and a computed index, a field, a variable through a reference and one at an address, widened and negated; an enumeration in place with named values; reference-skip), constinit (initial values that are constant expressions: constants of each type, of constants, global, a block’s in its method and in a derived block, in a TYPE; reference-skip), constfns (initial values that are standard functions of constants: worked out by the compiler, the mathematics of Table 28 checked by it and computed by the target as the variable is set up - in a program, a TYPE, a field, an array, a global, a block’s and a FUNCTION’s input; a part of a constant array or structure; reference-skip), single (a SINGLE-triggered task, one with SINGLE and an interval, one whose INTERVAL is a variable changed as it runs; reference-skip), retainloc (RETAIN variables at addresses, of a program, a global and a block, across a warm restart; reference-skip), enointo (a block’s ENO and outputs taken into an element, a field and a bit; the index checked before the block runs; reference-skip), locmore (located variables without a name; a FOR variable at an address, ending, left by EXIT, moved by its body, not run; reference-skip), partaccess (bits, bytes, words and double words of bit strings, read and written, of variables, elements, a variable at an address and through a reference; reference-skip), edgeinputs (R_EDGE and F_EDGE inputs of a block and a program over a toggling input; reference-skip), nesten (calls with EN nested in another’s arguments, ENO on each; EN on a FUNCTION giving an array or a structure; reference-skip), connect2 (program connections of strings, subranges and fields, and in-outs of every kind - structure, array, STRING, instance, at an address, a subrange of other bounds; reference-skip), targetidx (an assignment’s target indexed by a FUNCTION with effects and by a real converted; a statement whose conversion fails faulting on nothing after it; a real fault last; reference-skip), refmore (a FUNCTION giving a reference, stored and called through; a block’s input and a whole instance set through a reference; a standard block’s input set from outside; REF() of an interface variable; a NULL dereferenced last; reference-skip), selectdata (SEL, MUX and MOVE of arrays, structures, instances, interfaces and references, NULL among them; MUX’s K out of range last; reference-skip), extcompare (GT, GE, EQ, LE, LT of more than two inputs, a FUNCTION with effects among them called once; the negation of a TIME; the shifts of a BOOL; reference-skip), subparams (a subrange as a FUNCTION’s input, output, in-out and result and a block’s in-out; a subrange starting at its lower limit; reference-skip), stdinout (a standard block’s instance as a VAR_IN_OUT of a FUNCTION, one that passes it on, and a FUNCTION_BLOCK; reference-skip), litcalls (a standard function given literals alone, a quotient of real literals: computed in the type wanted, integers staying integers where a real is wanted; reference-skip), enums (types with named values used as their base type, a value not given, a WORD base, INT when none is named; an enumeration as a FUNCTION’s input, output and result and a block’s in-out, a result not set and an input not given the type’s initial value; one declared in place; reference-skip), fnparams (a FUNCTION’s and a method’s STRING, array and structure inputs, outputs, in-outs and results: a STRING input cut and flagged where it is declared, an input left to its initial value, an array result given to another’s input, a STRING result through an interface and an override, a WSTRING one; reference-skip), instances (instances besides a POU’s own variable: an ARRAY OF TON given [2(PT := T#20ms)], arrays of a block and of a class each element its own initial value, a STRUCT with a TOF field, called and read there, an element given to an in-out and an interface, an index out of bounds faulting before the call, a block holding an array of instances, global instances through VAR_EXTERNAL, RETAIN and plain arrays of a block that keeps a count across a warm restart; reference-skip), wrefs (REF_TO a WSTRING at an odd byte and in C memory, one reference switching between them, read, stored, by r^[i], a structure holding one copied through a reference; reference-skip), durations (TIME times and divided by a number), enable (EN and ENO on calls), sfcdemo (Prokop’s SFC demo), sfc (every qualifier, divergence and convergence, PRIORITY; reference-skip: matiec reads no action’s Q nor PRIORITY), blockdata (arrays, structures and STRINGs in and out of a FUNCTION_BLOCK), convert (to and from STRING, where matiec’s text is iecf’s), tasks (two intervals), temp (VAR_TEMP), fbretain (RETAIN and NON_RETAIN inside a FUNCTION_BLOCK), overflow and narrow (where iecf flags), infinity, unconverted, subrange. Apart from those five they stay inside what both compilers define: no overflow, no conversion out of range. A test runs iecf check on each: errors fail it, warnings (a deprecated form both compilers read) do not.

Running it

cargo run -p iecf-harness -- reference --reference ~/matiec          # compare
cargo run -p iecf-harness -- reference --reference ~/matiec --bless  # write the traces
cargo run -p iecf-harness -- reference --reference ~/matiec timers   # one case
cargo run -p iecf-harness -- iecf                                    # iecf's C
cargo run -p iecf-harness -- iecf --bless overflow                   # write its .iecf.trace

--reference (or $IECF_REFERENCE_DIR) is a built beremiz/matiec checkout: iec2c at its top, lib/ beside it. CI builds commit 7949c0b, the one the traces were written with. $CC is the C compiler, cc by default.

How matiec’s C is run: reference_host.c keeps matiec’s located variables in its own variables, copies the image into them before a tick and the changed ones back after (matiec has no process image of its own), sets __CURRENT_TIME to the tick times the base interval, and calls config_run__. reference_fixes.h covers two gaps between matiec’s code generator and its library, as Prokop does (PROPOSAL.md §1b).

The iecf side builds each case with iecf (the library, as iecf build does), compiles the C as strict C99 with every warning an error (docs/c-output.md §1), and runs it under iecf_host.c: the platform side of the contract in a page - the image, the context, a scheduler that runs each task on the ticks its interval divides, by priority. CI runs it with the sanitizers on. No matiec is needed.

reference-skip lists the cases matiec cannot compile (PROPOSAL.md §1b): the reference side skips them, and each has only a .iecf.trace, checked by hand - connections, a program’s inputs and outputs connected in the CONFIGURATION; sfc; parts and the four split* cases, Table 36’s CONCAT and SPLIT functions, which matiec lacks; fbskip, a negated output, which crashes it; progio, program connections again; ldates, the L dates, which matiec lacks; located, an LTIME at an address, and locdata, an array at an address, which matiec refuses; refimage, r^ := ... and REF_TO a function block, which it cannot compile; refto, REFERENCE TO, an extension it does not have; widen, implicit conversions, which matiec (Ed.2) does not make; fnoutx, whose NOT q => crashes it and whose output into an array element it cannot compile; fnorder, VAR_EXTERNAL in a FUNCTION, which it (Ed.2) refuses; chars, CHAR, which it (Ed.2) has not; implicit, the implicit-externals extension; namespaces, NAMESPACE and USING (Ed.3), which it has not, over two files; globalns, the global-namespace extension; fneffen, EN on a FUNCTION with effects, with VAR_EXTERNAL in a FUNCTION again; inoutcopy, whose array element given as an in-out the reference compiler’s C cannot compile; stringchars, a STRING’s character s[i] (Ed.3); wstrings, WCHAR and ws[i] (Ed.3); wlocated, the same and an array at an address.

The generator is built in slices; iecf-pending lists the cases it does not cover yet. Each must fail with E0009 alone, and the run fails when one of them builds - it comes off the list then, and its trace must match.