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}; // <--
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