packages feed

hsjuiz-0.2.6: cbits/hsjuiz.c

/*
 * hsjuiz の C の部分。
 *
 * juiz_hs_init: juiz が language: "haskell" のプラグインを開いた直後に呼ぶ関数 (bindings/cjuiz/README.md)。
 * GHC のランタイム (RTS) を初期化する。成功なら 0 を返す。
 *
 * - juiz は Process をワーカースレッドから呼ぶので、-threaded の RTS でビルドされていなければ
 *   初期化せずにエラー (HSJUIZ_NOT_THREADED_RTS_ERROR) を返す。
 * - hs_init はスレッド安全ではないので、pthread_once で一度だけ呼ぶ。別の Haskell ライブラリと
 *   同時に呼ばないよう、juiz の側でも一つずつ呼ぶ (RTS は libHSrts の共有ライブラリで共有される)。
 * - GHC は hs_exit の後に hs_init し直せないので、hs_exit は呼ばない (juiz_hs_exit も用意しない)。
 *   juiz はライブラリを閉じず、RTS はプロセスの終了とともに片付く。
 * - hs_init の前に setKeepCAFs を呼び、CAF (トップレベルの値) を GC で回収しないようにする。
 *   foreign export した関数は、ライブラリを開いたときに GC の根 (StablePtr) として登録を待つが、
 *   その登録は hs_init の中でしか処理されない。そのため hs_init の後に開いた 2 つ目以降の Haskell の
 *   ライブラリでは、foreign export した CAF (juizProcess などが生成するエントリポイント) が GC の根にならず、
 *   major GC で回収されて、次の呼び出しでプロセスが落ちる (issue #133)。GHCi が読み込んだコードと同じく
 *   CAF をすべて残すことで防ぐ。
 */
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <pthread.h>
#endif
#include <HsFFI.h>
#include <Rts.h> /* rtsSupportsBoundThreads, setKeepCAFs */

/* -threaded でない RTS にリンクされている */
#define HSJUIZ_NOT_THREADED_RTS_ERROR INT64_C(-601)

#ifdef _WIN32
static INIT_ONCE juiz_hs_init_once = INIT_ONCE_STATIC_INIT;
#else
static pthread_once_t juiz_hs_init_once = PTHREAD_ONCE_INIT;
#endif

static void juiz_hs_init_impl(void)
{
    static char program_name[] = "juiz";
    static char *argv_buf[] = { program_name, NULL };
    int argc = 1;
    char **argv = argv_buf;
    setKeepCAFs();
    hs_init(&argc, &argv);
}

#ifdef _WIN32
static BOOL CALLBACK juiz_hs_init_once_callback(PINIT_ONCE once, PVOID param, PVOID *context)
{
    (void)once; (void)param; (void)context;
    juiz_hs_init_impl();
    return TRUE;
}
#endif

int64_t juiz_hs_init(void)
{
    if (!rtsSupportsBoundThreads()) {
        return HSJUIZ_NOT_THREADED_RTS_ERROR;
    }
#ifdef _WIN32
    InitOnceExecuteOnce(&juiz_hs_init_once, juiz_hs_init_once_callback, NULL, NULL);
#else
    pthread_once(&juiz_hs_init_once, juiz_hs_init_impl);
#endif
    return 0;
}

/*
 * この hsjuiz (と GHC の RTS) の実体を表す値 (Juiz.Container が Container の目印と一緒に置く)。
 * Linux / macOS では hsjuiz も RTS も共有ライブラリで、すべての Haskell のプラグインが同じものを使うので、
 * どのプラグインでも同じ値になる。Windows では RTS と hsjuiz がプラグインの DLL ごとに静的に入るので、
 * DLL ごとに違う値になる。違う RTS の StablePtr は読めないので、ContainerProcess はこの値を比べて、
 * 別の DLL で作られた Container を受け付けない (issue #137)
 */
static const char hsjuiz_runtime_tag_value = 0;

const void* hsjuiz_runtime_tag(void)
{
    return &hsjuiz_runtime_tag_value;
}

/*
 * value の型を整数で返す (Juiz.Internal.FFI の valueType*)。
 * value_is_* は C の bool を返すので、Haskell から直接呼ばずにここで整数にする。
 */
typedef struct value_struct value;
bool value_is_null(value* v);
bool value_is_int(value* v);
bool value_is_uint(value* v);
bool value_is_float(value* v);
bool value_is_bool(value* v);
bool value_is_string(value* v);
bool value_is_object(value* v);
bool value_is_array(value* v);

int64_t hsjuiz_value_type(value* v)
{
    if (value_is_null(v)) return 0;
    if (value_is_bool(v)) return 1;
    if (value_is_int(v)) return 2;
    if (value_is_uint(v)) return 3;
    if (value_is_float(v)) return 4;
    if (value_is_string(v)) return 5;
    if (value_is_array(v)) return 6;
    if (value_is_object(v)) return 7;
    return -1;
}

/*
 * capsule_ptr の値が画像なら 1、そうでなければ 0 (Juiz.Internal.FFI の c_hsjuiz_capsule_ptr_is_image)。
 * capsule_ptr_is_image は C の bool を返すので、ここで整数にする。
 */
typedef struct capsule_ptr_struct capsule_ptr;
bool capsule_ptr_is_image(capsule_ptr* cp);

int64_t hsjuiz_capsule_ptr_is_image(capsule_ptr* cp)
{
    return capsule_ptr_is_image(cp) ? 1 : 0;
}

/*
 * 今このスレッドで呼ばれている ContainerProcess の引数 (capsule_map*) を置き換え、前の値を返す
 * (Juiz.Pose.withPoseCall、docs/spec/transformations.md §12.3)。juiz は ContainerProcess を呼ぶとき、
 * 引数に呼び出し元 Container の ContainerPose の口を載せるので、関数の中から読み書きするのに使う。
 * foreign export の呼び出しは呼んだ OS スレッドに結びついた (bound) Haskell スレッドで動くので、
 * スレッドごとの変数に置けばよい
 */
static _Thread_local void* hsjuiz_pose_call = NULL;

void* hsjuiz_swap_pose_call(void* cm)
{
    void* old = hsjuiz_pose_call;
    hsjuiz_pose_call = cm;
    return old;
}