blob: e9ef8bdfa0fc4d84998069b6a9da412278eea0b0 [file] [log] [blame]
/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "charmony.h"
#include <string.h>
#include <stdio.h>
#ifndef true
#define true 1
#define false 0
#endif
#define CFC_NEED_BASE_STRUCT_DEF
#include "CFCBase.h"
#include "CFCPerl.h"
#include "CFCParcel.h"
#include "CFCClass.h"
#include "CFCMethod.h"
#include "CFCHierarchy.h"
#include "CFCUtil.h"
#include "CFCPerlClass.h"
#include "CFCPerlSub.h"
#include "CFCPerlConstructor.h"
#include "CFCPerlMethod.h"
#include "CFCPerlTypeMap.h"
#include "CFCPerlPod.h"
#include "CFCBindCore.h"
#include "CFCDocument.h"
typedef struct CFCPerlPodFile {
char *path;
char *contents;
} CFCPerlPodFile;
struct CFCPerl {
CFCBase base;
CFCHierarchy *hierarchy;
char *lib_dir;
char *header;
char *footer;
char *c_header;
char *c_footer;
char *pod_header;
char *pod_footer;
};
// Modify a string in place, swapping out "::" for the supplied character.
static void
S_replace_double_colons(char *text, char replacement);
static CFCPerlPodFile*
S_write_class_pod(CFCPerl *self);
static CFCPerlPodFile*
S_write_standalone_pod(CFCPerl *self);
static const CFCMeta CFCPERL_META = {
"Clownfish::CFC::Binding::Perl",
sizeof(CFCPerl),
(CFCBase_destroy_t)CFCPerl_destroy
};
CFCPerl*
CFCPerl_new(CFCHierarchy *hierarchy, const char *lib_dir, const char *header,
const char *footer) {
CFCPerl *self = (CFCPerl*)CFCBase_allocate(&CFCPERL_META);
return CFCPerl_init(self, hierarchy, lib_dir, header, footer);
}
CFCPerl*
CFCPerl_init(CFCPerl *self, CFCHierarchy *hierarchy, const char *lib_dir,
const char *header, const char *footer) {
CFCUTIL_NULL_CHECK(hierarchy);
CFCUTIL_NULL_CHECK(lib_dir);
CFCUTIL_NULL_CHECK(header);
CFCUTIL_NULL_CHECK(footer);
self->hierarchy = (CFCHierarchy*)CFCBase_incref((CFCBase*)hierarchy);
self->lib_dir = CFCUtil_strdup(lib_dir);
self->header = CFCUtil_strdup(header);
self->footer = CFCUtil_strdup(footer);
self->c_header = CFCUtil_make_c_comment(header);
self->c_footer = CFCUtil_make_c_comment(footer);
self->pod_header = CFCUtil_make_perl_comment(header);
self->pod_footer = CFCUtil_make_perl_comment(footer);
return self;
}
void
CFCPerl_destroy(CFCPerl *self) {
CFCBase_decref((CFCBase*)self->hierarchy);
FREEMEM(self->lib_dir);
FREEMEM(self->header);
FREEMEM(self->footer);
FREEMEM(self->c_header);
FREEMEM(self->c_footer);
FREEMEM(self->pod_header);
FREEMEM(self->pod_footer);
CFCBase_destroy((CFCBase*)self);
}
static void
S_replace_double_colons(char *text, char replacement) {
size_t pos = 0;
for (char *ptr = text; *ptr != '\0'; ptr++) {
if (strncmp(ptr, "::", 2) == 0) {
text[pos++] = replacement;
ptr++;
}
else {
text[pos++] = *ptr;
}
}
text[pos] = '\0';
}
char**
CFCPerl_write_pod(CFCPerl *self) {
CFCPerlPodFile *class_pods = S_write_class_pod(self);
CFCPerlPodFile *standalone_pods = S_write_standalone_pod(self);
size_t max_paths = 0;
for (size_t i = 0; class_pods[i].contents; i++) { max_paths++; }
for (size_t i = 0; standalone_pods[i].contents; i++) { max_paths++; }
char **pod_paths = (char**)CALLOCATE(max_paths + 1, sizeof(char*));
// Write out any POD files that have changed.
CFCPerlPodFile *file_arrays[2] = {
class_pods,
standalone_pods
};
size_t num_written = 0;
for (size_t j = 0; j < 2; ++j) {
CFCPerlPodFile *pod_files = file_arrays[j];
for (size_t i = 0; pod_files[i].contents; i++) {
char *pod = pod_files[i].contents;
char *pod_path = pod_files[i].path;
if (CFCUtil_write_if_changed(pod_path, pod, strlen(pod))) {
pod_paths[num_written] = pod_path;
num_written++;
}
else {
FREEMEM(pod_path);
}
FREEMEM(pod);
}
FREEMEM(pod_files);
}
pod_paths[num_written] = NULL;
return pod_paths;
}
static CFCPerlPodFile*
S_write_class_pod(CFCPerl *self) {
CFCPerlClass **registry = CFCPerlClass_registry();
size_t num_registered = 0;
while (registry[num_registered] != NULL) { num_registered++; }
CFCPerlPodFile *pod_files
= (CFCPerlPodFile*)CALLOCATE(num_registered + 1,
sizeof(CFCPerlPodFile));
size_t count = 0;
// Generate POD, but don't write. That way, if there's an error while
// generating pod, we leak memory but don't clutter up the file system.
for (size_t i = 0; i < num_registered; i++) {
const char *class_name = CFCPerlClass_get_class_name(registry[i]);
char *raw_pod = CFCPerlClass_create_pod(registry[i]);
if (!raw_pod) { continue; }
char *pod = CFCUtil_sprintf("%s\n%s%s", self->pod_header, raw_pod,
self->pod_footer);
char *pod_path = CFCUtil_sprintf("%s" CHY_DIR_SEP "%s.pod",
self->lib_dir, class_name);
S_replace_double_colons(pod_path, CHY_DIR_SEP_CHAR);
pod_files[count].contents = pod;
pod_files[count].path = pod_path;
count++;
FREEMEM(raw_pod);
}
pod_files[count].contents = NULL;
pod_files[count].path = NULL;
return pod_files;
}
static CFCPerlPodFile*
S_write_standalone_pod(CFCPerl *self) {
CFCDocument **docs = CFCDocument_get_registry();
size_t num_pod_files = 0;
while (docs[num_pod_files]) { num_pod_files++; }
size_t alloc_size = (num_pod_files + 1) * sizeof(CFCPerlPodFile);
CFCPerlPodFile *pod_files = (CFCPerlPodFile*)MALLOCATE(alloc_size);
for (size_t i = 0; i < num_pod_files; i++) {
CFCDocument *doc = docs[i];
const char *path_part = CFCDocument_get_path_part(doc);
char *module = CFCUtil_global_replace(path_part, CHY_DIR_SEP, "::");
char *md = CFCDocument_get_contents(doc);
char *raw_pod = CFCPerlPod_md_doc_to_pod(module, md);
const char *pattern =
"%s"
"\n"
"=encoding utf8\n"
"\n"
"%s"
"%s";
char *pod = CFCUtil_sprintf(pattern, self->pod_header, raw_pod,
self->pod_footer);
char *pod_path = CFCUtil_sprintf("%s" CHY_DIR_SEP "%s.pod",
self->lib_dir, path_part);
pod_files[i].contents = pod;
pod_files[i].path = pod_path;
FREEMEM(raw_pod);
FREEMEM(md);
FREEMEM(module);
}
pod_files[num_pod_files].contents = NULL;
pod_files[num_pod_files].path = NULL;
return pod_files;
}
static void
S_write_host_h(CFCPerl *self, CFCParcel *parcel) {
const char *prefix = CFCParcel_get_prefix(parcel);
const char *PREFIX = CFCParcel_get_PREFIX(parcel);
char *guard = CFCUtil_sprintf("H_%sBOOT", PREFIX);
const char pattern[] =
"%s\n"
"\n"
"#ifndef %s\n"
"#define %s 1\n"
"\n"
"#ifdef __cplusplus\n"
"extern \"C\" {\n"
"#endif\n"
"\n"
"void\n"
"%sbootstrap_perl(void);\n"
"\n"
"#ifdef __cplusplus\n"
"}\n"
"#endif\n"
"\n"
"#endif /* %s */\n"
"\n"
"%s\n";
char *content
= CFCUtil_sprintf(pattern, self->c_header, guard, guard, prefix, guard,
self->c_footer);
const char *inc_dest = CFCHierarchy_get_include_dest(self->hierarchy);
char *host_h_path = CFCUtil_sprintf("%s" CHY_DIR_SEP "%sperl.h", inc_dest,
prefix);
CFCUtil_write_file(host_h_path, content, strlen(content));
FREEMEM(host_h_path);
FREEMEM(content);
FREEMEM(guard);
}
static void
S_write_host_c(CFCPerl *self, CFCParcel *parcel) {
CFCClass **ordered = CFCHierarchy_ordered_classes(self->hierarchy);
const char *prefix = CFCParcel_get_prefix(parcel);
const char *privacy_sym = CFCParcel_get_privacy_sym(parcel);
char *includes = CFCUtil_strdup("");
char *cb_defs = CFCUtil_strdup("");
char *alias_adds = CFCUtil_strdup("");
for (size_t i = 0; ordered[i] != NULL; i++) {
CFCClass *klass = ordered[i];
if (CFCClass_inert(klass)) { continue; }
const char *class_prefix = CFCClass_get_prefix(klass);
if (strcmp(class_prefix, prefix) != 0) { continue; }
const char *class_name = CFCClass_get_name(klass);
const char *include_h = CFCClass_include_h(klass);
includes = CFCUtil_cat(includes, "#include \"", include_h,
"\"\n", NULL);
// Callbacks.
CFCMethod **fresh_methods = CFCClass_fresh_methods(klass);
for (int meth_num = 0; fresh_methods[meth_num] != NULL; meth_num++) {
CFCMethod *method = fresh_methods[meth_num];
// Define callback.
if (CFCMethod_novel(method) && !CFCMethod_final(method)) {
char *cb_def = CFCPerlMethod_callback_def(method, klass);
cb_defs = CFCUtil_cat(cb_defs, cb_def, "\n", NULL);
FREEMEM(cb_def);
}
}
// Add class aliases.
CFCPerlClass *class_binding = CFCPerlClass_singleton(class_name);
if (class_binding) {
const char *class_var = CFCClass_full_class_var(klass);
const char **aliases
= CFCPerlClass_get_class_aliases(class_binding);
for (size_t j = 0; aliases[j] != NULL; j++) {
const char *alias = aliases[j];
int alias_len = (int)strlen(alias);
const char pattern[] =
" cfish_Class_add_alias_to_registry("
"%s, \"%s\", %d);\n";
char *alias_add
= CFCUtil_sprintf(pattern, class_var, alias, alias_len);
alias_adds = CFCUtil_cat(alias_adds, alias_add, NULL);
FREEMEM(alias_add);
}
char *metadata_code
= CFCPerlClass_method_metadata_code(class_binding);
alias_adds = CFCUtil_cat(alias_adds, metadata_code, NULL);
FREEMEM(metadata_code);
}
}
const char pattern[] =
"%s"
"\n"
"#define %s\n" // privacy_sym
"\n"
"#include \"%sperl.h\"\n"
"#include \"XSBind.h\"\n"
"#include \"Clownfish/Class.h\"\n"
"#include \"Clownfish/Err.h\"\n"
"#include \"Clownfish/Obj.h\"\n"
"%s"
"\n"
"/* Avoid conflicts with Clownfish bool type. */\n"
"#define HAS_BOOL\n"
"#define PERL_NO_GET_CONTEXT\n"
"#include \"EXTERN.h\"\n"
"#include \"perl.h\"\n"
"#include \"XSUB.h\"\n"
"\n"
"static void\n"
"S_finish_callback_void(pTHX_ const char *meth_name) {\n"
" int count = call_method(meth_name, G_VOID | G_DISCARD);\n"
" if (count != 0) {\n"
" CFISH_THROW(CFISH_ERR, \"Bad callback to '%%s': %%i32\",\n"
" meth_name, (int32_t)count);\n"
" }\n"
" FREETMPS;\n"
" LEAVE;\n"
"}\n"
"\n"
"static CFISH_INLINE SV*\n"
"SI_do_callback_sv(pTHX_ const char *meth_name) {\n"
" int count = call_method(meth_name, G_SCALAR);\n"
" if (count != 1) {\n"
" CFISH_THROW(CFISH_ERR, \"Bad callback to '%%s': %%i32\",\n"
" meth_name, (int32_t)count);\n"
" }\n"
" dSP;\n"
" SV *return_sv = POPs;\n"
" PUTBACK;\n"
" return return_sv;\n"
"}\n"
"\n"
"static int64_t\n"
"S_finish_callback_i64(pTHX_ const char *meth_name) {\n"
" SV *return_sv = SI_do_callback_sv(aTHX_ meth_name);\n"
" int64_t retval;\n"
" if (sizeof(IV) == 8) {\n"
" retval = (int64_t)SvIV(return_sv);\n"
" }\n"
" else {\n"
" if (SvIOK(return_sv)) {\n"
" // It's already no more than 32 bits, so don't convert.\n"
" retval = SvIV(return_sv);\n"
" }\n"
" else {\n"
" // Maybe lossy.\n"
" double temp = SvNV(return_sv);\n"
" retval = (int64_t)temp;\n"
" }\n"
" }\n"
" FREETMPS;\n"
" LEAVE;\n"
" return retval;\n"
"}\n"
"\n"
"static double\n"
"S_finish_callback_f64(pTHX_ const char *meth_name) {\n"
" SV *return_sv = SI_do_callback_sv(aTHX_ meth_name);\n"
" double retval = SvNV(return_sv);\n"
" FREETMPS;\n"
" LEAVE;\n"
" return retval;\n"
"}\n"
"\n"
"static cfish_Obj*\n"
"S_finish_callback_obj(pTHX_ void *vself, const char *meth_name,\n"
" int nullable) {\n"
" SV *return_sv = SI_do_callback_sv(aTHX_ meth_name);\n"
" cfish_Obj *retval\n"
" = XSBind_perl_to_cfish_nullable(aTHX_ return_sv, CFISH_OBJ);\n"
" FREETMPS;\n"
" LEAVE;\n"
" if (!nullable && !retval) {\n"
" CFISH_THROW(CFISH_ERR, \"%%o#%%s cannot return NULL\",\n"
" cfish_Obj_get_class_name((cfish_Obj*)vself),\n"
" meth_name);\n"
" }\n"
" return retval;\n"
"}\n"
"\n"
"%s"
"\n"
"void\n"
"%sbootstrap_perl() {\n"
" dTHX;\n"
" %sbootstrap_parcel();\n"
"\n"
"%s"
"}\n"
"\n"
"%s";
char *content
= CFCUtil_sprintf(pattern, self->c_header, privacy_sym, prefix,
includes, cb_defs, prefix, prefix, alias_adds,
self->c_footer);
const char *src_dest = CFCHierarchy_get_source_dest(self->hierarchy);
char *host_c_path = CFCUtil_sprintf("%s" CHY_DIR_SEP "%sperl.c", src_dest,
prefix);
CFCUtil_write_file(host_c_path, content, strlen(content));
FREEMEM(host_c_path);
FREEMEM(content);
FREEMEM(alias_adds);
FREEMEM(cb_defs);
FREEMEM(includes);
FREEMEM(ordered);
}
void
CFCPerl_write_hostdefs(CFCPerl *self) {
const char pattern[] =
"%s\n"
"\n"
"#ifndef H_CFISH_HOSTDEFS\n"
"#define H_CFISH_HOSTDEFS 1\n"
"\n"
"/* Refcount / host object */\n"
"typedef union {\n"
" size_t count;\n"
" void *host_obj;\n"
"} cfish_ref_t;\n"
"\n"
"#define CFISH_OBJ_HEAD\\\n"
" cfish_ref_t ref;\n"
"\n"
"#endif /* H_CFISH_HOSTDEFS */\n"
"\n"
"%s\n";
char *content
= CFCUtil_sprintf(pattern, self->c_header, self->c_footer);
// Unlink then write file.
const char *inc_dest = CFCHierarchy_get_include_dest(self->hierarchy);
char *filepath = CFCUtil_sprintf("%s" CHY_DIR_SEP "cfish_hostdefs.h",
inc_dest);
remove(filepath);
CFCUtil_write_file(filepath, content, strlen(content));
FREEMEM(filepath);
FREEMEM(content);
}
void
CFCPerl_write_host_code(CFCPerl *self) {
CFCParcel **parcels = CFCParcel_all_parcels();
for (size_t i = 0; parcels[i]; ++i) {
CFCParcel *parcel = parcels[i];
if (!CFCParcel_included(parcel)) {
S_write_host_h(self, parcel);
S_write_host_c(self, parcel);
}
}
}
static char*
S_add_xsub_spec(char *xsub_specs, CFCPerlSub *xsub) {
const char *c_name = CFCPerlSub_c_name(xsub);
const char *alias = CFCPerlSub_get_alias(xsub);
const char *sep = xsub_specs[0] == '\0' ? "" : ",\n";
xsub_specs = CFCUtil_cat(xsub_specs, sep, " { \"", alias, "\", ",
c_name, " }", NULL);
return xsub_specs;
}
void
CFCPerl_write_bindings(CFCPerl *self, const char *boot_class,
CFCParcel **parcels) {
CFCUTIL_NULL_CHECK(boot_class);
CFCUTIL_NULL_CHECK(parcels);
CFCClass **ordered = CFCHierarchy_ordered_classes(self->hierarchy);
CFCPerlClass **registry = CFCPerlClass_registry();
char *privacy_syms = CFCUtil_strdup("");
char *includes = CFCUtil_strdup("");
char *generated_xs = CFCUtil_strdup("");
char *class_specs = CFCUtil_strdup("");
char *xsub_specs = CFCUtil_strdup("");
char *bootstrap_calls = CFCUtil_strdup("");
char *hand_rolled_xs = CFCUtil_strdup("");
for (size_t i = 0; parcels[i]; ++i) {
CFCParcel *parcel = parcels[i];
// Set host_module_name for parcel.
if (!CFCParcel_included(parcel) && CFCParcel_is_installed(parcel)) {
CFCParcel_set_host_module_name(parcel, boot_class);
}
// Bake the parcel privacy defines into the XS, so it can be compiled
// without any extra compiler flags.
const char *privacy_sym = CFCParcel_get_privacy_sym(parcel);
privacy_syms = CFCUtil_cat(privacy_syms, "#define ", privacy_sym,
"\n", NULL);
// Bootstrap calls.
const char *prefix = CFCParcel_get_prefix(parcel);
includes = CFCUtil_cat(includes, "#include \"", prefix, "perl.h\"\n",
NULL);
bootstrap_calls = CFCUtil_cat(bootstrap_calls, " ", prefix,
"bootstrap_perl();\n", NULL);
}
for (size_t i = 0; ordered[i] != NULL; i++) {
CFCClass *klass = ordered[i];
CFCParcel *parcel = CFCClass_get_parcel(klass);
int found = false;
for (size_t j = 0; parcels[j]; j++) {
if (parcel == parcels[j]) {
found = true;
break;
}
}
if (!found) { continue; }
// Pound-includes for generated headers.
const char *include_h = CFCClass_include_h(klass);
includes = CFCUtil_cat(includes, "#include \"", include_h, "\"\n",
NULL);
if (CFCClass_inert(klass)) { continue; }
int num_xsubs = 0;
// Constructors.
CFCPerlConstructor **constructors
= CFCPerlClass_constructor_bindings(klass);
for (size_t j = 0; constructors[j] != NULL; j++) {
CFCPerlSub *xsub = (CFCPerlSub*)constructors[j];
// Add the XSUB function definition.
char *xsub_def
= CFCPerlConstructor_xsub_def(constructors[j], klass);
generated_xs = CFCUtil_cat(generated_xs, xsub_def, "\n",
NULL);
FREEMEM(xsub_def);
// Add XSUB initialization at boot.
xsub_specs = S_add_xsub_spec(xsub_specs, xsub);
num_xsubs += 1;
CFCBase_decref((CFCBase*)constructors[j]);
}
FREEMEM(constructors);
// Methods.
CFCPerlMethod **methods = CFCPerlClass_method_bindings(klass);
for (size_t j = 0; methods[j] != NULL; j++) {
CFCPerlSub *xsub = (CFCPerlSub*)methods[j];
// Add the XSUB function definition.
char *xsub_def = CFCPerlMethod_xsub_def(methods[j], klass);
generated_xs = CFCUtil_cat(generated_xs, xsub_def, "\n",
NULL);
FREEMEM(xsub_def);
// Add XSUB initialization at boot.
xsub_specs = S_add_xsub_spec(xsub_specs, xsub);
num_xsubs += 1;
CFCBase_decref((CFCBase*)methods[j]);
}
FREEMEM(methods);
// Append XSBind_ClassSpec entry.
const char *class_name = CFCClass_get_name(klass);
CFCClass *parent = CFCClass_get_parent(klass);
char *parent_name;
if (parent) {
parent_name = CFCUtil_sprintf("\"%s\"", CFCClass_get_name(parent));
}
else {
parent_name = CFCUtil_strdup("NULL");
}
char *class_spec = CFCUtil_sprintf("{ \"%s\", %s, %d }", class_name,
parent_name, num_xsubs);
const char *sep = class_specs[0] == '\0' ? "" : ",\n";
class_specs = CFCUtil_cat(class_specs, sep, " ", class_spec,
NULL);
FREEMEM(class_spec);
FREEMEM(parent_name);
}
// Hand-rolled XS.
for (size_t i = 0; registry[i] != NULL; i++) {
CFCPerlClass *perl_class = registry[i];
CFCParcel *parcel = CFCPerlClass_get_parcel(perl_class);
int found = false;
for (size_t j = 0; parcels[j]; j++) {
if (parcel == parcels[j]) {
found = true;
break;
}
}
if (!found) { continue; }
const char *xs = CFCPerlClass_get_xs_code(perl_class);
hand_rolled_xs = CFCUtil_cat(hand_rolled_xs, xs, "\n", NULL);
}
const char pattern[] =
"%s" // Header.
"\n"
"%s" // Privacy syms.
"\n"
"#include \"XSBind.h\"\n"
"%s" // Includes.
"\n"
"#ifndef XS_INTERNAL\n"
" #define XS_INTERNAL XS\n"
"#endif\n"
"\n"
"%s" // Generated XS.
"\n"
"MODULE = %s PACKAGE = %s\n" // Boot class.
"\n"
"BOOT:\n"
"{\n"
" static const cfish_XSBind_ClassSpec class_specs[] = {\n"
"%s\n" // Class specs.
" };\n"
" static const cfish_XSBind_XSubSpec xsub_specs[] = {\n"
"%s\n" // XSUB specs.
" };\n"
" size_t num_classes\n"
" = sizeof(class_specs) / sizeof(class_specs[0]);\n"
" const char* file = __FILE__;\n"
"\n"
"%s" // Bootstrap calls.
"\n"
" cfish_XSBind_bootstrap(aTHX_ num_classes, class_specs,\n"
" xsub_specs, file);\n"
"}\n"
"\n"
"%s" // Hand-rolled XS.
"\n"
"%s"; // Footer
char *contents
= CFCUtil_sprintf(pattern, self->c_header, privacy_syms, includes,
generated_xs, boot_class, boot_class, class_specs,
xsub_specs, bootstrap_calls, hand_rolled_xs,
self->c_footer);
// Derive path to generated .xs file.
char *xs_path = CFCUtil_sprintf("%s" CHY_DIR_SEP "%s.xs", self->lib_dir,
boot_class);
S_replace_double_colons(xs_path, CHY_DIR_SEP_CHAR);
// Write out if there have been any changes.
CFCUtil_write_if_changed(xs_path, contents, strlen(contents));
FREEMEM(xs_path);
FREEMEM(contents);
FREEMEM(hand_rolled_xs);
FREEMEM(bootstrap_calls);
FREEMEM(xsub_specs);
FREEMEM(class_specs);
FREEMEM(generated_xs);
FREEMEM(includes);
FREEMEM(privacy_syms);
FREEMEM(ordered);
}
void
CFCPerl_write_xs_typemap(CFCPerl *self) {
CFCPerlTypeMap_write_xs_typemap(self->hierarchy);
}