Short answer:
We find that STRD is interruptible mid-instruction and is restarted rather than resumed when the interrupt returns.
Evidence:
To test, let's make a program and see what happens. The test program uses TIM1 as the interrupt source and interrupts execution every 40ish cycles, which get varied a bit in the ISR. This is on an STM32F411RE.
In the main loop, we modify a uint64_t with LDRD/STRD commands so that each half increments by one. The disassembly shows the compiler uses LDRD/STRD instructions for this.

In the interrupt handler, we check to see if the two halves are ever out of sync. If they are, save save the value and then reset it to 0 to see if both halves will be re-written (instruction is restarted) or if only the second half is (instruction is resumed). This is then detected in the main loop and the programs stops.
So what happens?
The interrupt handler is called between the two 32-bit writes and detects an intermediate state on the uint64_t. When this happens, the first uint32_t is updated while the second still has the old value, so they differ by 1. This means STRD is interruptible mid-instruction.
After returning from this intermediate state, the main thread re-writes both halves of the uint64_t. This means STRD is restarted rather than resumed when it gets interrupted.
Here's the interrupt handler:
void TIM1_UP_TIM10_IRQHandler(void) {
if (TIM1->SR & TIM_SR_UIF) {
TIM1->SR = ~TIM_SR_UIF;
// vary reload by up to 15 cycles
TIM1->ARR = (TIM1->ARR & 0xFFF0U) + (((TIM1->ARR & 0xFU) + 1) % 16);
uint64_t x = gData2;
if (x >> 32 != (uint32_t)x) {
gMismatch = x;
gData2 = 0;
}
}
}
Here's the relevant snippet in main.c:
const uint64_t gDelta = 0x0000000100000001U;
volatile uint64_t gData2;
volatile uint64_t gMismatch;
...
volatile uint64_t before = gData2;
UNUSED(before);
gData2 += gDelta;
if (gMismatch) {
volatile uint64_t after = gData2;
UNUSED(after);
while (1);
}
Here's the Expressions tab showing the result:
