This is where the complexity is, managing when to JIT compile a function and how to pass control back to the interpreted code. There are so many ways to do this, from compiling individual functions to doing a tracing JIT like luajit is.
So, yes, the compiler part of a JIT is "just" a compiler that outputs code to memory but the complexity is doing it just-in-time. Start executing code in an interpreter, find a hot path, then compile that and make the compiler emit instructions that return the control back to the interpreter when executing the hot path is done.
So you wouldn't consider the .NET framework a JIT? It never interprets any code, just compiles things as they are used.
Saying you need an interpreter is clearly a silly definition of a JIT.
It compiles some code, executes it and control returns back to either interpreted code or the framework that decides what to compile next. It's a JIT, because it can't be done ahead of time.
Just doing the final stage of code generation at runtime/startup (like, say, PNaCL does) would not qualify. You could as well generate code for all target architectures ahead of time, but it might be more practical to leave that to the client.
Feel free to disagree with my definition, but I like to draw a distinction between just compiling code at runtime and a JIT, that compiles/optimizes code as it becomes possible.
I agree with that, but I call the first thing a JIT, and the second thing a dynamic compiler. There's a PhD someone wrote about the difference but I can't find it now.
I'm not too familiar with .NET but based on your description, no I would not qualify that as a JIT according to the definition I gave above.
If you could as well do it ahead of time, I would not call it a JIT.
Is the reason for .NET's late compilation to generate code for the target CPU architecture? Or does it do some optimization based on runtime information (this would qualify as JIT)?
It doesn't necessarily have to be a (bytecode) interpreter to call it a JIT, but it does have to compile code at runtime and have the control return back to the framework for compiling more code as needed.
Do you know why it's doing incremental compilation? Is there something that can't be done ahead of time? E.g. inlining dynamic ("virtual function") calls and then applying optimization?
EDIT: spelling
This has some disadvantages - you have to wait for the JIT to run and can't keep running in the interpreter while it compiles, and some advantages - it's simpler.