Compilers and virtual machines effectively "implement" programming languages. They only have to do what is specified by the language semantics and observable for a given program in order to be correct.
When you write the definition statement int a = 12;
in a C program, you are informing the compiler that there is a variable named a
whose initial value is set to the constant literal twelve.
If you never observe a
, the compiler can completely get rid of it, because you can't tell the difference during program execution per the language specification. (For example, if you were to pause the program and scan the stack at that point, the value 12 need not be there at all. Its necessity for being on the machine stack is not part of the language specification.)
If you observe a
but find that it's impossible for your program to mutate its value, the compiler has inferred that it is actually static const
and can perform an optimization called "constant propagation" to push a hard-coded twelve into the dependent instructions.
If you take a reference to a
, a la int *aptr = &a;
, then the language specification says that a
must have a location in memory (IIRC). This means that, per the specification, there will be a valid, int-sized slot in memory (on the stack, in any reasonable implementation) that will contain 12.
In other languages there are obviously other specifications. Technically, in dynamic languages the compiler can perform similar optimizations. If you have a Python function:
def do_nothing():
a = 12
You could imagine that the Python compiler, which turns the language text into bytecodes, might choose to omit the body of that function entirely (for pedants: except the implicit return None
). Traditionally, Python implementations have no such optimizations because its language philosophy mandates debuggability, VM implementation simplicity, and programmer comprehension above optimizations.
There are also some tricky constructs in dynamic languages that make inference more difficult. For example, Python allows a great deal of code object inspection that is expected to work across implementations. Acquiring/inspecting stack traces and the local arguments of activated frames requires at least slow-path fallback mechanisms, which increase optimization complexity significantly. We're ditching support for some of these kinds of language-level-runtime-stack-inspection things in JavaScript with ECMAScript revision 5.
Supporting debuggers is hard, let's go optimizing!