via a pipe - preprocessing is completely separate from other parts, there are no common data - it could be run standalone, output into a temporary file and feed that into the lexer part of compiler)

parser calls the subsequent steps - function by function

once the parser recognizes that a function is complete, createFunction() (in src/SDCCast.c) is called, which has the AST annotated (probably via decorateType()), has it turned it into iCode (iCodeFromAst() calling ast2iCode()), and lets the backend emit appropriate code (via eBBlockFromiCode() in port->assignRegisters, which maps to, e.g.src/mcs51/ralloc.c, mcs51_assignRegisters())