-2

I am programming a virtual machine and I've made a union of restricted pointers to iterate the script's instruction stream:

union Pointer {
    uint8_t *restrict UInt8Ptr;
    uint16_t *restrict UInt16Ptr;
    uint32_t *restrict UInt32Ptr;
    uint64_t *restrict UInt64Ptr;

    int8_t *restrict Int8Ptr;
    int16_t *restrict Int16Ptr;
    int32_t *restrict Int32Ptr;
    int64_t *restrict Int64Ptr;

    float *restrict FloatPtr;
    double *restrict DoublePtr;
    const char *restrict CStrPtr;
    void *restrict Ptr;
};

For the CALL opcode, I have the instruction pointer's value saved (indirectly) which, if I understand the usage of the "restrict" keyword, would cause undefined behavior.

    (--regs[regStk].SelfPtr)->Ptr = ip.Ptr; /* push rip */
    *--regs[regStk].SelfPtr = regs[regBase];    /* push rbp */
    regs[regBase] = regs[regStk];   /* mov rbp, rsp */

I should also say that in the RET opcode, the instruction pointer's value is restored.

    regs[regStk] = regs[regBase]; /* mov rsp, rbp */
    regs[regBase] = *regs[regStk].SelfPtr++; /* pop rbp */
    ip.Ptr = (*regs[regStk].SelfPtr++).Ptr; /* pop rip */

I've done many many tests and even used different compilers (GCC and clang v3.5 and clang v6.0) and this didn't seem to produce undefined behavior, why is that?

EDIT UPDATE:

The variables are both declared one a local block scope:

int32_t VM_Exec(struct VM *const restrict vm)
{
    if( !vm or !vm->CurrScript.Ptr ) {
        return ErrInstrBounds;
    }

    union Value *const restrict regs = vm->Regs; // <--
    union Pointer pc = (union Pointer){.UInt8Ptr = regs[regInstr].UCharPtr}; // <--
Nergal
  • 349
  • 3
  • 14
  • 1
    There is no guarantee of Undefined Behaviour. What do you think should happen? – Weather Vane Jul 20 '18 at 20:29
  • 3
    "Appearing to work as intended" is a possible manifestation of undefined behavior. – Raymond Chen Jul 20 '18 at 20:29
  • "appearing to work as intended", including after hundreds of different tests and compilers? – Nergal Jul 20 '18 at 20:30
  • 1
    Because *undefined behaviour* is not defined. Even if you run it a billion times it might not fail, due to some local circumstances. [This post](https://www.geeksforgeeks.org/restrict-keyword-c/) says "If a programmer uses `restrict` keyword and violate the above condition, result is undefined behavior." But, at the moment you make your vital presentation to the funding committee, [Sod's law](https://en.wikipedia.org/wiki/Sod%27s_law) says ***that*** is when it will fail – Weather Vane Jul 20 '18 at 20:34
  • I understand that but I'm pretty sure that since executing recursive functions with the VM would cause some of sort of behavior that's not normal VM execution... Especially billions of recursive calls. – Nergal Jul 20 '18 at 20:41
  • @Nergal Undefined behavior is not necessarily about runtime, but about how the compiler is treating your code. It might or might not make some optimizations based on the assumptions that can be drawn from how the code is written. If these optimizations are not done, you can run your code billion times and nothing wrong will happen. – Eugene Sh. Jul 20 '18 at 20:49
  • 2
    if you do not modify the referenced object and you save the pointer itself - there is no UB. You need to show how those operations are related to this union. – 0___________ Jul 20 '18 at 20:52
  • @Nergal off topic but suppose there is `int a; int b[2]; int c;` where the variables `a` and `c` are never used. C does not require that `b` in memory is located between `a` and `c` but it may well be in your C implemntation. Now, when you write to the out-of-bounds `b[2]` you can run the program forever and never get a failure. Until you make it with another compiler which is C compliant but uses memory in its own way. You could find this with an MCU C compiler which has limited resources. – Weather Vane Jul 20 '18 at 21:07
  • "Undefined" doesn't mean "defined to do weird things". – user2357112 Jul 20 '18 at 21:19
  • thank you all for ignoring the question, P__J__ was the only one who focused on the question instead of lecturing me what undefined behavior is or isn't... – Nergal Jul 20 '18 at 22:34

2 Answers2

2

Restrict keyword is only taken into account if the higher levels of optimization. gcc -O2 & -O3 only.

in your examples I do not see anything which can cause the problem as we do not how those arrays are declares and how used.

Here you have an example - I break the contract with the compiler .

unsigned p = 100;

void foo1(void)
{
    p++;
}

void foo(unsigned *restrict x)
{
    printf("p = %u\n", *x);
    foo1();
    printf("p = %u\n", *x);
}

int main()
{
    foo(&p);
}

and the result is (-O3)

 100
 100

compiled with -O1

100
101

Another example: -O3

unsigned p = 100;

void foo1(void)
{
    p++;
}

void foo(unsigned *restrict x)
{
    unsigned *restrict p1;

    p1 = x;
    printf("p = %u\n", *x);
    foo1();
    printf("p = %u\n", *x);
    *p1++;
    printf("p = %u\n", *x);
}

int main()
{
    foo(&p);
}

and the result:

p = 100
p = 100
p = 101
0___________
  • 60,014
  • 4
  • 34
  • 74
  • how about now since I've added detail where the values are given. the `regs[regStk]` value points to an array from another stack frame. – Nergal Jul 20 '18 at 21:06
  • @Nergal you do not modify the object itself. At least I do not see it. BTW you overuse the restrict. See my another example - maybe it will explain the bahavior – 0___________ Jul 20 '18 at 21:20
  • 1
    When using a restrict-qualified automatic object, the "restriction" ends once execution leaves the scope wherein the object is defined. The OP's example uses `restrict`-qualified union members, however. If a pointer stored in such a member is used to write an object, what must happen before that object can be read via independent means? – supercat Jul 26 '18 at 18:51
0

The purpose of the restrict qualifier is to say that operations on certain pointers may be freely reordered with respect to any other operations within a certain region of code. While the Standard allows the qualifier to be used in association with pointer objects whose lifetime isn't tied to any particular region of code, the Standard would only allow a pointer value P to be stored into such an object if for every byte of storage that will ever be accessed via pointer derived from P one of the following is true:

  1. The byte is part of a static object whose value will never change during the execution of the program.

  2. The object in question has never been, and will never be, accessed via any pointer not derived from P.

Such limitations are sufficiently severe that there are very few cases where a restrict qualifier could sensibly be used on anything other than an automatic object, and I don't know of any compilers that try to do anything useful with such qualifiers in other contexts.

supercat
  • 77,689
  • 9
  • 166
  • 211