Q:

How does CPython's interpreter know to print the result of the last expression?

I've been digging around the source code to figure-out at which point the result is printed. For example:
>>> x = 1
>>> x + 2
3

The above two statements are compiled to:
  1           0 LOAD_CONST               0 (1)
              3 STORE_NAME               0 (x)
              6 LOAD_CONST               1 (None)
              9 RETURN_VALUE

and 
  1           0 LOAD_NAME                0 (x)
              3 LOAD_CONST               0 (2)
              6 BINARY_ADD
              7 RETURN_VALUE

The first statement doesn't print anything because None is the returned value. The second returns the result of the addition.
CPython's interactive loop calls PyRun_InteractiveOneObjectEx() for each input. This gets the AST and invokes run_mod() to compile that AST to byte code and then evaluate the result in the virtual machine. The returned Python object that PyRun_InteractiveOneObjectEx() gets is simply the top of the VM's stack.
So far all of this is what I would expect. But then the returned value seems to be thrown away! When is this printed by the REPL?
As an aside, I can see that interactive mode does change the tokenizer; it invokes PyOS_Readline with the sys.ps1 prompt (">>> " by default). I checked for a similar change in pythonrun.c, but no luck.

A:

You are showing disassemblies of the bytecode as generated by having the code in a function.  That isn't how interactive code is compiled: it uses a special 'single' mode (3rd parameter to compile(), if you were doing the equivalent in Python code).  In this mode, the POP_TOP opcode that discards the value of each expression gets turned into a PRINT_EXPR instead.  The reason why x = 1 prints nothing is that statements leave nothing on the stack that needs to be popped, so this transformation doesn't apply.

