aboutsummaryrefslogtreecommitdiff
path: root/gcc/frame.c
diff options
context:
space:
mode:
authorAndrew MacLeod <amacleod@cygnus.com>2000-05-25 15:21:51 +0000
committerAndrew Haley <aph@gcc.gnu.org>2000-05-25 15:21:51 +0000
commitce152ef8360320d27e1aacba9af8fdc4e1649941 (patch)
treefeadfb24e9829d3678b304cad386af16dda71a31 /gcc/frame.c
parentc66265e4833fb9553699839453725586cc248fc3 (diff)
downloadgcc-ce152ef8360320d27e1aacba9af8fdc4e1649941.zip
gcc-ce152ef8360320d27e1aacba9af8fdc4e1649941.tar.gz
gcc-ce152ef8360320d27e1aacba9af8fdc4e1649941.tar.bz2
except.c (func_eh_entry): Add emitted field.
2000-05-25 Andrew MacLeod <amacleod@cygnus.com> Andrew Haley <aph@cygnus.com> * except.c (func_eh_entry): Add emitted field. (new_eh_region_entry): Set emitted field to 0; (output_exception_table_entry): Only emit previously un-emitted data, and send it to the eh_data section. (output_exception_table): Break out common parts. Output exception table for entire compilation unit to eh_data section. (output_exception_table_data): Common parts of output_exception_table. Send output to eh_data section. (output_function_exception_table): Output exception table data for a single function to eh_data section. (free_exception_table): New external to free the table. * except.h (free_exception_table): Add prototype. (output_function_exception_table): Add prototype. * final.c (final_end_function): Output function exception table for IA64_UNWIND_INFO. (final_scan_insn): Emit any unwind directives for an insn. * frame-dwarf2.c: New file containing all DWARF 2 specific code from frame.c. * frame.c: Remove all DWARF 2 specific code. * config/ia64/frame-ia64.c: New file. (gthread_stuff): Make all gthread available with IA64_UNWIND_INFO. (dwarf_fde): Define an IA64 struct for dwarf_fde. (__register_frame_info, __register_frame): Move to common area of file. (__register_frame_info_table, __register_frame_table): Move to common i area. (__deregister_frame_info, __deregister_frame): Move to common area. (__frame_init, find_fde): New versions for IA64_UNWIND_INFO. (read_uleb128): New version for ia64. (get_unwind_record): Read the next IA-64 unwind record. (read_R_record): Read a region header record. (process_a_b_reg_code): X record helper. (read_X_record): Read an X format record. (read_B_record): Read a B format record. (P3_record_types): List of record types matching the P3 format. (P7_record_types): List of record types matching the P7 format. (P8_record_types): List of record types matching the P8 format. (read_P_record): Read a P format record. (init_ia64_reg_loc): Set default fields for a register. (init_ia64_unwind_frame): Set defaults for all register records. (execute_one_ia64_descriptor): Execute one descriptor record. (rse_address_add): Calculate the position of a local reg in memory. (normalize_reg_loc): Turn a location descriptor into a memory address. (maybe_normalize_reg_loc): Only normalize a descriptor if it falls within a specified PC offset range. (get_real_reg_value): Given a register location, retrieve its value. (set_real_reg_value): Change the value of a register location. (copy_reg_value): Copy reg values, if needed. (copy_saved_reg_state): Copy all registers that need to be copied. (process_state_between): Normalize all frame register records that fall within the specified PC range. (frame_translate): Take a processed frame description, and turn everything into addresses. (build_ia64_frame_state ): Find and create frame state record for a PC. (get_personality): Get the personality routine for a given frame. (get_except_table): Get the exception table for a given frame. (record_name): Unwind record names for debugging. (print_record): Print and unwind record. (print_all_records): Print an entire unwind image. (__ia64_backtrace): Print a backtrace. (ia64_backtrace_helper): New function. (__register_frame_info_aux): New function. * config/ia64/crtend.asm (__do_frame_setup_aux): New function. * frame.h (enum unw_record_type): New unwind record types. (struct unw_p_record, unw_b_record, unw_x_record) : New unwind records. (struct unw_r_record, unwind_record): New unwind record structs. (struct unwind_info_ptr): Unwind information layout. (IA64_UNW_LOC_TYPE_*): Macros for different types for location descriptors. (struct ia64_reg_loc): Register location description. (struct ia64_frame_state): Location of all registers in a frame. (struct object): Add pc_base and fde_end for IA64_UNWIND_INFO. * libgcc2.c (__ia64_personality_v1): Personality routine. (__calc_caller_bsp): Calculate the bsp register for the caller's frame. (ia64_throw_helper): Figure out who to return to and set up the registers. (__throw): Throw routine. * output.h (assemble_eh_align, assemble_eh_label): New functions to generate EH info where we want it. (assemble_eh_integer): New function. * toplev.c (compile_file): Output module level exception table for non-ia64 targets. (main): Set exceptions_via_longjump and flag_new_exceptions based on IA64_UNWIND_INFO too. * varasm.c (assemble_eh_label): Generate a label via ASM_OUTPUT_EH_LABEL if it has been specified. (assemble_eh_align): Generate an alignment directive via ASM_OUTPUT_EH_ALIGN if it has been specified. (assemble_eh_label): Generate an integer value via ASM_OUTPUT_EH_type if they have been specified. * config/ia64/ia64.c (rtx_needs_barrier): Add flushrs. (ia64_init_builtins): Add __builtin_ia64_bsp and __builtin_ia64_flushrs. (ia64_expand_builtin): Add IA64_BUILTIN_BSP and IA64_BUILTIN_FLUSHRS. * config/ia64/ia64.h (ia64_builtins): Add IA64_BUILTIN_BSP and IA64_BUILTIN_FLUSHRS. * config/ia64/ia64.md (flushrs): New insn to flush the register stack. Add to unspec list. * config/ia64/crtbegin.asm (frame_object): Change size. (__do_frame_setup_aux): New function. * config/ia64/crtend.asm: call __do_frame_setup_aux. * config/ia64/t-ia64 (LIB2ADDEH): Add. * Makefile.in (LIB2ADDEH): Add. (LIB2ADD): Use LIB2ADDEH. Co-Authored-By: Andrew Haley <aph@cygnus.com> From-SVN: r34169
Diffstat (limited to 'gcc/frame.c')
-rw-r--r--gcc/frame.c629
1 files changed, 0 insertions, 629 deletions
diff --git a/gcc/frame.c b/gcc/frame.c
index 92b30fd..e1448e3 100644
--- a/gcc/frame.c
+++ b/gcc/frame.c
@@ -29,216 +29,6 @@ along with GNU CC; see the file COPYING. If not, write to
the Free Software Foundation, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA. */
-/* It is incorrect to include config.h here, because this file is being
- compiled for the target, and hence definitions concerning only the host
- do not apply. */
-
-#include "tconfig.h"
-#include "tsystem.h"
-
-#include "defaults.h"
-
-#ifdef DWARF2_UNWIND_INFO
-#include "dwarf2.h"
-#include "frame.h"
-#include "gthr.h"
-
-#ifdef __GTHREAD_MUTEX_INIT
-static __gthread_mutex_t object_mutex = __GTHREAD_MUTEX_INIT;
-#else
-static __gthread_mutex_t object_mutex;
-#endif
-
-/* Don't use `fancy_abort' here even if config.h says to use it. */
-#ifdef abort
-#undef abort
-#endif
-
-/* Some types used by the DWARF 2 spec. */
-
-typedef int sword __attribute__ ((mode (SI)));
-typedef unsigned int uword __attribute__ ((mode (SI)));
-typedef unsigned int uaddr __attribute__ ((mode (pointer)));
-typedef int saddr __attribute__ ((mode (pointer)));
-typedef unsigned char ubyte;
-
-/* Terminology:
- CIE - Common Information Element
- FDE - Frame Descriptor Element
-
- There is one per function, and it describes where the function code
- is located, and what the register lifetimes and stack layout are
- within the function.
-
- The data structures are defined in the DWARF specfication, although
- not in a very readable way (see LITERATURE).
-
- Every time an exception is thrown, the code needs to locate the FDE
- for the current function, and starts to look for exception regions
- from that FDE. This works in a two-level search:
- a) in a linear search, find the shared image (i.e. DLL) containing
- the PC
- b) using the FDE table for that shared object, locate the FDE using
- binary search (which requires the sorting). */
-
-/* The first few fields of a CIE. The CIE_id field is 0 for a CIE,
- to distinguish it from a valid FDE. FDEs are aligned to an addressing
- unit boundary, but the fields within are unaligned. */
-
-struct dwarf_cie {
- uword length;
- sword CIE_id;
- ubyte version;
- char augmentation[0];
-} __attribute__ ((packed, aligned (__alignof__ (void *))));
-
-/* The first few fields of an FDE. */
-
-struct dwarf_fde {
- uword length;
- sword CIE_delta;
- void* pc_begin;
- uaddr pc_range;
-} __attribute__ ((packed, aligned (__alignof__ (void *))));
-
-typedef struct dwarf_fde fde;
-
-/* Objects to be searched for frame unwind info. */
-
-static struct object *objects;
-
-/* The information we care about from a CIE. */
-
-struct cie_info {
- char *augmentation;
- void *eh_ptr;
- int code_align;
- int data_align;
- unsigned ra_regno;
-};
-
-/* The current unwind state, plus a saved copy for DW_CFA_remember_state. */
-
-struct frame_state_internal
-{
- struct frame_state s;
- struct frame_state_internal *saved_state;
-};
-
-/* This is undefined below if we need it to be an actual function. */
-#define init_object_mutex_once()
-
-#if __GTHREADS
-#ifdef __GTHREAD_MUTEX_INIT_FUNCTION
-
-/* Helper for init_object_mutex_once. */
-
-static void
-init_object_mutex (void)
-{
- __GTHREAD_MUTEX_INIT_FUNCTION (&object_mutex);
-}
-
-/* Call this to arrange to initialize the object mutex. */
-
-#undef init_object_mutex_once
-static void
-init_object_mutex_once (void)
-{
- static __gthread_once_t once = __GTHREAD_ONCE_INIT;
- __gthread_once (&once, init_object_mutex);
-}
-
-#endif /* __GTHREAD_MUTEX_INIT_FUNCTION */
-#endif /* __GTHREADS */
-
-/* Decode the unsigned LEB128 constant at BUF into the variable pointed to
- by R, and return the new value of BUF. */
-
-static void *
-decode_uleb128 (unsigned char *buf, unsigned *r)
-{
- unsigned shift = 0;
- unsigned result = 0;
-
- while (1)
- {
- unsigned byte = *buf++;
- result |= (byte & 0x7f) << shift;
- if ((byte & 0x80) == 0)
- break;
- shift += 7;
- }
- *r = result;
- return buf;
-}
-
-/* Decode the signed LEB128 constant at BUF into the variable pointed to
- by R, and return the new value of BUF. */
-
-static void *
-decode_sleb128 (unsigned char *buf, int *r)
-{
- unsigned shift = 0;
- unsigned result = 0;
- unsigned byte;
-
- while (1)
- {
- byte = *buf++;
- result |= (byte & 0x7f) << shift;
- shift += 7;
- if ((byte & 0x80) == 0)
- break;
- }
- if (shift < (sizeof (*r) * 8) && (byte & 0x40) != 0)
- result |= - (1 << shift);
-
- *r = result;
- return buf;
-}
-
-/* Read unaligned data from the instruction buffer. */
-
-union unaligned {
- void *p;
- unsigned b2 __attribute__ ((mode (HI)));
- unsigned b4 __attribute__ ((mode (SI)));
- unsigned b8 __attribute__ ((mode (DI)));
-} __attribute__ ((packed));
-static inline void *
-read_pointer (void *p)
-{ union unaligned *up = p; return up->p; }
-static inline unsigned
-read_1byte (void *p)
-{ return *(unsigned char *)p; }
-static inline unsigned
-read_2byte (void *p)
-{ union unaligned *up = p; return up->b2; }
-static inline unsigned
-read_4byte (void *p)
-{ union unaligned *up = p; return up->b4; }
-static inline unsigned long
-read_8byte (void *p)
-{ union unaligned *up = p; return up->b8; }
-
-/* Ordering function for FDEs. Functions can't overlap, so we just compare
- their starting addresses. */
-
-static inline saddr
-fde_compare (fde *x, fde *y)
-{
- return (saddr)x->pc_begin - (saddr)y->pc_begin;
-}
-
-/* Return the address of the FDE after P. */
-
-static inline fde *
-next_fde (fde *p)
-{
- return (fde *)(((char *)p) + p->length + sizeof (p->length));
-}
-
/* Sorting an array of FDEs by address.
(Ideally we would have the linker sort the FDEs so we don't have to do
it at run time. But the linkers are not yet prepared for this.) */
@@ -452,377 +242,6 @@ end_fde_sort (fde_accumulator *accu, size_t count)
return accu->linear.array;
}
-static size_t
-count_fdes (fde *this_fde)
-{
- size_t count;
-
- for (count = 0; this_fde->length != 0; this_fde = next_fde (this_fde))
- {
- /* Skip CIEs and linked once FDE entries. */
- if (this_fde->CIE_delta == 0 || this_fde->pc_begin == 0)
- continue;
-
- ++count;
- }
-
- return count;
-}
-
-static void
-add_fdes (fde *this_fde, fde_accumulator *accu, void **beg_ptr, void **end_ptr)
-{
- void *pc_begin = *beg_ptr;
- void *pc_end = *end_ptr;
-
- for (; this_fde->length != 0; this_fde = next_fde (this_fde))
- {
- /* Skip CIEs and linked once FDE entries. */
- if (this_fde->CIE_delta == 0 || this_fde->pc_begin == 0)
- continue;
-
- fde_insert (accu, this_fde);
-
- if (this_fde->pc_begin < pc_begin)
- pc_begin = this_fde->pc_begin;
- if (this_fde->pc_begin + this_fde->pc_range > pc_end)
- pc_end = this_fde->pc_begin + this_fde->pc_range;
- }
-
- *beg_ptr = pc_begin;
- *end_ptr = pc_end;
-}
-
-/* search this fde table for the one containing the pc */
-static fde *
-search_fdes (fde *this_fde, void *pc)
-{
- for (; this_fde->length != 0; this_fde = next_fde (this_fde))
- {
- /* Skip CIEs and linked once FDE entries. */
- if (this_fde->CIE_delta == 0 || this_fde->pc_begin == 0)
- continue;
-
- if ((uaddr)((char *)pc - (char *)this_fde->pc_begin) < this_fde->pc_range)
- return this_fde;
- }
- return NULL;
-}
-
-/* Set up a sorted array of pointers to FDEs for a loaded object. We
- count up the entries before allocating the array because it's likely to
- be faster. We can be called multiple times, should we have failed to
- allocate a sorted fde array on a previous occasion. */
-
-static void
-frame_init (struct object* ob)
-{
- size_t count;
- fde_accumulator accu;
- void *pc_begin, *pc_end;
- fde **array;
-
- if (ob->pc_begin)
- count = ob->count;
- else if (ob->fde_array)
- {
- fde **p = ob->fde_array;
- for (count = 0; *p; ++p)
- count += count_fdes (*p);
- }
- else
- count = count_fdes (ob->fde_begin);
- ob->count = count;
-
- if (!start_fde_sort (&accu, count) && ob->pc_begin)
- return;
-
- pc_begin = (void*)(uaddr)-1;
- pc_end = 0;
-
- if (ob->fde_array)
- {
- fde **p = ob->fde_array;
- for (; *p; ++p)
- add_fdes (*p, &accu, &pc_begin, &pc_end);
- }
- else
- add_fdes (ob->fde_begin, &accu, &pc_begin, &pc_end);
-
- array = end_fde_sort (&accu, count);
- if (array)
- ob->fde_array = array;
- ob->pc_begin = pc_begin;
- ob->pc_end = pc_end;
-}
-
-/* Return a pointer to the FDE for the function containing PC. */
-
-static fde *
-find_fde (void *pc)
-{
- struct object *ob;
- size_t lo, hi;
-
- init_object_mutex_once ();
- __gthread_mutex_lock (&object_mutex);
-
- /* Linear search through the objects, to find the one containing the pc. */
- for (ob = objects; ob; ob = ob->next)
- {
- if (ob->pc_begin == 0)
- frame_init (ob);
- if (pc >= ob->pc_begin && pc < ob->pc_end)
- break;
- }
-
- if (ob == 0)
- {
- __gthread_mutex_unlock (&object_mutex);
- return 0;
- }
-
- if (!ob->fde_array || (void *)ob->fde_array == (void *)ob->fde_begin)
- frame_init (ob);
-
- if (ob->fde_array && (void *)ob->fde_array != (void *)ob->fde_begin)
- {
- __gthread_mutex_unlock (&object_mutex);
-
- /* Standard binary search algorithm. */
- for (lo = 0, hi = ob->count; lo < hi; )
- {
- size_t i = (lo + hi) / 2;
- fde *f = ob->fde_array[i];
-
- if (pc < f->pc_begin)
- hi = i;
- else if (pc >= f->pc_begin + f->pc_range)
- lo = i + 1;
- else
- return f;
- }
- }
- else
- {
- /* Long slow labourious linear search, cos we've no memory. */
- fde *f;
-
- if (ob->fde_array)
- {
- fde **p = ob->fde_array;
-
- do
- {
- f = search_fdes (*p, pc);
- if (f)
- break;
- p++;
- }
- while (*p);
- }
- else
- f = search_fdes (ob->fde_begin, pc);
- __gthread_mutex_unlock (&object_mutex);
- return f;
- }
- return 0;
-}
-
-static inline struct dwarf_cie *
-get_cie (fde *f)
-{
- return ((void *)&f->CIE_delta) - f->CIE_delta;
-}
-
-/* Extract any interesting information from the CIE for the translation
- unit F belongs to. */
-
-static void *
-extract_cie_info (fde *f, struct cie_info *c)
-{
- void *p;
- int i;
-
- c->augmentation = get_cie (f)->augmentation;
-
- if (strcmp (c->augmentation, "") != 0
- && strcmp (c->augmentation, "eh") != 0
- && c->augmentation[0] != 'z')
- return 0;
-
- p = c->augmentation + strlen (c->augmentation) + 1;
-
- if (strcmp (c->augmentation, "eh") == 0)
- {
- c->eh_ptr = read_pointer (p);
- p += sizeof (void *);
- }
- else
- c->eh_ptr = 0;
-
- p = decode_uleb128 (p, &c->code_align);
- p = decode_sleb128 (p, &c->data_align);
- c->ra_regno = *(unsigned char *)p++;
-
- /* If the augmentation starts with 'z', we now see the length of the
- augmentation fields. */
- if (c->augmentation[0] == 'z')
- {
- p = decode_uleb128 (p, &i);
- p += i;
- }
-
- return p;
-}
-
-/* Decode one instruction's worth of DWARF 2 call frame information.
- Used by __frame_state_for. Takes pointers P to the instruction to
- decode, STATE to the current register unwind information, INFO to the
- current CIE information, and PC to the current PC value. Returns a
- pointer to the next instruction. */
-
-static void *
-execute_cfa_insn (void *p, struct frame_state_internal *state,
- struct cie_info *info, void **pc)
-{
- unsigned insn = *(unsigned char *)p++;
- unsigned reg;
- int offset;
-
- if (insn & DW_CFA_advance_loc)
- *pc += ((insn & 0x3f) * info->code_align);
- else if (insn & DW_CFA_offset)
- {
- reg = (insn & 0x3f);
- p = decode_uleb128 (p, &offset);
- if (reg == state->s.cfa_reg)
- /* Don't record anything about this register; it's only used to
- reload SP in the epilogue. We don't want to copy in SP
- values for outer frames; we handle restoring SP specially. */;
- else
- {
- offset *= info->data_align;
- state->s.saved[reg] = REG_SAVED_OFFSET;
- state->s.reg_or_offset[reg] = offset;
- }
- }
- else if (insn & DW_CFA_restore)
- {
- reg = (insn & 0x3f);
- state->s.saved[reg] = REG_UNSAVED;
- }
- else switch (insn)
- {
- case DW_CFA_set_loc:
- *pc = read_pointer (p);
- p += sizeof (void *);
- break;
- case DW_CFA_advance_loc1:
- *pc += read_1byte (p);
- p += 1;
- break;
- case DW_CFA_advance_loc2:
- *pc += read_2byte (p);
- p += 2;
- break;
- case DW_CFA_advance_loc4:
- *pc += read_4byte (p);
- p += 4;
- break;
-
- case DW_CFA_offset_extended:
- p = decode_uleb128 (p, &reg);
- p = decode_uleb128 (p, &offset);
- if (reg == state->s.cfa_reg)
- /* Don't record anything; see above. */;
- else
- {
- offset *= info->data_align;
- state->s.saved[reg] = REG_SAVED_OFFSET;
- state->s.reg_or_offset[reg] = offset;
- }
- break;
- case DW_CFA_restore_extended:
- p = decode_uleb128 (p, &reg);
- state->s.saved[reg] = REG_UNSAVED;
- break;
-
- case DW_CFA_undefined:
- case DW_CFA_same_value:
- case DW_CFA_nop:
- break;
-
- case DW_CFA_register:
- {
- unsigned reg2;
- p = decode_uleb128 (p, &reg);
- p = decode_uleb128 (p, &reg2);
- state->s.saved[reg] = REG_SAVED_REG;
- state->s.reg_or_offset[reg] = reg2;
- }
- break;
-
- case DW_CFA_def_cfa:
- p = decode_uleb128 (p, &reg);
- p = decode_uleb128 (p, &offset);
- state->s.cfa_reg = reg;
- state->s.cfa_offset = offset;
- break;
- case DW_CFA_def_cfa_register:
- p = decode_uleb128 (p, &reg);
- state->s.cfa_reg = reg;
- break;
- case DW_CFA_def_cfa_offset:
- p = decode_uleb128 (p, &offset);
- state->s.cfa_offset = offset;
- break;
-
- case DW_CFA_remember_state:
- {
- struct frame_state_internal *save =
- (struct frame_state_internal *)
- malloc (sizeof (struct frame_state_internal));
- memcpy (save, state, sizeof (struct frame_state_internal));
- state->saved_state = save;
- }
- break;
- case DW_CFA_restore_state:
- {
- struct frame_state_internal *save = state->saved_state;
- memcpy (state, save, sizeof (struct frame_state_internal));
- free (save);
- }
- break;
-
- /* FIXME: Hardcoded for SPARC register window configuration. */
- case DW_CFA_GNU_window_save:
- for (reg = 16; reg < 32; ++reg)
- {
- state->s.saved[reg] = REG_SAVED_OFFSET;
- state->s.reg_or_offset[reg] = (reg - 16) * sizeof (void *);
- }
- break;
-
- case DW_CFA_GNU_args_size:
- p = decode_uleb128 (p, &offset);
- state->s.args_size = offset;
- break;
-
- case DW_CFA_GNU_negative_offset_extended:
- p = decode_uleb128 (p, &reg);
- p = decode_uleb128 (p, &offset);
- offset *= info->data_align;
- state->s.saved[reg] = REG_SAVED_OFFSET;
- state->s.reg_or_offset[reg] = -offset;
- break;
-
- default:
- abort ();
- }
- return p;
-}
-
/* Called from crtbegin.o to register the unwind info for an object. */
void
@@ -917,51 +336,3 @@ __deregister_frame (void *begin)
free (__deregister_frame_info (begin));
}
-/* Called from __throw to find the registers to restore for a given
- PC_TARGET. The caller should allocate a local variable of `struct
- frame_state' (declared in frame.h) and pass its address to STATE_IN. */
-
-struct frame_state *
-__frame_state_for (void *pc_target, struct frame_state *state_in)
-{
- fde *f;
- void *insn, *end, *pc;
- struct cie_info info;
- struct frame_state_internal state;
-
- f = find_fde (pc_target);
- if (f == 0)
- return 0;
-
- insn = extract_cie_info (f, &info);
- if (insn == 0)
- return 0;
-
- memset (&state, 0, sizeof (state));
- state.s.retaddr_column = info.ra_regno;
- state.s.eh_ptr = info.eh_ptr;
-
- /* First decode all the insns in the CIE. */
- end = next_fde ((fde*) get_cie (f));
- while (insn < end)
- insn = execute_cfa_insn (insn, &state, &info, 0);
-
- insn = ((fde *)f) + 1;
-
- if (info.augmentation[0] == 'z')
- {
- int i;
- insn = decode_uleb128 (insn, &i);
- insn += i;
- }
-
- /* Then the insns in the FDE up to our target PC. */
- end = next_fde (f);
- pc = f->pc_begin;
- while (insn < end && pc <= pc_target)
- insn = execute_cfa_insn (insn, &state, &info, &pc);
-
- memcpy (state_in, &state.s, sizeof (state.s));
- return state_in;
-}
-#endif /* DWARF2_UNWIND_INFO */