[RUBY] How mrbgem is "embedded"

This is the 15th article of the mruby advent calendar. I was really in the shade of the grass because I had a cold ... Please forgive me> <

mruby is a very simple API because you can create and use mrb_state * just by calling mrb_open () for use from within C language. The following is an excerpt of the C code generated by mruby-cli, but except for the definition of ARGV (also a few lines), it's actually doing two lines.

mrb_state *mrb = mrb_open();
// ...
mrb_funcall(mrb, mrb_top_self(mrb), "__main__", 1, ARGV);

On the other hand, in this mrb_state *, in a strange way, mrbgem is in a "built-in" form, but the image was hard to come up, so what kind of mechanism is it? I tried to chase after it, so I will talk about it.

In current master version d9049c10, the actual state of mrb_open is mrb_open_allocf () It is a function that can be specified so that it can be replaced. In it, we call mrb_open_core () to create mrb_state *.

MRB_API mrb_state*
mrb_open_core(mrb_allocf f, void *ud)
{
  static const mrb_state mrb_state_zero = { 0 };
  static const struct mrb_context mrb_context_zero = { 0 };
  mrb_state *mrb;

  mrb = (mrb_state *)(f)(NULL, NULL, sizeof(mrb_state), ud);
  if (mrb == NULL) return NULL;

  *mrb = mrb_state_zero;
  mrb->allocf_ud = ud;
  mrb->allocf = f;
  mrb->atexit_stack_len = 0;

  mrb_gc_init(mrb, &mrb->gc);
  mrb->c = (struct mrb_context*)mrb_malloc(mrb, sizeof(struct mrb_context));
  *mrb->c = mrb_context_zero;
  mrb->root_c = mrb->c;

  mrb_init_core(mrb); //Functions that initialize mruby built-in classes, etc.

  return mrb;
}

After mrb_open_core, if DISABLE_GEMS is not defined,mrb_init_mrbgems ()will be called.

MRB_API mrb_state*
mrb_open_allocf(mrb_allocf f, void *ud)
{
  mrb_state *mrb = mrb_open_core(f, ud);

  if (mrb == NULL) {
    return NULL;
  }

#ifndef DISABLE_GEMS
  mrb_init_mrbgems(mrb);
  mrb_gc_arena_restore(mrb, 0);
#endif
  return mrb;
}

This mrb_init_mrbgems () is the key. In fact, this function is not defined in the mruby source code.

Where it is defined is in mruby / build / $ {BUILD_HOST} /mrbgems/gem_init.c which is automatically generated during build.

void
mrb_init_mrbgems(mrb_state *mrb) {
  GENERATED_TMP_mrb_mruby_sprintf_gem_init(mrb);
  GENERATED_TMP_mrb_mruby_print_gem_init(mrb);
  GENERATED_TMP_mrb_mruby_math_gem_init(mrb);
  GENERATED_TMP_mrb_mruby_time_gem_init(mrb);
  GENERATED_TMP_mrb_mruby_struct_gem_init(mrb);
  GENERATED_TMP_mrb_mruby_compar_ext_gem_init(mrb);
  GENERATED_TMP_mrb_mruby_enum_ext_gem_init(mrb);
  GENERATED_TMP_mrb_mruby_string_ext_gem_init(mrb);
  GENERATED_TMP_mrb_mruby_numeric_ext_gem_init(mrb);
  GENERATED_TMP_mrb_mruby_array_ext_gem_init(mrb);
  GENERATED_TMP_mrb_mruby_hash_ext_gem_init(mrb);
  GENERATED_TMP_mrb_mruby_range_ext_gem_init(mrb);
  GENERATED_TMP_mrb_mruby_proc_ext_gem_init(mrb);
  GENERATED_TMP_mrb_mruby_symbol_ext_gem_init(mrb);
  GENERATED_TMP_mrb_mruby_random_gem_init(mrb);
  GENERATED_TMP_mrb_mruby_object_ext_gem_init(mrb);
  GENERATED_TMP_mrb_mruby_objectspace_gem_init(mrb);
  GENERATED_TMP_mrb_mruby_fiber_gem_init(mrb);
  GENERATED_TMP_mrb_mruby_enumerator_gem_init(mrb);
  GENERATED_TMP_mrb_mruby_enum_lazy_gem_init(mrb);
  GENERATED_TMP_mrb_mruby_toplevel_ext_gem_init(mrb);
  GENERATED_TMP_mrb_mruby_error_gem_init(mrb);
  GENERATED_TMP_mrb_mruby_kernel_ext_gem_init(mrb);
  GENERATED_TMP_mrb_mruby_class_ext_gem_init(mrb);
  GENERATED_TMP_mrb_mruby_io_gem_init(mrb);
  GENERATED_TMP_mrb_mruby_process_gem_init(mrb);
  GENERATED_TMP_mrb_mruby_exec_gem_init(mrb);
  mrb_state_atexit(mrb, mrb_final_mrbgems);
}

Where each of these GENERATED_TMP_ * functions is generated is that they are created in the middle of building each mrbgem. For example, if mruby-process is included as a dependency, the following file will be created.

The location where it is created is mruby / build / $ {BUILD_HOST} /mrbgems/mruby-process/gem_init.c.

/*
 * This file is loading the irep
 * Ruby GEM code.
 *
 * IMPORTANT:
 *   This file was generated!
 *   All manual changes will get lost.
 */
#include <stdlib.h>
#include <mruby.h>
#include <mruby/irep.h>
/* dumped in little endian order.
   use `mrbc -E` option for big endian CPU. */
#include <stdint.h>
extern const uint8_t gem_mrblib_irep_mruby_process[];
const uint8_t
#if defined __GNUC__
__attribute__((aligned(4)))
#elif defined _MSC_VER
__declspec(align(4))
#endif
gem_mrblib_irep_mruby_process[] = {
0x45,0x54,0x49,0x52,0x30,0x30,0x30,0x34,0x6c,0xec,0x00,0x00,0x07,0xd9,0x4d,0x41,
0x54,0x5a,0x30,0x30,0x30,0x30,0x49,0x52,0x45,0x50,0x00,0x00,0x04,0xd8,0x30,0x30,
0x30,0x30,0x00,0x00,0x00,0x40,0x00,0x01,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x07,
//....
0x00,0x00,0x05,0x00,0x02,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,
0x00,0x45,0x4e,0x44,0x00,0x00,0x00,0x00,0x08,
};
void mrb_mruby_process_gem_init(mrb_state *mrb);
void mrb_mruby_process_gem_final(mrb_state *mrb);

void GENERATED_TMP_mrb_mruby_process_gem_init(mrb_state *mrb) {
  int ai = mrb_gc_arena_save(mrb);
  mrb_mruby_process_gem_init(mrb);
  mrb_load_irep(mrb, gem_mrblib_irep_mruby_process);
  if (mrb->exc) {
    mrb_print_error(mrb);
    exit(EXIT_FAILURE);
  }
  mrb_gc_arena_restore(mrb, ai);
}

void GENERATED_TMP_mrb_mruby_process_gem_final(mrb_state *mrb) {
  mrb_mruby_process_gem_final(mrb);
}

You can see that we are doing two things with GENERATED_TMP_mrb_mruby_process_gem_init ().

With this, both the mgem implementation defined in C and the implementation defined in Ruby are loaded. It's like creating as many mgems as you depend on, linking them all, and finally you have your own mrb_open ().

It is often said that it is convenient to create a one-binary client with the strength of mruby-cli, but what I am doing is writing in Ruby. I would like you to have an image that you are doing so because you can bytecode the processing and incorporate it into the source code (you can do it immediately by using the mrbc command).

(That's why mgem using an external library may not be able to distribute one binary without static linking by itself. Well, it's all about static linking ...)


Regarding the use of mgem, especially for those who are accustomed to CRuby, the way of thinking is quite different, so I thought that it would be happy to recognize that, so I wrote it roughly.

Recommended Posts

How mrbgem is "embedded"
How slow is a Java Scanner?
How the website is displayed on the screen