Concreteness Checks If we do not distinguish between symbolic and concrete values in the program under test, then we end up passing every computation to the solver, including concrete ones. Since all parameters are known in the purely concrete case, the solver will just repeat the computation (most likely in a less efficient way) and conclude that there is no way to change its outcome. We can avoid such wasted effort by only passing symbolic computations to the solver. There are two stages at which data can be identified as concrete: 1. At compile time, if a value is known to be a constant we can conclude that it will always be concrete at run time. 2. At run time, a value that is not a constant may still turn out to be concrete. For example, data read from memory can be symbolic or concrete. If we detect in the compiler pass that a value is a compile-time constant (case 1 above), we do not emit code for symbolic handling at all. However, for any other type of data, we need to generate code that handles the case of it being symbolic at run time. Concretely (no pun intended), we mark concrete values at run time by setting their corresponding symbolic expression in shadow memory to null. This makes it very cheap to check concreteness during execution: just run a null check on the symbolic expression. The code that we inject into the program under test performs concreteness checks on the arguments of each instruction. For example, when the program adds two values, the generated code performs the addition and additionally represents it symbolically according to the concreteness of the two addends. There are multiple cases to distinguish: 1. If all arguments of a computation are concrete, we can skip symbolic processing altogether and just set the result expression to null, indicating that the result is a concrete value. 2. If at least one argument is symbolic, we need to generate an expression representing the result. Therefore, we generate expressions for all arguments (since the concrete arguments will have null expressions) and call into the run-time support library to produce an expression according to the performed computation. There are several opportunities for optimization, e.g., when the computation only has a single argument that is not a compile-time constant we do not need to check it for concreteness again. It is important to note that these checks cannot be performed by the compiler because the concreteness of non-constant data is not known at compile time. Instead, the compiler emits code that performs the required checks at run time and acts accordingly.