Dynamic languages are pretty hard to compile for since at any given time, you don't know the type for all the variables until runtime. Not only that, the type of a variable can change during execution. For example:
for (i = 0; i < 100; i++)
something += i;
if (something == 50)
something = "hello world";
Once i becomes greater than 50, the line "something += i" should be a string concatenation operation, not an integer.
During tracing, we would have to have two guard instructions: One to insure that i is still an integer, and two: to insure something is still an integer. The number of guard instructions can become exponentially big as the number of variables increase as you would need one guard instruction PER variable to ensure the type is the same.
To encapsulate the types of the variables at the current time into one check, we created the idea of a "shape". This shape is a unique id which represents the current type of all the variables in the system. For example, shape id 1 implies that both variable "i" and "something" are integers. Once "something" changes into a String, we have a new shape id 2: which represents variable "i" is an integer and "something" is a String.
Hence, once tracing begins, we only need one check: to ensure that the shape ID is the same.