A "fault" is usually a term for a trap/kernel-mode interrupt that results from a check at the hardware level, built into an instruction. For example, a protection fault occurs when the processor attempts to access a memory address that isn't currently mapped.
There might exist ISAs without any equivalent to faults—but I would guess that the semantics of any "faultable"-instruction-equivalent on such an ISA, would 1. actually take the slow path and use a flag register to return the status of the execution; and 2. would thus require C and C-like languages to generate shim code that e.g. checks for that flag result on every use of the dereference operator, in order to generate a userland-simulated "fault"-equivalent (i.e. an abort(3)). This is because these languages don't have any way to capture or represent the potential flag-result return value of these operations. Low-level languages expect dereferencing to raise exceptions, not return errors. There's no place for the errors to flow into.
The reason I’m asking is because we seem to be a in a conversation where we are redefining words and tying them to abstract concepts, so I wasn’t sure if by “fault” you meant “this means the processor literally faults” or “I’m going to call a fault the termination of a process when it does something illegal, named because that’s how processors usually work today”. From your response, it seems like you’ve taken the latter interpretation.
> Low-level languages expect dereferencing to raise exceptions, not return errors.
Are you talking about C? Because it doesn’t actually define anything in this case. Sure, on most OS/architecture combinations, it will trigger a memory protection violation, but this is not guaranteed and I’m sure that in these cases the compiler doesn’t literally insert checks before dereferencing anything to guarantee failure.
Yeah, the "C abstract machine" that most C-like languages rely on by the fact of relying on libraries like libc.
> Because it doesn’t actually define anything in this case.
To be clear, when I said expect, I meant expect. These languages expect invalid dereferences to fault—i.e. to cause control flow to be taken out of the program's hands. Which is to say, they expect a generated dereferencing instruction that returns to have put the system into a valid state.
But in practice, what that means is that compilers just expect dereferences to always be valid (because any dereference that returns is valid.) So they don't have to generate any check for null before doing a dereference; and they don't have to worry about having any way to represent the result of a null dereference.
Another way to say that is that dereferencing null pointers is implicit undefined behavior. There's no part of the spec that covers what would happen if you dereferenced a null pointer, because in the model as the spec lays it out, dereferencing is a happy, clean little operation that never goes wrong. (i.e. "The MMU might cause a CPU fault? Who cares! My program didn't cause that fault; it was the MMU's choice to decline to read from $0. Other MMUs could totally allow that, and then LEA would be defined for the entire domain.") Same goes for reading from unmapped regions—"it's the MMU's fault; it could have just as well returned 0, after all."
> I’m sure that in these cases the compiler doesn’t literally insert checks
Yep, you're right. Again, it's because this is what these languages expect. It's not quite the same as undefined behavior; it's that the "C abstract-machine evaluation model" was defined to assume that architectures will always have their IDIV-equivalent instruction fault if invalid. If it returns a flagged value on some arch instead, that's something the C evaluation model is not prepared to deal with.
(Technically, I believe that you'd say that a C compiler "cannot be written" for such an arch while retaining conformance to any existing C standard, and for such an arch to gain a conformant C compiler, a new C standard draft would have to be written that specifies an evaluation model for such architectures—as then the compiler could at least be conformant to that.)
Which is helpful to know: just compiling C for such an arch at all, is undefined behavior, no matter what you write ;)
I’m not sure I follow your description of undefined behavior, which seems to me to deviate from the standard’s? Correct me if I’m wrong, but to me it seems like you’re saying something along the lines of “the C standard ‘expects’ that null dereferences fault, and ‘assumes’ division by zero to trap; it is impossible to write a standards compliant compiler for architectures where this is not true”. If so, let me know an I’ll explain why I disagree; otherwise, it would be nice if you could clarify what you actually meant.
There might exist ISAs without any equivalent to faults—but I would guess that the semantics of any "faultable"-instruction-equivalent on such an ISA, would 1. actually take the slow path and use a flag register to return the status of the execution; and 2. would thus require C and C-like languages to generate shim code that e.g. checks for that flag result on every use of the dereference operator, in order to generate a userland-simulated "fault"-equivalent (i.e. an abort(3)). This is because these languages don't have any way to capture or represent the potential flag-result return value of these operations. Low-level languages expect dereferencing to raise exceptions, not return errors. There's no place for the errors to flow into.