- essential primitives : provides features that cannot be implemented in Smalltalk (SmallInteger>>#+ for instance, requires to use the cpu addition)
- optimisation primitives : optional, they just improve performance
Optimisation primitives are optional and hence the fall-back code should be the Smalltalk implementation of the primitive.
For essential primitives, the fall-back code is what is called when the primitive fails. If we look at SmallInteger>>#+, it can fail because one operand is not a SmallInteger, so the fall-back code use other code to perform the addition (LargeInteger, Integer code, etc.). In other cases, such as #at:put:, an error is raised when the primitive fails depending on why the primitive failed (bounds check, non integer index, etc.). The primitive is retried in case of number non integer (Try #(1) at: 1.0 put: $x).
Some essential primitives should not fail in any circumstances and they can't really be simulated. Good examples is #== primitive or #basicIdentityHash, they should not fail. If it fails it means likely something is wrong in the VM or in the kernel, in which case the primitiveFail error is sent. PrimitiveFail error announces that a primitive has failed and there is no appropriate Smalltalk code to run.
So no, most primitives should not call primitiveFail, you cannot assume that.