Package: src/packages/embed.fdoc

Driver Embedding Technology

key file
flx_world_config.hpp share/lib/rtl/flx_world_config.hpp
flx_world_config.cpp share/src/rtl/flx_world_config.cpp
flx_world.hpp share/lib/rtl/flx_world.hpp
flx_world.cpp share/src/rtl/flx_world.cpp

Embedding

This technology is designed to allow Felix to be embedded in any C or C++ program or library.

The embedding library code is used by the core drivers.

The flx_config class.

The flx_config class is used to store configuration data used by subsequent initialisation steps used to initiate a Felix world.

//[flx_world_config.hpp]

#ifndef __flx_world_config_H_
#define __flx_world_config_H_

#include "flx_rtl_config.hpp"
#include "flx_gc.hpp"
#include "flx_collector.hpp"
#include "flx_dynlink.hpp"

// for async_sched
#include <list>
#include "flx_async.hpp"
#include "flx_sync.hpp"

namespace flx { namespace run {
class RTL_EXTERN flx_config {
public:
  bool  debug;

  bool debug_threads;
  bool debug_allocations;
  bool debug_collections;
  bool report_collections;
  bool report_gcstats;

  bool debug_driver;
  bool finalise;

  size_t gc_freq;
  size_t min_mem;
  size_t max_mem;
  int gcthreads;

  double free_factor;

  bool allow_collection_anywhere;

  bool static_link;
  char *filename; // expected to live forever
  char **flx_argv;
  int flx_argc;

  // TODO: fn up in macro area
  int init(int argc, char **argv);

// interface for drivers. there's more, create_frame, etc
  create_async_hooker_t *ptr_create_async_hooker=nullptr;

  typedef ::flx::dynlink::flx_dynlink_t *(*link_library_t)(flx_config *c, ::flx::gc::generic::gc_profile_t*);
  typedef void (*init_ptr_create_async_hooker_t)(flx_config *, bool debug_driver);
  typedef int (*get_flx_args_config_t)(int argc, char **argv, flx_config* c);

  link_library_t link_library;
  init_ptr_create_async_hooker_t init_ptr_create_async_hooker;
  get_flx_args_config_t get_flx_args_config;

  flx_config (link_library_t, init_ptr_create_async_hooker_t, get_flx_args_config_t);


};
}} // namespaces
#endif
//[flx_world_config.cpp]

#include "flx_world_config.hpp"
#include <cstdlib>

static double egetv(char const *name, double dflt)
{
  char *env = ::std::getenv(name);
  double val = env?::std::atof(env):dflt;
  return val;
}

namespace flx { namespace run {

// =================================================================
// flx_config: Constructor
// =================================================================
flx_config::flx_config
(
  link_library_t link_library_arg,
  init_ptr_create_async_hooker_t init_ptr_create_async_hooker_arg,
  get_flx_args_config_t get_flx_args_config_arg
) :
  link_library(link_library_arg),
  init_ptr_create_async_hooker(init_ptr_create_async_hooker_arg),
  get_flx_args_config(get_flx_args_config_arg)
{
  //fprintf(stderr,"flx_config constrfuctor\n");
}

// =================================================================
// flx_config: Initialiser
// =================================================================

int
flx_config::init(int argc, char **argv) {
  if(get_flx_args_config(argc, argv, this)) return 1;

  debug = (bool)egetv("FLX_DEBUG", debug);
  if (debug) {
    fprintf(stderr,
      "[FLX_DEBUG] Debug enabled for %s link program\n",
      static_link ? "static" : "dynamic");
  }

  debug_threads = (bool)egetv("FLX_DEBUG_THREADS", debug);
  if (debug_threads) {
    fprintf(stderr, "[FLX_DEBUG_THREADS] Threads debug enabled\n");
  }

  debug_allocations = (bool)egetv("FLX_DEBUG_ALLOCATIONS", debug);
  if (debug_allocations) {
    fprintf(stderr, "[FLX_DEBUG_ALLOCATIONS] Allocation debug enabled\n");
  }

  debug_collections = (bool)egetv("FLX_DEBUG_COLLECTIONS", debug);
  if (debug_collections)
  {
    fprintf(stderr, "[FLX_DEBUG_COLLECTIONS] Collection debug enabled\n");
  }

  report_collections = (bool)egetv("FLX_REPORT_COLLECTIONS", debug);
  if (report_collections)
  {
    fprintf(stderr, "[FLX_REPORT_COLLECTIONS] Collection report enabled\n");
  }

  report_gcstats = (bool)egetv("FLX_REPORT_GCSTATS", report_collections);
  if (report_collections)
  {
    fprintf(stderr, "[FLX_REPORT_GCSTATS] GC statistics report enabled\n");
  }


  debug_driver = (bool)egetv("FLX_DEBUG_DRIVER", debug);
  if (debug_driver)
  {
    fprintf(stderr, "[FLX_DEBUG_DRIVER] Driver debug enabled\n");
  }

  finalise = (bool)egetv("FLX_FINALISE", 0);
  if (debug)
    fprintf(stderr,
      "[FLX_FINALISE] Finalisation %s\n", finalise ? "Enabled" : "Disabled");

  // default collection frequency is 1000 interations
  gc_freq = (size_t)egetv("FLX_GC_FREQ", 1000);
  if (gc_freq < 1) gc_freq = 1;
  if (debug)
    fprintf(stderr, "[FLX_GC_FREQ] call gc every %zu iterations\n", gc_freq);

  // default min mem is 10 Meg
  min_mem = (size_t)(egetv("FLX_MIN_MEM", 10) * 1000000.0);
  if (debug)
    fprintf(stderr, "[FLX_MIN_MEM] call gc only if more than %zu Meg heap used\n", min_mem/1000000);

  // default max mem is unlimited
  max_mem = (size_t)(egetv("FLX_MAX_MEM", 0) * 1000000.0);
  if (max_mem == 0) max_mem = (size_t)-1;
  if (debug)
    fprintf(stderr, "[FLX_MAX_MEM] terminate if more than %zu Meg heap used\n", max_mem/1000000);

  // default free factor is 10%, this is also the minimum allowed
  free_factor = egetv("FLX_FREE_FACTOR", 1.1);
  if (free_factor < 1.1) free_factor = 1.1;
  if (debug)
    fprintf(stderr, "[FLX_FREE_FACTOR] reset gc trigger %4.2f times heap used after collection\n", free_factor);

  // experimental flag to allow collection anywhere
  // later, we default this one to true if we can
  // find all the thread stacks, which should be possible
  // with gcc and probably msvc++

  allow_collection_anywhere = (bool)egetv("FLX_ALLOW_COLLECTION_ANYWHERE", 1);
  if (debug)
    fprintf(stderr, "[FLX_ALLOW_COLLECTION_ANYWHERE] %s\n", allow_collection_anywhere ? "True" : "False");

  gcthreads = (int)egetv("FLX_GCTHREADS",0);
  if (debug)
    fprintf(stderr, "[FLX_GCTHREADS] %d\n",gcthreads);

  if (debug) {
    for (int i=0; i<flx_argc; ++i)
      fprintf(stderr, "flx_argv[%d]->%s\n", i, flx_argv[i]);
  }
  return 0;
}

}} // namespaces

The flx_world class.

Objects of the flx_world class are used to represent a Felix world.

//[flx_world.hpp]

#ifndef __flx_world_H_
#define __flx_world_H_
#include "flx_rtl_config.hpp"

#include "flx_gc.hpp"
#include "flx_collector.hpp"
#include "flx_dynlink.hpp"

// for async_sched
#include <list>
#include "flx_async.hpp"
#include "flx_sync.hpp"
#include "flx_world_config.hpp"
#include "flx_async_world.hpp"

namespace flx { namespace run {

class RTL_EXTERN flx_world {
  bool debug;
  bool debug_driver;

  ::flx::gc::generic::allocator_t *allocator;

  ::flx::gc::collector::flx_collector_t *collector;

  ::flx::gc::generic::gc_profile_t *gcp;

  ::flx::dynlink::flx_dynlink_t *library;
  ::flx::dynlink::flx_libinst_t *instance;

  struct async_sched *async_scheduler;

  int explicit_dtor();
public:
  flx_config *c;
  flx_world(flx_config *);
  int setup(int argc, char **argv);

  int teardown();

  // add/remove (current pthread, stack pointer) for garbage collection
  void begin_flx_code();
  void end_flx_code();

  // returns number of pending operations scheduled by svc_general
  // return error code < 0 otherwise
  // catches all known exceptions
  int run();

  void* ptf()const { return instance->thread_frame; } // for creating con_t

  async_hooker *create_demux();

  void spawn_fthread(::flx::rtl::con_t *top);

  void external_multi_swrite (::flx::rtl::schannel_t *chan, void *data);

  async_sched *get_async_scheduler()const { return async_scheduler; }
  sync_sched *get_sync_scheduler()const { return async_scheduler->ss; }
};


}} // namespaces
#endif //__flx_world_H_
//[flx_world.cpp]

#include "flx_world.hpp"
#include "flx_eh.hpp"
#include "flx_ts_collector.hpp"
#include "flx_rtl.hpp"

using namespace ::std;
using namespace ::flx::rtl;
using namespace ::flx::pthread;
using namespace ::flx::run;

namespace flx { namespace run {

// =================================================================
// flx_world : final cleanup
// =================================================================

// terminates process!
// Not called by default (let the OS clean up)

static int do_final_cleanup(
  bool debug_driver,
  flx::gc::generic::gc_profile_t *gcp,
  ::flx::dynlink::flx_dynlink_t *library,
  ::flx::dynlink::flx_libinst_t *instance
)
{
  flx::gc::generic::collector_t *collector = gcp->collector;

  // garbage collect application objects
  {
    if (debug_driver || gcp->debug_collections)
      fprintf(stderr, "[do_final_cleanup] Finalisation: pass 1 Data collection starts ..\n");

    size_t n = collector->collect();
    size_t a = collector->get_allocation_count();

    if (debug_driver || gcp->debug_collections)
      fprintf(stderr, "[do_final_cleanup] flx_run collected %zu objects, %zu left\n", n, a);
  }

  // garbage collect system objects
  {
    if (debug_driver || gcp->debug_collections)
      fprintf(stderr, "[do_final_cleanup] Finalisation: pass 2 Final collection starts ..\n");

    collector->free_all_mem();
    size_t a = collector->get_allocation_count();

    if (debug_driver || gcp->debug_collections)
      fprintf(stderr, "[do_final_cleanup] Remaining %zu objects (should be 0)\n", a);

    if (a != 0){
      fprintf(stderr, "[do_final_cleanup] flx_run %zu uncollected objects, should be zero!! return code 5\n", a);
      return 5;
    }
  }

  if (debug_driver)
    fprintf(stderr, "[do_final_cleanup] exit 0\n");

  return 0;
}

static void *get_stack_pointer() { void *x=(void*)&x; return x; }

// =================================================================
// flx_world : run mainline pthread constructor
// =================================================================
// RUN A FELIX INSTANCE IN THE CURRENT PTHREAD
//
// CURRENTLY ONLY CALLED ONCE IN MAIN THREAD
// RETURNS A LIST OF FTHREADS
//

static fthread_list*
run_felix_pthread_ctor(
  flx::gc::generic::gc_profile_t *gcp,
  ::flx::dynlink::flx_libinst_t *instance)
{
  //fprintf(stderr, "run_felix_pthread_ctor -- the MAIN THREAD: library instance: %p\n", instance);
  flx::gc::generic::collector_t *collector = gcp->collector;
  fthread_list *active = new(*gcp, ::flx::run::fthread_list_ptr_map,false)  fthread_list(gcp);

  {
    con_t *top = instance->main_proc;
    //fprintf(stderr, "  ** MAIN THREAD: flx_main entry point : %p\n", top);
    if (top)
    {
      fthread_t *flx_main = new (*gcp, _fthread_ptr_map, false) fthread_t(top);
      active->push_front(flx_main);
    }
  }

  {
    con_t *top = instance->start_proc;
    //fprintf(stderr, "  ** MAIN THREAD: flx_start (initialisation) entry point : %p\n", top);
    if (top)
    {
      fthread_t *ft = new (*gcp, _fthread_ptr_map, false) fthread_t(top);
      active->push_front(ft);
    }
  }
  return active;
}

// =================================================================
// flx_world : run mainline pthread destructor
// =================================================================
static void run_felix_pthread_dtor(
  bool debug_driver,
  flx::gc::generic::gc_profile_t *gcp,
  ::flx::dynlink::flx_dynlink_t *library,
  ::flx::dynlink::flx_libinst_t *instance
)
{
  if (debug_driver)
    fprintf(stderr, "[run_felix_pthread_dtor] MAIN THREAD FINISHED: waiting for other threads\n");

  gcp->collector->get_thread_control()->join_all();

  if (debug_driver)
    fprintf(stderr, "[run_felix_pthread_dtor] ALL THREADS DEAD: mainline cleanup!\n");

  if (debug_driver) {
    flx::gc::generic::collector_t *collector = gcp->collector;

    size_t uncollected = collector->get_allocation_count();
    size_t roots = collector->get_root_count();
    fprintf(stderr,
      "[run_felix_pthread_dtor] program finished, %zu collections, %zu uncollected objects, roots %zu\n",
      gcp->collections, uncollected, roots);
  }
  gcp->collector->remove_root(instance);

  if (gcp->finalise)
    (void)do_final_cleanup(debug_driver, gcp, library, instance);

  if (debug_driver)
    fprintf(stderr, "[run_felix_pthread_dtor] mainline cleanup complete, exit\n");

}

// =================================================================
// flx_world : Constructor
// =================================================================
// construct from flx_config pointer
flx_world::flx_world(flx_config *c_arg) : c(c_arg) {}

int flx_world::setup(int argc, char **argv) {
  int res;
  if((res = c->init(argc, argv) != 0)) return res;

  debug = c->debug;
  debug_driver = c->debug_driver;

  if(debug_driver)
    fprintf(stderr, "[flx_world %p: setup]\n", this);

  allocator = new flx::gc::collector::malloc_free();
  if(debug_driver)
    fprintf(stderr, "[flx_world: setup] Created allocator %p\n", allocator);
  allocator->set_debug(c->debug_allocations);

  char *tracecmd = getenv("FLX_TRACE_ALLOCATIONS");
  if(tracecmd && strlen(tracecmd)>0) {
     FILE *f = fopen(tracecmd,"w");
     if(f) {
       fprintf(stderr, "Allocation tracing active, file = %s\n",tracecmd);
       allocator = new flx::gc::collector::tracing_allocator(f,allocator);
     }
     else
       fprintf(stderr, "Unable to open allocation trace file %s for output (ignored)\n",tracecmd);
  }

  // previous direct ctor scope ended at closing brace of FLX_MAIN
  // but delete can probably be moved up after collector delete (also used by explicit_dtor)
  ::flx::pthread::thread_control_t *thread_control = new ::flx::pthread::thread_control_t(c->debug_threads);
  if(debug_driver)
    fprintf(stderr, "[flx_world: setup] Created thread control object  %p\n", thread_control);

  // NB: !FLX_SUPPORT_ASYNC refers to async IO, hence ts still needed thanks to flx pthreads
  FILE *tracefile = NULL;
  {
    char *tracecmd = getenv("FLX_TRACE_GC");
    if(tracecmd && strlen(tracecmd)>0) {
      tracefile = fopen(tracecmd,"w");
      if(tracefile)
        fprintf(stderr, "GC tracing active, file = %s\n",tracecmd);
    }
  }

  // Create Garbage Collector
  collector = new flx::gc::collector::flx_ts_collector_t(
    allocator,
    thread_control,
    c->gcthreads, tracefile
  );
  collector->set_debug(c->debug_collections, c->report_gcstats);
  if(debug_driver)
    fprintf(stderr, "[flx_world: setup] Created ts collector %p\n", collector);

  // Create Collector Profile
  gcp = new flx::gc::generic::gc_profile_t(
    c->debug_driver,
    c->debug_allocations,
    c->debug_collections,
    c->report_collections,
    c->report_gcstats,
    c->allow_collection_anywhere,
    c->gc_freq,
    c->min_mem,
    c->max_mem,
    c->free_factor,
    c->finalise,
    collector
  );

  if(debug_driver)
    fprintf(stderr, "[flx_world: setup] Created gc profile object gcp=%p\n",gcp);

  library = c->link_library(c,gcp);
  collector->add_root (library);

  if(debug_driver)
    fprintf(stderr, "[flx_world: setup] Created library object %p\n", library);

  if (debug_driver)
  {
    fprintf(stderr, "[flx_world:setup] flx_run driver begins argv[0]=%s\n", c->flx_argv[0]);
    for (int i=1; i<argc-1; ++i)
      fprintf(stderr, "[flx_world:setup]                       argv[%d]=%s\n", i,c->flx_argv[i]);
  }

  // flx_libinst_t::create can run code, so add thread to avoid world_stop abort
  thread_control->add_thread(get_stack_pointer());

  // Create the usercode driver instance
  // NB: seems to destroy()ed in do_final_cleanup
  instance = new (*gcp, ::flx::dynlink::flx_libinst_ptr_map, false) ::flx::dynlink::flx_libinst_t(debug_driver);
  collector->add_root(instance);
  instance->create(
    library,
    gcp,
    this,
    c->flx_argc,
    c->flx_argv,
    stdin,
    stdout,
    stderr,
    debug_driver);

  thread_control->remove_thread();

  if (debug_driver) {
    fprintf(stderr, "[flx_world:setup] loaded library %s at %p\n", c->filename, library->library);
    fprintf(stderr, "[flx_world:setup] thread frame at %p\n", instance->thread_frame);
    fprintf(stderr, "[flx_world:setup] initial continuation at %p\n", instance->start_proc);
    fprintf(stderr, "[flx_world:setup] main continuation at %p\n", instance->main_proc);
    fprintf(stderr, "[flx_world:setup] creating async scheduler\n");
  }

  // FIXME: this doesn't belong in this subroutine
  // The above stuff sets universal variables
  // the below stuff sets variables that ONLY apply
  // to the mainline thread
  auto schedlist = run_felix_pthread_ctor(gcp, instance);

  async_scheduler = new (*gcp,async_sched_ptr_map,false) async_sched(
    this,
    debug_driver,
    gcp, schedlist,async_sched::mainline
    ); // deletes active for us!

  return 0;
}

// =================================================================
// flx_world : Explicit Destructor
// =================================================================
int flx_world::explicit_dtor()
{
  if (debug_driver)
    fprintf(stderr, "[explicit_dtor] entry\n");

  run_felix_pthread_dtor(debug_driver, gcp, library, instance);

  if (gcp->finalise)
  {
    if (debug_driver)
      fprintf(stderr, "[explicit_dtor] flx_run driver ends with finalisation complete\n");
  }
  else
  {
    if (debug_driver || gcp->debug_collections)
    {
      size_t a = gcp->collector->get_allocation_count();
      fprintf(stderr,
        "[explicit_dtor] flx_run driver ends with finalisation skipped, %zu uncollected "
          "objects\n", a);
    }
  }

  if (debug_driver)
    fprintf(stderr, "[explicit_dtor] exit 0\n");

  return 0;
}

// =================================================================
// flx_world : Teardown
//
// IRREVERSIBLY DESTROYS THE WORLD. Kills the allocator, collector,
// collector profile and thread control object.
// =================================================================
int flx_world::teardown() {
  if (debug_driver)
    fprintf(stderr, "[teardown] entry\n");

  collector->get_thread_control()->add_thread(get_stack_pointer());

  // could this override error_exit_code if something throws?
  int error_exit_code = explicit_dtor();
  if (debug_driver)
    fprintf(stderr,"[teardown] explicit dtor run code %d\n", error_exit_code);

  thread_control_base_t *thread_control = collector->get_thread_control();

  instance=0;
  library=0;
  if (debug_driver)
    fprintf(stderr,"[teardown] library & instance NULLED\n");

  // And we're done, so start cleaning up.
  delete gcp;

  delete collector;
  if (debug_driver)
    fprintf(stderr,"[teardown] collector deleted\n");

  delete allocator;
  if (debug_driver)
    fprintf(stderr,"[teardown] allocator deleted\n");

  if (debug_driver)
    fprintf(stderr, "[teardown] flx_run driver ends code=%d\n", error_exit_code);

  delete thread_control;  // RF: cautiously delete here
  if (debug_driver)
    fprintf(stderr,"[teardown] thread control deleted\n");
  return error_exit_code;
}

// =================================================================
// flx_world : Resume Felix
// =================================================================
void flx_world::begin_flx_code() {
  collector->get_thread_control() -> add_thread(get_stack_pointer());
}

// =================================================================
// flx_world :  Suspend Felix
// =================================================================
void flx_world::end_flx_code() {
  collector->get_thread_control()->remove_thread();
}


// =================================================================
// flx_world :  Run Felix Mainline
// =================================================================
int flx_world::run() {
  // this may not be called on the same thread, so let thread control know
  // when we exit, main thread is not running so pthreads can garbage collect without waiting for us

  try {
    return async_scheduler->prun();
  }
  catch (flx_exception_t &x) { return - flx_exception_handler (&x); }
  catch (std::exception &x) { return - std_exception_handler (&x); }
  catch (int &x) { fprintf (stderr, "Exception type int: %d\n", x); return -x; }
  catch (::std::string &x) { fprintf (stderr, "Exception type string : %s\n", x.c_str()); return -1; }
  catch (::flx::rtl::con_t &x) { fprintf (stderr, "Rogue continuatiuon caught\n"); return -6; }
  catch (...) { fprintf(stderr, "[flx_world:run_until_complete] Unknown exception in thread!\n"); return -5; }
}


// =================================================================
// flx_world :  Spawn fibre hook
// =================================================================
// TODO: factor into async_sched. run_felix_pthread_ctor does this twice
void flx_world::spawn_fthread(con_t *top) {
      fthread_t *ft = new (*gcp, _fthread_ptr_map, false) fthread_t(top);
  get_sync_scheduler()->push_front(ft);
}

// =================================================================
// flx_world :  External multiwrite hook
// =================================================================
void flx_world::external_multi_swrite (schannel_t *chan, void *data)
{
  async_scheduler->external_multi_swrite (chan,data);
}

// =================================================================
// flx_world :  Create Demux event polling system and thread
// =================================================================
async_hooker *flx_world::create_demux()
{
  if(debug_driver)
    fprintf(stderr,"[create_demux]: svc_general] trying to create async system..\n");

  if (c->ptr_create_async_hooker == NULL) {
    if(debug_driver)
      fprintf(stderr,"[create_demux: svc_general] trying to create async hooker..\n");
    c->init_ptr_create_async_hooker(c,debug_driver);
  }
  // Error out if we don't have the hooker function.
  if (c->ptr_create_async_hooker == NULL) {
    fprintf(stderr,
      "[create_demux: svc_general] Unable to initialise async I/O system: terminating\n");
    exit(1);
  }

  // CREATE A NEW ASYNCHRONOUS EVENT MANAGER
  // DONE ON DEMAND ONLY
  auto demux_hook = (*c->ptr_create_async_hooker)(
    gcp->collector->get_thread_control(), // thread_control object
    20000, // bound on resumable thread queue
    50,    // bound on general input job queue
    2,     // number of threads in job pool
    50,    // bound on async fileio job queue
    1      // number of threads doing async fileio
  );
  return demux_hook;
}


}} // namespaces