In simple words, synthesis describes the step which turns a piece of HDL code into an RTL netlist, also called the synthesized netlist. If you know how compilers for ordinary programming languages work, the synthesis step for an HDL could be compared to the conversion of C code into assembly code.
For simple code like a single if-condition this may seem trivial. The challenge lies in the fact that the same RTL element can be described in many different ways when writing HDL.

Example 1: 2-to-1 multiplexer

entity mux2 is
    port ( x  : in  std_logic;
           y  : in  std_logic;
           s  : in  std_logic;
           oa : out std_logic;
           ob : out std_logic; 
           oc : out std_logic);
end entity mux2;

architecture rtl of mux2 is begin

-- Variant A, using a process;
process (x, y, s) is begin
    if s = '1' then oa <= x;
               else oa <= y;
    end if;
end process;

-- Variant B, using a concurrent when-else statement.
ob <= x when s = '1' else y;

-- Variant C, using a concurrent logic expression.
oc <= (x and s) or (y and not s);

-- Many more are possible.

end architecture rtl;

All of the above variations describe the same 2-to-1 multiplexer behavior.

Furthermore, things start to get much more complicated quickly when synthesis does additional optimizations. Code written in HDL is usually not optimized to result in the least amount of RTL resources, but instead to model the intended logic behavior in an easy to understand way. The way we humans describe the behavior of a logic function may be full of redundancies and obsolete information. The job of the synthesis tool is to remove the redundant and obsolete bits (no pun intended) and to come up with an equivalent logic function that realizes the same behavior, but uses only the minimum number of logic gates (different goals for synthesis can be defined, but minimizing the amount of logic gates is the most common one).

Example 2: complex logic

entity complex_logic is
    port ( x : in  std_logic;
           y : in  std_logic;
           z : in  std_logic;
           a : in  std_logic;
           b : in  std_logic; 
           c : in  std_logic;
           o : out std_logic);
end entity complex_logic;

architecture rtl of complex_logic is begin

o <= (x and y and not z) or (a and z) or ((c and y) or a) or not (z or b) or (c or b or not (a and not x)) or (a and z) or (b or c or y);

end architecture rtl;

After running the above VHDL code through synthesis (using Xilinx Vivado 2018.3, default settings, target part xa7a12tcpg238-2I) we get the following result:

Example 2 synthesis result (Vivado schematic)
Report Cell Usage: 
 |      |Cell |Count |
 |1     |OBUF |     1|

Now that’s what I call redundant and obsolete! The complex logic function is always true, who would have guessed. The synthesis tool had no problem to reduce the complex logic function it was given and replace it with a very efficient, yet equivalent, function.

As shown by some simple examples we saw how the synthesis process turns HDL code into an RTL netlist. Along the way a number of optimizations can be applied to create a “better” RTL netlist, which however must be 100% equivalent to the behavior described in the underlying HDL code.
Since no software written by humans is free of errors, synthesis tools too suffer from bugs and unexpected behavior. Therefore it can happen that the behavior of a synthesized netlist can deviate from the HDL description. This is called a functional mismatch and is very much undesired, since it can hide a bug which was introduced during synthesis.
There is also a phenomenon called simulation mismatch, which results in different simulation outcomes, when comparing the simulation results of the HDL description to the simulation results of the synthesized netlist. This can imply a functional mismatch, but does not mandate it. To be absolutely safe a common requirement is to have zero simulation mismatch between the HDL code and the synthesized netlist. This is ensured by a process of thorough verification.