Skip to content

STruC++ emits an invalid C++ literal for fractional TASK INTERVAL values #211

Description

@dreamclass

Problem

When a CONFIGURATION declares a TASK whose INTERVAL does not convert to a
whole number of nanoseconds, STruC++ generates a floating-point value carrying
an integer (LL) suffix
for the task's interval_ns field. This is not a
valid C++ literal, so the generated .cpp fails to compile with g++/clang.

Environment

  • STruC++ version 0.6.2 (win64, strucpp.exe)
  • Compiler: g++ 14 (MSYS2 UCRT64), -std=c++17
  • Also reproduces with -fext-numeric-literals (that flag does not fix it)

Impact

Blocks compilation of the generated code entirely. Any TASK whose INTERVAL
is not a whole number of nanoseconds hits this — e.g. a 60 Hz rate expressed as
a 16.6667 ms task period.

Reproduction

min.st:

PROGRAM PLC_PRG
VAR
    x : INT;
END_VAR
    x := x + 1;
END_PROGRAM
CONFIGURATION MainConfig
    RESOURCE MainResource ON PLC
        TASK MainTask (INTERVAL := T#16.6667ms, PRIORITY := 0);
        PROGRAM MainInstance WITH MainTask : PLC_PRG;
    END_RESOURCE
END_CONFIGURATION

Compile to C++:

strucpp min.st -o min.cpp --no-default-libs

Actual result

min.cpp contains (constructor of Configuration_MAINCONFIG):

tasks_storage[0] = TaskInstance("MAINTASK", 16666699.999999998LL, 0,
                                &task_programs_storage[0], 1);

Compiling min.cpp fails:

min.cpp:33:49: error: unable to find numeric literal operator 'operator""LL'
   33 |     tasks_storage[0] = TaskInstance("MAINTASK", 16666699.999999998LL, 0, ...);
      |                                                 ^~~~~~~~~~~~~~~~~~~~
      note: use '-fext-numeric-literals' to enable more built-in suffixes

(The suggested -fext-numeric-literals does not help — this is a malformed
literal, not a missing user-defined suffix.)

Code Locations

No response

Fix

Root cause

STruC++ appears to compute the interval as
literal_value * scale in floating point (here 16.6667 * 1e6) and then append
the LL suffix to the resulting double without converting it to an integer.
16.6667 * 1e6 is not exactly representable, so it lands on
16666699.999999998, and 16666699.999999998LL is emitted verbatim.

The bug is intermittent because it only surfaces when the nanosecond value is not
exactly representable as a floating-point integer:

INTERVAL emitted literal valid?
T#16.6667ms 16666699.999999998LL ❌ no
T#16.667ms 16667000.000000002LL ❌ no
T#33.3333ms 33333300LL ✅ yes
T#10ms 10000000LL ✅ yes
T#0.5s 500000000LL ✅ yes
T#1000ms 1000000000LL ✅ yes

Suggested fix

Truncate/round the computed nanosecond value to an integer before formatting
the literal, so an int64_t-typed field always receives an integer token.
Equivalently: build the interval in integer arithmetic (accumulate each
time-component's contribution as int64_t nanoseconds) rather than multiplying a
double and casting at the literal-emission step.

Acceptance

TaskInstance::interval_ns is declared int64_t (runtime/include/iec_std_lib.hpp):

struct TaskInstance {
    ...
    int64_t interval_ns;   ///< Execution interval in nanoseconds (0 = event-driven)
    ...
    TaskInstance(const char* n, int64_t interval, int32_t prio, ...);
};

so the emitted argument should be an integer literal, e.g. 16666700LL
(or 16666699LL). A 1 ns rounding difference is immaterial.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions