Memory Layout Inference
Now that we are able to test if the code accessed a certain cache set, we can use it to infer the memory layout of JavaScript objects. If we access a JavaScript array at different offsets, we can infer which offset corresponds to which cache set. Since the L1 cache set is usually chosen based on bits 6 to 11 of the address, this directly translates into the page offset of the array element. The lowest 6 bits can be inferred by searching for the cache set boundary. For example, if array index 19 is in cache set 5 and index 20 is in cache set 6, we know that index 20 is the start of the cache set.
For this demo, you can control how many indices should be tested and how often every test should be repeated. The code will then try to find the pattern described to find the cache set boundary.
If the demo is successful, you should see a hit on a cache line that moves forward after exactly 16 index increments. The number 16 comes from the size of a cache line (64 bytes) divided by the size of the elements (4 bytes). You should also see a few cache sets that always show hits as they are probably used by the code, and a bunch of noise. But the incrementing pattern should still stand out and be easily recognizable.
Note that this code is just a demo to show the technique and can't be used for calibration of the Spectre demo since the memory layout will change on every run and needs to be recomputed.