In Depth - OCaml Compiler¶
We will begin with an overview of how OCaml is compiled. We will see that a boot OCaml system is used to compile the OCaml system you have on your machine.
The second part is how C and assembly language are compiled, and where the C and assembly language compilers get invoked.
Then we will discuss how cross-compilation typically works in the C language.
Finally we'll use the understanding we've gained for how one OCaml system compiles another system to understand how cross-compilation works in OCaml.
How OCaml is compiled¶
In this section we will walk through the source code for OCaml at https://github.com/ocaml/ocaml/tree/4.13.
Note
Parts of the explanation below comes from https://github.com/ocaml/ocaml/blob/4.13/BOOTSTRAP.adoc
Let's start with the boot/ directory:
boot
├── menhir
│ ├── menhirLib.ml
│ ├── menhirLib.mli
│ ├── parser.ml
│ └── parser.mli
├── ocamlc
└── ocamllex
Both boot/ocamlc and boot/ocamllex are OCaml bytecode files. OCaml bytecode is a machine language code
for a very simple virtual machine called the
Caml Virtual Machine (or Zinc Machine).
ocamllex is the token lexer. If we could run ocamllex then we could take a .ml OCaml source file and emit OCaml tokens
(ex. =) and keywords (ex. module) that could be consumed by the OCaml parser parser.ml and its runtime library
menhirLib.ml. The end result would be an abstract syntax tree called a Parsetree
from a single .ml source file.
ocamlc is the bytecode compiler. If we could run ocamlc then we could take any .ml source code
and generate more bytecode.
All we are missing so far is a way to run arbitrary bytecode files like ocamlc and ocamllex. That missing
program is ocamlrun: ocamlrun is the bytecode interpreter for the Caml Virtual Machine.
To build executables like the ocamlrun bytecode interpreter, OCaml uses an "autoconf" build system where:
./configureinspects your system to find C compilers (that is how we know which C compiler to use to generate theocamlrunprogram) and assembly language compilers. It captures the C and assembly compilers and their flags into./Makefile.configusing a template fileMakefile.config.in. There are also other files created from*.intemplatesmakeuses the configuration captured in./Makefile.configto generate theutils/config.mlmodule from theutils/config.mlptemplate. There are also other modules created from*.mlptemplates. Aftermakehas generated all the source files, it can build C programs likeocamlrunusing the C compiler and OCaml programs likeocamlandocamlcusingocamlrun boot/ocamlc.
ocamlrun is compiled using C code (.c); here is an abbreviated listing of ocamlrun source code:
runtime
├── alloc.c
├── array.c
├── backtrace.c
├── callback.c
├── caml
│ ├── alloc.h
│ ├── backtrace.h
│ ├── callback.h
│ ├── config.h
│ ├── domain.h
│ ├── domain_state.h
│ ├── domain_state.tbl
│ ├── gc.h
│ ├── gc_ctrl.h
│ ├── io.h
│ ├── m.h
│ ├── m.h.in
│ ├── major_gc.h
│ ├── minor_gc.h
│ ├── prims.h
│ ├── s.h
│ ├── s.h.in
│ ├── signals.h
│ ├── startup.h
│ ├── startup_aux.h
│ └── sys.h
├── domain.c
├── hash.c
├── io.c
├── lexing.c
├── main.c
├── major_gc.c
├── minor_gc.c
├── parsing.c
├── prims.c
├── riscv.S
├── signals.c
├── startup_aux.c
├── startup_nat.c
├── str.c
├── sys.c
├── unix.c
└── win32.c
After make compiles the runtime/ directory with the C compiler we will have the
runtime/ocamlrun executable that can run any bytecode file and a runtime library
for bytecode called libcamlrun. We can now:
compile OCaml files with
runtime/ocamlrun boot/ocamlcand run the generated bytecode withruntime/ocamlruninteract with Unix/Windows system library C functions from within bytecode since the compiled assembly language (ex.
amd64.S) contains low-level logic for OCaml to call C functions and C functions to callback into OCaml
That sounds like we are finished, but we now have three problems.
Problem 1: Creating a modern OCaml compiler¶
The first problem is that we have been using the boot/ocamlc OCaml compiler. That boot OCaml compiler may be an old OCaml compiler
that can't compile the latest OCaml source code. So we compile a new OCaml compiler ./ocamlc bytecode file from the following
abbreviated OCaml compiler source code:
.
├── bytecomp
│ ├── bytegen.ml
│ ├── bytelibrarian.ml
│ ├── bytelink.ml
│ ├── bytepackager.ml
│ ├── bytesections.ml
│ ├── dll.ml
│ ├── emitcode.ml
│ ├── instruct.ml
│ ├── meta.ml
│ ├── opcodes.ml
│ ├── printinstr.ml
│ └── symtable.ml
├── driver
│ ├── compenv.ml
│ ├── compile.ml
│ ├── compile_common.ml
│ ├── compmisc.ml
│ ├── errors.ml
│ ├── main.ml
│ ├── main_args.ml
│ ├── maindriver.ml
│ ├── makedepend.ml
│ ├── optcompile.ml
│ ├── opterrors.ml
│ ├── optmain.ml
│ ├── optmaindriver.ml
│ └── pparse.ml
├── lambda
│ ├── debuginfo.ml
│ ├── lambda.ml
│ ├── matching.ml
│ ├── printlambda.ml
│ ├── runtimedef.ml
│ ├── simplif.ml
│ ├── switch.ml
│ ├── translattribute.ml
│ ├── translclass.ml
│ ├── translcore.ml
│ ├── translmod.ml
│ ├── translobj.ml
| └── translprim.ml
└── typing
| ├── btype.ml
| ├── ...
| ├── ctype.ml
| ├── ...
| ├── primitive.ml
| ├── ...
| ├── type_immediacy.ml
| ├── typeclass.ml
| ├── typecore.ml
| ├── typedecl.ml
| ├── typedecl_immediacy.ml
| ├── typedecl_properties.ml
| ├── typedecl_separability.ml
| ├── typedecl_unboxed.ml
| ├── typedecl_variance.ml
| ├── typedtree.ml
| ├── typemod.ml
| ├── typeopt.ml
| ├── types.ml
| ├── typetexp.ml
| └── untypeast.ml
└── utils/
├── ...
├── clflags.ml
├── config.ml
├── config.mlp
├── ...
Once we have a modern ./ocamlc we can see the configuration constants embedded in utils/config.ml if you run runtime/ocamlrun ./ocamlc -config:
version: 4.12.1
...
ccomp_type: cc
c_compiler: gcc
ocamlc_cflags: -O2 -fno-strict-aliasing -fwrapv -fPIC
ocamlc_cppflags: -D_FILE_OFFSET_BITS=64 -D_REENTRANT
ocamlopt_cflags: -O2 -fno-strict-aliasing -fwrapv -fPIC
ocamlopt_cppflags: -D_FILE_OFFSET_BITS=64 -D_REENTRANT
bytecomp_c_compiler: gcc -O2 -fno-strict-aliasing -fwrapv -fPIC -D_FILE_OFFSET_BITS=64 -D_REENTRANT
native_c_compiler: gcc -O2 -fno-strict-aliasing -fwrapv -fPIC -D_FILE_OFFSET_BITS=64 -D_REENTRANT
bytecomp_c_libraries: -lm -ldl -lpthread
native_c_libraries: -lm -ldl
native_pack_linker: ld -r -o
ranlib: ranlib
...
asm: gcc -c
...
The net effect is that the C and assembly compilers are hardcoded _inside_ the ocamlc executable.
Problem 2: Creating the standard library¶
The second problem is that we don't have the OCaml standard library. We can compile the standard library
bytecode (.cmo object files and .cma object libraries) from:
stdlib
├── arg.ml
├── array.ml
├── arrayLabels.ml
├── atomic.ml
├── bigarray.ml
├── bool.ml
├── buffer.ml
├── bytes.ml
├── bytesLabels.ml
├── callback.ml
├── char.ml
├── complex.ml
├── digest.ml
├── either.ml
├── ephemeron.ml
├── filename.ml
├── float.ml
├── format.ml
├── fun.ml
├── gc.ml
├── genlex.ml
├── hashbang
├── hashtbl.ml
├── header.c
├── headernt.c
├── int.ml
├── int32.ml
├── int64.ml
├── lazy.ml
├── lexing.ml
├── list.ml
├── listLabels.ml
├── map.ml
├── marshal.ml
├── moreLabels.ml
├── nativeint.ml
├── obj.ml
├── oo.ml
├── option.ml
├── parsing.ml
├── pervasives.ml
├── printexc.ml
├── printf.ml
├── queue.ml
├── random.ml
├── result.ml
├── scanf.ml
├── seq.ml
├── set.ml
├── stack.ml
├── stdLabels.ml
├── std_exit.ml
├── stdlib.a
├── stdlib.ml
├── stream.ml
├── string.ml
├── stringLabels.ml
├── sys.ml
├── sys.mlp
├── uchar.ml
├── unit.ml
└── weak.ml
Problem 3: Generating native code¶
The third problem is that we completely ignored how we will generate native code. That is the subject of discussion for the next section.
Where C and assembly compilers are used¶
We've already discussed how the OCaml compiler itself uses a C compiler to compile the runtime/ directory into the ocamlrun program. And
how ocamlrun can run other bytecode files and generate (with ocamlc) more bytecode files.
OCaml's native code compiler program ./ocamlopt is build the same way we built ./ocamlc, except bytecomp/ has been replaced by asmcomp/
and middle_end/:
.
├── asmcomp
│ ├── CSE.ml -> arm/CSE.ml
│ ├── CSEgen.ml
│ ├── amd64
│ │ ├── CSE.ml
│ │ ├── arch.ml
│ │ ├── emit.mlp
│ │ ├── proc.ml
│ │ ├── reload.ml
│ │ ├── scheduling.ml
│ │ └── selection.ml
│ ├── arch.ml -> arm/arch.ml
│ ├── arm
│ │ ├── CSE.ml
│ │ ├── arch.ml
│ │ ├── emit.mlp
│ │ ├── proc.ml
│ │ ├── reload.ml
│ │ ├── scheduling.ml
│ │ └── selection.ml
│ ├── arm64
│ │ └── \*.ml
│ ├── asmgen.ml
│ ├── asmlibrarian.ml
│ ├── asmlink.ml
│ ├── asmpackager.ml
│ ├── branch_relaxation.ml
│ ├── branch_relaxation_intf.ml
│ ├── cmm.ml
│ ├── cmm_helpers.ml
│ ├── cmmgen.ml
│ ├── cmmgen_state.ml
│ ├── coloring.ml
│ ├── comballoc.ml
│ ├── deadcode.ml
│ ├── emit.ml
│ ├── emitaux.ml
│ ├── i386
│ │ └── \*.ml
│ ├── interf.ml
│ ├── interval.ml
│ ├── linear.ml
│ ├── linearize.ml
│ ├── linscan.ml
│ ├── liveness.ml
│ ├── mach.ml
│ ├── power
│ │ └── \*.ml
│ ├── printcmm.ml
│ ├── printlinear.ml
│ ├── printmach.ml
│ ├── proc.ml -> arm/proc.ml
│ ├── reg.ml
│ ├── reload.ml -> arm/reload.ml
│ ├── reloadgen.ml
│ ├── riscv
│ │ └── \*.ml
│ ├── s390x
│ │ └── \*.ml
│ ├── schedgen.ml
│ ├── scheduling.ml -> arm/scheduling.ml
│ ├── selectgen.ml
│ ├── selection.ml -> arm/selection.ml
│ ├── spill.ml
│ ├── split.ml
│ ├── strmatch.ml
│ ├── x86_dsl.ml
│ ├── x86_gas.ml
│ ├── x86_masm.ml
│ └── x86_proc.ml
├── driver
│ └── \*.ml
├── lambda
│ └── \*.ml
├── middle_end
│ ├── backend_var.ml
│ ├── clambda.ml
│ ├── clambda_primitives.ml
│ ├── closure
│ │ ├── closure.ml
│ │ └── closure_middle_end.ml
│ ├── compilation_unit.ml
│ ├── compilenv.ml
│ ├── convert_primitives.ml
│ ├── flambda
│ │ ├── alias_analysis.ml
│ │ ├── allocated_const.ml
│ │ ├── augment_specialised_args.ml
│ │ ├── base_types
│ │ ├── build_export_info.ml
│ │ ├── closure_conversion.ml
│ │ ├── closure_conversion_aux.ml
│ │ ├── closure_offsets.ml
│ │ ├── effect_analysis.ml
│ │ ├── export_info.ml
│ │ ├── export_info_for_pack.ml
│ │ ├── extract_projections.ml
│ │ ├── find_recursive_functions.ml
│ │ ├── flambda.ml
│ │ ├── flambda_invariants.ml
│ │ ├── flambda_iterators.ml
│ │ ├── flambda_middle_end.ml
│ │ ├── flambda_to_clambda.ml
│ │ ├── flambda_utils.ml
│ │ ├── freshening.ml
│ │ ├── import_approx.ml
│ │ ├── inconstant_idents.ml
│ │ ├── initialize_symbol_to_let_symbol.ml
│ │ ├── inline_and_simplify.ml
│ │ ├── inline_and_simplify_aux.ml
│ │ ├── inlining_cost.ml
│ │ ├── inlining_decision.ml
│ │ ├── inlining_stats.ml
│ │ ├── inlining_stats_types.ml
│ │ ├── inlining_transforms.ml
│ │ ├── invariant_params.ml
│ │ ├── lift_code.ml
│ │ ├── lift_constants.ml
│ │ ├── lift_let_to_initialize_symbol.ml
│ │ ├── parameter.ml
│ │ ├── pass_wrapper.ml
│ │ ├── projection.ml
│ │ ├── ref_to_variables.ml
│ │ ├── remove_free_vars_equal_to_args.ml
│ │ ├── remove_unused_arguments.ml
│ │ ├── remove_unused_closure_vars.ml
│ │ ├── remove_unused_program_constructs.ml
│ │ ├── share_constants.ml
│ │ ├── simple_value_approx.ml
│ │ ├── simplify_boxed_integer_ops.ml
│ │ ├── simplify_common.ml
│ │ ├── simplify_primitives.ml
│ │ ├── traverse_for_exported_symbols.ml
│ │ ├── un_anf.ml
│ │ ├── unbox_closures.ml
│ │ ├── unbox_free_vars_of_closures.ml
│ │ └── unbox_specialised_args.ml
│ ├── internal_variable_names.ml
│ ├── linkage_name.ml
│ ├── printclambda.ml
│ ├── printclambda_primitives.ml
│ ├── semantics_of_primitives.ml
│ ├── symbol.ml
│ └── variable.ml
└── typing
└── \*.ml
Where bytecode uses the bytecode runtime library libcamlrun, native code uses a different
runtime library libasmrun that is built in the same runtime/ directory but
that includes the assembly code (.asm and .S) in that directory:
runtime
├── amd64.S
├── amd64nt.asm
├── arm.S
├── arm64.S
├── i386.S
├── i386nt.asm
├── power.S
├── riscv.S
└── s390x.S
ocamlopt performs a variety of activities including:
it translates
.mlfiles into architecture-specific assembly language source code (.sfiles) using the code inasmcomp/.ocamloptthen compiles the assembly language into native object files (Unix.oor Windows.objfiles) using the assembly compiler named in theutils/config.mlmodule (the same module you saw withocamlc -config)it compiles
.Cfiles into native object files using the C compiler named inutils/config.mlit links the native object files into a native executable using the native linker named in
utils/config.ml
Now we are basically done. With ocamlopt we can recompile everything into native code, including the compilers.
For example ./ocamlc.opt and ./ocamlopt.opt are created using the same procedure as ./ocamlc and ./ocamlopt, except instead
of using ./ocamlc to compile them into bytecode executable, ./ocamlopt is used to compile them into native code executables.
How C code is cross-compiled¶
For this section we'll use an example where we cross-compile the Java compiler
javac. Just like OCaml much of the low-level Java source code is C code. The
concepts introduced in this section will carry forward to the
next section where we describe the cross-compiling of OCaml compilers ocamlc
and ocamlopt.
We want to use an Ubuntu 64-bit machine to create a javac.exe that can run
on Windows. To compile javac.exe there are three different machines we need to
consider:
- Build Machine
You would use the following "autoconf" pattern to build yourself a new
javacbinary:git clone https://git.openjdk.java.net/jdk/ cd jdk ./configure ... make ... make install
The machine where you type "git clone" and "./configure" is the Build Machine.
The Build Machine is a Ubuntu (Linux x86_64) 64-bit machine.
- Host Machine
Since we want to run
javac.exeon Windows machines, the Host Machine is a Windows Intel/AMD 64-bit machine.- Target Machine
javac.exewill compile.javasource files into.classbytecode files. These.classbytecode files can be run on any machine.The Target Machine is any machine; we can take the Java compiled output
Main.classthat was produced on a Windows machine and then run theMain.classon a macOS machine.
To produce a Windows javac.exe from a Linux machine we need to supply
a C compiler that is a Linux executable that runs on Linux but generate
Windows executables (.exe) and Windows shared libraries (.dll).
If you want a GCC compiler you would install and use the compiler
/usr/bin/x86_64-w64-mingw32-gcc-win32using your Linux package manager (ex.apt install gcc-mingw-w64-x86-64on Ubuntu). If you instead wanted to generate 32-bit Windows executables you would install and use/usr/bin/i686-w64-mingw32-gcc-win32.In general, GCC prefixes its compilers and tools with the Host Machine "triple" (ex.
i686-w64-mingw32describes 32-bit Intel/AMD Windows targets).If you want a Clang compiler you would install and the compiler
/usr/bin/clang(ex.apt install clangon Ubuntu). You would use "target" compiler flags (typically theCFLAGSenvironment variable) to set the Host Machine./usr/bin/clang --target=i686-pc-win32would create executables for 32-bit Intel/AMD Windows while/usr/bin/clang --target=x86_64-pc-win32would create executables for 64-bit Intel/AMD Windows.
In addition to the Build-Machine-running, Host-Machine-producing compiler we must also have C libraries to link against and C headers to compile against. Since the C compiler will produce code that runs on the Host Machine, the C libraries and C headers must be for the Host Machine. Those C libraries and headers are collected in a directory structure called a sysroot:
<sysroot>
└── usr
├── include
│ └── *.h
└── lib
├── *.a or *.lib
└── *.so or *.dll
Changing OCaml to do cross-compilation¶
Note
The technique presented here was first described by EduardoRFS, Antonio Nuno Monteiro and Romain Beauxis in discuss.ocaml.org: Cross-compiling implementations / how they work
The build procedure for compiling the OCaml system starts with the Build Machine
generating the ocamlc compiler and the ocamlrun bytecode interpreter; in
other words both ocamlc and ocamlrun are Host Machine executables. To
continue building the rest of the OCaml system the ocamlc and ocamlrun
executables must be run. So building an OCaml system requires that
the Build Machine must be capable of running Host Machine executables.
Without loss of generality we will only describe the Host Machine, and expect you to use a compatible Build Machine. So a Windows 32-bit Host Machine executable can be run on a Windows 32-bit or 64-bit Build Machine, but a Windows 64-bit Host Machine executable cannot be run on a 32-bit Build Machine.
Warning
When can't a 64-bit Build Machine run 32-bit executables? Apple Silicon (M1, etc.) hardware has 64-bit ARM processors, but for cost and energy savings the 32-bit ARM circuitry has been removed. You cannot run 32-bit ARM executables on Apple Silicon.
Most 64-bit Linux distributions do not come natively with 32-bit system libraries; you have to install something called the "i386" architecture to run most 32-bit binaries. There are tricks to avoid needing these 32-bit system libraries, but usually the i386 architecture is easy to install in popular Linux distributions.
Here are the modified steps to get a cross-compiling OCaml system:
Use
./configureto establish constants for the Target Machine.utils/config.mlandruntime/sys.cwill contain settings for the Target Machine.Compile
ocamlcandocamloptthat runs on both the Build Machine and Host Machine.Note
ocamlc/ocamloptnow will have configuration (ex.ocamlc -config) for the Target Machine, which is precisely what we wantCompile all executable tools like
ocamlyacc,ocamldebugger, etc. using the Host Machineocamlc/ocamloptAt this point all of the executables run on the Host Machine
Note
ocamlc/ocamlopt now will produce bytecode + native code that runs on the Host Machine. We don't that. We want to fix ocamlc/ocamlopt to produce bytecode + native code that runs on the Target Machine.
Remove all OCaml compiled intermediate code. We could be selective and just remove the portions that contain instructions for producing Host Machine bytecode + native code, but it is easier and safer to remove everything intermediate. The executable tools are not deleted because they are not intermediate.
Recompile
stdliband its runtime dependencies (libcamlrunbytecode andlibasmrunnative code runtime libraries) for the Target MachineRegenerate
ocamlc/ocamloptusing Host Machineocamlc(which will produce Host Machine executableocamlcand Host Machine executableocamloptbut that contain Target Machine standard + runtime libraries)Recompile
stdlibagain but include other libraries (unix,str,bigarray) for the Target MachineRecompile the bytecode and native code compiler libraries for the Target Machine, in case an Opam package or other OCaml package wants to bypass the compiler executables
ocamlc/ocamlopt.
The end result will be:
- Host Machine
The
ocamlcandocamloptwill be able to run on the Host machine.- Target Machine
ocamlcwill compile.mlsource files into bytecode executables and.cmo/.cmabytecode object files. These bytecode files in theory can be run on any machine that has a bytecode interpreterocamlrun. However because bytecode can make calls to external C libraries, the C libraries need to have the same APIs and the C libraries have to have the same C calling convention (also known as "ABI" or application binary interface). Practically speaking that means bytecode created on a 32-bit system may not work on a 64-bit system, and bytecode created on Windows may not work on Unix. Unlike Java, care has to be taken so that bytecode is portable to all Target Machines.ocamloptwill compile.mlsource files into native executables and.cmx/.cmxanative object files. The native files will run only on the Target Machine configured at./configuretime.
Limitations¶
Not all Host Machine / Target Machine combinations are possible.
The host ocamlrun is linked against the host's runtime/ library
which defines the following constants in runtime/sys.c:
Constant |
Sample |
Description |
|---|---|---|
word_size |
32 or 64 |
Number of bits in a word as detected
by a C compiler |
int_size |
31 or 63 32 or 64 |
Number of bits in an OCaml
|
max_wosize |
2^22 - 1 or 2^(54-P) -1 |
Max size in bytes of a block of memory on the heap
|
ostype_unix |
True or False |
Whether system is Unix |
ostype_win32 |
True or False |
Whether system is Windows |
ostype_cygwin |
True or False |
Whether system is Cygwin |
backend_type |
0 or 1 |
Native (0) or Bytecode (1) backend
|
naked_pointers_checked |
True or False |
Whether the naked pointers checker is enabled |
Any commands like <host>/ocamlrun <target>/ocamlc -config will inherit
the host runtime constants (<host>/runtime/sys.c) even if all of the target
configuration (<target>/utils/config.ml) is correct. For example
you can easily get conflicting configurations from ocamlc -config:
# ...
architecture: i386 # From <target>/utils/config.ml
model: default # From <target>/utils/config.ml
int_size: 63 # From <host>/runtime/sys.c . No, i386 does not support 63-bit integers!
word_size: 64 # From <host>/runtime/sys.c . No, i386 does not support 64-bit words!
# ...
Important
During a cross-compilation the Host Machine and the Target Machine runtime library constants should be the same. The most critical limitations are:
if your Target Machine is a 32-bit system, make sure you use a 32-bit Host Machine compiler. That equalizes
word_sizeif your Target Machine is a Unix system, make sure you run the Host Machine cross-compiler on a Unix system. That equalizes
ostype_unixif your Target Machine is a Windows system, make sure you run the Host Machine cross-compiler on a Windows system. That equalizes
ostype_windowsno cross-compilation is supported in Cygwin because Cygwin only supports x86_64 Windows
Even if the Host Machine runtime constants were not inherited, there are a few more limitations created by:
When
ocamloptis linking object files into an executable on Windows, it uses an executable calledflexlink.exethat expects Windows.obj(COFF) object files for linking. However Linux uses ELF object files and macOS uses Mach-O object files, so a Windows Host Machine cannot support a non-Windows Target Machine.When
ocamloptis linking object files into an executable on Windows, the Host/Target Machine compiler must match (ie. MSVC or MinGW) and the Host/Target Machine word size (ie. 32 or 64) must match becauseflexlink.exebundles a word size + compiler named object fileflexdll_msvc.obj,flexdll_mingw64.obj, etc. into the final executable.If function sections are not supported on the host (ex. always disabled on macOS since macOS' Mach-O binary format limits section names), but are supported on the target at OCaml ./configure time, then cross-compiling will fail during stdlib because the target OCaml compile will expect the
ocamlopt -function-sectionscommand to work. This host/target combination appears when compiling Android targets that support function section on a macOS host which does not support function sections. The solution is to disable function sections for the target with./configure --disable-function-sections
