How does NVIC know what function to call on an interrupt (libopencm3)
usually the library provider, defines the ISR table in the startup file. they use a predefined scheme for better readability (e.g. [peripheral_name]_isr) and make some empty "weak" functions to fill it up. when you want to use an ISR, all you have to do, is to redefine the origianl "weak" ones and the linker prefers your "strong" functions to the "weak" attributed ones and ignores them. read about the weak symbol in wiki :

https://en.wikipedia.org/wiki/Weak_symbol

and here are the predefined prototypes as documented on the official website:

http://libopencm3.org/docs/latest/stm32f0/html/group__CM3__nvic__isrprototypes__STM32F0.html