The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "ppport.h"

#include <stdint.h>
#include <assert.h>

#include "spvm_compiler.h"
#include "spvm_hash.h"
#include "spvm_dynamic_array.h"
#include "spvm_util_allocator.h"
#include "spvm_constant_pool.h"
#include "spvm_bytecode_array.h"
#include "spvm_runtime.h"
#include "spvm_op.h"
#include "spvm_sub.h"
#include "spvm_package.h"
#include "spvm_sub.h"
#include "spvm_my_var.h"
#include "spvm_type.h"
#include "spvm_field.h"

#include "spvm_api.h"
#include "spvm_xs_util.h"

MODULE = SPVM::Object		PACKAGE = SPVM::Object

SV*
malloc_object(...)
  PPCODE:
{
  SV* sv_class = ST(0);
  SV* sv_type = ST(1);
  
  if (!SvOK(sv_type)) {
    croak("Type must be specified(SPVM::Object::malloc_object)");
  }
  
  const char* type = SvPV_nolen(sv_type);
  int32_t type_id = SPVM_XS_UTIL_search_type_id(type);
  if (type_id < 0) {
    croak("Unkown type \"%s\"(SPVM::Object::malloc_object", type);
  }
  
  // Set API
  SPVM_API* api = SPVM_XS_UTIL_get_api();
  
  // Malloc array
  SPVM_API_OBJECT* object =  api->malloc_object_noinc(api, type_id);
  
  // New sv object
  SV* sv_object = SPVM_XS_UTIL_new_sv_object(type, object);
  
  XPUSHs(sv_object);
  XSRETURN(1);
}

MODULE = SPVM::Array		PACKAGE = SPVM::Array

SV*
malloc_byte_array(...)
  PPCODE:
{
  SV* sv_class = ST(0);
  SV* sv_length = ST(1);
  
  int32_t length = (int32_t)SvIV(sv_length);
  
  // Set API
  SPVM_API* api = SPVM_XS_UTIL_get_api();
  
  // Malloc array
  SPVM_API_ARRAY* array =  api->malloc_byte_array_noinc(api, length);
  
  // New sv array
  SV* sv_array = SPVM_XS_UTIL_new_sv_array("byte[]", array);
  
  XPUSHs(sv_array);
  XSRETURN(1);
}

SV*
set_byte_array_elements(...)
  PPCODE:
{
  SV* sv_array = ST(0);
  SV* sv_nums = ST(1);
  AV* av_nums = SvRV(sv_nums);

  // Set API
  SPVM_API* api = SPVM_XS_UTIL_get_api();
  
  // Get content
  SPVM_API_ARRAY* array = SPVM_XS_UTIL_get_array(sv_array);
  
  int32_t length = api->get_array_length(api, array);
  
  int8_t* elements = api->get_byte_array_elements(api, array);
  
  {
    int32_t i;
    for (i = 0; i < length; i++) {
      SV** sv_num_ptr = av_fetch(av_nums, i, 0);
      SV* sv_num = sv_num_ptr ? *sv_num_ptr : &PL_sv_undef;
      elements[i] = (int8_t)SvIV(sv_num);
    }
  }
  
  XSRETURN(0);
}

SV*
malloc_short_array(...)
  PPCODE:
{
  SV* sv_class = ST(0);
  SV* sv_length = ST(1);
  
  int32_t length = (int32_t)SvIV(sv_length);
  
  // Set API
  SPVM_API* api = SPVM_XS_UTIL_get_api();
  
  // Malloc array
  SPVM_API_ARRAY* array =  api->malloc_short_array_noinc(api, length);
  
  // New sv array
  SV* sv_array = SPVM_XS_UTIL_new_sv_array("short[]", array);
  
  XPUSHs(sv_array);
  XSRETURN(1);
}

SV*
set_short_array_elements(...)
  PPCODE:
{
  SV* sv_array = ST(0);
  SV* sv_nums = ST(1);
  AV* av_nums = SvRV(sv_nums);

  // Set API
  SPVM_API* api = SPVM_XS_UTIL_get_api();
  
  // Get content
  SPVM_API_ARRAY* array = SPVM_XS_UTIL_get_array(sv_array);
  
  int32_t length = api->get_array_length(api, array);
  
  int16_t* elements = api->get_short_array_elements(api, array);
  
  {
    int32_t i;
    for (i = 0; i < length; i++) {
      SV** sv_num_ptr = av_fetch(av_nums, i, 0);
      SV* sv_num = sv_num_ptr ? *sv_num_ptr : &PL_sv_undef;
      elements[i] = (int16_t)SvIV(sv_num);
    }
  }
  
  XSRETURN(0);
}

SV*
malloc_int_array(...)
  PPCODE:
{
  SV* sv_class = ST(0);
  SV* sv_length = ST(1);
  
  int32_t length = (int32_t)SvIV(sv_length);
  
  // Set API
  SPVM_API* api = SPVM_XS_UTIL_get_api();
  
  // Malloc array
  SPVM_API_ARRAY* array =  api->malloc_int_array_noinc(api, length);
  
  // New sv array
  SV* sv_array = SPVM_XS_UTIL_new_sv_array("int[]", array);
  
  XPUSHs(sv_array);
  XSRETURN(1);
}

SV*
set_int_array_elements(...)
  PPCODE:
{
  SV* sv_array = ST(0);
  SV* sv_nums = ST(1);
  AV* av_nums = SvRV(sv_nums);

  // Set API
  SPVM_API* api = SPVM_XS_UTIL_get_api();
  
  // Get content
  SPVM_API_ARRAY* array = SPVM_XS_UTIL_get_array(sv_array);
  
  int32_t length = api->get_array_length(api, array);
  
  int32_t* elements = api->get_int_array_elements(api, array);
  
  {
    int32_t i;
    for (i = 0; i < length; i++) {
      SV** sv_num_ptr = av_fetch(av_nums, i, 0);
      SV* sv_num = sv_num_ptr ? *sv_num_ptr : &PL_sv_undef;
      elements[i] = (int32_t)SvIV(sv_num);
    }
  }
  
  XSRETURN(0);
}

SV*
malloc_long_array(...)
  PPCODE:
{
  SV* sv_class = ST(0);
  SV* sv_length = ST(1);
  
  int32_t length = (int32_t)SvIV(sv_length);
  
  // Set API
  SPVM_API* api = SPVM_XS_UTIL_get_api();
  
  // Malloc array
  SPVM_API_ARRAY* array =  api->malloc_long_array_noinc(api, length);
  
  // New sv array
  SV* sv_array = SPVM_XS_UTIL_new_sv_array("long[]", array);
  
  XPUSHs(sv_array);
  XSRETURN(1);
}

SV*
set_long_array_elements(...)
  PPCODE:
{
  SV* sv_array = ST(0);
  SV* sv_nums = ST(1);
  AV* av_nums = SvRV(sv_nums);

  // Set API
  SPVM_API* api = SPVM_XS_UTIL_get_api();
  
  // Get content
  SPVM_API_ARRAY* array = SPVM_XS_UTIL_get_array(sv_array);
  
  int32_t length = api->get_array_length(api, array);
  
  int64_t* elements = api->get_long_array_elements(api, array);
  
  {
    int32_t i;
    for (i = 0; i < length; i++) {
      SV** sv_num_ptr = av_fetch(av_nums, i, 0);
      SV* sv_num = sv_num_ptr ? *sv_num_ptr : &PL_sv_undef;
      elements[i] = (int64_t)SvIV(sv_num);
    }
  }
  
  XSRETURN(0);
}

SV*
malloc_float_array(...)
  PPCODE:
{
  SV* sv_class = ST(0);
  SV* sv_length = ST(1);
  
  int32_t length = (int32_t)SvIV(sv_length);
  
  // Set API
  SPVM_API* api = SPVM_XS_UTIL_get_api();
  
  // Malloc array
  SPVM_API_ARRAY* array =  api->malloc_float_array_noinc(api, length);
  
  // New sv array
  SV* sv_array = SPVM_XS_UTIL_new_sv_array("float[]", array);
  
  XPUSHs(sv_array);
  XSRETURN(1);
}

SV*
set_float_array_elements(...)
  PPCODE:
{
  SV* sv_array = ST(0);
  SV* sv_nums = ST(1);
  AV* av_nums = SvRV(sv_nums);

  // Set API
  SPVM_API* api = SPVM_XS_UTIL_get_api();
  
  // Get content
  SPVM_API_ARRAY* array = SPVM_XS_UTIL_get_array(sv_array);
  
  int32_t length = api->get_array_length(api, array);
  
  float* elements = api->get_float_array_elements(api, array);
  
  {
    int32_t i;
    for (i = 0; i < length; i++) {
      SV** sv_num_ptr = av_fetch(av_nums, i, 0);
      SV* sv_num = sv_num_ptr ? *sv_num_ptr : &PL_sv_undef;
      elements[i] = (float)SvNV(sv_num);
    }
  }
  
  XSRETURN(0);
}

SV*
malloc_double_array(...)
  PPCODE:
{
  SV* sv_class = ST(0);
  SV* sv_length = ST(1);
  
  int32_t length = (int32_t)SvIV(sv_length);
  
  // Set API
  SPVM_API* api = SPVM_XS_UTIL_get_api();
  
  // Malloc array
  SPVM_API_ARRAY* array =  api->malloc_double_array_noinc(api, length);
  
  // New sv array
  SV* sv_array = SPVM_XS_UTIL_new_sv_array("double[]", array);
  
  XPUSHs(sv_array);
  XSRETURN(1);
}

SV*
set_double_array_elements(...)
  PPCODE:
{
  SV* sv_array = ST(0);
  SV* sv_nums = ST(1);
  AV* av_nums = SvRV(sv_nums);

  // Set API
  SPVM_API* api = SPVM_XS_UTIL_get_api();
  
  // Get content
  SPVM_API_ARRAY* array = SPVM_XS_UTIL_get_array(sv_array);
  
  int32_t length = api->get_array_length(api, array);
  
  double* elements = api->get_double_array_elements(api, array);
  
  {
    int32_t i;
    for (i = 0; i < length; i++) {
      SV** sv_num_ptr = av_fetch(av_nums, i, 0);
      SV* sv_num = sv_num_ptr ? *sv_num_ptr : &PL_sv_undef;
      elements[i] = (double)SvNV(sv_num);
    }
  }
  
  XSRETURN(0);
}

SV*
malloc_string_raw(...)
  PPCODE:
{
  SV* sv_class = ST(0);
  SV* sv_string = ST(1);
  
  int32_t length = (int32_t)sv_len(sv_string);
  
  // Set API
  SPVM_API* api = SPVM_XS_UTIL_get_api();
  
  // Malloc array
  SPVM_API_ARRAY* array =  api->malloc_byte_array_noinc(api, length);
  
  const char* string = SvPV_nolen(sv_string);
  int8_t* elements = api->get_byte_array_elements(api, array);
  memcpy(elements, string, length);
  
  // New sv array
  SV* sv_array = SPVM_XS_UTIL_new_sv_array("string", array);
  
  XPUSHs(sv_array);
  XSRETURN(1);
}

MODULE = SPVM		PACKAGE = SPVM

SV*
compile(...)
  PPCODE:
{
  // Create compiler
  SPVM_COMPILER* compiler = SPVM_COMPILER_new();

  // Add package
  AV* av_package_infos = get_av("SPVM::PACKAGE_INFOS", 0);
  int32_t av_package_infos_length = (int32_t)av_len(av_package_infos) + 1;
  {
    int32_t i;
    for (i = 0; i < av_package_infos_length; i++) {
      SV** sv_package_info_ptr = av_fetch(av_package_infos, i, 0);
      SV* sv_package_info = sv_package_info_ptr ? *sv_package_info_ptr : &PL_sv_undef;
      HV* hv_package_info = (HV*)SvRV(sv_package_info);
      
      // Name
      SV** sv_name_ptr = hv_fetch(hv_package_info, "name", strlen("name"), 0);
      SV* sv_name = sv_name_ptr ? *sv_name_ptr : &PL_sv_undef;
      const char* name = SvPV_nolen(sv_name);
      
      // File
      SV** sv_file_ptr = hv_fetch(hv_package_info, "file", strlen("file"), 0);
      SV* sv_file = sv_file_ptr ? *sv_file_ptr : &PL_sv_undef;
      const char* file = SvPV_nolen(sv_file);
      
      // Line
      SV** sv_line_ptr = hv_fetch(hv_package_info, "line", strlen("line"), 0);
      SV* sv_line = sv_line_ptr ? *sv_line_ptr : &PL_sv_undef;
      int32_t line = (int32_t)SvIV(sv_line);
      
      // push package to compiler use stack
      SPVM_OP* op_use_package = SPVM_OP_new_op_use_from_package_name(compiler, name, file, line);
      SPVM_DYNAMIC_ARRAY_push(compiler->op_use_stack, op_use_package);
      SPVM_HASH_insert(compiler->op_use_symtable, name, strlen(name), op_use_package);
    }
  }
  
  // Add include paths
  AV* av_include_paths = get_av("main::INC", 0);;
  int32_t av_include_paths_length = (int32_t)av_len(av_include_paths) + 1;
  {
    int32_t i;
    for (i = 0; i < av_include_paths_length; i++) {
      SV** sv_include_path_ptr = av_fetch(av_include_paths, i, 0);
      SV* sv_include_path = sv_include_path_ptr ? *sv_include_path_ptr : &PL_sv_undef;
      char* include_path = SvPV_nolen(sv_include_path);
      SPVM_DYNAMIC_ARRAY_push(compiler->include_pathes, include_path);
    }
  }
  
  // Compile SPVM
  SPVM_COMPILER_compile(compiler);
  if (compiler->error_count > 0) {
    croak("SPVM compile error %d", compiler->error_count);
  }
  
  // Set compiler
  size_t iv_compiler = PTR2IV(compiler);
  SV* sviv_compiler = sv_2mortal(newSViv(iv_compiler));
  SV* sv_compiler = sv_2mortal(newRV_inc(sviv_compiler));
  sv_setsv(get_sv("SPVM::COMPILER", 0), sv_compiler);
  
  XSRETURN(0);
}

SV*
build_sub_symtable(...)
  PPCODE:
{
  // Get compiler
  SV* sv_compiler = get_sv("SPVM::COMPILER", 0);
  SV* sviv_compiler = SvROK(sv_compiler) ? SvRV(sv_compiler) : sv_compiler;
  size_t iv_compiler = SvIV(sviv_compiler);
  SPVM_COMPILER* compiler = INT2PTR(SPVM_COMPILER*, iv_compiler);
  
  // Subroutine information
  HV* hv_sub_symtable = get_hv("SPVM::SUB_SYMTABLE", 0);
  
  // abs_name, arg_types, return_type, id, type_id
  SPVM_DYNAMIC_ARRAY* op_packages = compiler->op_packages;
  {
    int32_t package_index;
    for (package_index = 0; package_index < op_packages->length; package_index++) {
      SPVM_OP* op_package = SPVM_DYNAMIC_ARRAY_fetch(op_packages, package_index);
      SPVM_DYNAMIC_ARRAY* op_subs = op_package->uv.package->op_subs;
      {
        int32_t sub_index;
        for (sub_index = 0; sub_index < op_subs->length; sub_index++) {
          // Sub information
          HV* hv_sub_info = (HV*)sv_2mortal((SV*)newHV());
          
          SPVM_OP* op_sub = SPVM_DYNAMIC_ARRAY_fetch(op_subs, sub_index);
          SPVM_SUB* sub = op_sub->uv.sub;
          const char* sub_abs_name = sub->abs_name;
          
          // Subroutine id
          int32_t sub_id = sub->constant_pool_index;
          SV* sv_sub_id = sv_2mortal(newSViv(sub_id));
          hv_store(hv_sub_info, "id", strlen("id"), SvREFCNT_inc(sv_sub_id), 0);
          
          // Argument types
          AV* av_arg_type_names = (AV*)sv_2mortal((SV*)newAV());
          SPVM_DYNAMIC_ARRAY* op_args = sub->op_args;
          {
            int32_t arg_index;
            for (arg_index = 0; arg_index < op_args->length; arg_index++) {
              SPVM_OP* op_arg = SPVM_DYNAMIC_ARRAY_fetch(op_args, arg_index);
              SPVM_OP* op_arg_type = op_arg->uv.my_var->op_type;
              const char* arg_type_name = op_arg_type->uv.type->name;
              
              SV* sv_arg_type_name = sv_2mortal(newSVpv(arg_type_name, 0));
              av_push(av_arg_type_names, SvREFCNT_inc(sv_arg_type_name));
            }
          }
          SV* sv_arg_type_names = sv_2mortal(newRV_inc((SV*)av_arg_type_names));
          hv_store(hv_sub_info, "arg_types", strlen("arg_types"), SvREFCNT_inc(sv_arg_type_names), 0);
          
          // Return type
          SPVM_OP* op_return_type = sub->op_return_type;
          SPVM_TYPE* return_type = SPVM_OP_get_type(compiler, op_return_type);
          if (return_type) {
            const char* return_type = op_return_type->uv.type->name;
            SV* sv_return_type = sv_2mortal(newSVpv(return_type, 0));
            hv_store(hv_sub_info, "return_type", strlen("return_type"), SvREFCNT_inc(sv_return_type), 0);
          }
          else {
            hv_store(hv_sub_info, "return_type", strlen("return_type"), &PL_sv_undef, 0);
          }
          
          SV* sv_sub_info = sv_2mortal(newRV_inc((SV*)hv_sub_info));
          hv_store(hv_sub_symtable, sub_abs_name, strlen(sub_abs_name), SvREFCNT_inc(sv_sub_info), 0);
        }
      }
    }
  }
  
  XSRETURN(0);
}

SV*
build_field_symtable(...)
  PPCODE:
{
  // Get compiler
  SV* sv_compiler = get_sv("SPVM::COMPILER", 0);
  SV* sviv_compiler = SvROK(sv_compiler) ? SvRV(sv_compiler) : sv_compiler;
  size_t iv_compiler = SvIV(sviv_compiler);
  SPVM_COMPILER* compiler = INT2PTR(SPVM_COMPILER*, iv_compiler);
  
  // Field symbol table
  HV* hv_field_symtable = get_hv("SPVM::FIELD_SYMTABLE", 0);
  
  // name, arg_types, return_type
  SPVM_DYNAMIC_ARRAY* op_packages = compiler->op_packages;
  {
    int32_t package_index;
    for (package_index = 0; package_index < op_packages->length; package_index++) {
      SPVM_OP* op_package = SPVM_DYNAMIC_ARRAY_fetch(op_packages, package_index);
      const char* package_name = op_package->uv.package->op_name->uv.name;
      
      HV* hv_package_info = (HV*)sv_2mortal((SV*)newHV());
      
      SPVM_DYNAMIC_ARRAY* op_fields = op_package->uv.package->op_fields;
      {
        int32_t field_index;
        for (field_index = 0; field_index < op_fields->length; field_index++) {
          HV* hv_field_info = (HV*)sv_2mortal((SV*)newHV());
          
          SPVM_OP* op_field = SPVM_DYNAMIC_ARRAY_fetch(op_fields, field_index);
          SPVM_FIELD* field = op_field->uv.field;
          const char* field_name = field->op_name->uv.name;
          
          // Field id
          int32_t field_id = field->index;
          SV* sv_field_id = sv_2mortal(newSViv(field_id));
          hv_store(hv_field_info, "id", strlen("id"), SvREFCNT_inc(sv_field_id), 0);
          
          // Field type
          const char* field_type = field->op_type->uv.type->name;
          SV* sv_field_type = sv_2mortal(newSVpv(field_type, 0));
          hv_store(hv_field_info, "type", strlen("type"), SvREFCNT_inc(sv_field_type), 0);
          
          SV* sv_field_info = sv_2mortal(newRV_inc((SV*)hv_field_info));
          hv_store(hv_package_info, field_name, strlen(field_name), SvREFCNT_inc(sv_field_info), 0);
        }
      }
      
      SV* sv_package_info = sv_2mortal(newRV_inc((SV*)hv_package_info));
      hv_store(hv_field_symtable, package_name, strlen(package_name), SvREFCNT_inc(sv_package_info), 0);
    }
  }
  
  XSRETURN(0);
}

SV*
build_type_symtable(...)
  PPCODE:
{
  // Get compiler
  SV* sv_compiler = get_sv("SPVM::COMPILER", 0);
  SV* sviv_compiler = SvROK(sv_compiler) ? SvRV(sv_compiler) : sv_compiler;
  size_t iv_compiler = SvIV(sviv_compiler);
  SPVM_COMPILER* compiler = INT2PTR(SPVM_COMPILER*, iv_compiler);
  
  // Subroutine information
  HV* hv_type_symtable = get_hv("SPVM::TYPE_SYMTABLE", 0);
  
  // abs_name, arg_types, return_type, id, type_id
  SPVM_DYNAMIC_ARRAY* types = compiler->types;
  {
    int32_t type_index;
    for (type_index = 0; type_index < types->length; type_index++) {
      SPVM_TYPE* type = SPVM_DYNAMIC_ARRAY_fetch(types, type_index);
      
      const char* type_name = type->name;
      int32_t type_id = type->id;
      SV* sv_type_id = sv_2mortal(newSViv(type_id));
      
      hv_store(hv_type_symtable, type_name, strlen(type_name), SvREFCNT_inc(sv_type_id), 0);
    }
  }
  
  XSRETURN(0);
}

SV*
build_runtime(...)
  PPCODE:
{
  // Get compiler
  SV* sv_compiler = get_sv("SPVM::COMPILER", 0);
  SV* sviv_compiler = SvROK(sv_compiler) ? SvRV(sv_compiler) : sv_compiler;
  size_t iv_compiler = SvIV(sviv_compiler);
  SPVM_COMPILER* compiler = INT2PTR(SPVM_COMPILER*, iv_compiler);
  
  // Create run-time
  SPVM_RUNTIME* runtime = SPVM_COMPILER_new_runtime(compiler);
  
  // SPVM API
  SPVM_API* api = runtime->api;
  size_t iv_api = PTR2IV(api);
  SV* sviv_api = sv_2mortal(newSViv(iv_api));
  SV* sv_api = sv_2mortal(newRV_inc(sviv_api));
  sv_setsv(get_sv("SPVM::API", 0), sv_api);
  
  XSRETURN(0);
}

SV*
free_compiler(...)
  PPCODE:
{
  // Get compiler
  SV* sv_compiler = get_sv("SPVM::COMPILER", 0);
  SV* sviv_compiler = SvROK(sv_compiler) ? SvRV(sv_compiler) : sv_compiler;
  size_t iv_compiler = SvIV(sviv_compiler);
  SPVM_COMPILER* compiler = INT2PTR(SPVM_COMPILER*, iv_compiler);
  
  // Free compiler
  SPVM_COMPILER_free(compiler);
  
  // Set undef to compiler
  sv_setsv(get_sv("SPVM::COMPILER", 0), &PL_sv_undef);
  
  XSRETURN(0);
}

SV*
call_sub(...)
  PPCODE:
{
  SV* sv_sub_abs_name = ST(0);
  
  HV* hv_sub_symtable = get_hv("SPVM::SUB_SYMTABLE", 0);
  
  const char* sub_abs_name = SvPV_nolen(sv_sub_abs_name);
  SV** sv_sub_info_ptr = hv_fetch(hv_sub_symtable, sub_abs_name, strlen(sub_abs_name), 0);
  SV* sv_sub_info = sv_sub_info_ptr ? *sv_sub_info_ptr : &PL_sv_undef;
  HV* hv_sub_info = (HV*)SvRV(sv_sub_info);
  
  // Subroutine id
  SV** sv_sub_id_ptr = hv_fetch(hv_sub_info, "id", strlen("id"), 0);
  SV* sv_sub_id = sv_sub_id_ptr ? *sv_sub_id_ptr : &PL_sv_undef;
  int32_t sub_id = (int32_t)SvIV(sv_sub_id);
  
  // Argument types
  SV** sv_arg_type_names_ptr = hv_fetch(hv_sub_info, "arg_types", strlen("arg_types"), 0);
  SV* sv_arg_type_names = sv_arg_type_names_ptr ? *sv_arg_type_names_ptr : &PL_sv_undef;
  AV* av_arg_type_names = (AV*)SvRV(sv_arg_type_names);
  int32_t args_length = av_len(av_arg_type_names) + 1;
  
  // Return type
  SV** sv_return_type_ptr = hv_fetch(hv_sub_info, "return_type", strlen("return_type"), 0);
  SV* sv_return_type = sv_return_type_ptr ? *sv_return_type_ptr : &PL_sv_undef;
  
  // Get API
  SV* sv_api = get_sv("SPVM::API", 0);
  SV* sviv_api = SvROK(sv_api) ? SvRV(sv_api) : sv_api;
  size_t iv_api = SvIV(sviv_api);
  SPVM_API* api = INT2PTR(SPVM_API*, iv_api);
  
  // Check argument count
  if (items - 1 != args_length) {
    croak("Argument count is defferent");
  }
  
  // Push arguments
  {
    int32_t arg_index;
    for (arg_index = 0; arg_index < args_length; arg_index++) {
      SV* sv_base_object = ST(arg_index + 1);
      
      SV** sv_arg_type_name_ptr = av_fetch(av_arg_type_names, arg_index, 0);
      SV* sv_arg_type_name = sv_arg_type_name_ptr ? *sv_arg_type_name_ptr : &PL_sv_undef;
      const char* arg_type_name = SvPV_nolen(sv_arg_type_name);
      
      if (sv_isobject(sv_base_object) && sv_derived_from(sv_base_object, "SPVM::BaseObject")) {
        
        HV* hv_base_object = (HV*)SvRV(sv_base_object);
        
        SV** sv_base_object_type_name_ptr = hv_fetch(hv_base_object, "type", strlen("type"), 0);
        SV* sv_base_object_type_name = sv_base_object_type_name_ptr ? *sv_base_object_type_name_ptr : &PL_sv_undef;
        const char* base_object_type_name = SvPV_nolen(sv_base_object_type_name);
        
        if (!strEQ(base_object_type_name, arg_type_name)) {
          croak("Argument base_object type need %s, but %s", arg_type_name, base_object_type_name);
        }
        
        // Get content
        SV** sv_content_ptr = hv_fetch(hv_base_object, "content", strlen("content"), 0);
        SV* sv_content = sv_content_ptr ? *sv_content_ptr : &PL_sv_undef;
        SV* sviv_content = SvRV(sv_content);
        size_t iv_content = SvIV(sviv_content);
        SPVM_API_BASE_OBJECT* base_object = INT2PTR(SPVM_API_BASE_OBJECT*, iv_content);
        
        api->push_var_object(api, base_object);
      }
      else {
        SV* sv_value = sv_base_object;
        if (strEQ(arg_type_name, "byte")) {
          int8_t value = (int8_t)SvIV(sv_value);
          api->push_var_byte(api, value);
        }
        else if (strEQ(arg_type_name, "short")) {
          int16_t value = (int16_t)SvIV(sv_value);
          api->push_var_short(api, value);
        }
        else if (strEQ(arg_type_name, "int")) {
          int32_t value = (int32_t)SvIV(sv_value);
          api->push_var_int(api, value);
        }
        else if (strEQ(arg_type_name, "long")) {
          int64_t value = (int64_t)SvIV(sv_value);
          api->push_var_long(api, value);
        }
        else if (strEQ(arg_type_name, "float")) {
          float value = (float)SvNV(sv_value);
          api->push_var_float(api, value);
        }
        else if (strEQ(arg_type_name, "double")) {
          double value = (double)SvNV(sv_value);
          api->push_var_double(api, value);
        }
        else {
          assert(0);
        }
      }
    }
  }
  
  api->call_sub(api, sub_id);
  
  if (SvOK(sv_return_type)) {
    // Create base_object
    HV* hv_base_object = sv_2mortal((SV*)newHV());
    SV* sv_base_object = sv_2mortal(newRV_inc((SV*)hv_base_object));
    HV* hv_class = gv_stashpv("SPVM::Object", 0);
    sv_bless(sv_base_object, hv_class);
    
    const char* return_type = SvPV_nolen(sv_return_type);
    
    if (strEQ(return_type, "byte")) {
      int8_t return_value = api->pop_retval_byte(api);
      SV* sv_value = sv_2mortal(newSViv(return_value));
      XPUSHs(sv_value);
    }
    else if (strEQ(return_type, "short")) {
      int16_t return_value = api->pop_retval_short(api);
      SV* sv_value = sv_2mortal(newSViv(return_value));
      XPUSHs(sv_value);
    }
    else if (strEQ(return_type, "int")) {
      int32_t return_value = api->pop_retval_int(api);
      SV* sv_value = sv_2mortal(newSViv(return_value));
      XPUSHs(sv_value);
    }
    else if (strEQ(return_type, "long")) {
      int64_t return_value = api->pop_retval_long(api);
      SV* sv_value = sv_2mortal(newSViv(return_value));
      XPUSHs(sv_value);
    }
    else if (strEQ(return_type, "float")) {
      float return_value = api->pop_retval_float(api);
      SV* sv_value = sv_2mortal(newSVnv(return_value));
      NV value = SvNV(sv_value);
      XPUSHs(sv_value);
    }
    else if (strEQ(return_type, "double")) {
      double return_value = api->pop_retval_double(api);
      SV* sv_value = sv_2mortal(newSVnv(return_value));
      XPUSHs(sv_value);
    }
    else {
      SPVM_API_BASE_OBJECT* return_value = api->pop_retval_object(api);
      
      int32_t type_length = strlen(return_type);
      if (return_type[type_length - 1] == ']' || strcmp(return_type, "string") == 0) {
        SV* sv_array = SPVM_XS_UTIL_new_sv_array(return_type, (SPVM_API_ARRAY*)return_value);
        XPUSHs(sv_array);
      }
      else {
        SV* sv_object = SPVM_XS_UTIL_new_sv_object(return_type, (SPVM_API_OBJECT*)return_value);
        XPUSHs(sv_object);
      }
    }
    XSRETURN(1);
  }
  else {
    XSRETURN(0);
  }
}