They are implemented in the C library, not by the compiler. On a stand-alone (bare-metal) compiler such as arm-none-eabi-gcc, the C library is most normally Newlib (not glibc which is possibly the source you were looking at).
Newlib is retargetted to the platform by implementing a number of "syscalls". One of those syscalls is indeed sbrk/sbrk_r (most others are related to I/O - but anything target dependent or target defined).
In most cases sbrk is a simple implementation that simply increments a pointer within the statically allocated heap pool. Failing if the heap is exhausted.
The static heap block is usually allocated in the linker script (.ld file), and is typically automatically sized to use all available memory after all other allocations (static data objects and system stack).
If your implementation's malloc() already works, then your toolchain clearly already comes with a working sbrk implementation in syscalls.
The following would be a typical sbrk() implementation for a standalone system:
caddr_t sbrk(int incr)
{
extern char _end; // Defined by the linker
static char *heap_end;
char *prev_heap_end;
if (heap_end == 0)
{
heap_end = &_end;
}
prev_heap_end = heap_end;
if (heap_end + incr > stack_ptr)
{
write (1, "Heap and stack collision\n", 25);
abort ();
}
heap_end += incr;
return (caddr_t) prev_heap_end;
}
If sbrk were not defined, and your code used standard library dynamic memory allocation, your code would fail to link.
Often default syscall stubs are provided, most of which do nothing, and often that is fine - for example kill() and getpid() are often not relevant, and seldom necessary on a standalone system. But you can only getaway without implementing sbrk() if dynamic memory allocation is never used. Beating in mind that it is used internally by some standard library calls such as strdup() (explicitly) and in many printf implementations, of ncluding Newlib's.