So conceptually, the only thing that "use asm" does (in Firefox) is to tell the optimizer/JIT/whatever to assume certain types in advance rather than having it infer them at runtime?
Pretty much, yeah. The normal JIT is used, but first Firefox type checks the asm.js code, during which it detects all the types. Then it just tells the JIT what those types are. This reuses existing functionality in the JIT, as all modern JITs have mechanisms to receive type information - it's typically collected by monitoring types at runtime, the only difference is that here the types are collected through the asm.js type checker.
There are also a few runtime changes as well with asm.js code. For example, the JS engine knows that asm.js code cannot throw exceptions (asm.js code is very low-level, so JS exceptions are not possible), so it can avoid emitting some things it would normally emit in order to handle deopts due to exceptions and so forth. (Of course this does not require an AOT, a JIT could also detect the lack of need for that overhead, perhaps this is already done in some cases, but the AOT guarantees it is done for all asm.js code.)