From: Aleksey Demakov Date: Fri, 12 Dec 2008 11:30:57 +0000 (+0000) Subject: add Paul Brannan's ruby-libjit X-Git-Tag: before.move.to.git~44 X-Git-Url: https://git.unchartedbackwaters.co.uk/w/?a=commitdiff_plain;h=fd8a209adf2e72ff36c4ed48699fafd4b7110597;p=francis%2Flibjit.git add Paul Brannan's ruby-libjit --- diff --git a/ChangeLog b/ChangeLog index ca51844..a2b66e9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2008-12-12 Aleksey Demakov + + * jitruby/*: add Paul Brannan's ruby-libjit. + 2008-12-11 Juan Jesus Garcia de Soria * jit/jit-insn.c (jit_insn_call_native): extend small int return diff --git a/jitruby/README b/jitruby/README new file mode 100644 index 0000000..9c55e22 --- /dev/null +++ b/jitruby/README @@ -0,0 +1,50 @@ +Ruby-libjit 0.1.0 +Copyright (C) 2008 Paul Brannan + +Ruby-libjit is a wrapper for the libjit library. It provides basic +functionality for jit-compiling functions, including integrating those +functions as callable methods from within Ruby. Abstractions are also +provided so that jit code may be written in a ruby-like manner. + +Please see the file COPYING for license information. + +A simple example: + + :include: sample/simple.rb + +Looping structures and other abstractions are provided to make writing +jit code easier: + + :include: sample/fib.rb + +To build ruby-libjit, you will need to install libjit. If it is not +available pre-compiled for your platform, you may build the latest +release like this: + + $ wget ftp://ftp.gnu.org/gnu/dotgnu/pnet/libjit-0.1.0.tar.gz + $ tar xvfz libjit-0.1.0.tar.gz + $ cd libjit-0.1.0 + $ ./configure + $ make + $ sudo make install + +Or the latest development version like this: + + $ cvs -z3 -d:pserver:anonymous@cvs.sv.gnu.org:/sources/dotgnu-pnet co libjit + $ cd libjit + $ ./auto_gen.sh + $ ./configure + $ make + $ sudo make install + +To build ruby-libjit, run setup.rb: + + $ ruby setup.rb config + $ ruby setup.rb setup + $ sudo ruby setup.rb install + +For a more complete JIT framework and compiler for Ruby code, please +take a look at Ludicrous: + + http://rubystuff.org/ludicrous/ + diff --git a/jitruby/ext/extconf.rb b/jitruby/ext/extconf.rb new file mode 100644 index 0000000..9ec8804 --- /dev/null +++ b/jitruby/ext/extconf.rb @@ -0,0 +1,108 @@ +require 'mkmf' +require 'rbconfig' + +if Config::CONFIG['host_os'] =~ /cygwin|win32|windows/ then + need_windows_h = [ 'windows.h' ] + $defs << ' -DNEED_WINDOWS_H' +else + need_windows_h = [ ] +end + +if not have_library('jit', 'jit_init', need_windows_h + ["jit/jit.h"]) then + $stderr.puts "libjit not found" + exit 1 +end + +if not have_macro("SIZEOF_VALUE", "ruby.h") then + check_sizeof("VALUE", "ruby.h") +end + +if not have_macro("SIZEOF_ID", "ruby.h") then + check_sizeof("ID", "ruby.h") +end + +if have_macro("_GNU_SOURCE", "ruby.h") then + $defs.push("-DRUBY_DEFINES_GNU_SOURCE") +end + +have_func("rb_class_boot", "ruby.h") +have_func("rb_errinfo", "ruby.h") +have_func('fmemopen') +have_func("rb_ensure", "ruby.h") + +if have_header('ruby/node.h') then + # ruby.h defines HAVE_RUBY_NODE_H, even though it is not there + $defs.push("-DREALLY_HAVE_RUBY_NODE_H") +elsif have_header('node.h') then + # okay +else + $defs.push("-DNEED_MINIMAL_NODE") +end + +have_header('env.h') + +checking_for("whether VALUE is a pointer") do + if not try_link(<<"SRC") +#include +int main() +{ + VALUE v; + v /= 5; +} +SRC + then + $defs.push("-DVALUE_IS_PTR"); + end +end + +if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'rbx' then + $defs.push("-DHAVE_RUBINIUS") +end + +rb_files = Dir['*.rb'] +rpp_files = Dir['*.rpp'] +generated_files = rpp_files.map { |f| f.sub(/\.rpp$/, '') } + +srcs = Dir['*.c'] +generated_files.each do |f| + if f =~ /\.c$/ then + srcs << f + end +end +srcs.uniq! +$objs = srcs.map { |f| f.sub(/\.c$/, ".#{$OBJEXT}") } + +if Config::CONFIG['CC'] == 'gcc' then + # $CFLAGS << ' -Wall -g -pedantic -Wno-long-long' + $CFLAGS << ' -Wall -g' +end + +create_makefile("jit") + +append_to_makefile = '' + +rpp_files.each do |rpp_file| +dest_file = rpp_file.sub(/\.rpp$/, '') +append_to_makefile << < +#endif + +#include + +#include + +#include +#include + +#include "rubyjit.h" +#include "method_data.h" + +#ifdef NEED_MINIMAL_NODE +#include "minimal_node.h" +#endif + +#ifndef RARRAY_LEN +#define RARRAY_LEN(a) RARRAY(a)->len +#endif + +#ifndef RARRAY_PTR +#define RARRAY_PTR(a) RARRAY(a)->ptr +#endif + +static VALUE rb_mJIT; +static VALUE rb_cContext; +static VALUE rb_cFunction; +static VALUE rb_cType; +static VALUE rb_mABI; +static VALUE rb_cValue; +static VALUE rb_cLabel; +static VALUE rb_mCall; +static VALUE rb_cClosure; + +jit_type_t jit_type_VALUE; +jit_type_t jit_type_ID; +jit_type_t jit_type_Function_Ptr; + +static jit_type_t ruby_vararg_signature; + +typedef void (*Void_Function_Ptr)(); + +struct Closure +{ + VALUE function; + Void_Function_Ptr function_ptr; +}; + +#ifdef VALUE_IS_PTR +/* Rubinius */ +typedef jit_ptr jit_VALUE; +#define jit_underlying_type_VALUE jit_type_void_ptr +#define SET_CONSTANT_VALUE(c, v) (c.un.ptr_value = v) + +#elif SIZEOF_VALUE == 4 +/* 32-bit */ +typedef jit_uint jit_VALUE; +#define jit_underlying_type_VALUE jit_type_uint +#define SET_CONSTANT_VALUE(c, v) (c.un.uint_value = v) + +#elif SIZEOF_VALUE == 8 +/* 64-bit */ +typedef jit_ulong jit_VALUE; +#define jit_underlying_type_VALUE jit_type_ulong +#define SET_CONSTANT_VALUE(c, v) (c.un.ulong_value = v) + +#else +#error "Unsupported size for VALUE" +#endif + +#if SIZEOF_ID == 4 +/* 32-bit */ +typedef jit_uint jit_ID; +#define jit_underlying_type_ID jit_type_uint +#define SET_CONSTANT_ID(c, v) (c.un.uint_value = v) +#elif SIZEOF_ID == 8 +/* 64-bit */ +typedef jit_ulong jit_ID; +#define jit_underlying_type_ID jit_type_ulong +#define SET_CONSTANT_ID(c, v) (c.un.ulong_value = v) +#else +#error "Unsupported size for ID" +#endif + +typedef jit_ptr jit_Function_Ptr; +#define jit_underlying_type_void_ptr jit_type_void_ptr +#define SET_FUNCTION_POINTER_VALUE(c, v) (c.un.ptr_value = v) + +#ifdef HAVE_RB_ERRINFO +#define ruby_errinfo rb_errinfo() +#endif + +/* --------------------------------------------------------------------------- + * Utility functions + * --------------------------------------------------------------------------- + */ + +static VALUE lookup_const(VALUE module, VALUE symbol) +{ + if(SYMBOL_P(symbol)) + { + return rb_const_get(module, SYM2ID(symbol)); + } + else + { + return symbol; + } +} + +static void check_type(char const * param_name, VALUE expected_klass, VALUE val) +{ + if(!rb_obj_is_kind_of(val, expected_klass)) + { + rb_raise( + rb_eTypeError, + "Wrong type for %s; expected %s but got %s", + param_name, + rb_class2name(expected_klass), + rb_class2name(CLASS_OF(val))); + } +} + +void raise_memory_error_if_zero(void * v) +{ + if(!v) + { + rb_raise(rb_eNoMemError, "Out of memory"); + } +} + +/* --------------------------------------------------------------------------- + * Context + * --------------------------------------------------------------------------- + */ + +static void context_mark(jit_context_t context) +{ + VALUE functions = (VALUE)jit_context_get_meta(context, RJT_FUNCTIONS); + rb_gc_mark(functions); +} + +/* + * call-seq: + * context = Context.new + * + * Create a new context. + */ +static VALUE context_s_new(VALUE klass) +{ + jit_context_t context = jit_context_create(); + jit_context_set_meta(context, RJT_FUNCTIONS, (void*)rb_ary_new(), 0); + return Data_Wrap_Struct(rb_cContext, context_mark, jit_context_destroy, context); +} + +/* + * call-seq: + * context.build { ... } + * + * Acquire a lock on the context so it can be used to build a function. + */ +static VALUE context_build(VALUE self) +{ + jit_context_t context; + Data_Get_Struct(self, struct _jit_context, context); + jit_context_build_start(context); +#ifdef HAVE_RB_ENSURE + return rb_ensure( + rb_yield, + self, + RUBY_METHOD_FUNC(jit_context_build_end), + (VALUE)context); +#else + /* Rubinius does not yet have rb_ensure */ + rb_yield(self); + jit_context_build_end(context); +#endif +} + +/* + * call-seq: + * Context.build { |context| ... } + * + * Create a context and acquire a lock on it, then yield the context to + * the block. + */ +static VALUE context_s_build(VALUE klass) +{ + return context_build(context_s_new(klass)); +} + +/* --------------------------------------------------------------------------- + * Closure + * --------------------------------------------------------------------------- + */ + +static void mark_closure(struct Closure * closure) +{ + rb_gc_mark(closure->function); +} + +VALUE closure_to_int(VALUE self) +{ + struct Closure * closure; + Data_Get_Struct(self, struct Closure, closure); + VALUE v = ULONG2NUM((unsigned long)closure->function_ptr); + return v; +} + +VALUE closure_to_s(VALUE self) +{ + struct Closure * closure; + VALUE args[4]; + Data_Get_Struct(self, struct Closure, closure); + args[0] = rb_str_new2("#"); + args[1] = ULONG2NUM((unsigned long)self); + args[2] = rb_any_to_s(closure->function); + args[3] = ULONG2NUM((unsigned long)closure->function_ptr); + return rb_f_sprintf(sizeof(args)/sizeof(args[0]), args); +} + +VALUE closure_inspect(VALUE self) +{ + return closure_to_s(self); +} + +/* --------------------------------------------------------------------------- + * Function + * --------------------------------------------------------------------------- + */ + +static void mark_function(jit_function_t function) +{ + rb_gc_mark((VALUE)jit_function_get_meta(function, RJT_VALUE_OBJECTS)); + rb_gc_mark((VALUE)jit_function_get_meta(function, RJT_CONTEXT)); +} + +static VALUE create_function(int argc, VALUE * argv, VALUE klass) +{ + VALUE context_v; + VALUE signature_v; + VALUE parent_function_v; + + jit_function_t function; + jit_function_t parent_function; + jit_context_t context; + jit_type_t signature; + jit_type_t untagged_signature; + + VALUE function_v; + VALUE functions; + + int signature_tag; + + rb_scan_args(argc, argv, "21", &context_v, &signature_v, &parent_function_v); + + Data_Get_Struct(context_v, struct _jit_context, context); + Data_Get_Struct(signature_v, struct _jit_type, signature); + + signature_tag = jit_type_get_kind(signature); + + /* If this signature was tagged, get the untagged type */ + if((untagged_signature = jit_type_get_tagged_type(signature))) + { + signature = untagged_signature; + } + + if(RTEST(parent_function_v)) + { + /* If this function has a parent, then it is a nested function */ + Data_Get_Struct(parent_function_v, struct _jit_function, parent_function); + function = jit_function_create_nested(context, signature, parent_function); + } + else + { + /* Otherwise, it's a standalone function */ + function = jit_function_create(context, signature); + } + + /* Make sure the function is around as long as the context is */ + if(!jit_function_set_meta(function, RJT_VALUE_OBJECTS, (void *)rb_ary_new(), 0, 0)) + { + rb_raise(rb_eNoMemError, "Out of memory"); + } + + /* Remember the signature's tag for later */ + if(!jit_function_set_meta(function, RJT_TAG_FOR_SIGNATURE, (void *)signature_tag, 0, 0)) + { + rb_raise(rb_eNoMemError, "Out of memory"); + } + + /* And remember the function's context for later */ + if(!jit_function_set_meta(function, RJT_CONTEXT, (void *)context_v, 0, 0)) + { + rb_raise(rb_eNoMemError, "Out of memory"); + } + + function_v = Data_Wrap_Struct(rb_cFunction, mark_function, 0, function); + + /* Add this function to the context's list of functions */ + functions = (VALUE)jit_context_get_meta(context, RJT_FUNCTIONS); + rb_ary_push(functions, function_v); + + return function_v; +} + +/* + * call-seq: + * function.compile() + * + * Begin compiling a function. + */ +static VALUE function_compile(VALUE self) +{ + jit_function_t function; + Data_Get_Struct(self, struct _jit_function, function); + if(!jit_function_compile(function)) + { + rb_raise(rb_eRuntimeError, "Unable to compile function"); + } + return self; +} + +/* + * call-seq: + * function = Function.new(context, signature, [parent]) + * + * Create a new function. + */ +static VALUE function_s_new(int argc, VALUE * argv, VALUE klass) +{ + if(rb_block_given_p()) + { + rb_raise(rb_eArgError, "Function.new does not take a block"); + } + + return create_function(argc, argv, klass); +} + +static VALUE function_abandon_if_exception(VALUE function_v) +{ + if(ruby_errinfo) + { + jit_function_t function; + Data_Get_Struct(function_v, struct _jit_function, function); + jit_function_abandon(function); + } + return Qnil; +} + +/* + * call-seq: + * function = Function.new(context, signature, [parent]) { |function| ... } + * + * Create a new function, begin compiling it, and pass the function to + * the block. + */ +static VALUE function_s_compile(int argc, VALUE * argv, VALUE klass) +{ + VALUE function = create_function(argc, argv, klass); + rb_yield(function); + function_compile(function); +#ifdef HAVE_RB_ENSURE + rb_ensure( + function_compile, + function, + function_abandon_if_exception, + function); +#else + /* Rubinius does not yet have rb_ensure */ + function_compile(function); + function_abandon_if_exception(function); +#endif + return function; +} + +/* + * Get the value that corresponds to a specified function parameter. + * + * call-seq: + * value = function.get_param(index) + */ +static VALUE function_get_param(VALUE self, VALUE idx) +{ + jit_function_t function; + jit_value_t value; + Data_Get_Struct(self, struct _jit_function, function); + value = jit_value_get_param(function, NUM2INT(idx)); + raise_memory_error_if_zero(value); + return Data_Wrap_Struct(rb_cValue, 0, 0, value); +} + +#include "insns.inc" + +static VALUE function_value_klass(VALUE self, VALUE type_v, VALUE klass) +{ + jit_function_t function; + jit_type_t type; + jit_value_t value; + + Data_Get_Struct(self, struct _jit_function, function); + + type_v = lookup_const(rb_cType, type_v); + check_type("type", rb_cType, type_v); + Data_Get_Struct(type_v, struct _jit_type, type); + + /* TODO: When we wrap a value, we should inject a reference to the + * function in the object, so the function stays around as long as the + * value does */ + value = jit_value_create(function, type); + return Data_Wrap_Struct(klass, 0, 0, value); +} + +static VALUE coerce_to_jit(VALUE function, VALUE type_v, VALUE value_v); + +/* + * call-seq: + * value = function.value(type) + * value = function.value(type, initial_value) + * + * Create a value (placeholder/variable) with the given type. + */ +static VALUE function_value(int argc, VALUE * argv, VALUE self) +{ + VALUE type_v = Qnil; + VALUE initial_value_v = Qnil; + + VALUE new_value = Qnil; + + rb_scan_args(argc, argv, "11", &type_v, &initial_value_v); + + new_value = function_value_klass(self, type_v, rb_cValue); + + if(argc > 1) + { + function_insn_store( + self, + new_value, + coerce_to_jit(self, type_v, initial_value_v)); + } + + return new_value; +} + +static jit_value_t create_const(jit_function_t function, jit_type_t type, VALUE constant) +{ + jit_constant_t c; + int kind = jit_type_get_kind(type); + + switch(kind) + { + case JIT_TYPE_INT: + { + c.type = type; + c.un.int_value = NUM2INT(constant); + break; + } + + case JIT_TYPE_UINT: + { + c.type = type; + c.un.int_value = NUM2UINT(constant); + break; + } + + case JIT_TYPE_FLOAT32: + { + c.type = type; + c.un.float32_value = NUM2DBL(constant); + break; + } + + case JIT_TYPE_FLOAT64: + { + c.type = type; + c.un.float64_value = NUM2DBL(constant); + break; + } + + case JIT_TYPE_PTR: + { + c.type = type; + c.un.ptr_value = (void *)NUM2ULONG(constant); + break; + } + + case JIT_TYPE_FIRST_TAGGED + RJT_OBJECT: + { + VALUE value_objects = (VALUE)jit_function_get_meta(function, RJT_VALUE_OBJECTS); + + c.type = type; + SET_CONSTANT_VALUE(c, constant); + + /* Make sure the object gets marked as long as the function is + * around */ + rb_ary_push(value_objects, constant); + break; + } + + case JIT_TYPE_FIRST_TAGGED + RJT_ID: + { + c.type = type; + SET_CONSTANT_ID(c, SYM2ID(constant)); + break; + } + + case JIT_TYPE_FIRST_TAGGED + RJT_FUNCTION_PTR: + { + c.type = type; + SET_FUNCTION_POINTER_VALUE( + c, + (Void_Function_Ptr)NUM2ULONG(rb_to_int(constant))); + break; + } + + default: + rb_raise(rb_eTypeError, "Unsupported type"); + } + + return jit_value_create_constant(function, &c); +} + +/* + * call-seq: + * value = function.const(type, constant_value) + * + * Create a constant value with the given type. + */ +static VALUE function_const(VALUE self, VALUE type_v, VALUE constant) +{ + jit_function_t function; + jit_type_t type; + jit_value_t value; + + Data_Get_Struct(self, struct _jit_function, function); + + type_v = lookup_const(rb_cType, type_v); + check_type("type", rb_cType, type_v); + Data_Get_Struct(type_v, struct _jit_type, type); + + value = create_const(function, type, constant); + return Data_Wrap_Struct(rb_cValue, 0, 0, value); +} + +static VALUE coerce_to_jit(VALUE function, VALUE type_v, VALUE value_v) +{ + if(rb_obj_is_kind_of(value_v, rb_cValue)) + { + return value_v; + } + else + { + return function_const(function, type_v, value_v); + } +} + +static void convert_call_args(jit_function_t function, jit_value_t * args, VALUE args_v, jit_type_t signature) +{ + int j; + + for(j = 0; j < RARRAY_LEN(args_v); ++j) + { + VALUE value = RARRAY_PTR(args_v)[j]; + jit_value_t arg; + + jit_type_t type = jit_type_get_param(signature, j); + if(!type) + { + rb_raise(rb_eArgError, "Type missing for param %d", j); + } + + if(rb_obj_is_kind_of(value, rb_cValue)) + { + Data_Get_Struct(value, struct _jit_value, arg); + if(!arg) + { + rb_raise(rb_eArgError, "Argument %d is invalid", j); + } + args[j] = arg; + } + else + { + args[j] = create_const(function, type, value); + } + } +} + +/* + * call-seq: + * value = function.call(name, called_function, flags, [arg1 [, ... ]]) + * + * Generate an instruction to call the specified function. + */ +static VALUE function_insn_call(int argc, VALUE * argv, VALUE self) +{ + jit_function_t function; + + VALUE name_v; + VALUE called_function_v; + VALUE args_v; + VALUE flags_v = Qnil; + + char const * name; + jit_function_t called_function; + jit_type_t signature; + jit_value_t * args; + jit_value_t retval; + int flags; + size_t num_args; + + rb_scan_args(argc, argv, "3*", &name_v, &called_function_v, &flags_v, &args_v); + + Data_Get_Struct(self, struct _jit_function, function); + + name = STR2CSTR(name_v); + + check_type("called function", rb_cFunction, called_function_v); + Data_Get_Struct(called_function_v, struct _jit_function, called_function); + + num_args = RARRAY_LEN(args_v); + args = ALLOCA_N(jit_value_t, num_args); + + signature = jit_function_get_signature(function); + convert_call_args(function, args, args_v, signature); + + flags = NUM2INT(flags_v); + + retval = jit_insn_call( + function, name, called_function, 0, args, num_args, flags); + return Data_Wrap_Struct(rb_cValue, 0, 0, retval); +} + +/* + * call-seq: + * value = function.call(name, flags, [arg1 [, ... ]]) + * + * Generate an instruction to call a native function. + */ +static VALUE function_insn_call_native(int argc, VALUE * argv, VALUE self) +{ + jit_function_t function; + + VALUE name_v; + VALUE args_v; + VALUE function_ptr_v; + VALUE signature_v; + VALUE flags_v; + + char const * name; + jit_value_t * args; + jit_value_t retval; + void * function_ptr; + jit_type_t signature; + int flags; + size_t num_args; + + rb_scan_args(argc, argv, "4*", &name_v, &function_ptr_v, &signature_v, &flags_v, &args_v); + + Data_Get_Struct(self, struct _jit_function, function); + + if(SYMBOL_P(name_v)) + { + name = rb_id2name(SYM2ID(name_v)); + } + else + { + name = StringValuePtr(name_v); + } + + function_ptr = (void *)NUM2ULONG(function_ptr_v); + + Data_Get_Struct(signature_v, struct _jit_type, signature); + + num_args = RARRAY_LEN(args_v); + args = ALLOCA_N(jit_value_t, num_args); + + if(num_args != jit_type_num_params(signature)) + { + rb_raise( + rb_eArgError, + "Wrong number of arguments passed for %s (expecting %d but got %d)", + name, + jit_type_num_params(signature), + num_args); + } + + convert_call_args(function, args, args_v, signature); + + flags = NUM2INT(flags_v); + + retval = jit_insn_call_native( + function, name, function_ptr, signature, args, num_args, flags); + return Data_Wrap_Struct(rb_cValue, 0, 0, retval); +} + +/* + * call-seq: + * function.insn_return() + * function.insn_return(value) + * + * Emit an instruction to return from the function. If value is + * specified, return the given value, otherwise return void. + */ +static VALUE function_insn_return(int argc, VALUE * argv, VALUE self) +{ + jit_function_t function; + jit_value_t value = 0; + VALUE value_v = Qnil; + + rb_scan_args(argc, argv, "01", &value_v); + + if(value_v != Qnil) + { + Data_Get_Struct(value_v, struct _jit_value, value); + } + + Data_Get_Struct(self, struct _jit_function, function); + jit_insn_return(function, value); + + return Qnil; +} + +/* + * call-seq: + * function.apply(arg1 [, arg2 [, ... ]]) + * + * Call a compiled function. Each argument passed in will be converted + * to the type specified by the function's signature. + * + * If the function's signature is Type::RUBY_VARARG_SIGNATURE, then the + * arguments will be passed in with the first parameter the count of the + * number of arguments, the second parameter a pointer to an array + * containing the second through the last argument, and the third + * parameter the explicit self (that is, the first argument passed to + * apply). + */ +static VALUE function_apply(int argc, VALUE * argv, VALUE self) +{ + jit_function_t function; + jit_type_t signature; + int j, n; + void * * args; + char * arg_data; + int signature_tag; + + Data_Get_Struct(self, struct _jit_function, function); + signature = jit_function_get_signature(function); + n = jit_type_num_params(signature); + + /* void pointers to each of the arguments */ + args = ALLOCA_N(void *, n); + + /* the actual data */ + /* TODO: we need to allocate the proper size (but 8 bytes per arg + * should be sufficient for now) */ + arg_data = (char *)ALLOCA_N(char, 8 * n); + + signature_tag = (int)jit_function_get_meta(function, RJT_TAG_FOR_SIGNATURE); + if(signature_tag == JIT_TYPE_FIRST_TAGGED + RJT_RUBY_VARARG_SIGNATURE) + { + jit_VALUE result; + int f_argc = argc - 1; + VALUE f_self = *(VALUE *)argv; + VALUE * f_argv = ((VALUE *)argv) + 1; + void * f_args[3]; + f_args[0] = &f_argc; + f_args[1] = &f_argv; + f_args[2] = &f_self; + jit_function_apply(function, f_args, &result); + return result; + } + + if(argc != n) + { + rb_raise( + rb_eArgError, + "Wrong number of arguments (expected %d but got %d)", + n, + argc); + } + + for(j = 0; j < n; ++j) + { + jit_type_t arg_type = jit_type_get_param(signature, j); + int kind = jit_type_get_kind(arg_type); + switch(kind) + { + case JIT_TYPE_INT: + { + *(int *)arg_data = NUM2INT(argv[j]); + args[j] = (int *)arg_data; + arg_data += sizeof(int); + break; + } + + case JIT_TYPE_UINT: + { + *(int *)arg_data = NUM2UINT(argv[j]); + args[j] = (int *)arg_data; + arg_data += sizeof(int); + break; + } + + case JIT_TYPE_FIRST_TAGGED + RJT_OBJECT: + { + *(VALUE *)arg_data = argv[j]; + args[j] = (VALUE *)arg_data; + arg_data += sizeof(VALUE); + break; + } + + case JIT_TYPE_FIRST_TAGGED + RJT_ID: + { + *(ID *)arg_data = SYM2ID(argv[j]); + args[j] = (ID *)arg_data; + arg_data += sizeof(ID); + break; + } + + case JIT_TYPE_FIRST_TAGGED + RJT_FUNCTION_PTR: + { + *(Void_Function_Ptr *)arg_data = + (Void_Function_Ptr)NUM2ULONG(rb_to_int(argv[j])); + args[j] = (Void_Function_Ptr *)(arg_data); + arg_data += sizeof(Void_Function_Ptr); + break; + } + + default: + rb_raise(rb_eTypeError, "Unsupported type %d", kind); + } + } + + { + jit_type_t return_type = jit_type_get_return(signature); + int return_kind = jit_type_get_kind(return_type); + switch(return_kind) + { + case JIT_TYPE_INT: + { + jit_int result; + jit_function_apply(function, args, &result); + return INT2NUM(result); + } + + case JIT_TYPE_FLOAT32: + { + jit_float32 result; + jit_function_apply(function, args, &result); + return rb_float_new(result); + } + + case JIT_TYPE_FLOAT64: + { + jit_float64 result; + jit_function_apply(function, args, &result); + return rb_float_new(result); + } + + case JIT_TYPE_FIRST_TAGGED + RJT_OBJECT: + { + jit_VALUE result; + jit_function_apply(function, args, &result); + return result; + } + + case JIT_TYPE_FIRST_TAGGED + RJT_ID: + { + jit_ID result; + jit_function_apply(function, args, &result); + return ID2SYM(result); + } + + default: + rb_raise(rb_eTypeError, "Unsupported return type %d", return_kind); + } + } +} + +/* + * call-seq: + * level = function.optimization_level() + * + * Get the optimization level for a function. + */ +static VALUE function_optimization_level(VALUE self) +{ + jit_function_t function; + Data_Get_Struct(self, struct _jit_function, function); + return INT2NUM(jit_function_get_optimization_level(function)); +} + +/* + * call-seq: + * function.optimization_level = level + * + * Set the optimization level for a function. + */ +static VALUE function_set_optimization_level(VALUE self, VALUE level) +{ + jit_function_t function; + Data_Get_Struct(self, struct _jit_function, function); + jit_function_set_optimization_level(function, NUM2INT(level)); + return level; +} + +/* + * call-seq: + * level = function.max_optimization_level() + * + * Get the maximum optimization level (which should be the same for any + * function). + */ +static VALUE function_max_optimization_level(VALUE klass) +{ + return INT2NUM(jit_function_get_max_optimization_level()); +} + +/* + * call-seq: + * str = function.dump() + * + * Dump the instructions in a function to a string. + */ +static VALUE function_dump(VALUE self) +{ +#ifdef HAVE_FMEMOPEN + jit_function_t function; + char buf[16*1024]; /* TODO: big enough? */ + FILE * fp = fmemopen(buf, sizeof(buf), "w"); + Data_Get_Struct(self, struct _jit_function, function); + jit_dump_function(fp, function, 0); + fclose(fp); + return rb_str_new2(buf); +#else + rb_raise(rb_eNotImpError, "Not implemented: missing fmemopen"); +#endif +} + +/* + * call-seq: + * ptr = function.to_closure() + * + * Return a pointer to a closure for a function. This pointer can be + * passed into other functions as a function pointer. + */ +static VALUE function_to_closure(VALUE self) +{ + jit_function_t function; + struct Closure * closure; + VALUE closure_v = Data_Make_Struct( + rb_cClosure, struct Closure, mark_closure, free, closure); + Data_Get_Struct(self, struct _jit_function, function); + closure->function = self; + closure->function_ptr = + (Void_Function_Ptr)jit_function_to_closure(function); + return closure_v; +} + +/* + * call-seq: + * context = function.context() + * + * Get a function's context. + */ +static VALUE function_get_context(VALUE self) +{ + jit_function_t function; + Data_Get_Struct(self, struct _jit_function, function); + return (VALUE)jit_function_get_meta(function, RJT_CONTEXT); +} + +/* + * call-seq: + * is_compiled = function.compiled? + * + * Determine whether a function is compiled. + */ +static VALUE function_is_compiled(VALUE self) +{ + jit_function_t function; + Data_Get_Struct(self, struct _jit_function, function); + return jit_function_is_compiled(function) ? Qtrue : Qfalse; +} + +/* --------------------------------------------------------------------------- + * Type + * --------------------------------------------------------------------------- + */ + +/* This function does not increment the reference count. It is assumed + * that the caller will increment the reference count and that the newly + * wrapped object will take ownership. */ +static VALUE wrap_type_with_klass(jit_type_t type, VALUE klass) +{ + return Data_Wrap_Struct(klass, 0, jit_type_free, type); +} + +static VALUE wrap_type(jit_type_t type) +{ + return wrap_type_with_klass(type, rb_cType); +} + +/* + * call-seq: + * type = Type.create_signature(abi, return_type, array_of_param_types) + * + * Create a new signature. + */ +static VALUE type_s_create_signature( + VALUE klass, VALUE abi_v, VALUE return_type_v, VALUE params_v) +{ + jit_abi_t abi; + jit_type_t return_type; + jit_type_t * params; + jit_type_t signature; + int j; + int len; + + return_type_v = lookup_const(rb_cType, return_type_v); + check_type("return type", rb_cType, return_type_v); + Data_Get_Struct(return_type_v, struct _jit_type, return_type); + + Check_Type(params_v, T_ARRAY); + len = RARRAY_LEN(params_v); + params = ALLOCA_N(jit_type_t, len); + for(j = 0; j < len; ++j) + { + VALUE param = RARRAY_PTR(params_v)[j]; + param = lookup_const(rb_cType, param); + check_type("param", rb_cType, param); + Data_Get_Struct(param, struct _jit_type, params[j]); + } + + abi_v = lookup_const(rb_mABI, abi_v); + abi = NUM2INT(abi_v); + + signature = jit_type_create_signature(abi, return_type, params, len, 1); + return wrap_type(signature); +} + +/* + * call-seq: + * type = Type.create_struct(array_of_field_types) + * + * Create a new struct type. + */ +static VALUE type_s_create_struct( + VALUE klass, VALUE fields_v) +{ + jit_type_t * fields; + jit_type_t struct_type; + int len; + int j; + + Check_Type(fields_v, T_ARRAY); + len = RARRAY_LEN(fields_v); + fields = ALLOCA_N(jit_type_t, len); + for(j = 0; j < len; ++j) + { + VALUE field = RARRAY_PTR(fields_v)[j]; + check_type("field", rb_cType, field); + Data_Get_Struct(field, struct _jit_type, fields[j]); + } + + struct_type = jit_type_create_struct(fields, RARRAY_LEN(fields_v), 1); + return wrap_type_with_klass(struct_type, klass); +} + +/* + * call-seq: + * type = Type.create_pointer(pointed_to_type) + * + * Create a new pointer type, pointing to the given type. + */ +static VALUE type_s_create_pointer( + VALUE klass, VALUE type_v) +{ + jit_type_t type; + jit_type_t pointer_type; + Data_Get_Struct(type_v, struct _jit_type, type); + pointer_type = jit_type_create_pointer(type, 1); + return wrap_type_with_klass(pointer_type, klass); +} + +/* + * call-seq: + * offset = struct_type.get_offset(index) + * + * Get the offset of the nth field in a struct. + */ +static VALUE type_get_offset(VALUE self, VALUE field_index_v) +{ + int field_index = NUM2INT(field_index_v); + jit_type_t type; + Data_Get_Struct(self, struct _jit_type, type); + return INT2NUM(jit_type_get_offset(type, field_index)); +} + +/* + * call-seq: + * offset = struct_type.set_offset(index, offset) + * + * Set the offset of the nth field in a struct. + */ +static VALUE type_set_offset(VALUE self, VALUE field_index_v, VALUE offset_v) +{ + int field_index = NUM2INT(field_index_v); + int offset = NUM2UINT(offset_v); + jit_type_t type; + Data_Get_Struct(self, struct _jit_type, type); + jit_type_set_offset(type, field_index, offset); + return Qnil; +} + +/* + * call-seq: + * offset = struct_type.size + * + * Get the size of a struct or union type. + */ +static VALUE type_size(VALUE self) +{ + jit_type_t type; + Data_Get_Struct(self, struct _jit_type, type); + return INT2NUM(jit_type_get_size(type)); +} + +/* --------------------------------------------------------------------------- + * Value + * --------------------------------------------------------------------------- + */ + +static VALUE value_s_new_value(VALUE klass, VALUE function, VALUE type) +{ + return function_value_klass(function, type, klass); +} + +/* + * call-seq: + * str = value.to_s + * + * Return a string representation of the value. + */ +static VALUE value_to_s(VALUE self) +{ +#ifdef HAVE_FMEMOPEN + char buf[1024]; + FILE * fp = fmemopen(buf, sizeof(buf), "w"); + jit_value_t value; + jit_function_t function; + Data_Get_Struct(self, struct _jit_value, value); + function = jit_value_get_function(value); + jit_dump_value(fp, function, value, 0); + fclose(fp); + return rb_str_new2(buf); +#else + rb_raise(rb_eNotImpError, "Not implemented: missing fmemopen"); +#endif +} + +/* + * call-seq: + * str = value.inspect + * + * Return a string representation of a value with additional + * information about the value. + */ +static VALUE value_inspect(VALUE self) +{ + jit_value_t value; + jit_type_t type; + char const * cname = rb_obj_classname(self); + VALUE args[6]; + Data_Get_Struct(self, struct _jit_value, value); + type = jit_value_get_type(value); + args[0] = rb_str_new2("#<%s:0x%x %s ptr=0x%x type=0x%x>"); + args[1] = rb_str_new2(cname); + args[2] = ULONG2NUM((unsigned long)self); + args[3] = value_to_s(self); + args[4] = ULONG2NUM((unsigned long)value); + args[5] = ULONG2NUM((unsigned long)type); + return rb_f_sprintf(sizeof(args)/sizeof(args[0]), args); +} + +/* + * call-seq: + * is_valid = value.valid? + * + * Determine if a value is valid (non-zero). + */ +static VALUE value_is_valid(VALUE self) +{ + jit_value_t value; + Data_Get_Struct(self, struct _jit_value, value); + return (value != 0) ? Qtrue : Qfalse; +} + +/* + * call-seq: + * is_temporary = value.temporary? + * + * Determine if a value represents a temporary. + */ +static VALUE value_is_temporary(VALUE self) +{ + jit_value_t value; + Data_Get_Struct(self, struct _jit_value, value); + return jit_value_is_temporary(value) ? Qtrue : Qfalse; +} + +/* + * call-seq: + * is_local = value.local? + * + * Determine if a value represents a local. + */ +static VALUE value_is_local(VALUE self) +{ + jit_value_t value; + Data_Get_Struct(self, struct _jit_value, value); + return jit_value_is_local(value) ? Qtrue : Qfalse; +} + +/* + * call-seq: + * is_local = value.constant? + * + * Determine if a value represents a constant. + */ +static VALUE value_is_constant(VALUE self) +{ + jit_value_t value; + Data_Get_Struct(self, struct _jit_value, value); + return jit_value_is_constant(value) ? Qtrue : Qfalse; +} + +/* + * call-seq: + * is_volatile = value.volatile? + * + * Determine if a value is volatile (that is, the contents must be + * reloaded from memory each time it is used). + */ +static VALUE value_is_volatile(VALUE self) +{ + jit_value_t value; + Data_Get_Struct(self, struct _jit_value, value); + return jit_value_is_volatile(value) ? Qtrue : Qfalse; +} + +/* + * call-seq: + * value.volatile = is_volatile + * + * Make a value volatile (that is, ensure that its contents are reloaded + * from memory each time it is used). + */ +static VALUE value_set_volatile(VALUE self) +{ + jit_value_t value; + Data_Get_Struct(self, struct _jit_value, value); + jit_value_set_volatile(value); + return Qnil; +} + +/* + * call-seq: + * is_addressable = value.addressable? + * + * Determine if a value is addressable. + */ +static VALUE value_is_addressable(VALUE self) +{ + jit_value_t value; + Data_Get_Struct(self, struct _jit_value, value); + return jit_value_is_addressable(value) ? Qtrue : Qfalse; +} + +/* + * call-seq: + * value.addressable = is_addressable + * + * Make a value addressable. + */ +static VALUE value_set_addressable(VALUE self) +{ + jit_value_t value; + Data_Get_Struct(self, struct _jit_value, value); + jit_value_set_addressable(value); + return Qnil; +} + +/* + * call-seq: + * function = value.function() + * + * Get a value's function. + */ +static VALUE value_function(VALUE self) +{ + jit_value_t value; + jit_function_t function; + Data_Get_Struct(self, struct _jit_value, value); + function = jit_value_get_function(value); + return Data_Wrap_Struct(rb_cFunction, mark_function, 0, function); +} + +/* + * call-seq: + * function = value.type() + * + * Get a value's type. + */ +static VALUE value_type(VALUE self) +{ + jit_value_t value; + jit_type_t type; + Data_Get_Struct(self, struct _jit_value, value); + type = jit_value_get_type(value); + type = jit_type_copy(type); + return wrap_type(type); +} + +/* + * call-seq: + * value1, value2 = value1.coerce(value2) + * + * If the given value is a JIT::Value, return an +[ self, value ]+, + * otherwise coerce the value to the same type as self and return + * +[ self, coerced_value ]+. + */ +static VALUE value_coerce(VALUE self, VALUE value) +{ + return rb_assoc_new( + self, + coerce_to_jit( + value_function(self), + value_type(self), + value)); +} + +/* --------------------------------------------------------------------------- + * Label + * --------------------------------------------------------------------------- + */ + +/* + * call-seq: + * label = Label.new + * + * Create a new label. + */ +static VALUE label_s_new(VALUE klass) +{ + jit_label_t * label; + VALUE labelval = Data_Make_Struct(rb_cLabel, jit_label_t, 0, xfree, label); + *label = jit_label_undefined; + return labelval; +} + +/* --------------------------------------------------------------------------- + * Module + * --------------------------------------------------------------------------- + */ + +/* + * call-seq: + * module.define_jit_method(name, function) + * + * Use a Function to define an instance method on a module. The + * function should have one of two signatures: + * + * * The first parameter to the function should be an OBJECT that + * represents the self parameter and the rest of the parameters, or + * + * * The function's signature should be Type::RUBY_VARARG_SIGNATURE. + */ +static VALUE module_define_jit_method(VALUE klass, VALUE name_v, VALUE function_v) +{ + char const * name; + jit_function_t function; + jit_type_t signature; + int signature_tag; + int arity; + VALUE closure_v; + struct Closure * closure; + + if(SYMBOL_P(name_v)) + { + name = rb_id2name(SYM2ID(name_v)); + } + else + { + name = STR2CSTR(name_v); + } + + Data_Get_Struct(function_v, struct _jit_function, function); + + signature = jit_function_get_signature(function); + signature_tag = (int)jit_function_get_meta(function, RJT_TAG_FOR_SIGNATURE); + if(signature_tag == JIT_TYPE_FIRST_TAGGED + RJT_RUBY_VARARG_SIGNATURE) + { + arity = -1; + } + else + { + /* TODO: check the types to make sure they are OBJECT */ + arity = jit_type_num_params(signature) - 1; + } + + closure_v = function_to_closure(function_v); + Data_Get_Struct(closure_v, struct Closure, closure); + define_method_with_data( + klass, rb_intern(name), RUBY_METHOD_FUNC(closure->function_ptr), + arity, closure_v); + return Qnil; +} + +/* --------------------------------------------------------------------------- + * Init + * --------------------------------------------------------------------------- + */ + +void Init_jit() +{ + jit_init(); + + rb_mJIT = rb_define_module("JIT"); + + rb_cContext = rb_define_class_under(rb_mJIT, "Context", rb_cObject); + rb_define_singleton_method(rb_cContext, "new", context_s_new, 0); + rb_define_method(rb_cContext, "build", context_build, 0); + rb_define_singleton_method(rb_cContext, "build", context_s_build, 0); + + rb_cClosure = rb_define_class_under(rb_mJIT, "Closure", rb_cObject); + rb_define_method(rb_cClosure, "to_int", closure_to_int, 0); + rb_define_method(rb_cClosure, "to_s", closure_to_s, 0); + rb_define_method(rb_cClosure, "inspect", closure_inspect, 0); + + rb_cFunction = rb_define_class_under(rb_mJIT, "Function", rb_cObject); + rb_define_singleton_method(rb_cFunction, "new", function_s_new, -1); + rb_define_method(rb_cFunction, "compile", function_compile, 0); + rb_define_singleton_method(rb_cFunction, "compile", function_s_compile, -1); + rb_define_method(rb_cFunction, "get_param", function_get_param, 1); + init_insns(); + rb_define_method(rb_cFunction, "insn_call", function_insn_call, -1); + rb_define_method(rb_cFunction, "insn_call_native", function_insn_call_native, -1); + rb_define_method(rb_cFunction, "insn_return", function_insn_return, -1); + rb_define_method(rb_cFunction, "apply", function_apply, -1); + rb_define_alias(rb_cFunction, "call", "apply"); + rb_define_method(rb_cFunction, "value", function_value, -1); + rb_define_method(rb_cFunction, "const", function_const, 2); + rb_define_method(rb_cFunction, "optimization_level", function_optimization_level, 0); + rb_define_method(rb_cFunction, "optimization_level=", function_set_optimization_level, 1); + rb_define_singleton_method(rb_cFunction, "max_optimization_level", function_max_optimization_level, 0); + rb_define_method(rb_cFunction, "dump", function_dump, 0); + rb_define_method(rb_cFunction, "to_closure", function_to_closure, 0); + rb_define_method(rb_cFunction, "context", function_get_context, 0); + rb_define_method(rb_cFunction, "compiled?", function_is_compiled, 0); + + rb_cType = rb_define_class_under(rb_mJIT, "Type", rb_cObject); + rb_define_singleton_method(rb_cType, "create_signature", type_s_create_signature, 3); + rb_define_singleton_method(rb_cType, "create_struct", type_s_create_struct, 1); + rb_define_singleton_method(rb_cType, "create_pointer", type_s_create_pointer, 1); + rb_define_method(rb_cType, "get_offset", type_get_offset, 1); + rb_define_method(rb_cType, "set_offset", type_set_offset, 2); + rb_define_method(rb_cType, "size", type_size, 0); + rb_define_const(rb_cType, "VOID", wrap_type(jit_type_void)); + rb_define_const(rb_cType, "SBYTE", wrap_type(jit_type_sbyte)); + rb_define_const(rb_cType, "UBYTE", wrap_type(jit_type_ubyte)); + rb_define_const(rb_cType, "SHORT", wrap_type(jit_type_short)); + rb_define_const(rb_cType, "USHORT", wrap_type(jit_type_ushort)); + rb_define_const(rb_cType, "INT", wrap_type(jit_type_int)); + rb_define_const(rb_cType, "UINT", wrap_type(jit_type_uint)); + rb_define_const(rb_cType, "NINT", wrap_type(jit_type_nint)); + rb_define_const(rb_cType, "NUINT", wrap_type(jit_type_nuint)); + rb_define_const(rb_cType, "LONG", wrap_type(jit_type_long)); + rb_define_const(rb_cType, "ULONG", wrap_type(jit_type_ulong)); + rb_define_const(rb_cType, "FLOAT32", wrap_type(jit_type_float32)); + rb_define_const(rb_cType, "FLOAT64", wrap_type(jit_type_float64)); + rb_define_const(rb_cType, "NFLOAT", wrap_type(jit_type_nfloat)); + rb_define_const(rb_cType, "VOID_PTR", wrap_type(jit_type_void_ptr)); + + jit_type_VALUE = jit_type_create_tagged(jit_underlying_type_VALUE, RJT_OBJECT, 0, 0, 1); + rb_define_const(rb_cType, "OBJECT", wrap_type(jit_type_VALUE)); + + jit_type_ID = jit_type_create_tagged(jit_underlying_type_ID, RJT_ID, 0, 0, 1); + rb_define_const(rb_cType, "ID", wrap_type(jit_type_ID)); + + jit_type_Function_Ptr = jit_type_create_tagged(jit_underlying_type_ID, RJT_FUNCTION_PTR, 0, 0, 1); + rb_define_const(rb_cType, "FUNCTION_PTR", wrap_type(jit_type_Function_Ptr)); + + { + jit_type_t ruby_vararg_param_types[3]; + jit_type_t ruby_vararg_signature_untagged; + ruby_vararg_param_types[0] = jit_type_int; + ruby_vararg_param_types[1] = jit_type_void_ptr; + ruby_vararg_param_types[2] = jit_type_VALUE; + ruby_vararg_signature_untagged = jit_type_create_signature( + jit_abi_cdecl, + jit_type_VALUE, + ruby_vararg_param_types, + 3, + 1); + ruby_vararg_signature = jit_type_create_tagged(ruby_vararg_signature_untagged, RJT_RUBY_VARARG_SIGNATURE, 0, 0, 1); + } + rb_define_const(rb_cType, "RUBY_VARARG_SIGNATURE", wrap_type(ruby_vararg_signature)); + + rb_mABI = rb_define_module_under(rb_mJIT, "ABI"); + rb_define_const(rb_mABI, "CDECL", INT2NUM(jit_abi_cdecl)); + rb_define_const(rb_mABI, "VARARG", INT2NUM(jit_abi_vararg)); + rb_define_const(rb_mABI, "STDCALL", INT2NUM(jit_abi_stdcall)); + rb_define_const(rb_mABI, "FASTCALL", INT2NUM(jit_abi_fastcall)); + + rb_cValue = rb_define_class_under(rb_mJIT, "Value", rb_cObject); + rb_define_singleton_method(rb_cValue, "new_value", value_s_new_value, 2); + rb_define_method(rb_cValue, "to_s", value_to_s, 0); + rb_define_method(rb_cValue, "inspect", value_inspect, 0); + rb_define_method(rb_cValue, "valid?", value_is_valid, 0); + rb_define_method(rb_cValue, "temporary?", value_is_temporary, 0); + rb_define_method(rb_cValue, "local?", value_is_local, 0); + rb_define_method(rb_cValue, "constant?", value_is_constant, 0); + rb_define_method(rb_cValue, "volatile?", value_is_volatile, 0); + rb_define_method(rb_cValue, "set_volatile", value_set_volatile, 0); + rb_define_method(rb_cValue, "addressable?", value_is_addressable, 0); + rb_define_method(rb_cValue, "addressable=", value_set_addressable, 0); + rb_define_method(rb_cValue, "function", value_function, 0); + rb_define_method(rb_cValue, "type", value_type, 0); + rb_define_method(rb_cValue, "coerce", value_coerce, 1); + + rb_cLabel = rb_define_class_under(rb_mJIT, "Label", rb_cObject); + rb_define_singleton_method(rb_cLabel, "new", label_s_new, 0); + + rb_mCall = rb_define_module_under(rb_mJIT, "Call"); + rb_define_const(rb_mCall, "NOTHROW", INT2NUM(JIT_CALL_NOTHROW)); + rb_define_const(rb_mCall, "NORETURN", INT2NUM(JIT_CALL_NORETURN)); + rb_define_const(rb_mCall, "TAIL", INT2NUM(JIT_CALL_TAIL)); + + /* VALUE rb_cModule = rb_define_module(); */ + rb_define_method(rb_cModule, "define_jit_method", module_define_jit_method, 2); + +#ifdef NEED_MINIMAL_NODE + Init_minimal_node(); +#endif +} + diff --git a/jitruby/ext/method_data.c.rpp b/jitruby/ext/method_data.c.rpp new file mode 100644 index 0000000..0f45b5d --- /dev/null +++ b/jitruby/ext/method_data.c.rpp @@ -0,0 +1,294 @@ +#include "method_data.h" + +#include + +#ifndef HAVE_RUBINIUS + +#ruby < +#elif defined(REALLY_HAVE_RUBY_NODE_H) +/* YARV */ +#include +#elif defined(RUBY_VM) +/* YARV without node.h */ +#include "minimal_node.h" +#else +/* something else */ +#error "Need node.h" +#endif + +#ifdef HAVE_ENV_H +/* pre-YARV */ +#include +#endif + +#ifdef RUBY_VM + +/* YARV */ + +struct rb_thread_struct +{ + VALUE self; + void *vm; + VALUE *stack; + unsigned long stack_size; + VALUE *cfp; + /* ... */ +}; + +typedef struct rb_thread_struct rb_thread_t; + +#define CFP_DATA_MEMO_NODE_AND_PC cfp[0] +#define CFP_METHOD_CLASS cfp[11] + +/* On YARV, we store the method data on the stack. We don't have to pop + * it off the stack, because the stack pointer will be reset to the + * previous frame's stack pointer when the function returns. + */ +static void fix_frame() +{ + do { + extern rb_thread_t * ruby_current_thread; + VALUE * cfp = ruby_current_thread->cfp; + CFP_DATA_MEMO_NODE_AND_PC = RBASIC(CFP_METHOD_CLASS)->klass; + + if(rb_type(CFP_DATA_MEMO_NODE_AND_PC) != T_NODE) + { + /* This can happen for module functions that are created after + * the stub function */ + rb_raise( + rb_eRuntimeError, + "Cannot find method data for module function"); + } + else + { + CFP_METHOD_CLASS = RCLASS_SUPER(CFP_METHOD_CLASS); + } + } while(0); +} + +#define FIX_FRAME() \ + fix_frame() + +static NODE * data_memo_node() +{ + extern rb_thread_t * ruby_current_thread; + VALUE * cfp = ruby_current_thread->cfp; + return (NODE *)CFP_DATA_MEMO_NODE_AND_PC; +} + +#else + +/* pre-YARV */ + +/* Okay to not pop this temporary frame, since it will be popped by the + * caller + */ +#define FIX_FRAME() \ + struct FRAME _frame = *ruby_frame; \ + _frame.last_class = RCLASS(ruby_frame->last_class)->super; \ + _frame.prev = ruby_frame; \ + ruby_frame = &_frame; \ + +static NODE * data_memo_node() +{ + return (NODE *)(RBASIC(ruby_frame->prev->last_class)->klass); +} + +#endif + +typedef VALUE (*Method_Func)(ANYARGS); + +static Method_Func actual_cfunc() +{ + return data_memo_node()->nd_cfnc; +} + +static VALUE data_wrapper_m1(int argc, VALUE * argv, VALUE self) +{ + VALUE result; + FIX_FRAME(); + result = (*actual_cfunc())(argc, argv, self); + return result; +} + +static VALUE data_wrapper_0(VALUE self) +{ + VALUE result; + FIX_FRAME(); + result = (*actual_cfunc())(self); + return result; +} + +#ruby <prev->last_class->rval. + * + * On YARV, the current frame is not duplicated; rather, the method data + * is placed on the stack and is referenced by one of the unused members + * of the control frame (the program counter): + * + * ruby_current_thread->cfp + * |- pc - NODE_MEMO + * | |- (u1) cfnc - actual C function to call + * | |- (u2) rval - stored data + * | +- (u3) 0 + * |- method_class - klass + * +- ... + * + */ +void define_method_with_data( + VALUE klass, ID id, VALUE (*cfunc)(ANYARGS), int arity, VALUE data) +{ + /* TODO: origin should have #to_s and #inspect methods defined */ +#ifdef HAVE_RB_CLASS_BOOT + VALUE origin = rb_class_boot(klass); +#else + VALUE origin = rb_class_new(klass); +#endif + NODE * node; + + VALUE (*data_wrapper)(ANYARGS); + switch(arity) + { +#ruby <klass = (VALUE)NEW_NODE(NODE_MEMO, cfunc, data, 0); + +#ifdef RUBY_VM + /* YARV */ + node = NEW_FBODY( + NEW_METHOD( + NEW_CFUNC(data_wrapper, arity), + origin, + NOEX_PUBLIC), + id); + st_insert(RCLASS_M_TBL(klass), id, (st_data_t)node); +#else + /* pre-YARV */ + node = NEW_FBODY( + NEW_CFUNC(data_wrapper, arity), + id, + origin); + rb_add_method(klass, id, node, NOEX_PUBLIC); +#endif +} + +VALUE get_method_data() +{ + return data_memo_node()->nd_rval; +} + +#else /* HAVE_RUBINIUS */ + +void define_method_with_data( + VALUE klass, ID id, VALUE (*cfunc)(ANYARGS), int arity, VALUE data) +{ + rb_raise(rb_eNotImpError, "Not implemented: define_method_with_data"); +} + +VALUE get_method_data() +{ + rb_raise(rb_eNotImpError, "Not implemented: get_method_data"); +} + +#endif diff --git a/jitruby/ext/method_data.h b/jitruby/ext/method_data.h new file mode 100644 index 0000000..d2e4f3b --- /dev/null +++ b/jitruby/ext/method_data.h @@ -0,0 +1,11 @@ +#ifndef METHOD_DATA_H +#define METHOD_DATA_H + +#include + +void define_method_with_data( + VALUE klass, ID id, VALUE (*cfunc)(ANYARGS), int arity, VALUE data); + +VALUE get_method_data(); + +#endif // METHOD_DATA_H diff --git a/jitruby/ext/minimal_node.c b/jitruby/ext/minimal_node.c new file mode 100644 index 0000000..01276ca --- /dev/null +++ b/jitruby/ext/minimal_node.c @@ -0,0 +1,35 @@ +#ifdef NEED_MINIMAL_NODE + +#include "minimal_node.h" +#include + +int NODE_MEMO = 0; +int NODE_METHOD = 0; +int NODE_FBODY = 0; +int NODE_CFUNC = 0; + +char const * ruby_node_name(int node); + +static int node_value(char const * name) +{ + /* TODO: any way to end the block? */ + int j; + for(j = 0; ; ++j) + { + if(!strcmp(name, ruby_node_name(j))) + { + return j; + } + } +} + +void Init_minimal_node() +{ + NODE_MEMO = node_value("NODE_MEMO"); + NODE_METHOD = node_value("NODE_METHOD"); + NODE_FBODY = node_value("NODE_FBODY"); + NODE_CFUNC = node_value("NODE_CFUNC"); +} + +#endif + diff --git a/jitruby/ext/minimal_node.h b/jitruby/ext/minimal_node.h new file mode 100644 index 0000000..423565f --- /dev/null +++ b/jitruby/ext/minimal_node.h @@ -0,0 +1,52 @@ +#ifndef minimal_node_h +#define minimal_node_h + +#ifdef NEED_MINIMAL_NODE + +#include "ruby.h" + +typedef struct RNode { + unsigned long flags; + void * reserved; + union { + struct RNode * node; + VALUE (*cfunc)(ANYARGS); + } u1; + union { + struct RNode * node; + VALUE value; + } u2; + union { + struct RNode * node; + } u3; +} NODE; + +#define nd_cfnc u1.cfunc +#define nd_rval u2.value + +#define NEW_NODE(t,a0,a1,a2) rb_node_newnode((t),(VALUE)(a0),(VALUE)(a1),(VALUE)(a2)) + +/* TODO: No way to know the correct size of node_type */ +enum node_type { + NODE_FOO, +}; + +void rb_add_method(VALUE, ID, NODE *, int); +NODE *rb_node_newnode(enum node_type, VALUE, VALUE, VALUE); + +extern int NODE_MEMO; +extern int NODE_METHOD; +extern int NODE_FBODY; +extern int NODE_CFUNC; + +void Init_minimal_node(); + +#define NOEX_PUBLIC 0x0 + +#define NEW_METHOD(n,x,v) NEW_NODE(NODE_METHOD,x,n,v) +#define NEW_FBODY(n,i) NEW_NODE(NODE_FBODY,i,n,0) +#define NEW_CFUNC(f,c) NEW_NODE(NODE_CFUNC,f,c,0) + +#endif + +#endif diff --git a/jitruby/ext/rubyjit.h b/jitruby/ext/rubyjit.h new file mode 100644 index 0000000..eabedc2 --- /dev/null +++ b/jitruby/ext/rubyjit.h @@ -0,0 +1,20 @@ +#ifndef RUBYJIT_H +#define RUBYJIT_H + +enum Ruby_Libjit_Tag +{ + RJT_OBJECT, + RJT_ID, + RJT_FUNCTION_PTR, + RJT_RUBY_VARARG_SIGNATURE, + RJT_VALUE_OBJECTS, + RJT_FUNCTIONS, + RJT_CONTEXT, + RJT_TAG_FOR_SIGNATURE +}; + +extern jit_type_t jit_type_VALUE; +extern jit_type_t jit_type_ID; +extern jit_type_t jit_type_Function_Ptr; + +#endif diff --git a/jitruby/ext/rubypp.rb b/jitruby/ext/rubypp.rb new file mode 100644 index 0000000..a4f0cd2 --- /dev/null +++ b/jitruby/ext/rubypp.rb @@ -0,0 +1,97 @@ +class Preprocessor + def initialize(input, output, filename) + @input = input + @output = output + @filename = filename + @linenum = 1 + end + + def getline + line = @input.gets + @linenum += 1 if not line.nil? + return line + end + + def preprocess + success = false + begin + loop do + line = getline + break if line.nil? + case line + when /(.*[^\\]|^)\#\{(.*?)\}(.*)/ + puts "#{$1}#{evaluate($2, @linenum)}#{$3}" + when /^\#ruby\s+<<(.*)/ + marker = $1 + str = '' + evalstart = @linenum + loop do + line = getline + if line.nil? then + raise "End of input without #{marker}" + end + break if line.chomp == marker + str << line + end + result = evaluate(str, evalstart) + puts result if not result.nil? + when /^\#ruby\s+(.*)/ + str = line = $1 + while line[-1] == ?\\ + str.chop! + line = getline + break if line.nil? + line.chomp! + str << line + end + result = evaluate(str, @linenum) + puts result if not result.nil? + else + puts line + end + end + success = true + ensure + if not success then + $stderr.puts "Error on line #{@linenum}:" + end + end + end + + def evaluate(str, linenum) + result = eval(str, TOPLEVEL_BINDING, @filename, linenum) + success = true + return result + end + + def puts(line='') + @output.puts(line) + end +end + +def puts(line='') + $preprocessor.puts(line) +end + +def rubypp(input_file, output_file) + input = input_file ? File.open(input_file) : $stdin + output = output_file ? File.open(output_file, 'w') : $stdout + + success = false + begin + $preprocessor = Preprocessor.new(input, output, input_file || "(stdin)") + $preprocessor.preprocess() + success = true + ensure + if not success then + File.unlink(output_file) rescue Errno::ENOENT + end + end +end + +if __FILE__ == $0 then + input_file = ARGV[0] + output_file = ARGV[1] + rubypp(input_file, output_file) +end + diff --git a/jitruby/generate_rdoc.rb b/jitruby/generate_rdoc.rb new file mode 100644 index 0000000..5084b0c --- /dev/null +++ b/jitruby/generate_rdoc.rb @@ -0,0 +1,33 @@ +require 'find' + +def list_files(dir, pattern) + arr = [] + Find.find(dir) do |filename| + if filename =~ pattern then + arr.push(filename) + end + end + return arr +end + +def generate_rdoc(*options) + begin + require 'rdoc/rdoc' + rescue LoadError + puts "WARNING: RDoc not installed; skipping generation of docs" + return + end + + r = RDoc::RDoc.new + rdoc_files = [] + rdoc_files.concat [ 'README' ] + rdoc_files.concat list_files('lib', /\.rb$/) if File.exist?('lib') + rdoc_files.concat list_files('ext', /\.c$/) if File.exist?('ext') + rdoc_files.reject! { |file| file =~ %r{^ext/cached/} } + r.document(options + rdoc_files) +end + +if __FILE__ == $0 then + generate_rdoc(*ARGV) +end + diff --git a/jitruby/lib/jit.rb b/jitruby/lib/jit.rb new file mode 100644 index 0000000..9f91423 --- /dev/null +++ b/jitruby/lib/jit.rb @@ -0,0 +1,5 @@ +require 'jit.so' +require 'jit/array' +require 'jit/function' +require 'jit/struct' +require 'jit/value' diff --git a/jitruby/lib/jit/array.rb b/jitruby/lib/jit/array.rb new file mode 100644 index 0000000..eec84b2 --- /dev/null +++ b/jitruby/lib/jit/array.rb @@ -0,0 +1,135 @@ +require 'jit' +require 'jit/value' + +module JIT + + # An abstraction for a fixed-length array type. + # + # Example usage: + # + # array_type = JIT::Array.new(JIT::Type::INT, 4) + # + # JIT::Context.build do |context| + # signature = JIT::Type.create_signature( + # JIT::ABI::CDECL, JIT::Type::INT, [ ]) + # + # function = JIT::Function.compile(context, signature) do |f| + # array_instance = array_type.create(f) + # array_instance[0] = f.const(JIT::Type::INT, 42) + # f.insn_return(array_instance[0]) + # end + # + # end + # + class Array < JIT::Type + attr_reader :type + attr_reader :length + + # Create a new JIT array type. + # + # +type+:: The type of the elements in the array. + # +length+:: The number of elements in the array. + # + def self.new(type, length) + array = self.create_struct([ type ] * length) + array.instance_eval do + @type = type + @length = length + end + return array + end + + # Wrap an existing array. + # + # +ptr+:: A pointer to the first element in the array. + # + def wrap(ptr) + return Instance.wrap(self, ptr) + end + + # Create a new array. + # + # +function+:: The JIT::Function this array will be used in. + # + def create(function) + instance = function.value(self) + ptr = function.insn_address_of(instance) + return wrap(ptr) + end + + # Return the offset (in bytes) of the element at the given +index+. + # + # +index+:: The index of the desired element. + # + def offset_of(index) + return self.get_offset(index) + end + + # Return the type of the element at the given +index+. + # + # +index+:: The index of the desired element. + # + def type_of(index) + return @type + end + + # An abstraction for an instance of a fixed-length array. + # + class Instance < JIT::Value + attr_reader :array_type + attr_reader :type + + # A pointer to the first element of the array. Note that this + # differs from +address+, which returns the address of a pointer + # to the array. + attr_reader :ptr + + # TODO: This breaks code below? + # attr_reader :function + + # Wrap an existing array. + # + # +array_type+:: The JIT::Array type to wrap. + # +ptr+:: A pointer to the first element in the array. + # + def self.wrap(array_type, ptr) + pointer_type = JIT::Type.create_pointer(array_type) + value = self.new_value(ptr.function, pointer_type) + value.store(ptr) + value.instance_eval do + @array_type = array_type + @type = array_type.type + @function = ptr.function + @ptr = ptr + end + return value + end + + # Generate JIT code to retrieve the element at the given +index+. + # + # +index+:: The index of the desired element. The value of the + # index must be known at compile-time. + # + def [](index) + @function.insn_load_relative( + @ptr, + @array_type.offset_of(index), + @array_type.type_of(index)) + end + + # Generate JIT code to assign to the element at the given +index+. + # + # +index+:: The index of the desired element. The value of the + # index must be known at compile-time. + # +value+:: The JIT::Value to assign to the element. + # + def []=(index, value) + @function.insn_store_relative( + @ptr, + @array_type.offset_of(index), + value) + end + end + end +end + diff --git a/jitruby/lib/jit/function.rb b/jitruby/lib/jit/function.rb new file mode 100644 index 0000000..91b3e40 --- /dev/null +++ b/jitruby/lib/jit/function.rb @@ -0,0 +1,200 @@ +require 'jit' + +module JIT + class Function + # An abstraction for conditionals. + # + # Example usage: + # + # function.if(condition) { + # # condition is true + # } .elsif(condition2) { + # # condition2 is true + # } .else { + # # condition1 and condition2 are false + # } .end + # + # Caution: if you omit end, then the generated code will have + # undefined behavior, but there will be no warning generated. + def if(cond, end_label = Label.new, &block) + false_label = Label.new + insn_branch_if_not(cond, false_label) + block.call + insn_branch(end_label) + insn_label(false_label) + return If.new(self, end_label) + end + + # An abstraction for an inverted conditional. + # + # Example usage: + # + # function.unless(condition) { + # # condition is false + # } .elsunless(condition2) { + # # condition2 is false + # } .else { + # # condition1 and condition2 are true + # } .end + # + # Caution: if you omit end, then the generated code will have + # undefined behavior, but there will be no warning generated. + def unless(cond, end_label = Label.new, &block) + true_label = Label.new + insn_branch_if(cond, true_label) + block.call + insn_branch(end_label) + insn_label(true_label) + return If.new(self, end_label) + end + + class If # :nodoc: + def initialize(function, end_label) + @function = function + @end_label = end_label + end + + def else(&block) + block.call + return self + end + + def elsif(cond, &block) + return @function.if(cond, @end_label, &block) + end + + def elsunless(cond, &block) + return @function.unless(cond, @end_label, &block) + end + + def end + @function.insn_label(@end_label) + end + end + + # An abstraction for a multi-way conditional. + # + # Example usage: + # + # function.case(value1) + # .when(value2) { + # # value1 == value2 + # }.when(value3) { + # # value1 == value3 + # } .else { + # # all other cases fell through + # } .end + # + # Caution: if you omit end, then the generated code will have + # undefined behavior, but there will be no warning generated. + def case(value) + return Case.new(self, value) + end + + class Case # :nodoc: + def initialize(function, value) + @function = function + @value = value + @if = nil + end + + def when(value, &block) + if not @if then + @if = @function.if(value == @value, &block) + else + @if.elsif(value == @value, &block) + end + return self + end + + def else(&block) + @if.else(&block) + end + + def end + @if.end if @if + end + end + + # Usage: + # + # until { }.do { + # # loop body + # } .end + # + def until(&block) + start_label = Label.new + done_label = Label.new + insn_label(start_label) + insn_branch_if(block.call, done_label) + loop = Loop.new(self, start_label, done_label) + return loop + end + + # Usage: + # + # while { }.do { + # # loop body + # } .end + # + def while(&block) + start_label = Label.new + done_label = Label.new + insn_label(start_label) + insn_branch_if_not(block.call, done_label) + loop = Loop.new(self, start_label, done_label) + return loop + end + + class Loop # :nodoc: + def initialize(function, start_label, done_label) + @function = function + @start_label = start_label + @redo_label = start_label + @done_label = done_label + end + + def do(&block) + block.call(self) + return self + end + + def end + @function.insn_branch(@start_label) + @function.insn_label(@done_label) + end + + def break + @function.insn_branch(@done_label) + end + + def redo + @function.insn_branch(@redo_label) + end + + def redo_from_here + @redo_label = JIT::Label.new + @function.insn_label(@redo_label) + end + end + + # An alias for get_param + def param(n) + self.get_param(n) + end + + # An alias for insn_return + def return(result) + self.insn_return(result) + end + + # Create a JIT::Context and compile a new function within that + # context. + def self.build(*args, &block) + JIT::Context.build do |context| + JIT::Function.compile(context, *args, &block) + end + end + end +end + diff --git a/jitruby/lib/jit/pointer.rb b/jitruby/lib/jit/pointer.rb new file mode 100644 index 0000000..db7eae9 --- /dev/null +++ b/jitruby/lib/jit/pointer.rb @@ -0,0 +1,98 @@ +require 'jit' +require 'jit/value' + +module JIT + + # An abstraction for a pointer to a non-void type. + # + # Example usage: + # + # TODO + # + class Pointer < JIT::Type + attr_reader :type + + # Create a new JIT pointer type. + # + # +type+:: The pointed-to type. + # + def self.new(type) + pointer_type = self.create_pointer(type) + pointer_type.instance_eval do + @type = type + end + return pointer_type + end + + # Wrap an existing void pointer. + # + # +ptr+:: The pointer to wrap. + # + def wrap(ptr) + return Instance.wrap(self, ptr) + end + + # Return the offset (in bytes) of the element at the given +index+. + # + # +index+:: The index of the desired element. + # + def offset_of(index) + return index * @type.size + end + + # Return the type of the element at the given +index+. + # + # +index+:: The index of the desired element. + # + def type_of(index) + return @type + end + + # An abstraction for a pointer object. + # + class Instance < JIT::Value + # Wrap an existing void pointer. + # + # +array_type+:: The JIT::Array type to wrap. + # +ptr+:: A pointer to the first element in the array. + # + def self.wrap(pointer_type, ptr) + value = self.new_value(ptr.function, pointer_type) + value.store(ptr) + value.instance_eval do + @pointer_type = pointer_type + @pointed_type = pointer_type.type + @function = ptr.function + @ptr = ptr + end + return value + end + + # Generate JIT code to retrieve the element at the given +index+. + # + # +index+:: The index of the desired element. The value of the + # index must be known at compile-time. + # + def [](index) + @function.insn_load_relative( + @ptr, + @pointer_type.offset_of(index), + @pointer_type.type_of(index)) + end + + # Generate JIT code to assign to the element at the given +index+. + # + # +index+:: The index of the desired element. The value of the + # index must be known at compile-time. + # +value+:: The JIT::Value to assign to the element. + # + def []=(index, value) + @function.insn_store_relative( + @ptr, + @pointer_type.offset_of(index), + value) + end + end + end +end + diff --git a/jitruby/lib/jit/struct.rb b/jitruby/lib/jit/struct.rb new file mode 100644 index 0000000..0ab6bd9 --- /dev/null +++ b/jitruby/lib/jit/struct.rb @@ -0,0 +1,158 @@ +require 'jit' + +module JIT + + # An abstraction for a record composed of heterogenous fields. + # + # Example usage: + # + # point_type = JIT::Struct.new( + # [ :x, JIT::Type::INT ], + # [ :y, JIT::Type::INT ], + # [ :z, JIT::Type::INT ]) + # + # JIT::Context.build do |context| + # signature = JIT::Type.create_signature( + # JIT::ABI::CDECL, JIT::Type::INT, [ ]) + # + # function = JIT::Function.compile(context, signature) do |f| + # point = point_type.create(f) + # point.x = function.const(JIT::Type::INT, 1) + # point.y = function.const(JIT::Type::INT, 2) + # point.z = function.const(JIT::Type::INT, 3) + # f.insn_return(point.x) + # end + # + # end + # + class Struct < JIT::Type + + # Construct a new JIT structure type. + # + # +members+:: A list of members, where each element in the list is a + # two-element array [ :name, type ] + # + def self.new(*members) + member_names = members.map { |m| m[0].to_s.intern } + member_types = members.map { |m| m[1] } + type = self.create_struct(member_types) + type.instance_eval do + @members = members + @member_names = member_names + @member_types = member_types + @index = Hash[*@member_names.zip((0..@member_names.size).to_a).flatten] + end + return type + end + + # Return the names of the members in the structure. + def members + return @member_names + end + + # Wrap an existing structure. + # + # +ptr+:: A pointer to the first element in the structure. + # + def wrap(ptr) + return Instance.new(self, ptr) + end + + # Create a new structure. + # + # +function+:: The JIT::Function this structure will be used in. + # + def create(function) + instance = function.value(self) + ptr = function.insn_address_of(instance) + return wrap(ptr) + end + + # Return the offset (in bytes) of the element with the given name. + # + # +name+:: The name of the desired element. + # + def offset_of(name) + name = (Symbol === name) ? name : name.to_s.intern + return self.get_offset(@index[name]) + end + + # Change the offset of the element with the given name. + # + # +name+:: The name of the desired element. + # +offset+:: The new offset. + # + def set_offset_of(name, offset) + name = (Symbol === name) ? name : name.to_s.intern + return self.set_offset(@index[name], offset) + end + + # Return the type of the element with the given name. + # + # +name+:: The name of the desired element. + # + def type_of(name) + name = (Symbol === name) ? name : name.to_s.intern + return @member_types[@index[name]] + end + + # An abstraction for an instance of a JIT::Struct. + # + class Instance + attr_reader :ptr + + # Wrap an existing structure. + # + # +struct+:: The JIT::Struct type to wrap. + # +ptr+ A pointer to the first element of the structure. + # + def initialize(struct, ptr) + @struct = struct + @function = ptr.function + @ptr = ptr + + mod = Module.new do + struct.members.each do |name| + define_method("#{name}") do + self[name] # return + end + + define_method("#{name}=") do |value| + self[name] = value # return + end + end + end + + extend(mod) + end + + # Generate JIT code to retrieve the element with the given name. + # + # +name+:: The name of the desired element. + # + def [](name) + @function.insn_load_relative( + @ptr, + @struct.offset_of(name), + @struct.type_of(name)) + end + + # Generate JIT code to assign to the element with the given name. + # + # +name+:: The name of the desired element. + # +value+:: The JIT::Value to assign to the element. + # + def []=(name, value) + @function.insn_store_relative( + @ptr, + @struct.offset_of(name), + value) + end + + def members + return @struct.members + end + end + end +end + diff --git a/jitruby/lib/jit/value.rb b/jitruby/lib/jit/value.rb new file mode 100644 index 0000000..ca2907d --- /dev/null +++ b/jitruby/lib/jit/value.rb @@ -0,0 +1,199 @@ +require 'jit' + +module JIT + class Value + module UNINITIALIZED; end + + # Create a new JIT::Value. If value is specified, the value will be + # variable, otherwise it will be a constant with the given value. + # + # +function+:: The function to which this value will belong. + # +type+:: The type of the new value. + # +value+: The value to use, if this is a constant. + # + def self.new(function, type, value=UNINITIALIZED) + # TODO: Not sure if I like this... + if value == UNINITIALIZED then + return function.value(type) + else + return function.const(type, value) + end + end + + # Assign +value+ to this JIT::Value. + # + # +value+:: The value to assign. + # + def store(value) + lhs, rhs = coerce(value) + self.function.insn_store(lhs, rhs) + end + + # Return the address of this value. + def address + return self.function.insn_address_of(self) + end + + # Add this value to another and return the result. + # + # +rhs+:: The right hand side of the addition operator. + # + def +(rhs) + lhs, rhs = coerce(rhs) + return self.function.insn_add(lhs, rhs) + end + + # Subtract another value from this one and return the result. + # + # +rhs+:: The right hand side of the subtraction operator. + # + def -(rhs) + lhs, rhs = coerce(rhs) + return self.function.insn_sub(lhs, rhs) + end + + # Multiply this value by another and return the result. + # + # +rhs+:: The right hand side of the multiplication operator. + # + def *(rhs) + lhs, rhs = coerce(rhs) + return self.function.insn_mul(lhs, rhs) + end + + # Divide this value by another and return the quotient. + # + # +rhs+:: The right hand side of the division operator. + # + def /(rhs) + lhs, rhs = coerce(rhs) + return self.function.insn_div(lhs, rhs) + end + + # Return the additive inverse (negation) of this value. + def -@() + rhs, lhs = coerce(0) # inverted, since we are subtracting from 0 + return lhs - rhs + end + + # Divide this value by another and return the remainder. + # + # +rhs+:: The right hand side of the modulus operator. + # + def %(rhs) + lhs, rhs = coerce(rhs) + return self.function.insn_rem(lhs, rhs) + end + + # Perform a bitwise and between this value and another. + # + # +rhs+:: The right hand side of the operator. + # + def &(rhs) + lhs, rhs = coerce(rhs) + return self.function.insn_and(lhs, rhs) + end + + # Perform a bitwise or between this value and another. + # + # +rhs+:: The right hand side of the operator. + # + def |(rhs) + lhs, rhs = coerce(rhs) + return self.function.insn_or(lhs, rhs) + end + + # Perform a bitwise xor between this value and another. + # + # +rhs+:: The right hand side of the operator. + # + def ^(rhs) + lhs, rhs = coerce(rhs) + return self.function.insn_xor(lhs, rhs) + end + + # Compare this value to another, returning 1 if the left hand is + # less than the right hand side or 0 otherwise. + # + # +rhs+:: The right hand side of the comparison. + # + def <(rhs) + lhs, rhs = coerce(rhs) + return self.function.insn_lt(lhs, rhs) + end + + # Compare this value to another, returning 1 if the left hand is + # greater than the right hand side or 0 otherwise. + # + # +rhs+:: The right hand side of the comparison. + # + def >(rhs) + lhs, rhs = coerce(rhs) + return self.function.insn_gt(lhs, rhs) + end + + # Compare this value to another, returning 1 if the left hand is + # equal to the right hand side or 0 otherwise. + # + # +rhs+:: The right hand side of the comparison. + # + def ==(rhs) + lhs, rhs = coerce(rhs) + return self.function.insn_eq(lhs, rhs) + end + + # Compare this value to another, returning 1 if the left hand is + # not equal to the right hand side or 0 otherwise. + # + # +rhs+:: The right hand side of the comparison. + # + def neq(rhs) + lhs, rhs = coerce(rhs) + return self.function.insn_ne(lhs, rhs) + end + + # Compare this value to another, returning 1 if the left hand is + # less than or equal to the right hand side or 0 otherwise. + # + # +rhs+:: The right hand side of the comparison. + # + def <=(rhs) + lhs, rhs = coerce(rhs) + return self.function.insn_le(lhs, rhs) + end + + # Compare this value to another, returning 1 if the left hand is + # greater than or equal to the right hand side or 0 otherwise. + # + # +rhs+:: The right hand side of the comparison. + # + def >=(rhs) + lhs, rhs = coerce(rhs) + return self.function.insn_ge(lhs, rhs) + end + + # Shift this value left by the given number of bits. + # + # +rhs+:: The number of bits to shift by. + # + def <<(rhs) + lhs, rhs = coerce(rhs) + return self.function.insn_shl(lhs, rhs) + end + + # Shift this value right by the given number of bits. + # + # +rhs+:: The number of bits to shift by. + # + def >>(rhs) + lhs, rhs = coerce(rhs) + return self.function.insn_shr(lhs, rhs) + end + + # Return the logical inverse of this value. + def ~() + return self.function.insn_not(self) + end + end +end + diff --git a/jitruby/metaconfig b/jitruby/metaconfig new file mode 100644 index 0000000..dd2a076 --- /dev/null +++ b/jitruby/metaconfig @@ -0,0 +1,4 @@ +add_bool_config( + 'without-tests', + false, + 'does not run tests') diff --git a/jitruby/post-install.rb b/jitruby/post-install.rb new file mode 100644 index 0000000..f7d53c6 --- /dev/null +++ b/jitruby/post-install.rb @@ -0,0 +1,4 @@ +require 'generate_rdoc' + +generate_rdoc('--ri-site') + diff --git a/jitruby/post-setup.rb b/jitruby/post-setup.rb new file mode 100644 index 0000000..002a037 --- /dev/null +++ b/jitruby/post-setup.rb @@ -0,0 +1,7 @@ +require 'run_tests' +require 'generate_rdoc' + +if config('without-tests') != 'yes' then + run_tests() +end + diff --git a/jitruby/ruby-libjit.gemspec b/jitruby/ruby-libjit.gemspec new file mode 100644 index 0000000..0d81f18 --- /dev/null +++ b/jitruby/ruby-libjit.gemspec @@ -0,0 +1,39 @@ +require 'enumerator' + +spec = Gem::Specification.new do |s| + s.name = 'ruby-libjit' + s.version = '0.1.0' + s.summary = 'A wrapper for the libjit library' + s.homepage = 'http://ruby-libjit.rubyforge.org' + s.rubyforge_project = 'ruby-libjit' + s.author = 'Paul Brannan' + s.email = 'curlypaul924@gmail.com' + + s.description = <<-END +A wrapper for the libjit library + END + + + patterns = [ + 'COPYING', + 'LGPL', + 'LICENSE', + 'README', + 'lib/*.rb', + 'lib/jit/*.rb', + 'ext/*.rb', + 'ext/*.c', + 'ext/*.h', + 'ext/*.rpp', + 'sample/*.rb', + ] + + s.files = patterns.collect { |p| Dir.glob(p) }.flatten + + s.test_files = Dir.glob('test/test_*.rb') + + s.extensions = 'ext/extconf.rb' + + s.has_rdoc = true +end + diff --git a/jitruby/run_tests.rb b/jitruby/run_tests.rb new file mode 100644 index 0000000..7f050b9 --- /dev/null +++ b/jitruby/run_tests.rb @@ -0,0 +1,81 @@ +$mini_unit_exit_code = 0 + +def disable_mini_unit_auto_run + MiniTest::Unit.class_eval do + alias :run_ :run + def run(*args) + return $mini_unit_exit_code + end + end +end + +def run_tests_with_mini_unit + begin + test = MiniTest::Unit.new + args = ARGV.dup + args << '-v' + $mini_unit_exit_code = test.run(args) + exit($mini_unit_exit_code) + ensure + disable_mini_unit_auto_run + end +end + +def run_tests_with_test_unit + tests = [] + ObjectSpace.each_object(Class) do |o| + if o < Test::Unit::TestCase then + tests << o + end + end + + suite = Test::Unit::TestSuite.new("RubyInternal") + tests.each do |test| + test.suite.tests.each do |testcase| + suite << testcase + end + end + + require 'test/unit/ui/console/testrunner' + verbose = nil + begin + verbose = Test::Unit::UI.const_get(:VERBOSE) + rescue NameError + verbose = Test::Unit::UI::Console::TestRunner.const_get(:VERBOSE) + end + + result = Test::Unit::UI::Console::TestRunner.run( + suite, + verbose) + exit(result.error_count + result.failure_count) +end + +def run_tests + begin + require 'test/unit' + rescue LoadError + puts "WARNING: Test::Unit not installed; skipping tests" + return + end + + $:.unshift(File.join(Dir.pwd, 'ext')) + $:.unshift(File.join(Dir.pwd, 'lib')) + Dir.chdir('test') + tests = Dir['test_*.rb'] + tests.each do |test| + load test + end + + if defined?(MiniTest) then + run_tests_with_mini_unit + else + run_tests_with_test_unit + end +end + +if __FILE__ == $0 then + require 'timeout' + result = nil + timeout(600) { run_tests() } +end + diff --git a/jitruby/sample/fib.rb b/jitruby/sample/fib.rb new file mode 100644 index 0000000..481c295 --- /dev/null +++ b/jitruby/sample/fib.rb @@ -0,0 +1,29 @@ +require 'jit' + +fib = nil +signature = JIT::Type.create_signature( + :CDECL, + :INT, + [ :INT ]) +fib = JIT::Function.build(signature) do |f| + n = f.param(0) + + a = f.value(:INT, 0) + b = f.value(:INT, 1) + c = f.value(:INT, 1) + + i = f.value(:INT, 0) + + f.while{ i < n }.do { + c.store(a + b) + a.store(b) + b.store(c) + i.store(i + 1) + }.end + + f.return(c) +end + +values = (0...10).collect { |x| fib.apply(x) } +p values #=> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] + diff --git a/jitruby/sample/gcd_benchmark.rb b/jitruby/sample/gcd_benchmark.rb new file mode 100644 index 0000000..66eacdc --- /dev/null +++ b/jitruby/sample/gcd_benchmark.rb @@ -0,0 +1,117 @@ +require 'jit' +require 'benchmark' + +# GCD, JIT-compiled + +jit_gcd = nil + +JIT::Context.build do |context| + signature = JIT::Type.create_signature( + JIT::ABI::CDECL, + JIT::Type::INT, + [ JIT::Type::INT, JIT::Type::INT ]) + jit_gcd = JIT::Function.compile(context, signature) do |f| + x = f.get_param(0) + y = f.get_param(1) + temp1 = f.insn_eq(x, y) + label1 = JIT::Label.new + f.insn_branch_if_not(temp1, label1) + f.insn_return(x) + f.insn_label(label1) + temp2 = f.insn_lt(x, y) + label2 = JIT::Label.new + f.insn_branch_if_not(temp2, label2) + s1 = f.insn_sub(y, x) + temp3 = f.insn_call("gcd", f, 0, x, s1) + f.insn_return(temp3) + f.insn_label(label2) + s2 = f.insn_sub(x, y) + temp4 = f.insn_call("gcd", f, 0, s2, y) + f.insn_return(temp4) + + f.optimization_level = 3 + end +end + +if jit_gcd.apply(28, 21) != 7 then + puts "jit_gcd is broken" + exit 1 +end + + +# GCD with tail recursion optimization + +jit_gcd_tail = nil + +JIT::Context.build do |context| + signature = JIT::Type.create_signature( + JIT::ABI::CDECL, + JIT::Type::INT, + [ JIT::Type::INT, JIT::Type::INT ]) + jit_gcd_tail = JIT::Function.compile(context, signature) do |f| + x = f.get_param(0) + y = f.get_param(1) + temp1 = f.insn_eq(x, y) + label1 = JIT::Label.new + f.insn_branch_if_not(temp1, label1) + f.insn_return(x) + f.insn_label(label1) + temp2 = f.insn_lt(x, y) + label2 = JIT::Label.new + f.insn_branch_if_not(temp2, label2) + s1 = f.insn_sub(y, x) + temp3 = f.insn_call("gcd", f, JIT::Call::TAIL, x, s1) + # f.insn_return(temp3) + f.insn_label(label2) + s2 = f.insn_sub(x, y) + temp4 = f.insn_call("gcd", f, JIT::Call::TAIL, s2, x) + # f.insn_return(temp4) + + f.optimization_level = 3 + end +end + +if jit_gcd_tail.apply(28, 21) != 7 then + puts "jit_gcd_tail is broken" + exit 1 +end + + +# GCD in ruby with recursion + +def gcd(x, y) + if x == y + return x + elsif x < y + return gcd(x, y - x) + else + return gcd(x - y, y) + end +end + + +# GCD in ruby without recursion + +def gcd2(x, y) + while x != y do + if x < y + y -= x + else + x -= y + end + end + return x +end + +N = 1000 + +X = 1000 +Y = 1005 + +Benchmark.bm(16) do |x| + x.report("jit") { N.times { jit_gcd.apply(X, Y) } } + x.report("jit tail:") { N.times { jit_gcd_tail.apply(X, Y) } } + x.report("ruby recur:") { N.times { gcd(X, Y) } } + x.report("ruby iter:") { N.times { gcd2(X, Y) } } +end + diff --git a/jitruby/sample/simple.rb b/jitruby/sample/simple.rb new file mode 100644 index 0000000..86b23f3 --- /dev/null +++ b/jitruby/sample/simple.rb @@ -0,0 +1,15 @@ +require 'jit' + +function = nil +JIT::Context.build do |context| + signature = JIT::Type.create_signature( + JIT::ABI::CDECL, + JIT::Type::INT, # returns an integer + [ JIT::Type::INT ]) # and tages an integer as a parameter + function = JIT::Function.compile(context, signature) do |f| + value = f.get_param(0) + f.insn_return(value) + end +end + +p function.apply(42) #=> 42 diff --git a/jitruby/setup.rb b/jitruby/setup.rb new file mode 100644 index 0000000..eebccd1 --- /dev/null +++ b/jitruby/setup.rb @@ -0,0 +1,1599 @@ +# +# setup.rb +# +# Copyright (c) 2000-2006 Minero Aoki +# +# This program is free software. +# You can distribute/modify this program under the terms of +# the GNU LGPL, Lesser General Public License version 2.1. +# + +unless Enumerable.method_defined?(:map) # Ruby 1.4.6 + module Enumerable + alias map collect + end +end + +unless File.respond_to?(:read) # Ruby 1.6 + def File.read(fname) + open(fname) {|f| + return f.read + } + end +end + +unless Errno.const_defined?(:ENOTEMPTY) # Windows? + module Errno + class ENOTEMPTY + # We do not raise this exception, implementation is not needed. + end + end +end + +def File.binread(fname) + open(fname, 'rb') {|f| + return f.read + } +end + +# for corrupted Windows' stat(2) +def File.dir?(path) + File.directory?((path[-1,1] == '/') ? path : path + '/') +end + + +class ConfigTable + + include Enumerable + + def initialize(rbconfig) + @rbconfig = rbconfig + @items = [] + @table = {} + # options + @install_prefix = nil + @config_opt = nil + @verbose = true + @no_harm = false + end + + attr_accessor :install_prefix + attr_accessor :config_opt + + attr_writer :verbose + + def verbose? + @verbose + end + + attr_writer :no_harm + + def no_harm? + @no_harm + end + + def [](key) + lookup(key).resolve(self) + end + + def []=(key, val) + lookup(key).set val + end + + def names + @items.map {|i| i.name } + end + + def each(&block) + @items.each(&block) + end + + def key?(name) + @table.key?(name) + end + + def lookup(name) + @table[name] or setup_rb_error "no such config item: #{name}" + end + + def add(item) + @items.push item + @table[item.name] = item + end + + def remove(name) + item = lookup(name) + @items.delete_if {|i| i.name == name } + @table.delete_if {|name, i| i.name == name } + item + end + + def load_script(path, inst = nil) + if File.file?(path) + MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path + end + end + + def savefile + '.config' + end + + def load_savefile + begin + File.foreach(savefile()) do |line| + k, v = *line.split(/=/, 2) + self[k] = v.strip + end + rescue Errno::ENOENT + setup_rb_error $!.message + "\n#{File.basename($0)} config first" + end + end + + def save + @items.each {|i| i.value } + File.open(savefile(), 'w') {|f| + @items.each do |i| + f.printf "%s=%s\n", i.name, i.value if i.value? and i.value + end + } + end + + def load_standard_entries + standard_entries(@rbconfig).each do |ent| + add ent + end + end + + def standard_entries(rbconfig) + c = rbconfig + + rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT']) + + major = c['MAJOR'].to_i + minor = c['MINOR'].to_i + teeny = c['TEENY'].to_i + version = "#{major}.#{minor}" + + # ruby ver. >= 1.4.4? + newpath_p = ((major >= 2) or + ((major == 1) and + ((minor >= 5) or + ((minor == 4) and (teeny >= 4))))) + + if c['rubylibdir'] + # V > 1.6.3 + libruby = "#{c['prefix']}/lib/ruby" + librubyver = c['rubylibdir'] + librubyverarch = c['archdir'] + siteruby = c['sitedir'] + siterubyver = c['sitelibdir'] + siterubyverarch = c['sitearchdir'] + elsif newpath_p + # 1.4.4 <= V <= 1.6.3 + libruby = "#{c['prefix']}/lib/ruby" + librubyver = "#{c['prefix']}/lib/ruby/#{version}" + librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" + siteruby = c['sitedir'] + siterubyver = "$siteruby/#{version}" + siterubyverarch = "$siterubyver/#{c['arch']}" + else + # V < 1.4.4 + libruby = "#{c['prefix']}/lib/ruby" + librubyver = "#{c['prefix']}/lib/ruby/#{version}" + librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" + siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby" + siterubyver = siteruby + siterubyverarch = "$siterubyver/#{c['arch']}" + end + parameterize = lambda {|path| + path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix') + } + + if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg } + makeprog = arg.sub(/'/, '').split(/=/, 2)[1] + else + makeprog = 'make' + end + + [ + ExecItem.new('installdirs', 'std/site/home', + 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\ + {|val, table| + case val + when 'std' + table['rbdir'] = '$librubyver' + table['sodir'] = '$librubyverarch' + when 'site' + table['rbdir'] = '$siterubyver' + table['sodir'] = '$siterubyverarch' + when 'home' + setup_rb_error '$HOME was not set' unless ENV['HOME'] + table['prefix'] = ENV['HOME'] + table['rbdir'] = '$libdir/ruby' + table['sodir'] = '$libdir/ruby' + end + }, + PathItem.new('prefix', 'path', c['prefix'], + 'path prefix of target environment'), + PathItem.new('bindir', 'path', parameterize.call(c['bindir']), + 'the directory for commands'), + PathItem.new('libdir', 'path', parameterize.call(c['libdir']), + 'the directory for libraries'), + PathItem.new('datadir', 'path', parameterize.call(c['datadir']), + 'the directory for shared data'), + PathItem.new('mandir', 'path', parameterize.call(c['mandir']), + 'the directory for man pages'), + PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']), + 'the directory for system configuration files'), + PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']), + 'the directory for local state data'), + PathItem.new('libruby', 'path', libruby, + 'the directory for ruby libraries'), + PathItem.new('librubyver', 'path', librubyver, + 'the directory for standard ruby libraries'), + PathItem.new('librubyverarch', 'path', librubyverarch, + 'the directory for standard ruby extensions'), + PathItem.new('siteruby', 'path', siteruby, + 'the directory for version-independent aux ruby libraries'), + PathItem.new('siterubyver', 'path', siterubyver, + 'the directory for aux ruby libraries'), + PathItem.new('siterubyverarch', 'path', siterubyverarch, + 'the directory for aux ruby binaries'), + PathItem.new('rbdir', 'path', '$siterubyver', + 'the directory for ruby scripts'), + PathItem.new('sodir', 'path', '$siterubyverarch', + 'the directory for ruby extentions'), + PathItem.new('rubypath', 'path', rubypath, + 'the path to set to #! line'), + ProgramItem.new('rubyprog', 'name', rubypath, + 'the ruby program using for installation'), + ProgramItem.new('makeprog', 'name', makeprog, + 'the make program to compile ruby extentions'), + SelectItem.new('shebang', 'all/ruby/never', 'ruby', + 'shebang line (#!) editing mode'), + BoolItem.new('without-ext', 'yes/no', 'no', + 'does not compile/install ruby extentions') + ] + end + private :standard_entries + + def load_multipackage_entries + multipackage_entries().each do |ent| + add ent + end + end + + def multipackage_entries + [ + PackageSelectionItem.new('with', 'name,name...', '', 'ALL', + 'package names that you want to install'), + PackageSelectionItem.new('without', 'name,name...', '', 'NONE', + 'package names that you do not want to install') + ] + end + private :multipackage_entries + + ALIASES = { + 'std-ruby' => 'librubyver', + 'stdruby' => 'librubyver', + 'rubylibdir' => 'librubyver', + 'archdir' => 'librubyverarch', + 'site-ruby-common' => 'siteruby', # For backward compatibility + 'site-ruby' => 'siterubyver', # For backward compatibility + 'bin-dir' => 'bindir', + 'bin-dir' => 'bindir', + 'rb-dir' => 'rbdir', + 'so-dir' => 'sodir', + 'data-dir' => 'datadir', + 'ruby-path' => 'rubypath', + 'ruby-prog' => 'rubyprog', + 'ruby' => 'rubyprog', + 'make-prog' => 'makeprog', + 'make' => 'makeprog' + } + + def fixup + ALIASES.each do |ali, name| + @table[ali] = @table[name] + end + end + + def options_re + /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/ + end + + def parse_opt(opt) + m = options_re().match(opt) or setup_rb_error "config: unknown option #{opt}" + m.to_a[1,2] + end + + def dllext + @rbconfig['DLEXT'] + end + + def value_config?(name) + lookup(name).value? + end + + class Item + def initialize(name, template, default, desc) + @name = name.freeze + @template = template + @value = default + @default = default + @description = desc + end + + attr_reader :name + attr_reader :description + + attr_accessor :default + alias help_default default + + def help_opt + "--#{@name}=#{@template}" + end + + def value? + true + end + + def value + @value + end + + def resolve(table) + @value.gsub(%r<\$([^/]+)>) { table[$1] } + end + + def set(val) + @value = check(val) + end + + private + + def check(val) + setup_rb_error "config: --#{name} requires argument" unless val + val + end + end + + class BoolItem < Item + def config_type + 'bool' + end + + def help_opt + "--#{@name}" + end + + private + + def check(val) + return 'yes' unless val + case val + when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes' + when /\An(o)?\z/i, /\Af(alse)\z/i then 'no' + else + setup_rb_error "config: --#{@name} accepts only yes/no for argument" + end + end + end + + class PathItem < Item + def config_type + 'path' + end + + private + + def check(path) + setup_rb_error "config: --#{@name} requires argument" unless path + path[0,1] == '$' ? path : File.expand_path(path) + end + end + + class ProgramItem < Item + def config_type + 'program' + end + end + + class SelectItem < Item + def initialize(name, selection, default, desc) + super + @ok = selection.split('/') + end + + def config_type + 'select' + end + + private + + def check(val) + unless @ok.include?(val.strip) + setup_rb_error "config: use --#{@name}=#{@template} (#{val})" + end + val.strip + end + end + + class ExecItem < Item + def initialize(name, selection, desc, &block) + super name, selection, nil, desc + @ok = selection.split('/') + @action = block + end + + def config_type + 'exec' + end + + def value? + false + end + + def resolve(table) + setup_rb_error "$#{name()} wrongly used as option value" + end + + undef set + + def evaluate(val, table) + v = val.strip.downcase + unless @ok.include?(v) + setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})" + end + @action.call v, table + end + end + + class PackageSelectionItem < Item + def initialize(name, template, default, help_default, desc) + super name, template, default, desc + @help_default = help_default + end + + attr_reader :help_default + + def config_type + 'package' + end + + private + + def check(val) + unless File.dir?("packages/#{val}") + setup_rb_error "config: no such package: #{val}" + end + val + end + end + + class MetaConfigEnvironment + def initialize(config, installer) + @config = config + @installer = installer + end + + def config_names + @config.names + end + + def config?(name) + @config.key?(name) + end + + def bool_config?(name) + @config.lookup(name).config_type == 'bool' + end + + def path_config?(name) + @config.lookup(name).config_type == 'path' + end + + def value_config?(name) + @config.lookup(name).config_type != 'exec' + end + + def add_config(item) + @config.add item + end + + def add_bool_config(name, default, desc) + @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc) + end + + def add_path_config(name, default, desc) + @config.add PathItem.new(name, 'path', default, desc) + end + + def set_config_default(name, default) + @config.lookup(name).default = default + end + + def remove_config(name) + @config.remove(name) + end + + # For only multipackage + def packages + raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer + @installer.packages + end + + # For only multipackage + def declare_packages(list) + raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer + @installer.packages = list + end + end + +end # class ConfigTable + + +# This module requires: #verbose?, #no_harm? +module FileOperations + + def mkdir_p(dirname, prefix = nil) + dirname = prefix + File.expand_path(dirname) if prefix + $stderr.puts "mkdir -p #{dirname}" if verbose? + return if no_harm? + + # Does not check '/', it's too abnormal. + dirs = File.expand_path(dirname).split(%r<(?=/)>) + if /\A[a-z]:\z/i =~ dirs[0] + disk = dirs.shift + dirs[0] = disk + dirs[0] + end + dirs.each_index do |idx| + path = dirs[0..idx].join('') + Dir.mkdir path unless File.dir?(path) + end + end + + def rm_f(path) + $stderr.puts "rm -f #{path}" if verbose? + return if no_harm? + force_remove_file path + end + + def rm_rf(path) + $stderr.puts "rm -rf #{path}" if verbose? + return if no_harm? + remove_tree path + end + + def remove_tree(path) + if File.symlink?(path) + remove_file path + elsif File.dir?(path) + remove_tree0 path + else + force_remove_file path + end + end + + def remove_tree0(path) + Dir.foreach(path) do |ent| + next if ent == '.' + next if ent == '..' + entpath = "#{path}/#{ent}" + if File.symlink?(entpath) + remove_file entpath + elsif File.dir?(entpath) + remove_tree0 entpath + else + force_remove_file entpath + end + end + begin + Dir.rmdir path + rescue Errno::ENOTEMPTY + # directory may not be empty + end + end + + def move_file(src, dest) + force_remove_file dest + begin + File.rename src, dest + rescue + File.open(dest, 'wb') {|f| + f.write File.binread(src) + } + File.chmod File.stat(src).mode, dest + File.unlink src + end + end + + def force_remove_file(path) + begin + remove_file path + rescue + end + end + + def remove_file(path) + File.chmod 0777, path + File.unlink path + end + + def install(from, dest, mode, prefix = nil) + $stderr.puts "install #{from} #{dest}" if verbose? + return if no_harm? + + realdest = prefix ? prefix + File.expand_path(dest) : dest + realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest) + str = File.binread(from) + if diff?(str, realdest) + verbose_off { + rm_f realdest if File.exist?(realdest) + } + File.open(realdest, 'wb') {|f| + f.write str + } + File.chmod mode, realdest + + File.open("#{objdir_root()}/InstalledFiles", 'a') {|f| + if prefix + f.puts realdest.sub(prefix, '') + else + f.puts realdest + end + } + end + end + + def diff?(new_content, path) + return true unless File.exist?(path) + new_content != File.binread(path) + end + + def command(*args) + $stderr.puts args.join(' ') if verbose? + system(*args) or raise RuntimeError, + "system(#{args.map{|a| a.inspect }.join(' ')}) failed" + end + + def ruby(*args) + command config('rubyprog'), *args + end + + def make(task = nil) + command(*[config('makeprog'), task].compact) + end + + def extdir?(dir) + File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb") + end + + def files_of(dir) + Dir.open(dir) {|d| + return d.select {|ent| File.file?("#{dir}/#{ent}") } + } + end + + DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn ) + + def directories_of(dir) + Dir.open(dir) {|d| + return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT + } + end + +end + + +# This module requires: #srcdir_root, #objdir_root, #relpath +module HookScriptAPI + + def get_config(key) + @config[key] + end + + alias config get_config + + # obsolete: use metaconfig to change configuration + def set_config(key, val) + @config[key] = val + end + + # + # srcdir/objdir (works only in the package directory) + # + + def curr_srcdir + "#{srcdir_root()}/#{relpath()}" + end + + def curr_objdir + "#{objdir_root()}/#{relpath()}" + end + + def srcfile(path) + "#{curr_srcdir()}/#{path}" + end + + def srcexist?(path) + File.exist?(srcfile(path)) + end + + def srcdirectory?(path) + File.dir?(srcfile(path)) + end + + def srcfile?(path) + File.file?(srcfile(path)) + end + + def srcentries(path = '.') + Dir.open("#{curr_srcdir()}/#{path}") {|d| + return d.to_a - %w(. ..) + } + end + + def srcfiles(path = '.') + srcentries(path).select {|fname| + File.file?(File.join(curr_srcdir(), path, fname)) + } + end + + def srcdirectories(path = '.') + srcentries(path).select {|fname| + File.dir?(File.join(curr_srcdir(), path, fname)) + } + end + +end + + +class ToplevelInstaller + + Version = '3.4.1' + Copyright = 'Copyright (c) 2000-2006 Minero Aoki' + + TASKS = [ + [ 'all', 'do config, setup, then install' ], + [ 'config', 'saves your configurations' ], + [ 'show', 'shows current configuration' ], + [ 'setup', 'compiles ruby extentions and others' ], + [ 'install', 'installs files' ], + [ 'test', 'run all tests in test/' ], + [ 'clean', "does `make clean' for each extention" ], + [ 'distclean',"does `make distclean' for each extention" ] + ] + + def ToplevelInstaller.invoke + config = ConfigTable.new(load_rbconfig()) + config.load_standard_entries + config.load_multipackage_entries if multipackage? + config.fixup + klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller) + klass.new(File.dirname($0), config).invoke + end + + def ToplevelInstaller.multipackage? + File.dir?(File.dirname($0) + '/packages') + end + + def ToplevelInstaller.load_rbconfig + if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg } + ARGV.delete(arg) + load File.expand_path(arg.split(/=/, 2)[1]) + $".push 'rbconfig.rb' + else + require 'rbconfig' + end + ::Config::CONFIG + end + + def initialize(ardir_root, config) + @ardir = File.expand_path(ardir_root) + @config = config + # cache + @valid_task_re = nil + end + + def config(key) + @config[key] + end + + def inspect + "#<#{self.class} #{__id__()}>" + end + + def invoke + run_metaconfigs + case task = parsearg_global() + when nil, 'all' + parsearg_config + init_installers + exec_config + exec_setup + exec_install + else + case task + when 'config', 'test' + ; + when 'clean', 'distclean' + @config.load_savefile if File.exist?(@config.savefile) + else + @config.load_savefile + end + __send__ "parsearg_#{task}" + init_installers + __send__ "exec_#{task}" + end + end + + def run_metaconfigs + @config.load_script "#{@ardir}/metaconfig" + end + + def init_installers + @installer = Installer.new(@config, @ardir, File.expand_path('.')) + end + + # + # Hook Script API bases + # + + def srcdir_root + @ardir + end + + def objdir_root + '.' + end + + def relpath + '.' + end + + # + # Option Parsing + # + + def parsearg_global + while arg = ARGV.shift + case arg + when /\A\w+\z/ + setup_rb_error "invalid task: #{arg}" unless valid_task?(arg) + return arg + when '-q', '--quiet' + @config.verbose = false + when '--verbose' + @config.verbose = true + when '--help' + print_usage $stdout + exit 0 + when '--version' + puts "#{File.basename($0)} version #{Version}" + exit 0 + when '--copyright' + puts Copyright + exit 0 + else + setup_rb_error "unknown global option '#{arg}'" + end + end + nil + end + + def valid_task?(t) + valid_task_re() =~ t + end + + def valid_task_re + @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/ + end + + def parsearg_no_options + unless ARGV.empty? + task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1) + setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}" + end + end + + alias parsearg_show parsearg_no_options + alias parsearg_setup parsearg_no_options + alias parsearg_test parsearg_no_options + alias parsearg_clean parsearg_no_options + alias parsearg_distclean parsearg_no_options + + def parsearg_config + evalopt = [] + set = [] + @config.config_opt = [] + while i = ARGV.shift + if /\A--?\z/ =~ i + @config.config_opt = ARGV.dup + break + end + name, value = *@config.parse_opt(i) + if @config.value_config?(name) + @config[name] = value + else + evalopt.push [name, value] + end + set.push name + end + evalopt.each do |name, value| + @config.lookup(name).evaluate value, @config + end + # Check if configuration is valid + set.each do |n| + @config[n] if @config.value_config?(n) + end + end + + def parsearg_install + @config.no_harm = false + @config.install_prefix = '' + while a = ARGV.shift + case a + when '--no-harm' + @config.no_harm = true + when /\A--prefix=/ + path = a.split(/=/, 2)[1] + path = File.expand_path(path) unless path[0,1] == '/' + @config.install_prefix = path + else + setup_rb_error "install: unknown option #{a}" + end + end + end + + def print_usage(out) + out.puts 'Typical Installation Procedure:' + out.puts " $ ruby #{File.basename $0} config" + out.puts " $ ruby #{File.basename $0} setup" + out.puts " # ruby #{File.basename $0} install (may require root privilege)" + out.puts + out.puts 'Detailed Usage:' + out.puts " ruby #{File.basename $0} " + out.puts " ruby #{File.basename $0} [] []" + + fmt = " %-24s %s\n" + out.puts + out.puts 'Global options:' + out.printf fmt, '-q,--quiet', 'suppress message outputs' + out.printf fmt, ' --verbose', 'output messages verbosely' + out.printf fmt, ' --help', 'print this message' + out.printf fmt, ' --version', 'print version and quit' + out.printf fmt, ' --copyright', 'print copyright and quit' + out.puts + out.puts 'Tasks:' + TASKS.each do |name, desc| + out.printf fmt, name, desc + end + + fmt = " %-24s %s [%s]\n" + out.puts + out.puts 'Options for CONFIG or ALL:' + @config.each do |item| + out.printf fmt, item.help_opt, item.description, item.help_default + end + out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's" + out.puts + out.puts 'Options for INSTALL:' + out.printf fmt, '--no-harm', 'only display what to do if given', 'off' + out.printf fmt, '--prefix=path', 'install path prefix', '' + out.puts + end + + # + # Task Handlers + # + + def exec_config + @installer.exec_config + @config.save # must be final + end + + def exec_setup + @installer.exec_setup + end + + def exec_install + @installer.exec_install + end + + def exec_test + @installer.exec_test + end + + def exec_show + @config.each do |i| + printf "%-20s %s\n", i.name, i.value if i.value? + end + end + + def exec_clean + @installer.exec_clean + end + + def exec_distclean + @installer.exec_distclean + end + +end # class ToplevelInstaller + + +class ToplevelInstallerMulti < ToplevelInstaller + + include FileOperations + + def initialize(ardir_root, config) + super + @packages = directories_of("#{@ardir}/packages") + raise 'no package exists' if @packages.empty? + @root_installer = Installer.new(@config, @ardir, File.expand_path('.')) + end + + def run_metaconfigs + @config.load_script "#{@ardir}/metaconfig", self + @packages.each do |name| + @config.load_script "#{@ardir}/packages/#{name}/metaconfig" + end + end + + attr_reader :packages + + def packages=(list) + raise 'package list is empty' if list.empty? + list.each do |name| + raise "directory packages/#{name} does not exist"\ + unless File.dir?("#{@ardir}/packages/#{name}") + end + @packages = list + end + + def init_installers + @installers = {} + @packages.each do |pack| + @installers[pack] = Installer.new(@config, + "#{@ardir}/packages/#{pack}", + "packages/#{pack}") + end + with = extract_selection(config('with')) + without = extract_selection(config('without')) + @selected = @installers.keys.select {|name| + (with.empty? or with.include?(name)) \ + and not without.include?(name) + } + end + + def extract_selection(list) + a = list.split(/,/) + a.each do |name| + setup_rb_error "no such package: #{name}" unless @installers.key?(name) + end + a + end + + def print_usage(f) + super + f.puts 'Inluded packages:' + f.puts ' ' + @packages.sort.join(' ') + f.puts + end + + # + # Task Handlers + # + + def exec_config + run_hook 'pre-config' + each_selected_installers {|inst| inst.exec_config } + run_hook 'post-config' + @config.save # must be final + end + + def exec_setup + run_hook 'pre-setup' + each_selected_installers {|inst| inst.exec_setup } + run_hook 'post-setup' + end + + def exec_install + run_hook 'pre-install' + each_selected_installers {|inst| inst.exec_install } + run_hook 'post-install' + end + + def exec_test + run_hook 'pre-test' + each_selected_installers {|inst| inst.exec_test } + run_hook 'post-test' + end + + def exec_clean + rm_f @config.savefile + run_hook 'pre-clean' + each_selected_installers {|inst| inst.exec_clean } + run_hook 'post-clean' + end + + def exec_distclean + rm_f @config.savefile + run_hook 'pre-distclean' + each_selected_installers {|inst| inst.exec_distclean } + run_hook 'post-distclean' + end + + # + # lib + # + + def each_selected_installers + Dir.mkdir 'packages' unless File.dir?('packages') + @selected.each do |pack| + $stderr.puts "Processing the package `#{pack}' ..." if verbose? + Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}") + Dir.chdir "packages/#{pack}" + yield @installers[pack] + Dir.chdir '../..' + end + end + + def run_hook(id) + @root_installer.run_hook id + end + + # module FileOperations requires this + def verbose? + @config.verbose? + end + + # module FileOperations requires this + def no_harm? + @config.no_harm? + end + +end # class ToplevelInstallerMulti + + +class Installer + + FILETYPES = %w( bin lib ext data conf man ) + + include FileOperations + include HookScriptAPI + + def initialize(config, srcroot, objroot) + @config = config + @srcdir = File.expand_path(srcroot) + @objdir = File.expand_path(objroot) + @currdir = '.' + end + + def inspect + "#<#{self.class} #{File.basename(@srcdir)}>" + end + + def noop(rel) + end + + # + # Hook Script API base methods + # + + def srcdir_root + @srcdir + end + + def objdir_root + @objdir + end + + def relpath + @currdir + end + + # + # Config Access + # + + # module FileOperations requires this + def verbose? + @config.verbose? + end + + # module FileOperations requires this + def no_harm? + @config.no_harm? + end + + def verbose_off + begin + save, @config.verbose = @config.verbose?, false + yield + ensure + @config.verbose = save + end + end + + # + # TASK config + # + + def exec_config + exec_task_traverse 'config' + end + + alias config_dir_bin noop + alias config_dir_lib noop + + def config_dir_ext(rel) + extconf if extdir?(curr_srcdir()) + end + + alias config_dir_data noop + alias config_dir_conf noop + alias config_dir_man noop + + def extconf + ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt + end + + # + # TASK setup + # + + def exec_setup + exec_task_traverse 'setup' + end + + def setup_dir_bin(rel) + files_of(curr_srcdir()).each do |fname| + update_shebang_line "#{curr_srcdir()}/#{fname}" + end + end + + alias setup_dir_lib noop + + def setup_dir_ext(rel) + make if extdir?(curr_srcdir()) + end + + alias setup_dir_data noop + alias setup_dir_conf noop + alias setup_dir_man noop + + def update_shebang_line(path) + return if no_harm? + return if config('shebang') == 'never' + old = Shebang.load(path) + if old + $stderr.puts "warning: #{path}: Shebang line includes too many args. It is not portable and your program may not work." if old.args.size > 1 + new = new_shebang(old) + return if new.to_s == old.to_s + else + return unless config('shebang') == 'all' + new = Shebang.new(config('rubypath')) + end + $stderr.puts "updating shebang: #{File.basename(path)}" if verbose? + open_atomic_writer(path) {|output| + File.open(path, 'rb') {|f| + f.gets if old # discard + output.puts new.to_s + output.print f.read + } + } + end + + def new_shebang(old) + if /\Aruby/ =~ File.basename(old.cmd) + Shebang.new(config('rubypath'), old.args) + elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby' + Shebang.new(config('rubypath'), old.args[1..-1]) + else + return old unless config('shebang') == 'all' + Shebang.new(config('rubypath')) + end + end + + def open_atomic_writer(path, &block) + tmpfile = File.basename(path) + '.tmp' + begin + File.open(tmpfile, 'wb', &block) + File.rename tmpfile, File.basename(path) + ensure + File.unlink tmpfile if File.exist?(tmpfile) + end + end + + class Shebang + def Shebang.load(path) + line = nil + File.open(path) {|f| + line = f.gets + } + return nil unless /\A#!/ =~ line + parse(line) + end + + def Shebang.parse(line) + cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ') + new(cmd, args) + end + + def initialize(cmd, args = []) + @cmd = cmd + @args = args + end + + attr_reader :cmd + attr_reader :args + + def to_s + "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}") + end + end + + # + # TASK install + # + + def exec_install + rm_f 'InstalledFiles' + exec_task_traverse 'install' + end + + def install_dir_bin(rel) + install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755, strip_ext? + end + + def strip_ext? + /mswin|mingw/ !~ RUBY_PLATFORM + end + + def install_dir_lib(rel) + install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644 + end + + def install_dir_ext(rel) + return unless extdir?(curr_srcdir()) + install_files rubyextentions('.'), + "#{config('sodir')}/#{File.dirname(rel)}", + 0555 + end + + def install_dir_data(rel) + install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644 + end + + def install_dir_conf(rel) + # FIXME: should not remove current config files + # (rename previous file to .old/.org) + install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644 + end + + def install_dir_man(rel) + install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644 + end + + def install_files(list, dest, mode, stripext = false) + mkdir_p dest, @config.install_prefix + list.each do |fname| + if stripext + install fname, "#{dest}/#{File.basename(fname, '.*')}", + mode, @config.install_prefix + else + install fname, dest, mode, @config.install_prefix + end + end + end + + def libfiles + glob_reject(%w(*.y *.output), targetfiles()) + end + + def rubyextentions(dir) + ents = glob_select("*.#{@config.dllext}", targetfiles()) + if ents.empty? + setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first" + end + ents + end + + def targetfiles + mapdir(existfiles() - hookfiles()) + end + + def mapdir(ents) + ents.map {|ent| + if File.exist?(ent) + then ent # objdir + else "#{curr_srcdir()}/#{ent}" # srcdir + end + } + end + + # picked up many entries from cvs-1.11.1/src/ignore.c + JUNK_FILES = %w( + core RCSLOG tags TAGS .make.state + .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb + *~ *.old *.bak *.BAK *.orig *.rej _$* *$ + + *.org *.in .* + ) + + def existfiles + glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.'))) + end + + def hookfiles + %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt| + %w( config setup install clean distclean ).map {|t| sprintf(fmt, t) } + }.flatten + end + + def glob_select(pat, ents) + re = globs2re([pat]) + ents.select {|ent| re =~ ent } + end + + def glob_reject(pats, ents) + re = globs2re(pats) + ents.reject {|ent| re =~ ent } + end + + GLOB2REGEX = { + '.' => '\.', + '$' => '\$', + '#' => '\#', + '*' => '.*' + } + + def globs2re(pats) + /\A(?:#{ + pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|') + })\z/ + end + + # + # TASK test + # + + TESTDIR = 'test' + + def exec_test + unless File.directory?('test') + $stderr.puts 'no test in this package' if verbose? + return + end + $stderr.puts 'Running tests...' if verbose? + begin + require 'test/unit' + rescue LoadError + setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.' + end + runner = Test::Unit::AutoRunner.new(true) + runner.to_run << TESTDIR + runner.run + end + + # + # TASK clean + # + + def exec_clean + exec_task_traverse 'clean' + rm_f @config.savefile + rm_f 'InstalledFiles' + end + + alias clean_dir_bin noop + alias clean_dir_lib noop + alias clean_dir_data noop + alias clean_dir_conf noop + alias clean_dir_man noop + + def clean_dir_ext(rel) + return unless extdir?(curr_srcdir()) + make 'clean' if File.file?('Makefile') + end + + # + # TASK distclean + # + + def exec_distclean + exec_task_traverse 'distclean' + rm_f @config.savefile + rm_f 'InstalledFiles' + end + + alias distclean_dir_bin noop + alias distclean_dir_lib noop + + def distclean_dir_ext(rel) + return unless extdir?(curr_srcdir()) + make 'distclean' if File.file?('Makefile') + end + + alias distclean_dir_data noop + alias distclean_dir_conf noop + alias distclean_dir_man noop + + # + # Traversing + # + + def exec_task_traverse(task) + run_hook "pre-#{task}" + FILETYPES.each do |type| + if type == 'ext' and config('without-ext') == 'yes' + $stderr.puts 'skipping ext/* by user option' if verbose? + next + end + traverse task, type, "#{task}_dir_#{type}" + end + run_hook "post-#{task}" + end + + def traverse(task, rel, mid) + dive_into(rel) { + run_hook "pre-#{task}" + __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '') + directories_of(curr_srcdir()).each do |d| + traverse task, "#{rel}/#{d}", mid + end + run_hook "post-#{task}" + } + end + + def dive_into(rel) + return unless File.dir?("#{@srcdir}/#{rel}") + + dir = File.basename(rel) + Dir.mkdir dir unless File.dir?(dir) + prevdir = Dir.pwd + Dir.chdir dir + $stderr.puts '---> ' + rel if verbose? + @currdir = rel + yield + Dir.chdir prevdir + $stderr.puts '<--- ' + rel if verbose? + @currdir = File.dirname(rel) + end + + def run_hook(id) + path = [ "#{curr_srcdir()}/#{id}", + "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) } + return unless path + $stderr.puts "invoking hook script #{path}" if verbose? + begin + instance_eval File.read(path), path, 1 + rescue + raise if $DEBUG + setup_rb_error "hook #{path} failed:\n" + $!.message + end + end + +end # class Installer + + +class SetupError < StandardError; end + +def setup_rb_error(msg) + raise SetupError, msg +end + +# $DEBUG = true + +if $0 == __FILE__ + ToplevelInstaller.invoke + # begin + # ToplevelInstaller.invoke + # rescue SetupError + # raise if $DEBUG + # $stderr.puts $!.message + # $stderr.puts "Try 'ruby #{$0} --help' for detailed usage." + # exit 1 + # end +end diff --git a/jitruby/test/assertions.rb b/jitruby/test/assertions.rb new file mode 100644 index 0000000..53a21c8 --- /dev/null +++ b/jitruby/test/assertions.rb @@ -0,0 +1,34 @@ +module JitAssertions + def assert_function_result(args, &block) + result_type = nil + expected_result = nil + param_types = [] + params = [] + + args.each do |k, v| + case k.to_s + when /^arg(\d+)$/ + n = $1.to_i + param_types[n] = v[0] + params[n] = v[1] + when 'result' + result_type = v[0] + expected_result = v[1] + else + raise "Bad arg #{arg}" + end + end + + function = nil + JIT::Context.build do |context| + signature = JIT::Type.create_signature( + JIT::ABI::CDECL, + result_type, + param_types) + function = JIT::Function.compile(context, signature, &block) + end + + assert_equal(expected_result, function.apply(*params)) + end +end + diff --git a/jitruby/test/test_jit_array.rb b/jitruby/test/test_jit_array.rb new file mode 100644 index 0000000..f81a4d5 --- /dev/null +++ b/jitruby/test/test_jit_array.rb @@ -0,0 +1,70 @@ +require 'jit/array' +require 'jit/function' +require 'test/unit' +require 'assertions' + +class TestJitArray < Test::Unit::TestCase + include JitAssertions + + def test_new_array + a_type = JIT::Array.new(JIT::Type::INT, 12) + assert_equal JIT::Type::INT, a_type.type + assert_equal 12, a_type.length + end + + # TODO: wrap + + def test_create + p = proc { |f| + a_type = JIT::Array.new(JIT::Type::INT, 4) + a = a_type.create(f) + f.return f.const(JIT::Type::INT, 0) + } + assert_function_result( + :result => [ JIT::Type::INT, 0 ], + &p) + end + + def test_offset_of + a_type = JIT::Array.new(JIT::Type::INT, 4) + assert_equal 0, a_type.offset_of(0) + assert_equal 4, a_type.offset_of(1) + assert_equal 8, a_type.offset_of(2) + assert_equal 12, a_type.offset_of(3) + # TODO: check out of bounds + end + + def test_type_of + a_type = JIT::Array.new(JIT::Type::INT, 4) + assert_equal JIT::Type::INT, a_type.type_of(0) + assert_equal JIT::Type::INT, a_type.type_of(1) + assert_equal JIT::Type::INT, a_type.type_of(2) + assert_equal JIT::Type::INT, a_type.type_of(3) + # TODO: check out of bounds + end + + def test_instance_bracket + p = proc { |f| + a_type = JIT::Array.new(JIT::Type::INT, 4) + a = a_type.create(f) + f.insn_store_relative(a.ptr, 4, f.const(JIT::Type::INT, 42)) + f.return a[1] + } + assert_function_result( + :result => [ JIT::Type::INT, 42 ], + &p) + end + + def test_instance_bracket_eq + p = proc { |f| + a_type = JIT::Array.new(JIT::Type::INT, 4) + a = a_type.create(f) + a[1] = f.const(JIT::Type::INT, 42) + f.return a[1] + } + assert_function_result( + :result => [ JIT::Type::INT, 42 ], + &p) + end +end + diff --git a/jitruby/test/test_jit_function.rb b/jitruby/test/test_jit_function.rb new file mode 100644 index 0000000..76103d7 --- /dev/null +++ b/jitruby/test/test_jit_function.rb @@ -0,0 +1,329 @@ +require 'jit/function' +require 'jit/value' +require 'test/unit' + +class TestJitFunction < Test::Unit::TestCase + def test_if_false + function = nil + JIT::Context.build do |context| + signature = JIT::Type.create_signature( + JIT::ABI::CDECL, + JIT::Type::INT, + [ JIT::Type::INT ]) + function = JIT::Function.compile(context, signature) do |f| + result = f.value(JIT::Type::INT) + result.store f.const(JIT::Type::INT, 1) + f.if(f.get_param(0)) { + result.store f.const(JIT::Type::INT, 2) + } .end + f.insn_return result + end + end + + assert_equal(1, function.apply(0)) + end + + def test_if_true + function = nil + JIT::Context.build do |context| + signature = JIT::Type.create_signature( + JIT::ABI::CDECL, + JIT::Type::INT, + [ JIT::Type::INT ]) + function = JIT::Function.compile(context, signature) do |f| + result = f.value(JIT::Type::INT) + result.store f.const(JIT::Type::INT, 1) + f.if(f.get_param(0)) { + result.store f.const(JIT::Type::INT, 2) + } .end + f.insn_return result + end + end + + assert_equal(2, function.apply(1)) + end + + def test_if_false_else + function = nil + JIT::Context.build do |context| + signature = JIT::Type.create_signature( + JIT::ABI::CDECL, + JIT::Type::INT, + [ JIT::Type::INT ]) + function = JIT::Function.compile(context, signature) do |f| + result = f.value(JIT::Type::INT) + result.store f.const(JIT::Type::INT, 1) + f.if(f.get_param(0)) { + result.store f.const(JIT::Type::INT, 2) + } .else { + result.store f.const(JIT::Type::INT, 3) + } .end + f.insn_return result + end + end + + assert_equal(3, function.apply(0)) + end + + def test_if_true_else + function = nil + JIT::Context.build do |context| + signature = JIT::Type.create_signature( + JIT::ABI::CDECL, + JIT::Type::INT, + [ JIT::Type::INT ]) + function = JIT::Function.compile(context, signature) do |f| + result = f.value(JIT::Type::INT) + result.store f.const(JIT::Type::INT, 1) + f.if(f.get_param(0)) { + result.store f.const(JIT::Type::INT, 2) + } .else { + result.store f.const(JIT::Type::INT, 3) + } .end + f.insn_return result + end + end + + assert_equal(2, function.apply(1)) + end + + def test_if_false_else_if_true_else + function = nil + JIT::Context.build do |context| + signature = JIT::Type.create_signature( + JIT::ABI::CDECL, + JIT::Type::INT, + [ JIT::Type::INT, JIT::Type::INT ]) + function = JIT::Function.compile(context, signature) do |f| + result = f.value(JIT::Type::INT) + result.store f.const(JIT::Type::INT, 1) + f.if(f.get_param(0)) { + result.store f.const(JIT::Type::INT, 2) + } .elsif(f.get_param(1)) { + result.store f.const(JIT::Type::INT, 3) + } .else { + result.store f.const(JIT::Type::INT, 4) + } .end + f.insn_return result + end + end + + assert_equal(3, function.apply(0, 1)) + end + + def test_if_true_else_if_false_else + function = nil + JIT::Context.build do |context| + signature = JIT::Type.create_signature( + JIT::ABI::CDECL, + JIT::Type::INT, + [ JIT::Type::INT, JIT::Type::INT ]) + function = JIT::Function.compile(context, signature) do |f| + result = f.value(JIT::Type::INT) + result.store f.const(JIT::Type::INT, 1) + f.if(f.get_param(0)) { + result.store f.const(JIT::Type::INT, 2) + } .elsif(f.get_param(1)) { + result.store f.const(JIT::Type::INT, 3) + } .else { + result.store f.const(JIT::Type::INT, 4) + } .end + f.insn_return result + end + end + + assert_equal(2, function.apply(1, 0)) + end + + def test_if_false_else_if_false_else + function = nil + JIT::Context.build do |context| + signature = JIT::Type.create_signature( + JIT::ABI::CDECL, + JIT::Type::INT, + [ JIT::Type::INT, JIT::Type::INT ]) + function = JIT::Function.compile(context, signature) do |f| + result = f.value(JIT::Type::INT) + result.store f.const(JIT::Type::INT, 1) + f.if(f.get_param(0)) { + result.store f.const(JIT::Type::INT, 2) + } .elsif(f.get_param(1)) { + result.store f.const(JIT::Type::INT, 3) + } .else { + result.store f.const(JIT::Type::INT, 4) + } .end + f.insn_return result + end + end + + assert_equal(4, function.apply(0, 0)) + end + + def test_while_true_enters_loop + function = nil + JIT::Context.build do |context| + signature = JIT::Type.create_signature( + JIT::ABI::CDECL, + JIT::Type::INT, + [ ]) + function = JIT::Function.compile(context, signature) do |f| + true_value = f.const(JIT::Type::INT, 1) + false_value = f.const(JIT::Type::INT, 0) + f.while{ true_value }.do { + f.insn_return true_value + }.end + f.insn_return false_value + end + end + + assert_equal(1, function.apply) + end + + def test_while_true_reenters_loop + function = nil + JIT::Context.build do |context| + signature = JIT::Type.create_signature( + JIT::ABI::CDECL, + JIT::Type::INT, + [ ]) + function = JIT::Function.compile(context, signature) do |f| + value = f.value(JIT::Type::INT) + value.store(f.const(JIT::Type::INT, 0)) + f.while{ value < f.const(JIT::Type::INT, 2) }.do { + value.store(value + f.const(JIT::Type::INT, 1)) + }.end + f.insn_return value + end + end + + assert_equal(2, function.apply) + end + + def test_while_false_does_not_enter_loop + function = nil + JIT::Context.build do |context| + signature = JIT::Type.create_signature( + JIT::ABI::CDECL, + JIT::Type::INT, + [ ]) + function = JIT::Function.compile(context, signature) do |f| + true_value = f.const(JIT::Type::INT, 1) + false_value = f.const(JIT::Type::INT, 0) + f.while{ false_value }.do { + f.insn_return true_value + }.end + f.insn_return false_value + end + end + + assert_equal(0, function.apply) + end + + def test_until_false_enters_loop + function = nil + JIT::Context.build do |context| + signature = JIT::Type.create_signature( + JIT::ABI::CDECL, + JIT::Type::INT, + [ ]) + function = JIT::Function.compile(context, signature) do |f| + true_value = f.const(JIT::Type::INT, 1) + false_value = f.const(JIT::Type::INT, 0) + f.until{ false_value }.do { + f.insn_return true_value + }.end + f.insn_return false_value + end + end + + assert_equal(1, function.apply) + end + + def test_until_false_reenters_loop + function = nil + JIT::Context.build do |context| + signature = JIT::Type.create_signature( + JIT::ABI::CDECL, + JIT::Type::INT, + [ ]) + function = JIT::Function.compile(context, signature) do |f| + value = f.value(JIT::Type::INT) + value.store(f.const(JIT::Type::INT, 0)) + f.until{ value == f.const(JIT::Type::INT, 2) }.do { + value.store(value + f.const(JIT::Type::INT, 1)) + }.end + f.insn_return value + end + end + + assert_equal(2, function.apply) + end + + def test_until_true_does_not_enter_loop + function = nil + JIT::Context.build do |context| + signature = JIT::Type.create_signature( + JIT::ABI::CDECL, + JIT::Type::INT, + [ ]) + function = JIT::Function.compile(context, signature) do |f| + true_value = f.const(JIT::Type::INT, 1) + false_value = f.const(JIT::Type::INT, 0) + f.until{ true_value }.do { + f.insn_return true_value + }.end + f.insn_return false_value + end + end + + assert_equal(0, function.apply) + end + + # TODO: while/break + # TODO: while/redo + # TODO: until/break + # TODO: until/redo + # TODO: unless + # TODO: elsunless + + def test_define_jit_method + function = nil + JIT::Context.build do |context| + signature = JIT::Type.create_signature( + JIT::ABI::CDECL, + JIT::Type::OBJECT, + [ JIT::Type::OBJECT ]) + function = JIT::Function.compile(context, signature) do |f| + f.insn_return(f.const(JIT::Type::OBJECT, 42)) + end + end + + c = Class.new + c.instance_eval do + define_jit_method('foo', function) + end + + o = c.new + assert_equal 42, o.foo + end + + def test_define_jit_method_non_object_param + # TODO: should raise an exception + end + + # TODO: get_param + # TODO: insn_call + # TODO: insn_call_native + # TODO: insn_return + # TODO: apply + # TODO: value + # TODO: const + # TODO: optimization_level + # TODO: optimization_level= + # TODO: max_optimization_level + # TODO: dump + # TODO: to_closure + # TODO: context + # TODO: compiled? +end + diff --git a/jitruby/test/test_jit_pointer.rb b/jitruby/test/test_jit_pointer.rb new file mode 100644 index 0000000..202a177 --- /dev/null +++ b/jitruby/test/test_jit_pointer.rb @@ -0,0 +1,63 @@ +require 'jit/pointer' +require 'jit/array' +require 'jit/function' +require 'test/unit' +require 'assertions' + +class TestJitArray < Test::Unit::TestCase + include JitAssertions + + def test_new_pointer + p_type = JIT::Pointer.new(JIT::Type::INT) + assert_equal JIT::Type::INT, p_type.type + end + + # TODO: wrap + + def test_offset_of + p_type = JIT::Pointer.new(JIT::Type::INT) + assert_equal 0, p_type.offset_of(0) + assert_equal 4, p_type.offset_of(1) + assert_equal 8, p_type.offset_of(2) + assert_equal 12, p_type.offset_of(3) + # TODO: check out of bounds + end + + def test_type_of + p_type = JIT::Pointer.new(JIT::Type::INT) + assert_equal JIT::Type::INT, p_type.type_of(0) + assert_equal JIT::Type::INT, p_type.type_of(1) + assert_equal JIT::Type::INT, p_type.type_of(2) + assert_equal JIT::Type::INT, p_type.type_of(3) + # TODO: check out of bounds + end + + def test_instance_bracket + p = proc { |f| + a_type = JIT::Array.new(JIT::Type::INT, 4) + p_type = JIT::Pointer.new(JIT::Type::INT) + a = a_type.create(f) + ptr = p_type.wrap(a.ptr) + f.insn_store_relative(a.ptr, 4, f.const(JIT::Type::INT, 42)) + f.return ptr[1] + } + assert_function_result( + :result => [ JIT::Type::INT, 42 ], + &p) + end + + def test_instance_bracket_eq + p = proc { |f| + a_type = JIT::Array.new(JIT::Type::INT, 4) + p_type = JIT::Pointer.new(JIT::Type::INT) + a = a_type.create(f) + ptr = p_type.wrap(a.ptr) + ptr[1] = f.const(JIT::Type::INT, 42) + f.return a[1] + } + assert_function_result( + :result => [ JIT::Type::INT, 42 ], + &p) + end +end + diff --git a/jitruby/test/test_jit_struct.rb b/jitruby/test/test_jit_struct.rb new file mode 100644 index 0000000..ac032f9 --- /dev/null +++ b/jitruby/test/test_jit_struct.rb @@ -0,0 +1,111 @@ +require 'jit/array' +require 'jit/function' +require 'test/unit' +require 'assertions' + +class TestJitStruct < Test::Unit::TestCase + include JitAssertions + + def test_new_struct + s_type = JIT::Struct.new( + [ :foo, JIT::Type::INT ], + [ :bar, JIT::Type::VOID_PTR ], + [ :baz, JIT::Type::FLOAT32 ]) + assert_equal [ :foo, :bar, :baz ], s_type.members + end + + def test_create + p = proc { |f| + s_type = JIT::Struct.new( + [ :foo, JIT::Type::INT ], + [ :bar, JIT::Type::VOID_PTR ], + [ :baz, JIT::Type::FLOAT32 ]) + s = s_type.create(f) + f.return f.const(JIT::Type::INT, 0) + } + assert_function_result( + :result => [ JIT::Type::INT, 0 ], + &p) + end + + def test_offset_of + s_type = JIT::Struct.new( + [ :foo, JIT::Type::INT ], + [ :bar, JIT::Type::FLOAT64 ], + [ :baz, JIT::Type::VOID_PTR ]) + assert_equal 0, s_type.offset_of(:foo) + assert_equal 4, s_type.offset_of(:bar) + assert_equal 12, s_type.offset_of(:baz) + end + + def test_type_of + s_type = JIT::Struct.new( + [ :foo, JIT::Type::INT ], + [ :bar, JIT::Type::FLOAT64 ], + [ :baz, JIT::Type::VOID_PTR ]) + assert_equal JIT::Type::INT, s_type.type_of(:foo) + assert_equal JIT::Type::FLOAT64, s_type.type_of(:bar) + assert_equal JIT::Type::VOID_PTR, s_type.type_of(:baz) + end + + def test_instance_bracket + p = proc { |f| + s_type = JIT::Struct.new( + [ :foo, JIT::Type::INT ], + [ :bar, JIT::Type::FLOAT64 ], + [ :baz, JIT::Type::VOID_PTR ]) + s = s_type.create(f) + f.insn_store_relative(s.ptr, 4, f.const(JIT::Type::FLOAT64, 42.0)) + f.return s[:bar] + } + assert_function_result( + :result => [ JIT::Type::FLOAT64, 42.0 ], + &p) + end + + def test_instance_attrget + p = proc { |f| + s_type = JIT::Struct.new( + [ :foo, JIT::Type::INT ], + [ :bar, JIT::Type::FLOAT64 ], + [ :baz, JIT::Type::VOID_PTR ]) + s = s_type.create(f) + f.insn_store_relative(s.ptr, 4, f.const(JIT::Type::FLOAT64, 42.0)) + f.return s.bar + } + assert_function_result( + :result => [ JIT::Type::FLOAT64, 42.0 ], + &p) + end + + def test_instance_bracket_eq + p = proc { |f| + s_type = JIT::Struct.new( + [ :foo, JIT::Type::INT ], + [ :bar, JIT::Type::FLOAT64 ], + [ :baz, JIT::Type::VOID_PTR ]) + s = s_type.create(f) + s[:bar] = f.const(JIT::Type::FLOAT64, 42.0) + f.return s[:bar] + } + assert_function_result( + :result => [ JIT::Type::FLOAT64, 42.0 ], + &p) + end + + def test_instance_attrset + p = proc { |f| + s_type = JIT::Struct.new( + [ :foo, JIT::Type::INT ], + [ :bar, JIT::Type::FLOAT64 ], + [ :baz, JIT::Type::VOID_PTR ]) + s = s_type.create(f) + s.bar = f.const(JIT::Type::FLOAT64, 42.0) + f.return s.bar + } + assert_function_result( + :result => [ JIT::Type::FLOAT64, 42.0 ], + &p) + end +end + diff --git a/jitruby/test/test_jit_value.rb b/jitruby/test/test_jit_value.rb new file mode 100644 index 0000000..bde565d --- /dev/null +++ b/jitruby/test/test_jit_value.rb @@ -0,0 +1,258 @@ +require 'jit/value' +require 'jit/function' +require 'test/unit' +require 'assertions' + +class TestJitValue < Test::Unit::TestCase + include JitAssertions + + def test_store + p = proc { |f| + v = f.value(JIT::Type::INT) + v.store(f.const(JIT::Type::INT, 42)) + f.return v + } + assert_function_result( + :result => [ JIT::Type::INT, 42 ], + &p) + end + + # TODO: address + + def test_int_plus + p = proc { |f| + v1 = f.get_param(0) + v2 = f.get_param(1) + f.return v1 + v2 + } + assert_function_result( + :arg0 => [ JIT::Type::INT, 1 ], + :arg1 => [ JIT::Type::INT, 2 ], + :result => [ JIT::Type::INT, 3 ], + &p) + end + + def test_int_minus + p = proc { |f| + v1 = f.get_param(0) + v2 = f.get_param(1) + f.return v1 - v2 + } + assert_function_result( + :arg0 => [ JIT::Type::INT, 3 ], + :arg1 => [ JIT::Type::INT, 2 ], + :result => [ JIT::Type::INT, 1 ], + &p) + end + + def test_int_mult + p = proc { |f| + v1 = f.get_param(0) + v2 = f.get_param(1) + f.return v1 * v2 + } + assert_function_result( + :arg0 => [ JIT::Type::INT, 3 ], + :arg1 => [ JIT::Type::INT, 2 ], + :result => [ JIT::Type::INT, 6 ], + &p) + end + + def test_int_div + p = proc { |f| + v1 = f.get_param(0) + v2 = f.get_param(1) + f.return v1 / v2 + } + assert_function_result( + :arg0 => [ JIT::Type::INT, 6 ], + :arg1 => [ JIT::Type::INT, 2 ], + :result => [ JIT::Type::INT, 3 ], + &p) + end + + def test_int_mod + p = proc { |f| + v1 = f.get_param(0) + v2 = f.get_param(1) + f.return v1 % v2 + } + assert_function_result( + :arg0 => [ JIT::Type::INT, 20 ], + :arg1 => [ JIT::Type::INT, 6 ], + :result => [ JIT::Type::INT, 2 ], + &p) + end + + def test_int_and + p = proc { |f| + v1 = f.get_param(0) + v2 = f.get_param(1) + f.return v1 & v2 + } + assert_function_result( + :arg0 => [ JIT::Type::INT, 11 ], + :arg1 => [ JIT::Type::INT, 3 ], + :result => [ JIT::Type::INT, 3 ], + &p) + assert_function_result( + :arg0 => [ JIT::Type::INT, 8 ], + :arg1 => [ JIT::Type::INT, 3 ], + :result => [ JIT::Type::INT, 0 ], + &p) + end + + def test_int_or + p = proc { |f| + v1 = f.get_param(0) + v2 = f.get_param(1) + f.return v1 | v2 + } + assert_function_result( + :arg0 => [ JIT::Type::INT, 10 ], + :arg1 => [ JIT::Type::INT, 3 ], + :result => [ JIT::Type::INT, 11 ], + &p) + end + + def test_int_xor + p = proc { |f| + v1 = f.get_param(0) + v2 = f.get_param(1) + f.return v1 ^ v2 + } + assert_function_result( + :arg0 => [ JIT::Type::INT, 10 ], + :arg1 => [ JIT::Type::INT, 3 ], + :result => [ JIT::Type::INT, 9 ], + &p) + end + + def test_int_lt + p = proc { |f| + v1 = f.get_param(0) + v2 = f.get_param(1) + f.return v1 < v2 + } + assert_function_result( + :arg0 => [ JIT::Type::INT, 1 ], + :arg1 => [ JIT::Type::INT, 2 ], + :result => [ JIT::Type::INT, 1 ], + &p) + assert_function_result( + :arg0 => [ JIT::Type::INT, 2 ], + :arg1 => [ JIT::Type::INT, 1 ], + :result => [ JIT::Type::INT, 0 ], + &p) + end + + def test_int_gt + p = proc { |f| + v1 = f.get_param(0) + v2 = f.get_param(1) + f.return v1 > v2 + } + assert_function_result( + :arg0 => [ JIT::Type::INT, 1 ], + :arg1 => [ JIT::Type::INT, 2 ], + :result => [ JIT::Type::INT, 0 ], + &p) + assert_function_result( + :arg0 => [ JIT::Type::INT, 2 ], + :arg1 => [ JIT::Type::INT, 1 ], + :result => [ JIT::Type::INT, 1 ], + &p) + end + + def test_int_eq + p = proc { |f| + v1 = f.get_param(0) + v2 = f.get_param(1) + f.return v1 == v2 + } + assert_function_result( + :arg0 => [ JIT::Type::INT, 1 ], + :arg1 => [ JIT::Type::INT, 2 ], + :result => [ JIT::Type::INT, 0 ], + &p) + assert_function_result( + :arg0 => [ JIT::Type::INT, 1 ], + :arg1 => [ JIT::Type::INT, 1 ], + :result => [ JIT::Type::INT, 1 ], + &p) + end + + def test_int_le + p = proc { |f| + v1 = f.get_param(0) + v2 = f.get_param(1) + f.return v1 <= v2 + } + assert_function_result( + :arg0 => [ JIT::Type::INT, 1 ], + :arg1 => [ JIT::Type::INT, 2 ], + :result => [ JIT::Type::INT, 1 ], + &p) + assert_function_result( + :arg0 => [ JIT::Type::INT, 2 ], + :arg1 => [ JIT::Type::INT, 1 ], + :result => [ JIT::Type::INT, 0 ], + &p) + assert_function_result( + :arg0 => [ JIT::Type::INT, 1 ], + :arg1 => [ JIT::Type::INT, 1 ], + :result => [ JIT::Type::INT, 1 ], + &p) + end + + def test_int_ge + p = proc { |f| + v1 = f.get_param(0) + v2 = f.get_param(1) + f.return v1 >= v2 + } + assert_function_result( + :arg0 => [ JIT::Type::INT, 1 ], + :arg1 => [ JIT::Type::INT, 2 ], + :result => [ JIT::Type::INT, 0 ], + &p) + assert_function_result( + :arg0 => [ JIT::Type::INT, 2 ], + :arg1 => [ JIT::Type::INT, 1 ], + :result => [ JIT::Type::INT, 1 ], + &p) + assert_function_result( + :arg0 => [ JIT::Type::INT, 1 ], + :arg1 => [ JIT::Type::INT, 1 ], + :result => [ JIT::Type::INT, 1 ], + &p) + end + + def test_int_lshift + p = proc { |f| + v1 = f.get_param(0) + v2 = f.get_param(1) + f.return v1 << v2 + } + assert_function_result( + :arg0 => [ JIT::Type::INT, 31 ], + :arg1 => [ JIT::Type::INT, 2 ], + :result => [ JIT::Type::INT, 124 ], + &p) + end + + def test_int_rshift + p = proc { |f| + v1 = f.get_param(0) + v2 = f.get_param(1) + f.return v1 >> v2 + } + assert_function_result( + :arg0 => [ JIT::Type::INT, 31 ], + :arg1 => [ JIT::Type::INT, 2 ], + :result => [ JIT::Type::INT, 7 ], + &p) + end +end + +