  Surrogates, models that mimic the behavior of programs, form the basis of a
variety of development workflows. We study three surrogate-based design
patterns, evaluating each in case studies on a large-scale CPU simulator.
  With surrogate compilation, programmers develop a surrogate that mimics the
behavior of a program to deploy to end-users in place of the original program.
Surrogate compilation accelerates the CPU simulator under study by $1.6\times$.
With surrogate adaptation, programmers develop a surrogate of a program then
retrain that surrogate on a different task. Surrogate adaptation decreases the
simulator's error by up to $50\%$. With surrogate optimization, programmers
develop a surrogate of a program, optimize input parameters of the surrogate,
then plug the optimized input parameters back into the original program.
Surrogate optimization finds simulation parameters that decrease the
simulator's error by $5\%$ compared to the error induced by expert-set
parameters.
  In this paper we formalize this taxonomy of surrogate-based design patterns.
We further describe the programming methodology common to all three design
patterns. Our work builds a foundation for the emerging class of workflows
based on programming with surrogates of programs.
