diff --git a/.gitignore b/.gitignore index b594d04d..7de7901d 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ spec/reports test/tmp test/version_tmp tmp +/GPATH +/GRTAGS +/GTAGS diff --git a/.travis.yml b/.travis.yml index 68d731ae..8e6e8eb2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -cache: bundler +#cache: bundler rvm: - 2.1.0 - 2.0.0 @@ -6,9 +6,30 @@ rvm: - 1.9.2 - 1.8.7 - rbx-2.2.3 +matrix: + allow_failures: + - rvm: 1.9.3 + - rvm: 1.9.2 + - rvm: 1.8.7 + - rvm: rbx-2.2.3 +env: + - CXX=g++-4.8 + - CXX=clang++ notifications: recipients: - cowboyd@thefrontside.net before_install: - gem update --system 2.1.11 -script: bundle exec rake compile spec +script: + - bundle exec rake compile + - bundle exec rspec spec/c spec/v8/context_spec.rb spec/v8/isolate_spec.rb + - bundle exec rspec spec/mem +sudo: false +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - gcc-4.8 + - g++-4.8 + - clang diff --git a/ext/v8/.dir-locals.el b/ext/v8/.dir-locals.el new file mode 100644 index 00000000..6b316388 --- /dev/null +++ b/ext/v8/.dir-locals.el @@ -0,0 +1,5 @@ +;;; Directory Local Variables +;;; For more information see (info "(emacs) Directory Variables") + +((c++-mode + (c-basic-offset . 2))) diff --git a/ext/v8/accessor.cc b/ext/v8/accessor.cc deleted file mode 100644 index 31bef241..00000000 --- a/ext/v8/accessor.cc +++ /dev/null @@ -1,181 +0,0 @@ -#include "rr.h" - -namespace rr { - - VALUE Accessor::Info::Class; - - void Accessor::Init() { - ClassBuilder("AccessorInfo"). - defineMethod("This", &Info::This). - defineMethod("Holder", &Info::Holder). - defineMethod("Data", &Info::Data). - store(&Info::Class); - } - - Accessor::Accessor(VALUE getter, VALUE setter, VALUE data_) : get(getter), set(setter), query(Qnil), deleter(Qnil), enumerator(Qnil), data(data_) {} - - Accessor::Accessor(VALUE get, VALUE set, VALUE query, VALUE deleter, VALUE enumerator, VALUE data) { - this->get = get; - this->set = set; - this->query = query; - this->deleter = deleter; - this->enumerator = enumerator; - this->data = data; - } - - Accessor::Accessor(v8::Handle value) { - v8::Local wrapper = value->ToObject(); - this->get = unwrap(wrapper, 0); - this->set = unwrap(wrapper, 1); - this->query = unwrap(wrapper, 2); - this->deleter = unwrap(wrapper, 3); - this->enumerator = unwrap(wrapper, 4); - v8::Handle data = wrapper->Get(5); - if (!data.IsEmpty() && !data->IsNull() && !data->IsUndefined()) { - this->data = Value(data); - } - } - - Accessor::operator v8::Handle() { - v8::Local wrapper = v8::Object::New(); - wrap(wrapper, 0, this->get); - wrap(wrapper, 1, this->set); - wrap(wrapper, 2, this->query); - wrap(wrapper, 3, this->deleter); - wrap(wrapper, 4, this->enumerator); - if (RTEST(this->data)) { - wrapper->Set(5, Value(this->data)); - } - return wrapper; - } - - void Accessor::wrap(v8::Handle wrapper, int index, VALUE value) { - if (RTEST(value)) { - wrapper->Set(index, External::wrap(value)); - } - } - - VALUE Accessor::unwrap(v8::Handle wrapper, int index) { - v8::Handle value = wrapper->Get(index); - if (value.IsEmpty() || !value->IsExternal()) { - return Qnil; - } else { - v8::Handle external(v8::External::Cast(*value)); - return External::unwrap(external); - } - } - - - VALUE Accessor::Info::This(VALUE self) { - return Object(Info(self)->This()); - } - - VALUE Accessor::Info::Holder(VALUE self) { - return Object(Info(self)->Holder()); - } - - VALUE Accessor::Info::Data(VALUE self) { - return Accessor(Info(self)->Data()).data; - } - - v8::Handle Accessor::AccessorGetter(v8::Local property, const v8::AccessorInfo& info) { - return Info(info).get(property); - } - - void Accessor::AccessorSetter(v8::Local property, v8::Local value, const v8::AccessorInfo& info) { - Info(info).set(property, value); - } - v8::Handle Accessor::NamedPropertyGetter(v8::Local property, const v8::AccessorInfo& info) { - return Info(info).get(property); - } - v8::Handle Accessor::NamedPropertySetter(v8::Local property, v8::Local value, const v8::AccessorInfo& info) { - return Info(info).set(property, value); - } - v8::Handle Accessor::NamedPropertyQuery(v8::Local property, const v8::AccessorInfo& info) { - return Info(info).query(property); - } - v8::Handle Accessor::NamedPropertyDeleter(v8::Local property, const v8::AccessorInfo& info) { - return Info(info).remove(property); - } - v8::Handle Accessor::NamedPropertyEnumerator(const v8::AccessorInfo& info) { - return Info(info).enumerateNames(); - } - - v8::Handle Accessor::IndexedPropertyGetter(uint32_t index, const v8::AccessorInfo& info) { - return Info(info).get(index); - } - v8::Handle Accessor::IndexedPropertySetter(uint32_t index, v8::Local value, const v8::AccessorInfo& info) { - return Info(info).set(index, value); - } - v8::Handle Accessor::IndexedPropertyQuery(uint32_t index, const v8::AccessorInfo& info) { - return Info(info).query(index); - } - v8::Handle Accessor::IndexedPropertyDeleter(uint32_t index, const v8::AccessorInfo& info) { - return Info(info).remove(index); - } - v8::Handle Accessor::IndexedPropertyEnumerator(const v8::AccessorInfo& info) { - return Info(info).enumerateIndices(); - } - - Accessor::Info::Info(const v8::AccessorInfo& info) { - this->info = &info; - } - - Accessor::Info::Info(VALUE value) { - Data_Get_Struct(value, class v8::AccessorInfo, info); - } - - v8::Handle Accessor::Info::get(v8::Local property) { - Accessor accessor(info->Data()); - return Value(rb_funcall(accessor.get, rb_intern("call"), 2, (VALUE)String(property), (VALUE)*this)); - } - - v8::Handle Accessor::Info::set(v8::Local property, v8::Local value) { - Accessor accessor(info->Data()); - return Value(rb_funcall(accessor.set, rb_intern("call"), 3, (VALUE)String(property), (VALUE)Value(value), (VALUE)*this)); - } - - v8::Handle Accessor::Info::query(v8::Local property) { - Accessor accessor(info->Data()); - return v8::Integer::New(NUM2INT(rb_funcall(accessor.query, rb_intern("call"), 2, (VALUE)String(property), (VALUE)*this))); - } - - v8::Handle Accessor::Info::remove(v8::Local property) { - Accessor accessor(info->Data()); - return v8::Boolean::New(Bool(rb_funcall(accessor.deleter, rb_intern("call"), 2, (VALUE)String(property), (VALUE)*this))); - } - - v8::Handle Accessor::Info::enumerateNames() { - Accessor accessor(info->Data()); - return Array(rb_funcall(accessor.enumerator, rb_intern("call"), 1, (VALUE)*this)); - } - - v8::Handle Accessor::Info::get(uint32_t index) { - Accessor accessor(info->Data()); - return Value(rb_funcall(accessor.get, rb_intern("call"), 2, UINT2NUM(index), (VALUE)*this)); - } - - v8::Handle Accessor::Info::set(uint32_t index, v8::Local value) { - Accessor accessor(info->Data()); - return Value(rb_funcall(accessor.set, rb_intern("call"), 3, UINT2NUM(index), (VALUE)Value(value), (VALUE)*this)); - } - - v8::Handle Accessor::Info::query(uint32_t index) { - Accessor accessor(info->Data()); - return v8::Integer::New(NUM2INT(rb_funcall(accessor.query, rb_intern("call"), 2, UINT2NUM(index), (VALUE)*this))); - } - - v8::Handle Accessor::Info::remove(uint32_t index) { - Accessor accessor(info->Data()); - return v8::Boolean::New(Bool(rb_funcall(accessor.deleter, rb_intern("call"), 2, UINT2NUM(index), (VALUE)*this))); - } - - v8::Handle Accessor::Info::enumerateIndices() { - Accessor accessor(info->Data()); - return Array(rb_funcall(accessor.enumerator, rb_intern("call"), 1, (VALUE)*this)); - } - - Accessor::Info::operator VALUE() { - return Data_Wrap_Struct(Class, 0, 0, (void*)this->info); - } -} \ No newline at end of file diff --git a/ext/v8/array.cc b/ext/v8/array.cc index 9f9bfc13..d3813ec1 100644 --- a/ext/v8/array.cc +++ b/ext/v8/array.cc @@ -2,25 +2,39 @@ namespace rr { -void Array::Init() { - ClassBuilder("Array", Object::Class). - defineSingletonMethod("New", &New). - defineMethod("Length", &Length). - defineMethod("CloneElementAt", &CloneElementAt). - store(&Class); -} + void Array::Init() { + ClassBuilder("Array", Object::Class). -VALUE Array::New(int argc, VALUE argv[], VALUE self) { - VALUE length; rb_scan_args(argc, argv, "01", &length); - return Array(v8::Array::New(RTEST(length) ? NUM2INT(length) : 0)); -} + defineSingletonMethod("New", &New). -VALUE Array::Length(VALUE self) { - return UInt32(Array(self)->Length()); -} + defineMethod("Length", &Length). + defineMethod("CloneElementAt", &CloneElementAt). -VALUE Array::CloneElementAt(VALUE self, VALUE index) { - return Object(Array(self)->CloneElementAt(UInt32(index))); -} + store(&Class); + } + + VALUE Array::New(int argc, VALUE argv[], VALUE self) { + VALUE rb_isolate, length; + rb_scan_args(argc, argv, "11", &rb_isolate, &length); + + Isolate isolate(rb_isolate); + Locker lock(isolate); -} \ No newline at end of file + return Array(isolate, v8::Array::New(isolate, RTEST(length) ? NUM2INT(length) : 0)); + } + + VALUE Array::Length(VALUE self) { + Array array(self); + Locker lock(array); + + return Uint32_t(array->Length()); + } + + VALUE Array::CloneElementAt(VALUE self, VALUE context, VALUE index) { + Array array(self); + Locker lock(array); + + return Object::Maybe(array.getIsolate(), array->CloneElementAt(Context(context), Uint32_t(index))); + } + +} diff --git a/ext/v8/array.h b/ext/v8/array.h new file mode 100644 index 00000000..f872daa1 --- /dev/null +++ b/ext/v8/array.h @@ -0,0 +1,22 @@ +#ifndef RR_ARRAY +#define RR_ARRAY + +namespace rr { + + class Array : public Ref { + public: + static void Init(); + + static VALUE New(int argc, VALUE argv[], VALUE self); + static VALUE Length(VALUE self); + static VALUE CloneElementAt(VALUE self, VALUE context, VALUE index); + + inline Array(v8::Isolate* isolate, v8::Handle array) : Ref(isolate, array) {} + inline Array(VALUE value) : Ref(value) {} + + typedef MaybeLocal Maybe; + }; + +} + +#endif diff --git a/ext/v8/backref.cc b/ext/v8/backref.cc deleted file mode 100644 index 25421b30..00000000 --- a/ext/v8/backref.cc +++ /dev/null @@ -1,45 +0,0 @@ -#include "rr.h" - -namespace rr { - - VALUE Backref::Storage; - ID Backref::_new; - ID Backref::object; - - void Backref::Init() { - Storage = rb_eval_string("V8::Weak::Ref"); - rb_gc_register_address(&Storage); - _new = rb_intern("new"); - object = rb_intern("object"); - } - - Backref::Backref(VALUE initial) { - set(initial); - rb_gc_register_address(&storage); - } - - Backref::~Backref() { - rb_gc_unregister_address(&storage); - } - - VALUE Backref::set(VALUE data) { - this->storage = rb_funcall(Storage, _new, 1, data); - return data; - } - - VALUE Backref::get() { - return rb_funcall(storage, object, 0); - } - - v8::Handle Backref::toExternal() { - v8::Local wrapper = v8::External::New(this); - v8::Persistent::New(wrapper).MakeWeak(this, &release); - return wrapper; - } - - void Backref::release(v8::Persistent handle, void* data) { - handle.Dispose(); - Backref* backref = (Backref*)data; - delete backref; - } -} diff --git a/ext/v8/bool.h b/ext/v8/bool.h new file mode 100644 index 00000000..8bdb47bd --- /dev/null +++ b/ext/v8/bool.h @@ -0,0 +1,67 @@ +// -*- mode: c++ -*- +#ifndef RR_BOOL +#define RR_BOOL + +namespace rr { + + /** + * Seemlessly convert between Ruby booleans and C/C++. + * + * The `Bool` equivalence lets you plop in a Ruby boolean anywhere + * you might need a C++ boolean, and, by the same token, drop in a + * C/C++ boolean anywhere you might need a Ruby boolean. E.g. + * + * // Ruby -> C/C++ + * if (Bool(Qtrue)) { + * //always executed + * } + * + * // C/C++ -> Ruby + * if (RTEST(Bool(true))) { + * //always executed + * } + */ + class Bool : public Equiv { + public: + /** + * Use to convert methods that return Maybe to a Ruby + * VALUE, Such as `Maybe v8::Object::Get()`: + * + * return Bool::Maybe(object->Get(context, key)); + */ + typedef Equiv::Maybe Maybe; + + /** + * Construct a Bool from a Ruby VALUE + */ + Bool(VALUE val) : Equiv(val) {} + + /** + * Constructo a Bool from a C/C++ boo. It is immediately + * converted into the corresponding; + */ + Bool(bool b) : Equiv(b ? Qtrue : Qfalse) {} + + /** + * Construct a Bool from JavaScript. + */ + Bool(v8::Handle b) : Equiv(b->Value() ? Qtrue : Qfalse) {} + + /** + * Coerce this into a native C/C++ bool. Since it is stored as a + * Ruby VALUE, this is just a simple RTEST. + * + * bool b = Bool(true); // true + * b = Bool(false); // false + * b = Bool(Qtrue); // true + * b = Bool(Qfalse); // false + * b = Bool(Qnil); // false + * b = Bool(rb_cObject); // true + */ + inline operator bool() { + return RTEST(value); + } + }; +} + +#endif diff --git a/ext/v8/boolean.h b/ext/v8/boolean.h new file mode 100644 index 00000000..49dff722 --- /dev/null +++ b/ext/v8/boolean.h @@ -0,0 +1,40 @@ +// -*- mode: c++ -*- + +#ifndef RR_BOOLEAN_H +#define RR_BOOLEAN_H + +namespace rr { + class Boolean : public Ref { + public: + Boolean(v8::Isolate* isolate, v8::Local handle) : + Ref(isolate, handle) {} + Boolean(VALUE value) : Ref(value) {} + + static void Init() { + ClassBuilder("Boolean", Value::Class). + defineSingletonMethod("New", &New). + defineMethod("Value", &Value). + store(&Class); + } + + static VALUE New(VALUE self, VALUE r_isolate, VALUE boolean) { + Isolate isolate(r_isolate); + Locker lock(isolate); + + return Boolean(isolate, v8::Boolean::New(isolate, RTEST(boolean))); + } + + static VALUE Value(VALUE self) { + Boolean boolean(self); + Locker lock(boolean); + + if (boolean->Value()) { + return Qtrue; + } else { + return Qfalse; + } + } + }; +} + +#endif /* RR_BOOLEAN_H */ diff --git a/ext/v8/rr.cc b/ext/v8/class_builder.cc similarity index 71% rename from ext/v8/rr.cc rename to ext/v8/class_builder.cc index 2896d1a8..00cd6f2b 100644 --- a/ext/v8/rr.cc +++ b/ext/v8/class_builder.cc @@ -1,7 +1,9 @@ #include "rr.h" +#include "class_builder.h" namespace rr { - VALUE defineClass(const char *name, VALUE superclass = rb_cObject) { + + VALUE ClassBuilder::defineClass(const char *name, VALUE superclass) { VALUE V8 = rb_define_module("V8"); VALUE V8_C = rb_define_module_under(V8, "C"); VALUE klass = rb_define_class_under(V8_C, name, superclass); @@ -9,44 +11,64 @@ namespace rr { return klass; } - VALUE defineModule(const char *name) { + VALUE ClassBuilder::defineClassUnder(const char* name, VALUE module, VALUE superclass) { + VALUE klass = rb_define_class_under(module, name, superclass); + rb_funcall(klass, rb_intern("private_class_method"), 1, rb_str_new2("new")); + return klass; + } + + VALUE ClassBuilder::defineModule(const char *name) { VALUE V8 = rb_define_module("V8"); VALUE V8_C = rb_define_module_under(V8, "C"); return rb_define_module_under(V8_C, name); } - VALUE not_implemented(const char* message) { - Void(rb_raise(rb_eStandardError, "not yet implemented %s", message)); + ClassBuilder::ClassBuilder(const char* name, VALUE superclass) { + this->value = ClassBuilder::defineClass(name, superclass); } - ClassBuilder::ClassBuilder(const char* name, VALUE superclass) { - this->value = defineClass(name, superclass); + ClassBuilder::ClassBuilder(const char* name, VALUE module, VALUE superclass) { + this->value = ClassBuilder::defineClassUnder(name, module, superclass); } ClassBuilder::ClassBuilder(const char* name, const char* supername) { - VALUE superclass = defineClass(supername); - this->value = defineClass(name, superclass); + VALUE superclass = ClassBuilder::defineClass(supername); + this->value = ClassBuilder::defineClass(name, superclass); } + ClassBuilder& ClassBuilder::defineConst(const char* name, VALUE value) { rb_define_const(this->value, name, value); return *this; } + + ClassBuilder& ClassBuilder::defineConstMethod(const char* name, VALUE value) { + VALUE symbol = rb_funcall(rb_str_cat2(rb_str_new2("@"), name), rb_intern("to_sym"), 0); + VALUE singleton_class = rb_funcall(this->value, rb_intern("singleton_class"), 0); + rb_ivar_set(this->value, SYM2ID(symbol), value); + rb_funcall(singleton_class, rb_intern("attr_reader"), 1, ID2SYM(rb_intern(name))); + return *this; + } + ClassBuilder& ClassBuilder::defineMethod(const char* name, VALUE (*impl)(int, VALUE*, VALUE)) { rb_define_method(this->value, name, (VALUE (*)(...))impl, -1); return *this; } + ClassBuilder& ClassBuilder::defineMethod(const char* name, VALUE (*impl)(VALUE)) { rb_define_method(this->value, name, (VALUE (*)(...))impl, 0); return *this; } + ClassBuilder& ClassBuilder::defineMethod(const char* name, VALUE (*impl)(VALUE, VALUE)) { rb_define_method(this->value, name, (VALUE (*)(...))impl, 1); return *this; } + ClassBuilder& ClassBuilder::defineMethod(const char* name, VALUE (*impl)(VALUE, VALUE, VALUE)) { rb_define_method(this->value, name, (VALUE (*)(...))impl, 2); return *this; } + ClassBuilder& ClassBuilder::defineMethod(const char* name, VALUE (*impl)(VALUE, VALUE, VALUE, VALUE)) { rb_define_method(this->value, name, (VALUE (*)(...))impl, 3); return *this; @@ -55,29 +77,36 @@ namespace rr { rb_define_singleton_method(this->value, name, (VALUE (*)(...))impl, -1); return *this; } + ClassBuilder& ClassBuilder::defineSingletonMethod(const char* name, VALUE (*impl)(VALUE)) { rb_define_singleton_method(this->value, name, (VALUE (*)(...))impl, 0); return *this; } + ClassBuilder& ClassBuilder::defineSingletonMethod(const char* name, VALUE (*impl)(VALUE, VALUE)) { rb_define_singleton_method(this->value, name, (VALUE (*)(...))impl, 1); return *this; } + ClassBuilder& ClassBuilder::defineSingletonMethod(const char* name, VALUE (*impl)(VALUE, VALUE, VALUE)) { rb_define_singleton_method(this->value, name, (VALUE (*)(...))impl, 2); return *this; } + ClassBuilder& ClassBuilder::defineSingletonMethod(const char* name, VALUE (*impl)(VALUE, VALUE, VALUE, VALUE)) { rb_define_singleton_method(this->value, name, (VALUE (*)(...))impl, 3); return *this; } + ClassBuilder& ClassBuilder::defineEnumConst(const char* name, int value) { rb_define_const(this->value, name, INT2FIX(value)); return *this; } + ClassBuilder& ClassBuilder::store(VALUE* storage) { rb_gc_register_address(storage); *storage = this->value; return *this; } -} \ No newline at end of file + +} diff --git a/ext/v8/class_builder.h b/ext/v8/class_builder.h new file mode 100644 index 00000000..41f5a846 --- /dev/null +++ b/ext/v8/class_builder.h @@ -0,0 +1,44 @@ +// -*- mode: c++ -*- +#ifndef RR_CLASS_BUILDER +#define RR_CLASS_BUILDER + +namespace rr { + + class ClassBuilder { + public: + static VALUE defineClass(const char *name, VALUE superclass = rb_cObject); + static VALUE defineClassUnder(const char *name, VALUE module, VALUE superclass = rb_cObject); + static VALUE defineModule(const char *name); + + ClassBuilder() {}; + ClassBuilder(const char* name, VALUE superclass = rb_cObject); + ClassBuilder(const char* name, VALUE module, VALUE superclass); + ClassBuilder(const char* name, const char* supername); + + ClassBuilder& defineConst(const char* name, VALUE value); + ClassBuilder& defineConstMethod(const char* name, VALUE value); + + ClassBuilder& defineMethod(const char* name, VALUE (*impl)(int, VALUE*, VALUE)); + ClassBuilder& defineMethod(const char* name, VALUE (*impl)(VALUE)); + ClassBuilder& defineMethod(const char* name, VALUE (*impl)(VALUE, VALUE)); + ClassBuilder& defineMethod(const char* name, VALUE (*impl)(VALUE, VALUE, VALUE)); + ClassBuilder& defineMethod(const char* name, VALUE (*impl)(VALUE, VALUE, VALUE, VALUE)); + + ClassBuilder& defineSingletonMethod(const char* name, VALUE (*impl)(int, VALUE*, VALUE)); + ClassBuilder& defineSingletonMethod(const char* name, VALUE (*impl)(VALUE)); + ClassBuilder& defineSingletonMethod(const char* name, VALUE (*impl)(VALUE, VALUE)); + ClassBuilder& defineSingletonMethod(const char* name, VALUE (*impl)(VALUE, VALUE, VALUE)); + ClassBuilder& defineSingletonMethod(const char* name, VALUE (*impl)(VALUE, VALUE, VALUE, VALUE)); + + ClassBuilder& defineEnumConst(const char* name, int value); + + ClassBuilder& store(VALUE* storage); + + inline operator VALUE() {return this->value;} + protected: + VALUE value; + }; + +} + +#endif diff --git a/ext/v8/constants.cc b/ext/v8/constants.cc deleted file mode 100644 index 65ca1b03..00000000 --- a/ext/v8/constants.cc +++ /dev/null @@ -1,34 +0,0 @@ -#include "rr.h" - -namespace rr { - VALUE Constants::_Undefined; - VALUE Constants::_Null; - VALUE Constants::_True; - VALUE Constants::_False; - void Constants::Init() { - ModuleBuilder("V8::C"). - defineSingletonMethod("Undefined", &Undefined). - defineSingletonMethod("Null", &Null). - defineSingletonMethod("True", &True). - defineSingletonMethod("False", &False); - - _Undefined = _Null = _True = _False = Qnil; - rb_gc_register_address(&_Undefined); - rb_gc_register_address(&_Null); - rb_gc_register_address(&_True); - rb_gc_register_address(&_False); - } - - VALUE Constants::Undefined(VALUE self) { - return cached(&_Undefined, v8::Undefined()); - } - VALUE Constants::Null(VALUE self) { - return cached(&_Null, v8::Null()); - } - VALUE Constants::True(VALUE self) { - return cached(&_True, v8::True()); - } - VALUE Constants::False(VALUE self) { - return cached(&_False, v8::False()); - } -} \ No newline at end of file diff --git a/ext/v8/constraints.cc b/ext/v8/constraints.cc deleted file mode 100644 index 5ba5e863..00000000 --- a/ext/v8/constraints.cc +++ /dev/null @@ -1,52 +0,0 @@ -#include "rr.h" - -namespace rr { - void ResourceConstraints::Init() { - ClassBuilder("ResourceConstraints"). - defineSingletonMethod("new", &initialize). - defineMethod("max_young_space_size", &max_young_space_size). - defineMethod("set_max_young_space_size", &set_max_young_space_size). - defineMethod("max_old_space_size", &max_old_space_size). - defineMethod("set_max_old_space_size", &set_max_old_space_size). - defineMethod("max_executable_size", &set_max_executable_size). - defineMethod("set_max_executable_size", &set_max_executable_size). - store(&Class); - ModuleBuilder("V8::C"). - defineSingletonMethod("SetResourceConstraints", &SetResourceConstraints); - } - - VALUE ResourceConstraints::SetResourceConstraints(VALUE self, VALUE constraints) { - Void(v8::SetResourceConstraints(ResourceConstraints(constraints))); - } - - VALUE ResourceConstraints::initialize(VALUE self) { - return ResourceConstraints(new v8::ResourceConstraints()); - } - VALUE ResourceConstraints::max_young_space_size(VALUE self) { - return INT2FIX(ResourceConstraints(self)->max_young_space_size()); - } - VALUE ResourceConstraints::set_max_young_space_size(VALUE self, VALUE value) { - Void(ResourceConstraints(self)->set_max_young_space_size(NUM2INT(value))); - } - VALUE ResourceConstraints::max_old_space_size(VALUE self) { - return INT2FIX(ResourceConstraints(self)->max_old_space_size()); - } - VALUE ResourceConstraints::set_max_old_space_size(VALUE self, VALUE value) { - Void(ResourceConstraints(self)->set_max_old_space_size(NUM2INT(value))); - } - VALUE ResourceConstraints::max_executable_size(VALUE self) { - return INT2FIX(ResourceConstraints(self)->max_executable_size()); - } - VALUE ResourceConstraints::set_max_executable_size(VALUE self, VALUE value) { - Void(ResourceConstraints(self)->set_max_executable_size(NUM2INT(value))); - } - - // What do these even mean? - // uint32_t* stack_limit() const { return stack_limit_; } - // // Sets an address beyond which the VM's stack may not grow. - // void set_stack_limit(uint32_t* value) { stack_limit_ = value; } - - template <> void Pointer::unwrap(VALUE value) { - Data_Get_Struct(value, class v8::ResourceConstraints, pointer); - } -} \ No newline at end of file diff --git a/ext/v8/context.cc b/ext/v8/context.cc index f5849f54..c2e4bdc8 100644 --- a/ext/v8/context.cc +++ b/ext/v8/context.cc @@ -2,129 +2,66 @@ namespace rr { -void Context::Init() { - ClassBuilder("Context"). - defineSingletonMethod("New", &New). - defineSingletonMethod("GetCurrent", &GetCurrent). - defineSingletonMethod("GetEntered", &GetEntered). - defineSingletonMethod("GetCalling", &GetCalling). - defineSingletonMethod("InContext", &InContext). - defineMethod("Dispose", &Dispose). - defineMethod("Global", &Global). - defineMethod("DetachGlobal", &Global). - defineMethod("ReattachGlobal", &ReattachGlobal). - defineMethod("SetSecurityToken", &SetSecurityToken). - defineMethod("UseDefaultSecurityToken", &UseDefaultSecurityToken). - defineMethod("GetSecurityToken", &GetSecurityToken). - defineMethod("HasOutOfMemoryException", &HasOutOfMemoryException). - defineMethod("SetEmbedderData", &SetEmbedderData). - defineMethod("GetEmbedderData", &GetEmbedderData). - defineMethod("AllowCodeGenerationFromStrings", &AllowCodeGenerationFromStrings). - defineMethod("IsCodeGenerationFromStringsAllowed", &IsCodeGenerationFromStringsAllowed). - defineMethod("Enter", &Enter). - defineMethod("Exit", &Exit). - store(&Class); - ClassBuilder("ExtensionConfiguration"). - defineSingletonMethod("new", &ExtensionConfiguration::initialize). - store(&ExtensionConfiguration::Class); -} + void Context::Init() { + ClassBuilder("Context"). + defineSingletonMethod("New", &New). -VALUE Context::Dispose(VALUE self) { - Void(Context(self).dispose()) -} + defineMethod("Enter", &Enter). + defineMethod("Exit", &Exit). + defineMethod("Global", &Global). -VALUE Context::Global(VALUE self) { - return Object(Context(self)->Global()); -} - -VALUE Context::DetachGlobal(VALUE self) { - Void(Context(self)->DetachGlobal()); -} - -VALUE Context::ReattachGlobal(VALUE self, VALUE global) { - Void(Context(self)->ReattachGlobal(Object(global))); -} - -VALUE Context::GetEntered(VALUE self) { - return Context(v8::Context::GetEntered()); -} - -VALUE Context::GetCurrent(VALUE self) { - return Context(v8::Context::GetCurrent()); -} - -VALUE Context::GetCalling(VALUE self) { - return Context(v8::Context::GetCalling()); -} - -VALUE Context::SetSecurityToken(VALUE self, VALUE token) { - Void(Context(self)->SetSecurityToken(Value(token))); -} - -VALUE Context::UseDefaultSecurityToken(VALUE self) { - Void(Context(self)->UseDefaultSecurityToken()); -} + store(&Class); -VALUE Context::GetSecurityToken(VALUE self) { - return Value(Context(self)->GetSecurityToken()); -} + // TODO + // ClassBuilder("ExtensionConfiguration"). + // defineSingletonMethod("new", &ExtensionConfiguration::initialize). + // store(&ExtensionConfiguration::Class); + } -VALUE Context::HasOutOfMemoryException(VALUE self) { - return Bool(Context(self)->HasOutOfMemoryException()); -} + VALUE Context::New(int argc, VALUE argv[], VALUE self) { + VALUE rb_isolate, extension_configuration, global_template, global_object; + rb_scan_args(argc, argv, "13", &rb_isolate, &extension_configuration, &global_template, &global_object); + + Isolate isolate(rb_isolate); + Locker lock(isolate); + return Context(v8::Context::New( + isolate + // TODO + // , + // ExtensionConfiguration(extension_configuration), + // *ObjectTemplate(global_template), + // *Object(global_object) + )); + } -VALUE Context::InContext(VALUE self) { - return Bool(v8::Context::InContext()); -} + VALUE Context::Enter(VALUE self) { + Context context(self); + Locker lock(context.getIsolate()); -VALUE Context::SetEmbedderData(VALUE self, VALUE index, VALUE data) { - Void(Context(self)->SetEmbedderData(NUM2INT(index), Value(data))); -} + context->Enter(); -VALUE Context::GetEmbedderData(VALUE self, VALUE index) { - Void(Context(self)->GetEmbedderData(NUM2INT(index))); -} + return Qnil; + } -VALUE Context::AllowCodeGenerationFromStrings(VALUE self, VALUE allow) { - Void(Context(self)->AllowCodeGenerationFromStrings(RTEST(allow))); -} + VALUE Context::Exit(VALUE self) { + Context context(self); + Locker lock(context.getIsolate()); -VALUE Context::IsCodeGenerationFromStringsAllowed(VALUE self) { - return Bool(Context(self)->IsCodeGenerationFromStringsAllowed()); -} + context->Exit(); -VALUE ExtensionConfiguration::initialize(VALUE self, VALUE names) { - int length = RARRAY_LENINT(names); - const char* array[length]; - for (int i = 0; i < length; i++) { - array[i] = RSTRING_PTR(rb_ary_entry(names, i)); + return Qnil; } - return ExtensionConfiguration(new v8::ExtensionConfiguration(length, array)); -} - -VALUE Context::New(int argc, VALUE argv[], VALUE self) { - VALUE extension_configuration; VALUE global_template; VALUE global_object; - rb_scan_args(argc, argv, "03", &extension_configuration, &global_template, &global_object); - v8::Persistent context(v8::Context::New( - ExtensionConfiguration(extension_configuration), - *ObjectTemplate(global_template), - *Object(global_object) - )); - Context reference(context); - context.Dispose(); - return reference; -} -VALUE Context::Enter(VALUE self) { - Void(Context(self)->Enter()); -} + VALUE Context::Global(VALUE self) { + Context context(self); + Locker lock(context.getIsolate()); -VALUE Context::Exit(VALUE self) { - Void(Context(self)->Exit()); -} + return Object(context->GetIsolate(), context->Global()); + } -template <> void Pointer::unwrap(VALUE value) { - Data_Get_Struct(value, class v8::ExtensionConfiguration, pointer); -} + // TODO + // template <> void Pointer::unwrap(VALUE value) { + // Data_Get_Struct(value, class v8::ExtensionConfiguration, pointer); + // } } diff --git a/ext/v8/context.h b/ext/v8/context.h new file mode 100644 index 00000000..6a20ce8f --- /dev/null +++ b/ext/v8/context.h @@ -0,0 +1,37 @@ +#ifndef RR_CONTEXT +#define RR_CONTEXT + +namespace rr { + + class Context : public Ref { + public: + static void Init(); + + static VALUE New(int argc, VALUE argv[], VALUE self); + static VALUE Enter(VALUE self); + static VALUE Exit(VALUE self); + static VALUE Global(VALUE self); + + // TODO + // static VALUE DetachGlobal(VALUE self); + // static VALUE ReattachGlobal(VALUE self, VALUE global); + // static VALUE GetEntered(VALUE self); + // static VALUE GetCurrent(VALUE self); + // static VALUE GetCalling(VALUE self); + // static VALUE SetSecurityToken(VALUE self, VALUE token); + // static VALUE UseDefaultSecurityToken(VALUE self); + // static VALUE GetSecurityToken(VALUE self); + // static VALUE HasOutOfMemoryException(VALUE self); + // static VALUE InContext(VALUE self); + // static VALUE SetEmbedderData(VALUE self, VALUE index, VALUE data); + // static VALUE GetEmbedderData(VALUE self, VALUE index); + // static VALUE AllowCodeGenerationFromStrings(VALUE self, VALUE allow); + // static VALUE IsCodeGenerationFromStringsAllowed(VALUE self); + + inline Context(VALUE value) : Ref(value) {} + inline Context(v8::Handle ctx) : Ref(ctx->GetIsolate(), ctx) {} + }; + +} + +#endif diff --git a/ext/v8/date.cc b/ext/v8/date.cc deleted file mode 100644 index 1c51b1b4..00000000 --- a/ext/v8/date.cc +++ /dev/null @@ -1,18 +0,0 @@ -#include "rr.h" - -namespace rr { - - void Date::Init() { - ClassBuilder("Date", Value::Class). - defineSingletonMethod("New", &New). - defineMethod("NumberValue", &NumberValue). - store(&Class); - } - - VALUE Date::New(VALUE self, VALUE time) { - return Value(v8::Date::New(NUM2DBL(time))); - } - VALUE Date::NumberValue(VALUE self) { - return rb_float_new(Date(self)->NumberValue()); - } -} \ No newline at end of file diff --git a/ext/v8/date.h b/ext/v8/date.h new file mode 100644 index 00000000..7ffcb7f6 --- /dev/null +++ b/ext/v8/date.h @@ -0,0 +1,18 @@ +// -*- mode: c++ -*- +#ifndef RR_DATE_H +#define RR_DATE_H + +namespace rr { + class Date : public Ref { + public: + Date(v8::Isolate* isolate, v8::Local date) : + Ref(isolate, date) {} + + static void Init() { + ClassBuilder("Date", Object::Class). + store(&Class); + }; + }; +} + +#endif /* RR_DATE_H */ diff --git a/ext/v8/enum.h b/ext/v8/enum.h new file mode 100644 index 00000000..ed4b0053 --- /dev/null +++ b/ext/v8/enum.h @@ -0,0 +1,46 @@ +#include "rr.h" + +#ifndef ENUM_H +#define ENUM_H + +namespace rr { + + template + class Enum { + VALUE value; + T defaultValue; + + public: + Enum(VALUE value_, T defaultValue_) : value(value_), defaultValue(defaultValue_) { } + + operator T() { + return RTEST(value) ? (T)NUM2INT(value) : defaultValue; + } + + class Maybe : public rr::Maybe { + public: + Maybe(v8::Maybe maybe) { + if (maybe.IsJust()) { + just(INT2FIX((int)maybe.FromJust())); + } + } + }; + }; + + inline void DefineEnums() { + ClassBuilder("AccessControl"). + defineConst("DEFAULT", INT2FIX(v8::DEFAULT)). + defineConst("ALL_CAN_READ", INT2FIX(v8::ALL_CAN_READ)). + defineConst("ALL_CAN_WRITE", INT2FIX(v8::ALL_CAN_WRITE)). + defineConst("PROHIBITS_OVERWRITING", INT2FIX(v8::PROHIBITS_OVERWRITING)); + + ClassBuilder("PropertyAttribute"). + defineConst("None", INT2FIX(v8::None)). + defineConst("ReadOnly", INT2FIX(v8::ReadOnly)). + defineConst("DontEnum", INT2FIX(v8::DontEnum)). + defineConst("DontDelete", INT2FIX(v8::DontDelete)); + } + +}; + +#endif /* ENUM_H */ diff --git a/ext/v8/equiv.h b/ext/v8/equiv.h new file mode 100644 index 00000000..82578c43 --- /dev/null +++ b/ext/v8/equiv.h @@ -0,0 +1,74 @@ +// -*- mode: c++ -*- +#ifndef RR_EQUIV +#define RR_EQUIV + +namespace rr { + + /** + * Defines an equivalence between a primitive Ruby and C/C++ types so + * that they can be used inter-changeably. In other words: given a + * Ruby `VALUE` it can convert it to a C/C++ type, and given a C/C++ + * type, it can convert it to a Ruby type. For example, the Boolean + * equivalence class defines the relationship between Ruby booleans + * and C/++ booleans, and can be used to place a Ruby VALUE anywhere + * you might need a C/C++ boolean such as an "if" statement: + * + * if (rr::Bool(Qnil)) { // + * throw "not going to happen!"; + * } + * + * This is the superclass of all equivalences classes, + * and is not meant to be instantiated. + * + * Internally, `Equiv`s are always stored as a Ruby `VALUE`, and so + * part of the job of the subclass is to have an appropriate + * constructor that converts the C/C++ type to a `VALUE` + * + * It is handy to have a class to do the conversions instead of + * preprocessor macros like `NUM2INT`, et al. because classes can be + * easily used in C++ templates. + */ + class Equiv { + public: + /** + * Constructs an `Equiv` from a Ruby VALUE. It's up to the + * subclass to determine what it can be converted to. + */ + Equiv(VALUE val) : value(val) {} + + /** + * Converts this `Equiv` into a Ruby `VALUE`. This, of course, is + * just the value of the internal storage. In the Boolean example: + * + * VALUE val = Bool(Qnil); //=> Qnil; + */ + inline operator VALUE() { return value; } + + + /** + * A Maybe class for primitive conversions. Specify the primitive + * type and the covversion type, and it will be wrapped in a + * Maybe. E.g. + * + * v8::Maybe maybe = methodReturningMaybeBool(); + * return Equiv::Maybe(maybe); + * + * will yield a V8::C::Maybe wrapping the underlying maybe value. + */ + template + class Maybe : public rr::Maybe { + public: + Maybe(v8::Maybe maybe) { + if (maybe.IsJust()) { + just(S(maybe.FromJust())); + } + } + }; + + protected: + VALUE value; + }; + +} + +#endif diff --git a/ext/v8/exception.cc b/ext/v8/exception.cc deleted file mode 100644 index 8fb23de5..00000000 --- a/ext/v8/exception.cc +++ /dev/null @@ -1,38 +0,0 @@ -#include "rr.h" - -namespace rr { - void Exception::Init() { - ModuleBuilder("V8::C"). - defineSingletonMethod("ThrowException", &ThrowException); - ClassBuilder("Exception"). - defineSingletonMethod("RangeError", &RangeError). - defineSingletonMethod("ReferenceError", &ReferenceError). - defineSingletonMethod("SyntaxError", &SyntaxError). - defineSingletonMethod("TypeError", &TypeError). - defineSingletonMethod("Error", &Error); - } - - VALUE Exception::ThrowException(VALUE self, VALUE exception) { - return Value(v8::ThrowException(Value(exception))); - } - - VALUE Exception::RangeError(VALUE self, VALUE message) { - return Value(v8::Exception::RangeError(String(message))); - } - - VALUE Exception::ReferenceError(VALUE self, VALUE message) { - return Value(v8::Exception::ReferenceError(String(message))); - } - - VALUE Exception::SyntaxError(VALUE self, VALUE message) { - return Value(v8::Exception::SyntaxError(String(message))); - } - - VALUE Exception::TypeError(VALUE self, VALUE message) { - return Value(v8::Exception::TypeError(String(message))); - } - - VALUE Exception::Error(VALUE self, VALUE message) { - return Value(v8::Exception::Error(String(message))); - } -} \ No newline at end of file diff --git a/ext/v8/exception.h b/ext/v8/exception.h new file mode 100644 index 00000000..37bc6021 --- /dev/null +++ b/ext/v8/exception.h @@ -0,0 +1,50 @@ +// -*- mode: c++ -*- +#ifndef RR_EXCEPTION_H +#define RR_EXCEPTION_H +#include "rr.h" + +namespace rr { + class Exception { + public: + static inline void Init() { + ClassBuilder("Exception"). + defineSingletonMethod("RangeError", &RangeError). + defineSingletonMethod("ReferenceError", &ReferenceError). + defineSingletonMethod("SyntaxError", &SyntaxError). + defineSingletonMethod("TypeError", &TypeError). + defineSingletonMethod("Error", &Error); + } + + static VALUE RangeError(VALUE self, VALUE rb_message) { + String message(rb_message); + Locker lock(message); + return Value(message, v8::Exception::RangeError(message)); + } + + static VALUE ReferenceError(VALUE self, VALUE rb_message) { + String message(rb_message); + Locker lock(message); + return Value(message, v8::Exception::ReferenceError(message)); + } + + static VALUE SyntaxError(VALUE self, VALUE rb_message) { + String message(rb_message); + Locker lock(message); + return Value(message, v8::Exception::SyntaxError(message)); + } + + static VALUE TypeError(VALUE self, VALUE rb_message) { + String message(rb_message); + Locker lock(message); + return Value(message, v8::Exception::TypeError(message)); + } + + static VALUE Error(VALUE self, VALUE rb_message) { + String message(rb_message); + Locker lock(message); + return Value(message, v8::Exception::Error(message)); + } + }; +} + +#endif /* RR_EXCEPTION_H */ diff --git a/ext/v8/extconf.rb b/ext/v8/extconf.rb index 1bc885b1..67ff23c5 100644 --- a/ext/v8/extconf.rb +++ b/ext/v8/extconf.rb @@ -1,11 +1,18 @@ require 'mkmf' +cxx = RbConfig::MAKEFILE_CONFIG['CXX'] = ENV['CXX'] if ENV['CXX'] have_library('pthread') have_library('objc') if RUBY_PLATFORM =~ /darwin/ + $CPPFLAGS += " -Wall" unless $CPPFLAGS.split.include? "-Wall" $CPPFLAGS += " -g" unless $CPPFLAGS.split.include? "-g" -$CPPFLAGS += " -rdynamic" unless $CPPFLAGS.split.include? "-rdynamic" +$CPPFLAGS += " -rdynamic" unless $CPPFLAGS.split.include? "-rdynamic" unless RUBY_PLATFORM =~ /darwin/ $CPPFLAGS += " -fPIC" unless $CPPFLAGS.split.include? "-rdynamic" or RUBY_PLATFORM =~ /darwin/ +$CPPFLAGS += " -std=c++11" + +if cxx =~ /clang/ + $LDFLAGS += " -stdlib=libstdc++" +end CONFIG['LDSHARED'] = '$(CXX) -shared' unless RUBY_PLATFORM =~ /darwin/ if CONFIG['warnflags'] @@ -16,7 +23,7 @@ $CFLAGS += " -O0 -ggdb3" end -LIBV8_COMPATIBILITY = '~> 3.16.14' +LIBV8_COMPATIBILITY = '~> 4.5.95' begin require 'rubygems' diff --git a/ext/v8/external.cc b/ext/v8/external.cc index c4f67013..7eb004ae 100644 --- a/ext/v8/external.cc +++ b/ext/v8/external.cc @@ -1,43 +1,26 @@ #include "rr.h" +#include "external.h" namespace rr { + void External::Init() { + ClassBuilder("External"). + defineSingletonMethod("New", &New). -void External::Init() { - ClassBuilder("External", "Value"). - defineSingletonMethod("New", &New). - defineMethod("Value", &Value). - store(&Class); -} -VALUE External::New(VALUE self, VALUE data) { - return External(wrap(data)); -} + defineMethod("Value", &Value). -v8::Handle External::wrap(VALUE data) { - Data* holder = new Data(data); - v8::Local ext = v8::External::New(holder); - v8::Persistent::New(ext).MakeWeak(holder, &release); - return ext; -} + store(&Class); + } -VALUE External::unwrap(v8::Handle external) { - Data* data = (Data*)(external->Value()); - return data->value; -} + VALUE External::New(VALUE self, VALUE r_isolate, VALUE object) { + Isolate isolate(r_isolate); + Locker lock(isolate); -VALUE External::Value(VALUE self) { - return unwrap(External(self)); -} + return External(isolate, wrap(isolate, object)); + } -void External::release(v8::Persistent handle, void* parameter) { - handle.Dispose(); - Data* data = (Data*)parameter; - delete data; -} -External::Data::Data(VALUE data) { - this->value = data; - rb_gc_register_address(&value); -} -External::Data::~Data() { - rb_gc_unregister_address(&value); + VALUE External::Value(VALUE self) { + External external(self); + Locker lock(external); + return unwrap(external); + } } -} \ No newline at end of file diff --git a/ext/v8/external.h b/ext/v8/external.h new file mode 100644 index 00000000..e6dfdf67 --- /dev/null +++ b/ext/v8/external.h @@ -0,0 +1,75 @@ +// -*- mode: c++ -*- +#ifndef EXTERNAL_H +#define EXTERNAL_H + +namespace rr { + class External : public Ref { + public: + + static void Init(); + static VALUE New(VALUE self, VALUE isolate, VALUE object); + static VALUE Value(VALUE self); + + inline External(VALUE value) : Ref(value) {} + inline External(v8::Isolate* isolate, v8::Handle handle) : + Ref(isolate, handle) {} + inline External(v8::Isolate* isolate, v8::Handle value) : + External(isolate, v8::Handle::Cast(value)) {} + + static inline v8::Local wrap(Isolate isolate, VALUE object) { + // as long as this external is alive within JavaScript, it should not be + // garbage collected by Ruby. + isolate.retainObject(object); + + // create the external. + Container* container = new Container(object); + v8::Local external(v8::External::New(isolate, (void*)container)); + + // next, we create a weak reference to this external so that we can be + // notified when V8 is done with it. At that point, we can let Ruby know + // that this external is done with it. + v8::Global* global(new v8::Global(isolate, external)); + global->SetWeak(container, &release, v8::WeakCallbackType::kParameter); + container->global = global; + + return External(isolate, external); + } + static inline VALUE unwrap(v8::Local external) { + Container* container = (Container*)external->Value(); + return container->object; + }; + + static inline VALUE unwrap(v8::Local external) { + return unwrap(v8::Local::Cast(external)); + } + + struct Container { + Container(VALUE v) : object(v) {} + + v8::Global* global; + VALUE object; + }; + + /** + * Implements a v8::WeakCallbackInfo::Callback with all + * of its idiosyncracies. It happens in two passes. In the first + * pass, you are required to only reset the weak reference. In the + * second pass, you can actually do your cleanup. In this case, we + * schedule the referenced Ruby object to be released in the next + * Ruby gc pass. + */ + static void release(const v8::WeakCallbackInfo& info) { + Container* container(info.GetParameter()); + if (info.IsFirstPass()) { + container->global->Reset(); + info.SetSecondPassCallback(&release); + } else { + Isolate isolate(info.GetIsolate()); + isolate.scheduleReleaseObject(container->object); + delete container; + } + } + }; +} + +#endif /* EXTERNAL_H */ diff --git a/ext/v8/function-callback.h b/ext/v8/function-callback.h new file mode 100644 index 00000000..5224b1d8 --- /dev/null +++ b/ext/v8/function-callback.h @@ -0,0 +1,144 @@ +// -*- mode: c++ -*- +#ifndef RR_FUNCTION_CALLBACK_H +#define RR_FUNCTION_CALLBACK_H + +namespace rr { + + typedef Wrapper> FunctionCallbackInfoWrapper; + + class FunctionCallbackInfo : public FunctionCallbackInfoWrapper { + public: + + inline FunctionCallbackInfo(v8::FunctionCallbackInfo info) : + FunctionCallbackInfoWrapper(info) {} + + inline FunctionCallbackInfo(VALUE self) : FunctionCallbackInfoWrapper(self) {} + + inline v8::Local operator [](int i) { + return this->container->content[i]; + } + + static VALUE Length(VALUE self) { + FunctionCallbackInfo info(self); + Locker lock(info->GetIsolate()); + return INT2FIX(info->Length()); + } + + static VALUE at(VALUE self, VALUE i) { + FunctionCallbackInfo info(self); + Locker lock(info->GetIsolate()); + return Value(info->GetIsolate(), info[NUM2INT(i)]); + } + + static VALUE Callee(VALUE self) { + FunctionCallbackInfo info(self); + Locker lock(info->GetIsolate()); + return Function(info->GetIsolate(), info->Callee()); + } + + static VALUE This(VALUE self) { + FunctionCallbackInfo info(self); + Locker lock(info->GetIsolate()); + return Object(info->GetIsolate(), info->This()); + } + + static VALUE IsConstructCall(VALUE self) { + FunctionCallbackInfo info(self); + Locker lock(info->GetIsolate()); + return Bool(info->IsConstructCall()); + } + + static VALUE Data(VALUE self) { + FunctionCallbackInfo info(self); + Isolate isolate(info->GetIsolate()); + Locker lock(info->GetIsolate()); + + v8::Local holder = v8::Local::Cast(info->Data()); + v8::Local data_key = v8::String::NewFromUtf8(isolate, "rr::data"); + v8::Local data(holder->GetHiddenValue(data_key)); + + return Value(info->GetIsolate(), data); + } + + static VALUE GetIsolate(VALUE self) { + FunctionCallbackInfo info(self); + return Isolate(info->GetIsolate()); + } + + static VALUE GetReturnValue(VALUE self) { + FunctionCallbackInfo info(self); + Locker lock(info->GetIsolate()); + return ReturnValue::Value(info->GetReturnValue()); + } + + static inline void Init() { + ClassBuilder("FunctionCallbackInfo"). + defineMethod("Length", &Length). + defineMethod("[]", &at). + defineMethod("Callee", &Callee). + defineMethod("This", &This). + defineMethod("IsConstructCall", &IsConstructCall). + defineMethod("Data", &Data). + defineMethod("GetIsolate", &GetIsolate). + defineMethod("GetReturnValue", &GetReturnValue). + store(&Class); + } + }; + + class FunctionCallback { + public: + FunctionCallback(v8::Isolate* i, VALUE c, VALUE d) : isolate(i), code(c), data(d) {} + + /** + * Package up the callback data for this function so that it can + * invoke a Ruby callable. + * + * Each `v8::Function` can have one `v8::Value` associated with it + * that is passed to its `v8::FunctionCallback`. To support this + * same API from ruby, we take the `Value` passed into the + * Function constructor *and* the callback and store them *both* + * in a single `v8::Object` which we use for the C++ level + * callback data. + */ + inline operator v8::Local() { + v8::Local holder = v8::Object::New(isolate); + v8::Local callback_key = v8::String::NewFromUtf8(isolate, "rr::callback"); + v8::Local data_key = v8::String::NewFromUtf8(isolate, "rr::data"); + holder->SetHiddenValue(callback_key, External::wrap(isolate, code)); + holder->SetHiddenValue(data_key, Value(data)); + return holder; + } + + /** + * Call the Ruby code associated with this callback. + * + * Unpack the Ruby code, and the callback data from the C++ + * callback data, and then invoke that code. + * + * Note: This function implements the `v8::FunctionCallback` API. + */ + static void invoke(const v8::FunctionCallbackInfo& info) { + v8::Isolate* isolate = info.GetIsolate(); + v8::Local holder = v8::Local::Cast(info.Data()); + v8::Local callback_key = v8::String::NewFromUtf8(isolate, "rr::callback"); + + VALUE code(External::unwrap(holder->GetHiddenValue(callback_key))); + Unlocker unlock(info.GetIsolate()); + rb_funcall(code, rb_intern("call"), 1, (VALUE)FunctionCallbackInfo(info)); + } + + inline operator v8::FunctionCallback() { + if (RTEST(code)) { + return &invoke; + } else { + return 0; + } + } + + v8::Isolate* isolate; + VALUE code; + VALUE data; + }; +} + +#endif /* RR_FUNCTION_CALLBACK_H */ diff --git a/ext/v8/function-template.h b/ext/v8/function-template.h new file mode 100644 index 00000000..9069fae4 --- /dev/null +++ b/ext/v8/function-template.h @@ -0,0 +1,44 @@ +// -*- mode: c++ -*- + +#ifndef RR_FUNCTION_TEMPLATE_H +#define RR_FUNCTION_TEMPLATE_H + +namespace rr { + + + class FunctionTemplate : public Ref { + public: + FunctionTemplate(VALUE self) : Ref(self) {} + FunctionTemplate(v8::Isolate* isolate, v8::Local t) : + Ref(isolate, t) {} + static inline void Init() { + ClassBuilder("FunctionTemplate", Template::Class). + defineSingletonMethod("New", &New). + defineMethod("GetFunction", &GetFunction). + store(&Class); + } + + static VALUE New(int argc, VALUE argv[], VALUE self) { + VALUE r_isolate, r_callback, r_data, r_signature, r_length; + rb_scan_args(argc, argv, "14", &r_isolate, &r_callback, &r_data, &r_signature, &r_length); + Isolate isolate(r_isolate); + Locker lock(isolate); + + FunctionCallback callback(isolate, r_callback, r_data); + Signature signature(r_signature); + int length(RTEST(r_length) ? NUM2INT(r_length) : 0); + + return FunctionTemplate(isolate, v8::FunctionTemplate::New(isolate, callback, callback, v8::Local(), length)); + } + + static VALUE GetFunction(VALUE self, VALUE context) { + FunctionTemplate t(self); + Isolate isolate(t.getIsolate()); + Locker lock(isolate); + + return Function::Maybe(isolate, t->GetFunction(Context(context))); + } + }; +} + +#endif /* RR_FUNCTION_TEMPLATE_H */ diff --git a/ext/v8/function.cc b/ext/v8/function.cc index c4380c28..570311d6 100644 --- a/ext/v8/function.cc +++ b/ext/v8/function.cc @@ -1,8 +1,10 @@ #include "rr.h" namespace rr { + void Function::Init() { ClassBuilder("Function", Object::Class). + defineSingletonMethod("New", &New). defineMethod("NewInstance", &NewInstance). defineMethod("Call", &Call). defineMethod("SetName", &SetName). @@ -10,49 +12,118 @@ namespace rr { defineMethod("GetInferredName", &GetInferredName). defineMethod("GetScriptLineNumber", &GetScriptLineNumber). defineMethod("GetScriptColumnNumber", &GetScriptColumnNumber). - defineMethod("GetScriptId", &GetScriptId). + defineMethod("IsBuiltin", &IsBuiltin). + defineMethod("ScriptId", &ScriptId). + defineMethod("GetBoundFunction", &GetBoundFunction). defineMethod("GetScriptOrigin", &GetScriptOrigin). + store(&Class); } + VALUE Function::New(int argc, VALUE argv[], VALUE self) { + VALUE r_isolate, r_callback, r_data, r_length; + rb_scan_args(argc, argv, "22", &r_isolate, &r_callback, &r_data, &r_length); + Isolate isolate(r_isolate); + Locker lock(isolate); + + // package up the function's callback data to have both the code and the data + // parameter. + FunctionCallback callback(isolate, r_callback, r_data); + + int length = RTEST(r_length) ? NUM2INT(r_length) : 0; + + return Function(isolate, v8::Function::New(isolate, callback, callback, length)); + } + VALUE Function::NewInstance(int argc, VALUE argv[], VALUE self) { VALUE args; - rb_scan_args(argc,argv,"01", &args); + rb_scan_args(argc, argv, "01", &args); + + Function function(self); + v8::Isolate* isolate = function.getIsolate(); + Locker lock(isolate); + if (RTEST(args)) { - return Object(Function(self)->NewInstance(RARRAY_LENINT(args), Value::array(args))); + std::vector< v8::Handle > vector(Value::convertRubyArray(isolate, args)); + return Object(isolate, function->NewInstance(RARRAY_LENINT(args), &vector[0])); } else { - return Object(Function(self)->NewInstance()); + return Object(isolate, function->NewInstance()); } } + VALUE Function::Call(VALUE self, VALUE receiver, VALUE argv) { - return Value(Function(self)->Call(Object(receiver), RARRAY_LENINT(argv), Value::array(argv))); + Function function(self); + v8::Isolate* isolate = function.getIsolate(); + Locker lock(isolate); + + std::vector< v8::Handle > vector(Value::convertRubyArray(isolate, argv)); + + return Value(isolate, function->Call(Value(receiver), RARRAY_LENINT(argv), &vector[0])); } VALUE Function::SetName(VALUE self, VALUE name) { - Void(Function(self)->SetName(String(name))); + Function function(self); + Locker lock(function.getIsolate()); + + function->SetName(String(name)); + + return Qnil; } VALUE Function::GetName(VALUE self) { - return Value(Function(self)->GetName()); + Function function(self); + Locker lock(function.getIsolate()); + + return Value(function.getIsolate(), function->GetName()); } VALUE Function::GetInferredName(VALUE self) { - return Value(Function(self)->GetInferredName()); + Function function(self); + Locker lock(function.getIsolate()); + + return Value(function.getIsolate(), function->GetInferredName()); } VALUE Function::GetScriptLineNumber(VALUE self) { - return INT2FIX(Function(self)->GetScriptLineNumber()); + Function function(self); + Locker lock(function.getIsolate()); + + return INT2FIX(function->GetScriptLineNumber()); } VALUE Function::GetScriptColumnNumber(VALUE self) { - return INT2FIX(Function(self)->GetScriptColumnNumber()); + Function function(self); + Locker lock(function.getIsolate()); + + return INT2FIX(function->GetScriptColumnNumber()); } - VALUE Function::GetScriptId(VALUE self) { - return Value(Function(self)->GetScriptId()); + VALUE Function::IsBuiltin(VALUE self) { + Function function(self); + Locker lock(function); + + return Bool(function->IsBuiltin()); + } + + VALUE Function::ScriptId(VALUE self) { + Function function(self); + Locker lock(function.getIsolate()); + + return INT2FIX(function->ScriptId()); + } + + VALUE Function::GetBoundFunction(VALUE self) { + Function function(self); + Locker lock(function); + + return Value(function.getIsolate(), function->GetBoundFunction()); } VALUE Function::GetScriptOrigin(VALUE self) { - return not_implemented("GetScriptOrigin"); + Function function(self); + Locker lock(function); + + return ScriptOrigin(function, function->GetScriptOrigin()); } -} \ No newline at end of file + +} diff --git a/ext/v8/function.h b/ext/v8/function.h new file mode 100644 index 00000000..15a1fde1 --- /dev/null +++ b/ext/v8/function.h @@ -0,0 +1,31 @@ +// -*- mode: c++ -*- +#ifndef RR_FUNCTION +#define RR_FUNCTION + +namespace rr { + + class Function : public Ref { + public: + static void Init(); + + static VALUE New(int argc, VALUE argv[], VALUE self); + static VALUE NewInstance(int argc, VALUE argv[], VALUE self); + static VALUE Call(VALUE self, VALUE receiver, VALUE arguments); + static VALUE SetName(VALUE self, VALUE name); + static VALUE GetName(VALUE self); + static VALUE GetInferredName(VALUE self); + static VALUE GetDisplayName(VALUE self); + static VALUE GetScriptLineNumber(VALUE self); + static VALUE GetScriptColumnNumber(VALUE self); + static VALUE IsBuiltin(VALUE self); + static VALUE ScriptId(VALUE self); + static VALUE GetBoundFunction(VALUE self); + static VALUE GetScriptOrigin(VALUE self); + + inline Function(VALUE value) : Ref(value) {} + inline Function(v8::Isolate* isolate, v8::Handle function) : Ref(isolate, function) {} + typedef MaybeLocal Maybe; + }; +} + +#endif diff --git a/ext/v8/gc.cc b/ext/v8/gc.cc deleted file mode 100644 index 9412e9b8..00000000 --- a/ext/v8/gc.cc +++ /dev/null @@ -1,43 +0,0 @@ -#include "rr.h" - -namespace rr { - GC::Queue* queue; - - GC::Queue::Queue() : first(0), divider(0), last(0){ - first = new GC::Queue::Node(NULL); - divider = first; - last = first; - } - - void GC::Queue::Enqueue(void* reference) { - last->next = new Node(reference); - last = last->next; - while (first != divider) { - Node* tmp = first; - first = first->next; - delete tmp; - } - } - - void* GC::Queue::Dequeue() { - void* result = NULL; - if (divider != last) { - result = divider->next->value; - divider = divider->next; - } - return result; - } - - void GC::Finalize(void* phantom) { - queue->Enqueue(phantom); - } - void GC::Drain(v8::GCType type, v8::GCCallbackFlags flags) { - for(Phantom phantom = queue->Dequeue(); phantom.NotNull(); phantom = queue->Dequeue()) { - phantom.destroy(); - } - } - void GC::Init() { - queue = new GC::Queue(); - v8::V8::AddGCPrologueCallback(GC::Drain); - } -} \ No newline at end of file diff --git a/ext/v8/handles.cc b/ext/v8/handles.cc index d51c9092..c771fe30 100644 --- a/ext/v8/handles.cc +++ b/ext/v8/handles.cc @@ -2,33 +2,40 @@ namespace rr { -void Handles::Init() { - VALUE v8 = rb_define_module("V8"); - VALUE c = rb_define_module_under(v8, "C"); - rb_define_singleton_method(c, "HandleScope", (VALUE (*)(...))&HandleScope, -1); -} + void Handles::Init() { + VALUE v8 = rb_define_module("V8"); + VALUE c = rb_define_module_under(v8, "C"); + rb_define_singleton_method(c, "HandleScope", (VALUE (*)(...))&HandleScope, -1); + } + + VALUE Handles::HandleScope(int argc, VALUE* argv, VALUE self) { + if (!rb_block_given_p()) { + return Qnil; + } -VALUE Handles::HandleScope(int argc, VALUE* argv, VALUE self) { - if (!rb_block_given_p()) { - return Qnil; + VALUE isolate, block; + rb_scan_args(argc, argv, "10&", &isolate, &block); + + int state = 0; + VALUE result = SetupAndCall(Isolate(isolate), &state, block); + if (state != 0) { + rb_jump_tag(state); + } + return result; } - int state = 0; - VALUE code; - rb_scan_args(argc,argv,"00&", &code); - VALUE result = SetupAndCall(&state, code); - if (state != 0) { - rb_jump_tag(state); + + VALUE Handles::SetupAndCall(Isolate isolate, int* state, VALUE block) { + Locker lock(isolate); + v8::HandleScope handle_scope(isolate); + + { + Unlocker unlock(isolate); + return rb_protect(&DoCall, block, state); + } } - return result; -} -VALUE Handles::SetupAndCall(int* state, VALUE code) { - v8::HandleScope scope; - return rb_protect(&DoCall, code, state); -} + VALUE Handles::DoCall(VALUE block) { + return rb_funcall(block, rb_intern("call"), 0); + } -VALUE Handles::DoCall(VALUE code) { - return rb_funcall(code, rb_intern("call"), 0); } - -} \ No newline at end of file diff --git a/ext/v8/handles.h b/ext/v8/handles.h new file mode 100644 index 00000000..3d57ebf1 --- /dev/null +++ b/ext/v8/handles.h @@ -0,0 +1,17 @@ +#ifndef RR_HANDLES +#define RR_HANDLES + +namespace rr { + + class Handles { + public: + static void Init(); + static VALUE HandleScope(int argc, VALUE* argv, VALUE self); + private: + static VALUE SetupAndCall(Isolate isolate, int* state, VALUE code); + static VALUE DoCall(VALUE code); + }; + +} + +#endif diff --git a/ext/v8/heap.cc b/ext/v8/heap.cc deleted file mode 100644 index cf930b69..00000000 --- a/ext/v8/heap.cc +++ /dev/null @@ -1,35 +0,0 @@ -#include "rr.h" - -namespace rr { - void HeapStatistics::Init() { - ClassBuilder("HeapStatistics"). - defineSingletonMethod("new", &initialize). - defineMethod("total_heap_size", &total_heap_size). - defineMethod("total_heap_size_executable", &total_heap_size_executable). - defineMethod("total_physical_size", &total_physical_size). - defineMethod("used_heap_size", &used_heap_size). - defineMethod("heap_size_limit", &heap_size_limit). - store(&Class); - } - VALUE HeapStatistics::initialize(VALUE self) { - return HeapStatistics(new v8::HeapStatistics()); - } - VALUE HeapStatistics::total_heap_size(VALUE self) { - return SIZET2NUM(HeapStatistics(self)->total_heap_size()); - } - VALUE HeapStatistics::total_heap_size_executable(VALUE self) { - return SIZET2NUM(HeapStatistics(self)->total_heap_size_executable()); - } - VALUE HeapStatistics::total_physical_size(VALUE self) { - return SIZET2NUM(HeapStatistics(self)->total_physical_size()); - } - VALUE HeapStatistics::used_heap_size(VALUE self) { - return SIZET2NUM(HeapStatistics(self)->used_heap_size()); - } - VALUE HeapStatistics::heap_size_limit(VALUE self) { - return SIZET2NUM(HeapStatistics(self)->heap_size_limit()); - } - template <> void Pointer::unwrap(VALUE value) { - Data_Get_Struct(value, class v8::HeapStatistics, pointer); - } -} diff --git a/ext/v8/init.cc b/ext/v8/init.cc index 94d6f01d..0a0a9a29 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -6,34 +6,53 @@ extern "C" { using namespace rr; +VALUE PropertyCallbackInfo::Class; +VALUE ReturnValue::Class; + extern "C" { void Init_init() { - v8::Locker lock(); - GC::Init(); + rb_eval_string("require 'v8/c'"); + V8::Init(); + DefineEnums(); + Isolate::Init(); Handles::Init(); - Accessor::Init(); Context::Init(); - Invocation::Init(); - Signature::Init(); + Maybe::Init(); Value::Init(); + Object::Init(); + Date::Init(); Primitive::Init(); + Null::Init(); + Undefined::Init(); + Boolean::Init(); + Number::Init(); + Integer::Init(); + Name::Init(); String::Init(); - Object::Init(); - Array::Init(); + Symbol::Init(); Function::Init(); - Date::Init(); - Constants::Init(); - External::Init(); + FunctionCallbackInfo::Init(); + PropertyCallbackInfo::Init(); + ReturnValue::Init(); Script::Init(); + ScriptOrigin::Init(); + Array::Init(); + External::Init(); Template::Init(); - Stack::Init(); + ObjectTemplate::Init(); + FunctionTemplate::Init(); + Signature::Init(); + StackFrame::Init(); + StackTrace::Init(); Message::Init(); - TryCatch::Init(); Exception::Init(); - Locker::Init(); - ResourceConstraints::Init(); - HeapStatistics::Init(); - Backref::Init(); + TryCatch::Init(); + + // Invocation::Init(); + // Constants::Init(); + // Template::Init(); + // ResourceConstraints::Init(); + // HeapStatistics::Init(); } -} \ No newline at end of file +} diff --git a/ext/v8/int.h b/ext/v8/int.h new file mode 100644 index 00000000..92376694 --- /dev/null +++ b/ext/v8/int.h @@ -0,0 +1,54 @@ +// -*- mode: c++ -*- +#ifndef RR_INT_H +#define RR_INT_H + + +namespace rr { + /** + * Converts between Ruby `Number` and the C/C++ `int`. + * + * This allows you to easily pass in `int` values whenever a + * Ruby VALUE is expected (such as a method call) E.g. + * + * int myInt = 5; + * rb_funcall(Uint32_t(myInt), rb_intern("to_s")); //=> + * + * It also converts a Ruby `VALUE` into its corresponding + * `int`: + * + * intt myInt = Int(rb_eval_string("5")); //=> 5 + * + * Like all `Equiv`s, it stores itself internally as a Ruby `VALUE` + */ + class Int : public Equiv { + public: + /** + * Use to convert methods that return Maybe to a Ruby + * VALUE + * + * return Int::Maybe(stack_trace->GetStartColumn(context)); + */ + typedef Equiv::Maybe Maybe; + + /** + * Construct an Int from a Ruby `VALUE` + */ + Int(VALUE val) : Equiv(val) {} + + /** + * Construct an Int from a `int` by converting it into its + * corresponding `VALUE`. + */ + Int(int i) : Equiv(INT2FIX(i)) {} + + /** + * Coerce the Ruby `VALUE` into an `int`. + */ + inline operator int() { + return RTEST(value) ? NUM2UINT(value) : 0; + } + }; + +} + +#endif diff --git a/ext/v8/integer.h b/ext/v8/integer.h new file mode 100644 index 00000000..d5266aff --- /dev/null +++ b/ext/v8/integer.h @@ -0,0 +1,89 @@ +// -*- mode: c++ -*- +#ifndef RR_INTEGER_H +#define RR_INTEGER_H + +namespace rr { + class Uint32 : public Ref { + public: + Uint32(VALUE self) : + Ref(self) {} + Uint32(v8::Isolate* isolate, v8::Handle value) : + Ref(isolate, value.As()) {} + + static VALUE Value(VALUE self) { + Uint32 uint32(self); + Locker lock(uint32); + + return UINT2NUM(uint32->Value()); + } + }; + + class Int32 : public Ref { + public: + Int32(VALUE self) : + Ref(self) {} + Int32(v8::Isolate* isolate, v8::Handle value) : + Ref(isolate, value.As()) {} + + static VALUE Value(VALUE self) { + Int32 int32(self); + Locker lock(int32); + + return INT2NUM(int32->Value()); + } + }; + + class Integer : public Ref { + public: + Integer(v8::Isolate* isolate, v8::Handle integer) : + Ref(isolate, integer) {} + Integer(VALUE self) : + Ref(self) {} + + static VALUE New(VALUE self, VALUE r_isolate, VALUE value) { + Isolate isolate(r_isolate); + Locker lock(isolate); + + v8::Local integer(v8::Integer::New(isolate, NUM2INT(value))); + if (integer->IsUint32()) { + return Uint32(isolate, integer); + } else if (integer->IsInt32()) { + return Int32(isolate, integer); + } else { + return Integer(isolate, integer); + } + } + + static VALUE NewFromUnsigned(VALUE self, VALUE r_isolate, VALUE value) { + Isolate isolate(r_isolate); + Locker lock(isolate); + + return Uint32(isolate, v8::Integer::NewFromUnsigned(isolate, NUM2UINT(value))); + } + + static VALUE Value(VALUE self) { + Integer integer(self); + Locker lock(integer); + + return INT2NUM(integer->Value()); + } + + static void Init() { + ClassBuilder("Integer", Number::Class). + defineSingletonMethod("New", &New). + defineSingletonMethod("NewFromUnsigned", &NewFromUnsigned). + defineMethod("Value", &Value). + store(&Class); + + ClassBuilder("Int32", Integer::Class). + defineMethod("Value", &Value). + store(&Int32::Class); + + ClassBuilder("Uint32", Integer::Class). + defineMethod("Value", &Value). + store(&Uint32::Class); + } + }; +} + +#endif /* RR_INTEGER_H */ diff --git a/ext/v8/invocation.cc b/ext/v8/invocation.cc deleted file mode 100644 index 497e5304..00000000 --- a/ext/v8/invocation.cc +++ /dev/null @@ -1,86 +0,0 @@ -#include "rr.h" - -namespace rr { - - VALUE Invocation::Arguments::Class; - - void Invocation::Init() { - Arguments::Init(); - } - - void Invocation::Arguments::Init() { - ClassBuilder("Arguments"). - defineMethod("Length", &Length). - defineMethod("[]", &Get). - defineMethod("Callee", &Callee). - defineMethod("This", &This). - defineMethod("Holder", &Holder). - defineMethod("IsConstructCall", &IsConstructCall). - defineMethod("Data", &Data). - store(&Invocation::Arguments::Class); - } - - Invocation::Invocation(VALUE code, VALUE data) { - this->code = code; - this->data = data; - } - Invocation::Invocation(v8::Handle value) { - v8::Local wrapper = value->ToObject(); - this->code = External::unwrap((v8::Handle)v8::External::Cast(*wrapper->Get(0))); - this->data = Value(wrapper->Get(1)); - } - Invocation::operator v8::InvocationCallback() { - return &Callback; - } - Invocation::operator v8::Handle() { - v8::Local wrapper = v8::Object::New(); - wrapper->Set(0, External::wrap(this->code)); - wrapper->Set(1, Value(this->data)); - return wrapper; - } - - v8::Handle Invocation::Callback(const v8::Arguments& args) { - return Arguments(args).Call(); - } - - Invocation::Arguments::Arguments(const v8::Arguments& args) { - this->args = &args; - } - - Invocation::Arguments::Arguments(VALUE value) { - Data_Get_Struct(value, class v8::Arguments, args); - } - - v8::Handle Invocation::Arguments::Call() { - Invocation invocation(args->Data()); - return Value(rb_funcall(invocation.code, rb_intern("call"), 1, Data_Wrap_Struct(Class, 0, 0, (void*)this->args))); - } - - VALUE Invocation::Arguments::Length(VALUE self) { - return INT2FIX(Arguments(self)->Length()); - } - - VALUE Invocation::Arguments::Get(VALUE self, VALUE index) { - return Value((*Arguments(self))[NUM2INT(index)]); - } - - VALUE Invocation::Arguments::Callee(VALUE self) { - return Function(Arguments(self)->Callee()); - } - - VALUE Invocation::Arguments::This(VALUE self) { - return Object(Arguments(self)->This()); - } - - VALUE Invocation::Arguments::Holder(VALUE self) { - return Object(Arguments(self)->Holder()); - } - - VALUE Invocation::Arguments::IsConstructCall(VALUE self) { - return Bool(Arguments(self)->IsConstructCall()); - } - - VALUE Invocation::Arguments::Data(VALUE self) { - return Invocation(Arguments(self)->Data()).data; - } -} \ No newline at end of file diff --git a/ext/v8/isolate.cc b/ext/v8/isolate.cc new file mode 100644 index 00000000..b14e532e --- /dev/null +++ b/ext/v8/isolate.cc @@ -0,0 +1,75 @@ +// -*- mode: c++ -*- +#include "rr.h" + +namespace rr { + + VALUE Isolate::Class; + + void Isolate::Init() { + rb_eval_string("require 'v8/retained_objects'"); + ClassBuilder("Isolate"). + defineSingletonMethod("New", &New). + defineMethod("TerminateExecution", &TerminateExecution). + defineMethod("IsExecutionTerminating", &IsExecutionTerminating). + defineMethod("CancelTerminateExecution", &CancelTerminateExecution). + defineMethod("ThrowException", &ThrowException). + defineMethod("SetCaptureStackTraceForUncaughtExceptions", &SetCaptureStackTraceForUncaughtExceptions). + defineMethod("IdleNotificationDeadline", &IdleNotificationDeadline). + + store(&Class); + } + + VALUE Isolate::New(VALUE self) { + Isolate::IsolateData* data = new IsolateData(); + + VALUE rb_cRetainedObjects = rb_eval_string("V8::RetainedObjects"); + data->retained_objects = rb_funcall(rb_cRetainedObjects, rb_intern("new"), 0); + + v8::Isolate::CreateParams create_params; + create_params.array_buffer_allocator = &data->array_buffer_allocator; + v8::Isolate* isolate = v8::Isolate::New(create_params); + + isolate->SetData(0, data); + isolate->AddGCPrologueCallback(&clearReferences); + + data->isolate = isolate; + return Isolate(isolate); + } + + VALUE Isolate::TerminateExecution(VALUE self) { + Isolate(self)->TerminateExecution(); + return Qnil; + }; + + VALUE Isolate::IsExecutionTerminating(VALUE self) { + return Bool(Isolate(self)->IsExecutionTerminating()); + } + VALUE Isolate::CancelTerminateExecution(VALUE self) { + Isolate(self)->CancelTerminateExecution(); + return Qnil; + } + + VALUE Isolate::ThrowException(VALUE self, VALUE error) { + Isolate isolate(self); + Locker lock(isolate); + return Value(isolate, isolate->ThrowException(Value(error))); + } + + VALUE Isolate::SetCaptureStackTraceForUncaughtExceptions(VALUE self, VALUE capture, VALUE stack_limit, VALUE options) { + Isolate isolate(self); + Locker lock(isolate); + + isolate->SetCaptureStackTraceForUncaughtExceptions( + Bool(capture), + RTEST(stack_limit) ? NUM2INT(stack_limit) : 10, + Enum(options, v8::StackTrace::kOverview)); + return Qnil; + } + + + VALUE Isolate::IdleNotificationDeadline(VALUE self, VALUE deadline_in_seconds) { + Isolate isolate(self); + Locker lock(isolate); + return Bool(isolate->IdleNotificationDeadline(NUM2DBL(deadline_in_seconds))); + } +} diff --git a/ext/v8/isolate.h b/ext/v8/isolate.h new file mode 100644 index 00000000..c60c571e --- /dev/null +++ b/ext/v8/isolate.h @@ -0,0 +1,268 @@ +// -*- mode: c++ -*- +#ifndef RR_ISOLATE +#define RR_ISOLATE + +#include "vendor/concurrentqueue.h" + +using namespace moodycamel; + +namespace rr { + /** + * V8::C::Isolate + * + * Represents a fully encapsulated V8 virtual machine. Allocated + * from Ruby by calling `V8::C::Isolate::New()` + * + * Every v8::Isolate wrapped in Ruby will have an instance of + * `IsolateData` embedded in it that can be used for bookkeeping + * between the V8 and Ruby worlds. For example, when v8 objects are + * no longer needed by ruby, they'll be enqueued for later release + * inside the V8 garbarge collection thread. This queue lives in the + * `IsolateData` + * + * Note: You must call `Dispose()` on the isolate for its resources + * to be released, otherwise, it will be leaked. + */ + class Isolate { + public: + struct IsolateData; + static VALUE Class; + static void Init(); + + static VALUE New(VALUE self); + static VALUE TerminateExecution(VALUE self); + static VALUE IsExecutionTerminating(VALUE self); + static VALUE CancelTerminateExecution(VALUE self); + static VALUE ThrowException(VALUE self, VALUE error); + static VALUE SetCaptureStackTraceForUncaughtExceptions(VALUE self, VALUE capture, VALUE stack_limit, VALUE options); + + inline Isolate(IsolateData* data_) : data(data_) {} + inline Isolate(v8::Isolate* isolate) : + data((IsolateData*)isolate->GetData(0)) {} + inline Isolate(VALUE value) { + Data_Get_Struct(value, struct IsolateData, data); + } + + /** + * Called, when Ruby no longer has any more references to this + * instance of `V8::C::Isolate`. However, this does not + * necessarily mean that we can delete the isolate data because there + * could be outstanding objects... things like a V8::C::String + * that are part of this isolate that have yet to be garbage + * collected. The last object in the isolate, including the + * isolate itself is responsible for deleting the isolate data. + * + */ + static void destroy(IsolateData* data) { + Isolate isolate(data); + isolate->Dispose(); + isolate.decrementTotalReferences(); + } + + /** + * Every time we take out a reference to a V8 object, call this + * method. + */ + inline void incrementTotalReferences() { + char counter(0); + data->total_ruby_references_queue.enqueue(counter); + } + + /** + * Every time a Ruby reference to a V8 object is garbage + * collected, call this method. If this is the last object + * associated with this isolate, then the isolate data can, and + * will, be safely deleted. + */ + inline bool decrementTotalReferences() { + char counter(0); + if (data->total_ruby_references_queue.try_dequeue(counter)) { + return true; + } else { + delete data; + return false; + } + } + + /** + * Converts the v8::Isolate into a Ruby Object, while setting up + * its book keeping data. E.g. + * VALUE rubyObject = Isolate(v8::Isolate::New()); + */ + virtual operator VALUE() { + return Data_Wrap_Struct(Class, &releaseAndMarkRetainedObjects, &destroy, data); + } + + /** + * Convert this into a v8::Isolate* for those areas of the API + * that call for it: E.g. + * + * v8::Context::New(Isolate(self)); + */ + inline operator v8::Isolate*() { return data->isolate; } + + /** + * Dereference the underlying v8::Isolate so that we can call methods + * on it. E.g. + * + * Isolate(self)->IsDead(); + */ + + inline v8::Isolate* operator ->() { return data->isolate; } + + /** + * Schedule a v8::Persistent reference to be be deleted with the next + * invocation of the V8 Garbarge Collector. It is safe to call + * this method from within the Ruby garbage collection thread or a place + * where you do not want to acquire any V8 locks. + */ + template + inline void scheduleReleaseObject(v8::Persistent* cell) { + if (this->decrementTotalReferences()) { + data->v8_release_queue.enqueue((v8::Persistent*)cell); + } + } + + /** + * Schedule a Ruby object to be released with the next invocation + * of the Ruby garbage collector. This method is safe to call from places + * where you do not hold any Ruby locks (such as the V8 GC thread) + */ + inline void scheduleReleaseObject(VALUE object) { + data->rb_release_queue.enqueue(object); + } + + /** + * Increase the reference count to this Ruby object by one. As + * long as there is more than 1 reference to this object, it will + * not be garbage collected, even if there are no references to + * from within Ruby code. + * + * Note: should be called from a place where Ruby locks are held. + */ + inline void retainObject(VALUE object) { + rb_funcall(data->retained_objects, rb_intern("add"), 1, object); + } + + /** + * Decrease the reference count to this Ruby object by one. If the + * count falls below zero, this object will no longer be marked my + * this Isolate and will be eligible for garbage collection. + * + * Note: should be called from a place where Ruby locks are held. + */ + inline void releaseObject(VALUE object) { + rb_funcall(data->retained_objects, rb_intern("remove"), 1, object); + } + + /** + * The `gc_mark()` callback for this Isolate's + * Data_Wrap_Struct. It releases all pending Ruby objects. + */ + static void releaseAndMarkRetainedObjects(IsolateData* data) { + Isolate isolate(data); + VALUE object; + while (data->rb_release_queue.try_dequeue(object)) { + isolate.releaseObject(object); + } + //TODO: This should not be necessary since sometimes the + //instance of V8::RetainedObjects appears to magically be of + //type T_NONE instead of T_OBJECT. Later, it will be T_OBJECT, + //but if called while T_NONE, it will cause rb_gc_mark to dump + //core. + //See https://bugs.ruby-lang.org/issues/10803 + if (TYPE(data->retained_objects) != T_NONE) { + rb_gc_mark(data->retained_objects); + } + } + + /** + * An instance of v8::GCPrologueCallback, this will run in the v8 + * GC thread, and clear out all the references that have been + * released from Ruby. + */ + static void clearReferences(v8::Isolate* i, v8::GCType type, v8::GCCallbackFlags flags) { + Isolate isolate(i); + v8::Persistent* cell; + while (isolate.data->v8_release_queue.try_dequeue(cell)) { + cell->Reset(); + delete cell; + } + } + + + static VALUE IdleNotificationDeadline(VALUE self, VALUE deadline_in_seconds); + + /** + * Recent versions of V8 will segfault unless you pass in an + * ArrayBufferAllocator into the create params of an isolate. This + * is the simplest implementation possible. + */ + class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator { + public: + virtual void* Allocate(size_t length) { + void* data = AllocateUninitialized(length); + return data == NULL ? data : memset(data, 0, length); + } + virtual void* AllocateUninitialized(size_t length) { return malloc(length); } + virtual void Free(void* data, size_t) { free(data); } + }; + + /** + * Data specific to the Ruby embedding. It has the same life span + * as the isolate. + */ + struct IsolateData { + + /** + * The actual Isolate + */ + v8::Isolate* isolate; + + /** + * An instance of `V8::RetainedObjects` that contains all + * references held from from V8. As long as they are in this + * list, they won't be gc'd by Ruby. + */ + VALUE retained_objects; + + /** + * A custom ArrayBufferAllocator for this isolate. Why? because + * if you don't, it will segfault when you try and create a + * Context. That's why. + */ + ArrayBufferAllocator array_buffer_allocator; + + /** + * Queue to hold unused references to v8 objects. Once Ruby is + * finished with an object it will be enqueued here so that it + * can be released by the v8 garbarge collector later. + */ + ConcurrentQueue*> v8_release_queue; + + /** + * Ruby objects that had been retained by this isolate, but that + * are eligible for release. Generally, an object ends up in a + * queue when the v8 object that had referenced it no longer + * needs it. + */ + ConcurrentQueue rb_release_queue; + + /** + * Contains a number of tokens representing all of the live Ruby + * references that are currently active in this Isolate. Every + * time a Ruby reference to a V8 object is added, an item is + * added to this queue. Every time a ruby reference is garbage + * collected, an item is pulled out of the queue. Whenever this + * queue reaches 0, then it is time to delete the isolate data + * since there are no more object associated with it. + */ + ConcurrentQueue total_ruby_references_queue; + + }; + + IsolateData* data; + }; +} + +#endif diff --git a/ext/v8/locker.cc b/ext/v8/locker.cc deleted file mode 100644 index b9fbe788..00000000 --- a/ext/v8/locker.cc +++ /dev/null @@ -1,77 +0,0 @@ -#include "rr.h" - -namespace rr { - void Locker::Init() { - ClassBuilder("Locker"). - defineSingletonMethod("StartPreemption", &StartPreemption). - defineSingletonMethod("StopPreemption", &StopPreemption). - defineSingletonMethod("IsLocked", &IsLocked). - defineSingletonMethod("IsActive", &IsActive); - VALUE v8 = rb_define_module("V8"); - VALUE c = rb_define_module_under(v8, "C"); - rb_define_singleton_method(c, "Locker", (VALUE (*)(...))&doLock, -1); - rb_define_singleton_method(c, "Unlocker",(VALUE (*)(...))&doUnlock, -1); - } - - VALUE Locker::StartPreemption(VALUE self, VALUE every_n_ms) { - Void(v8::Locker::StartPreemption(NUM2INT(every_n_ms))); - } - - VALUE Locker::StopPreemption(VALUE self) { - Void(v8::Locker::StopPreemption()); - } - - VALUE Locker::IsLocked(VALUE self) { - return Bool(v8::Locker::IsLocked(v8::Isolate::GetCurrent())); - } - - VALUE Locker::IsActive(VALUE self) { - return Bool(v8::Locker::IsActive()); - } - - VALUE Locker::doLock(int argc, VALUE* argv, VALUE self) { - if (!rb_block_given_p()) { - return Qnil; - } - int state = 0; - VALUE code; - rb_scan_args(argc,argv,"00&", &code); - VALUE result = setupLockAndCall(&state, code); - if (state != 0) { - rb_jump_tag(state); - } - return result; - } - - VALUE Locker::setupLockAndCall(int* state, VALUE code) { - v8::Locker locker; - return rb_protect(&doLockCall, code, state); - } - - VALUE Locker::doLockCall(VALUE code) { - return rb_funcall(code, rb_intern("call"), 0); - } - - VALUE Locker::doUnlock(int argc, VALUE* argv, VALUE self) { - if (!rb_block_given_p()) { - return Qnil; - } - int state = 0; - VALUE code; - rb_scan_args(argc,argv,"00&", &code); - VALUE result = setupUnlockAndCall(&state, code); - if (state != 0) { - rb_jump_tag(state); - } - return result; - } - - VALUE Locker::setupUnlockAndCall(int* state, VALUE code) { - v8::Unlocker unlocker; - return rb_protect(&doUnlockCall, code, state); - } - - VALUE Locker::doUnlockCall(VALUE code) { - return rb_funcall(code, rb_intern("call"), 0); - } -} diff --git a/ext/v8/locks.h b/ext/v8/locks.h new file mode 100644 index 00000000..78a19403 --- /dev/null +++ b/ext/v8/locks.h @@ -0,0 +1,32 @@ +// -*- mode: c++ -*- +#ifndef RR_LOCKER +#define RR_LOCKER + +namespace rr { + + class Locker { + public: + Locker(v8::Isolate* _isolate): isolate(_isolate), locker(_isolate) { + isolate->Enter(); + } + + ~Locker() { + isolate->Exit(); + } + + protected: + v8::Isolate* isolate; + v8::Locker locker; + }; + + class Unlocker { + public: + Unlocker(v8::Isolate* isolate): unlocker(isolate) {} + + protected: + v8::Unlocker unlocker; + }; + +} + +#endif diff --git a/ext/v8/maybe.cc b/ext/v8/maybe.cc new file mode 100644 index 00000000..f342f3c8 --- /dev/null +++ b/ext/v8/maybe.cc @@ -0,0 +1,6 @@ +#include "rr.h" + +namespace rr { + VALUE Maybe::JustClass; + VALUE Maybe::Nothing; +} diff --git a/ext/v8/maybe.h b/ext/v8/maybe.h new file mode 100644 index 00000000..3837ee48 --- /dev/null +++ b/ext/v8/maybe.h @@ -0,0 +1,81 @@ +// -*- mode: c++ -*- +#ifndef RR_MAYBE_H +#define RR_MAYBE_H + +namespace rr { + + /** + * A base class for conversion objects that will return an instance + * of `V8::C::Maybe` depending on whether a value is available. + * + * Sprinkled throughout the V8 API, there are methods that may or + * may not return a value based on whether there is an Exception + * that is pending in the context of that operation. + * + * This class provides the basis for helpers to convert the C++ + * Maybe and MaybeLocal objects into Ruby objects that force + * you to check if an operation suceeded. + * + * By default, it will be `V8::C::Nothing`, but if, in the + * constructor, you call `just(VALUE value)`, then it will take on + * that value. + */ + class Maybe { + public: + + /** + * V8::C::Just + */ + static VALUE JustClass; + + /** + * Singleton instance of `V8::C::Nothing` + */ + static VALUE Nothing; + + /** + * By default, everything starts out as nothing. + */ + Maybe() : object(Nothing) {} + + /** + * Convert this seemlessly to a VALUE, which in this case is just + * the v8::C::Maybe ruby instance. + */ + inline operator VALUE() { + return object; + } + + static inline void Init() { + rb_eval_string("require 'v8/c/maybe'"); + JustClass = rb_eval_string("::V8::C::Maybe::Just"); + Nothing = rb_eval_string("::V8::C::Maybe.nothing"); + } + + protected: + + /** + * Subclasses call this method in the constructor in order to + * indicate that this is a `V8::C::Just`, and to pass the + * underlying value. + */ + inline void just(VALUE v) { + object = rb_funcall(JustClass, rb_intern("new"), 1, v); + } + + // the underlying value. + VALUE object; + }; + + template + class MaybeLocal : public Maybe { + public: + MaybeLocal(v8::Isolate* isolate, v8::MaybeLocal maybe) { + if (!maybe.IsEmpty()) { + just(T(isolate, maybe.ToLocalChecked())); + } + } + }; +} + +#endif /* RR_MAYBE_H */ diff --git a/ext/v8/message.cc b/ext/v8/message.cc deleted file mode 100644 index 0d732ed8..00000000 --- a/ext/v8/message.cc +++ /dev/null @@ -1,51 +0,0 @@ -#include "rr.h" - -namespace rr { - - void Message::Init() { - ClassBuilder("Message"). - defineMethod("Get", &Get). - defineMethod("GetSourceLine", &GetSourceLine). - defineMethod("GetScriptResourceName", &GetScriptResourceName). - defineMethod("GetScriptData", &GetScriptData). - defineMethod("GetStackTrace", &GetStackTrace). - defineMethod("GetLineNumber", &GetLineNumber). - defineMethod("GetStartPosition", &GetStartPosition). - defineMethod("GetEndPosition", &GetEndPosition). - defineMethod("GetStartColumn", &GetEndColumn). - defineSingletonMethod("kNoLineNumberInfo", &kNoLineNumberInfo). - defineSingletonMethod("kNoColumnInfo", &kNoColumnInfo). - store(&Class); - } - - VALUE Message::Get(VALUE self) { - return String(Message(self)->Get()); - } - VALUE Message::GetSourceLine(VALUE self) { - return String(Message(self)->GetSourceLine()); - } - VALUE Message::GetScriptResourceName(VALUE self) { - return Value(Message(self)->GetScriptResourceName()); - } - VALUE Message::GetScriptData(VALUE self) { - return Value(Message(self)->GetScriptData()); - } - VALUE Message::GetStackTrace(VALUE self) { - return Stack::Trace(Message(self)->GetStackTrace()); - } - VALUE Message::GetLineNumber(VALUE self) { - return INT2FIX(Message(self)->GetLineNumber()); - } - VALUE Message::GetStartPosition(VALUE self) { - return INT2FIX(Message(self)->GetStartPosition()); - } - VALUE Message::GetEndPosition(VALUE self) { - return INT2FIX(Message(self)->GetEndPosition()); - } - VALUE Message::GetStartColumn(VALUE self) { - return INT2FIX(Message(self)->GetStartColumn()); - } - VALUE Message::GetEndColumn(VALUE self) { - return INT2FIX(Message(self)->GetEndColumn()); - } -} \ No newline at end of file diff --git a/ext/v8/message.h b/ext/v8/message.h new file mode 100644 index 00000000..20ed82c7 --- /dev/null +++ b/ext/v8/message.h @@ -0,0 +1,95 @@ +// -*- mode: c++ -*- + +#ifndef RR_MESSAGE_H +#define RR_MESSAGE_H +#include "rr.h" + +namespace rr { + class Message : public Ref { + public: + Message(VALUE self) : Ref(self) {} + Message(v8::Isolate* isolate, v8::Local msg) : + Ref(isolate, msg) {} + + static VALUE Get(VALUE self) { + Message message(self); + Locker lock(message); + return String(message, message->Get()); + } + static VALUE GetSourceLine(VALUE self, VALUE cxt) { + Message message(self); + Locker lock(message); + return String::Maybe(message, message->GetSourceLine(Context(cxt))); + } + static VALUE GetScriptOrigin(VALUE self) { + Message message(self); + Locker lock(message); + return ScriptOrigin(message, message->GetScriptOrigin()); + } + static VALUE GetScriptResourceName(VALUE self) { + Message message(self); + Locker lock(message); + return Value(message, message->GetScriptResourceName()); + } + static VALUE GetStackTrace(VALUE self) { + Message message(self); + Locker lock(message); + return StackTrace(message, message->GetStackTrace()); + } + static VALUE GetLineNumber(VALUE self, VALUE cxt) { + Message message(self); + Locker lock(message); + return Int::Maybe(message->GetLineNumber(Context(cxt))); + } + static VALUE GetStartPosition(VALUE self) { + Message message(self); + Locker lock(message); + return Int(message->GetStartPosition()); + } + static VALUE GetEndPosition(VALUE self) { + Message message(self); + Locker lock(message); + return Int(message->GetEndPosition()); + } + static VALUE GetStartColumn(VALUE self, VALUE cxt) { + Message message(self); + Locker lock(message); + return Int::Maybe(message->GetStartColumn(Context(cxt))); + } + static VALUE GetEndColumn(VALUE self, VALUE cxt) { + Message message(self); + Locker lock(message); + return Int::Maybe(message->GetEndColumn(Context(cxt))); + } + static VALUE IsOpaque(VALUE self) { + Message message(self); + Locker lock(message); + return Bool(message->IsOpaque()); + } + static VALUE IsSharedCrossOrigin(VALUE self) { + Message message(self); + Locker lock(message); + return Bool(message->IsSharedCrossOrigin()); + } + + static inline void Init() { + ClassBuilder("Message"). + defineMethod("Get", &Get). + defineMethod("GetSourceLine", &GetSourceLine). + defineMethod("GetScriptOrigin", &GetScriptOrigin). + defineMethod("GetScriptResourceName", &GetScriptResourceName). + defineMethod("GetStackTrace", &GetStackTrace). + defineMethod("GetLineNumber", &GetLineNumber). + defineMethod("GetStartPosition", &GetStartPosition). + defineMethod("GetEndPosition", &GetEndPosition). + defineMethod("GetStartColumn", &GetStartColumn). + defineMethod("GetEndColumn", &GetEndColumn). + defineMethod("IsSharedCrossOrigin", &IsSharedCrossOrigin). + defineMethod("IsOpaque", &IsOpaque). + store(&Class); + } + }; +} + + +#endif /* RR_MESSAGE_H */ diff --git a/ext/v8/name.cc b/ext/v8/name.cc new file mode 100644 index 00000000..55c341b4 --- /dev/null +++ b/ext/v8/name.cc @@ -0,0 +1,16 @@ +#include "rr.h" + +namespace rr { + void Name::Init() { + ClassBuilder("Name", Primitive::Class). + defineMethod("GetIdentityHash", &GetIdentityHash). + + store(&Class); + } + VALUE Name::GetIdentityHash(VALUE self) { + Name name(self); + Locker lock(name.getIsolate()); + + return INT2FIX(name->GetIdentityHash()); + } +} diff --git a/ext/v8/name.h b/ext/v8/name.h new file mode 100644 index 00000000..a1062510 --- /dev/null +++ b/ext/v8/name.h @@ -0,0 +1,17 @@ +//mode -*- c++ -*- +#ifndef NAME_H +#define NAME_H + +namespace rr { + class Name : public Ref { + public: + static void Init(); + static VALUE GetIdentityHash(VALUE self); + + Name(VALUE self) : Ref(self) {} + Name(v8::Isolate* isolate, v8::Local name) : + Ref(isolate, name) {} + }; +} + +#endif /* NAME_H */ diff --git a/ext/v8/null.h b/ext/v8/null.h new file mode 100644 index 00000000..8912e1e0 --- /dev/null +++ b/ext/v8/null.h @@ -0,0 +1,18 @@ +// -*- mode: c++ -*- +#ifndef RR_NULL_H +#define RR_NULL_H + +namespace rr { + class Null : public Ref { + public: + Null(v8::Isolate* isolate, v8::Local undefined) : + Ref(isolate, undefined) {} + + static inline void Init() { + ClassBuilder("Null", Primitive::Class). + store(&Class); + } + }; +} + +#endif /* RR_NULL_H */ diff --git a/ext/v8/number.h b/ext/v8/number.h new file mode 100644 index 00000000..7b907a48 --- /dev/null +++ b/ext/v8/number.h @@ -0,0 +1,36 @@ +// -*- mode: c++ -*- +#ifndef RR_NUMBER_H +#define RR_NUMBER_H + +namespace rr { + class Number : public Ref { + public: + Number(v8::Isolate* isolate, v8::Handle value) : + Ref(isolate, value.As()) {} + Number(VALUE self) : + Ref(self) {} + + static VALUE New(VALUE self, VALUE r_isolate, VALUE value) { + Isolate isolate(r_isolate); + Locker lock(isolate); + + return Number(isolate, v8::Number::New(isolate, NUM2DBL(value))); + } + + static VALUE Value(VALUE self) { + Number number(self); + Locker lock(number); + + return DBL2NUM(number->Value()); + } + static void Init() { + ClassBuilder("Number", Primitive::Class). + defineSingletonMethod("New", &New). + defineMethod("Value", &Value). + store(&Class); + } + }; +} + + +#endif /* RR_NUMBER_H */ diff --git a/ext/v8/object-template.h b/ext/v8/object-template.h new file mode 100644 index 00000000..98f1342f --- /dev/null +++ b/ext/v8/object-template.h @@ -0,0 +1,50 @@ +// -*- mode: c++ -*- +#ifndef RR_OBJECT_TEMPLATE_H +#define RR_OBJECT_TEMPLATE_H + +namespace rr { + class ObjectTemplate : Ref { + public: + ObjectTemplate(VALUE self) : Ref(self) {} + ObjectTemplate(v8::Isolate* isolate, v8::Handle tmpl) : + Ref(isolate, tmpl) {} + + inline static void Init() { + ClassBuilder("ObjectTemplate", Template::Class). + defineSingletonMethod("New", &New). + defineMethod("NewInstance", &NewInstance). + defineMethod("SetInternalFieldCount", &SetInternalFieldCount). + store(&Class); + } + + static VALUE New(int argc, VALUE argv[], VALUE self) { + VALUE r_isolate, r_constructor; + rb_scan_args(argc, argv, "11", &r_isolate, &r_constructor); + Isolate isolate(r_isolate); + Locker lock(isolate); + + return ObjectTemplate(isolate, v8::ObjectTemplate::New(isolate)); + } + + static VALUE NewInstance(VALUE self, VALUE r_context) { + ObjectTemplate t(self); + Context context(r_context); + Isolate isolate(context.getIsolate()); + Locker lock(isolate); + + return Object::Maybe(isolate, t->NewInstance(context)); + } + + static VALUE SetInternalFieldCount(VALUE self, VALUE value) { + ObjectTemplate t(self); + Locker lock(t); + + t->SetInternalFieldCount(NUM2INT(value)); + + return Qnil; + } + }; +} + + +#endif /* RR_OBJECT_TEMPLATE_H */ diff --git a/ext/v8/object.cc b/ext/v8/object.cc index e63ee3b7..53015aca 100644 --- a/ext/v8/object.cc +++ b/ext/v8/object.cc @@ -1,335 +1,457 @@ #include "rr.h" namespace rr { + void Object::Init() { + ClassBuilder("Object", Value::Class). + defineSingletonMethod("New", &New). + + defineMethod("Set", &Set). + defineMethod("Get", &Get). + defineMethod("GetIdentityHash", &GetIdentityHash). + defineMethod("Has", &Has). + defineMethod("Delete", &Delete). + defineMethod("SetAccessor", &SetAccessor). + defineMethod("SetAccessorProperty", &SetAccessorProperty). + defineMethod("CreateDataProperty", &CreateDataProperty). + defineMethod("DefineOwnProperty", &DefineOwnProperty). + defineMethod("GetPropertyAttributes", &GetPropertyAttributes). + defineMethod("GetOwnPropertyDescriptor", &GetOwnPropertyDescriptor). + defineMethod("GetPropertyNames", &GetPropertyNames). + defineMethod("GetOwnPropertyNames", &GetOwnPropertyNames). + defineMethod("GetPrototype", &GetPrototype). + defineMethod("SetPrototype", &SetPrototype). + defineMethod("ObjectProtoToString", &ObjectProtoToString). + defineMethod("GetConstructorName", &GetConstructorName). + defineMethod("InternalFieldCount", &InternalFieldCount). + defineMethod("GetInternalField", &GetInternalField). + defineMethod("SetInternalField", &SetInternalField). + defineMethod("HasOwnProperty", &HasOwnProperty). + defineMethod("HasRealNamedProperty", &HasRealNamedProperty). + defineMethod("HasRealIndexedProperty", &HasRealIndexedProperty). + defineMethod("HasRealNamedCallbackProperty", &HasRealNamedCallbackProperty). + defineMethod("GetRealNamedPropertyInPrototypeChain", &GetRealNamedPropertyInPrototypeChain). + defineMethod("GetRealNamedProperty", &GetRealNamedProperty). + defineMethod("GetRealNamedPropertyAttributes", &GetRealNamedPropertyAttributes). + defineMethod("GetRealNamedPropertyAttributesInPrototypeChain", &GetRealNamedPropertyAttributesInPrototypeChain). + defineMethod("HasNamedLookupInterceptor", &HasNamedLookupInterceptor). + defineMethod("HasIndexedLookupInterceptor", &HasIndexedLookupInterceptor). + defineMethod("SetHiddenValue", &SetHiddenValue). + defineMethod("GetHiddenValue", &GetHiddenValue). + defineMethod("DeleteHiddenValue", &DeleteHiddenValue). + defineMethod("Clone", &Clone). + defineMethod("CreationContext", &CreationContext). + defineMethod("IsCallable", &IsCallable). + defineMethod("CallAsFunction", &CallAsFunction). + defineMethod("CallAsConstructor", &CallAsConstructor). + + store(&Class); + } -void Object::Init() { - ClassBuilder("Object", Value::Class). - defineSingletonMethod("New", &New). - defineMethod("Set", &Set). - defineMethod("ForceSet", &ForceSet). - defineMethod("Get", &Get). - defineMethod("GetPropertyAttributes", &GetPropertyAttributes). - defineMethod("Has", &Has). - defineMethod("Delete", &Delete). - defineMethod("ForceDelete", &ForceDelete). - defineMethod("SetAccessor", &SetAccessor). - defineMethod("GetPropertyNames", &GetPropertyNames). - defineMethod("GetOwnPropertyNames", &GetOwnPropertyNames). - defineMethod("GetPrototype", &GetPrototype). - defineMethod("SetPrototype", &SetPrototype). - defineMethod("FindInstanceInPrototypeChain", &FindInstanceInPrototypeChain). - defineMethod("ObjectProtoToString", &ObjectProtoToString). - defineMethod("GetConstructorName", &GetConstructorName). - defineMethod("InternalFieldCount", &InternalFieldCount). - defineMethod("GetInternalField", &GetInternalField). - defineMethod("SetInternalField", &SetInternalField). - defineMethod("HasOwnProperty", &HasOwnProperty). - defineMethod("HasRealNamedProperty", &HasRealNamedProperty). - defineMethod("HasRealIndexedProperty", &HasRealIndexedProperty). - defineMethod("HasRealNamedCallbackProperty", &HasRealNamedCallbackProperty). - defineMethod("GetRealNamedPropertyInPrototypeChain", &GetRealNamedPropertyInPrototypeChain). - defineMethod("GetRealNamedProperty", &GetRealNamedProperty). - defineMethod("HasNamedLookupInterceptor", &HasNamedLookupInterceptor). - defineMethod("HasIndexedLookupInterceptor", &HasIndexedLookupInterceptor). - defineMethod("TurnOnAccessCheck", &TurnOnAccessCheck). - defineMethod("GetIdentityHash", &GetIdentityHash). - defineMethod("SetHiddenValue", &SetHiddenValue). - defineMethod("GetHiddenValue", &GetHiddenValue). - defineMethod("DeleteHiddenValue", &DeleteHiddenValue). - defineMethod("IsDirty", &IsDirty). - defineMethod("Clone", &Clone). - defineMethod("CreationContext", &CreationContext). - defineMethod("SetIndexedPropertiesToPixelData", &SetIndexedPropertiesToPixelData). - defineMethod("GetIndexedPropertiesPixelData", &GetIndexedPropertiesPixelData). - defineMethod("HasIndexedPropertiesToPixelData", &HasIndexedPropertiesInPixelData). - defineMethod("GetIndexedPropertiesPixelDataLength", &GetIndexedPropertiesPixelDataLength). - defineMethod("SetIndexedPropertiesToExternalArrayData", &SetIndexedPropertiesToExternalArrayData). - defineMethod("HasIndexedPropertiesInExternalArrayData", &HasIndexedPropertiesInExternalArrayData). - defineMethod("GetIndexedPropertiesExternalArrayData", &GetIndexedPropertiesExternalArrayData). - defineMethod("GetIndexedPropertiesExternalArrayDataType", &GetIndexedPropertiesExternalArrayDataType). - defineMethod("GetIndexedPropertiesExternalArrayDataLength", &GetIndexedPropertiesExternalArrayDataLength). - defineMethod("IsCallable", &IsCallable). - defineMethod("CallAsFunction", &CallAsFunction). - defineMethod("CallAsConstructor", &CallAsConstructor). - store(&Class); - ClassBuilder("PropertyAttribute"). - defineEnumConst("None", v8::None). - defineEnumConst("ReadOnly", v8::ReadOnly). - defineEnumConst("DontEnum", v8::DontEnum). - defineEnumConst("DontDelete", v8::DontDelete); - ClassBuilder("AccessControl"). - defineEnumConst("DEFAULT", v8::DEFAULT). - defineEnumConst("ALL_CAN_READ", v8::ALL_CAN_READ). - defineEnumConst("ALL_CAN_WRITE", v8::ALL_CAN_WRITE). - defineEnumConst("PROHIBITS_OVERWRITING", v8::PROHIBITS_OVERWRITING); -} + VALUE Object::New(VALUE self, VALUE rb_isolate) { + Isolate isolate(rb_isolate); + Locker lock(isolate); + return Object(isolate, v8::Object::New(isolate)); + } -VALUE Object::New(VALUE self) { - return Object(v8::Object::New()); -} + // TODO: Allow setting of property attributes + VALUE Object::Set(VALUE self, VALUE r_context, VALUE key, VALUE value) { + Object object(self); + Context context(r_context); + Isolate isolate(object.getIsolate()); + Locker lock(isolate); + + if (rb_obj_is_kind_of(key, rb_cNumeric)) { + return Bool::Maybe(object->Set(context, Uint32_t(key), Value::rubyObjectToHandle(isolate, value))); + } else { + return Bool::Maybe(object->Set(context, *Value(key), Value::rubyObjectToHandle(isolate, value))); + } + } -//TODO: Allow setting of property attributes -VALUE Object::Set(VALUE self, VALUE key, VALUE value) { - if (rb_obj_is_kind_of(key, rb_cNumeric)) { - return Bool(Object(self)->Set(UInt32(key), Value(value))); - } else { - return Bool(Object(self)->Set(*Value(key), Value(value))); + VALUE Object::Get(VALUE self, VALUE r_context, VALUE key) { + Object object(self); + Context context(r_context); + Isolate isolate(object.getIsolate()); + Locker lock(isolate); + + if (rb_obj_is_kind_of(key, rb_cNumeric)) { + return Value::Maybe(isolate, object->Get(context, Uint32_t(key))); + } else { + return Value::Maybe(isolate, object->Get(context, *Value(key))); + } } -} -VALUE Object::ForceSet(VALUE self, VALUE key, VALUE value) { - return Bool(Object(self)->ForceSet(Value(key), Value(value))); -} + VALUE Object::GetIdentityHash(VALUE self) { + Object object(self); + Locker lock(object); -VALUE Object::Get(VALUE self, VALUE key) { - if (rb_obj_is_kind_of(key, rb_cNumeric)) { - return Value(Object(self)->Get(UInt32(key))); - } else { - return Value(Object(self)->Get(*Value(key))); + return INT2FIX(object->GetIdentityHash()); } -} -VALUE Object::GetPropertyAttributes(VALUE self, VALUE key) { - return PropertyAttribute(Object(self)->GetPropertyAttributes(Value(key))); -} + VALUE Object::Has(VALUE self, VALUE r_context, VALUE key) { + Object object(self); + Locker lock(object); -VALUE Object::Has(VALUE self, VALUE key) { - Object obj(self); - if (rb_obj_is_kind_of(key, rb_cNumeric)) { - return Bool(obj->Has(UInt32(key))); - } else { - return Bool(obj->Has(*String(key))); + if (rb_obj_is_kind_of(key, rb_cNumeric)) { + return Bool::Maybe(object->Has(Context(r_context), Uint32_t(key))); + } else { + return Bool::Maybe(object->Has(Context(r_context), *Value(key))); + } } -} -VALUE Object::Delete(VALUE self, VALUE key) { - Object obj(self); - if (rb_obj_is_kind_of(key, rb_cNumeric)) { - return Bool(obj->Delete(UInt32(key))); - } else { - return Bool(obj->Delete(*String(key))); + VALUE Object::Delete(VALUE self, VALUE r_context, VALUE key) { + Object object(self); + Locker lock(object); + + if (rb_obj_is_kind_of(key, rb_cNumeric)) { + return Bool::Maybe(object->Delete(Context(r_context), Uint32_t(key))); + } else { + return Bool::Maybe(object->Delete(Context(r_context), *Value(key))); + } } -} -VALUE Object::ForceDelete(VALUE self, VALUE key) { - return Bool(Object(self)->ForceDelete(Value(key))); -} + VALUE Object::SetAccessor(int argc, VALUE* argv, VALUE self) { + VALUE r_context, name, getter, setter, data, settings, attribute; + rb_scan_args(argc, argv, "34", &r_context, &name, &getter, &setter, &data, &settings, &attribute); + + Object object(self); + Context context(r_context); + Isolate isolate(object.getIsolate()); + Locker lock(isolate); + + return Bool::Maybe(object->SetAccessor( + context, + Name(name), + &PropertyCallback::invokeGetter, + RTEST(setter) ? &PropertyCallback::invokeSetter : 0, + v8::MaybeLocal(PropertyCallback::wrapData(isolate, getter, setter, data)), + Enum(settings, v8::DEFAULT), + Enum(attribute, v8::None) + )); + } + VALUE Object::SetAccessorProperty(int argc, VALUE* argv, VALUE self) { + VALUE r_context, name, getter, setter, attribute, settings; + rb_scan_args(argc, argv, "33", &r_context, &name, &getter, &setter, &attribute, &settings); -VALUE Object::SetAccessor(int argc, VALUE* argv, VALUE self) { - VALUE name; VALUE get; VALUE set; VALUE data; VALUE settings; VALUE attribs; - rb_scan_args(argc, argv, "24", &name, &get, &set, &data, &settings, &attribs); - Accessor access(get, set, data); - return Bool(Object(self)->SetAccessor( - String(name), - access.accessorGetter(), - access.accessorSetter(), - access, - AccessControl(settings), - PropertyAttribute(attribs)) - ); -} + Object object(self); + Locker lock(object); + + object->SetAccessorProperty( + Name(name), + *Function(getter), + RTEST(setter) ? *Function(setter) : v8::Local(), + Enum(attribute, v8::None), + Enum(settings, v8::DEFAULT) + ); -Object::operator VALUE() { - if (handle.IsEmpty()) { return Qnil; } - Backref* backref; - v8::Local key(v8::String::NewSymbol("rr::Backref")); - v8::Local external = handle->GetHiddenValue(key); - VALUE value; - if (external.IsEmpty()) { - value = downcast(); - backref = new Backref(value); - handle->SetHiddenValue(key, backref->toExternal()); - } else { - v8::Local wrapper = v8::External::Cast(*external); - backref = (Backref*)wrapper->Value(); - value = backref->get(); - if (!RTEST(value)) { - value = downcast(); - backref->set(value); + + VALUE Object::CreateDataProperty(VALUE self, VALUE r_context, VALUE key, VALUE value) { + Object object(self); + Locker lock(object); + + if (rb_obj_is_kind_of(key, rb_cNumeric)) { + return Bool::Maybe(object->CreateDataProperty(Context(r_context), Uint32_t(key), Value(value))); + } else { + return Bool::Maybe(object->CreateDataProperty(Context(r_context), *Name(key), Value(value))); } } - return value; -} -VALUE Object::downcast() { - if (handle->IsFunction()) { - return Function((v8::Handle) v8::Function::Cast(*handle)); + VALUE Object::DefineOwnProperty(int argc, VALUE* argv, VALUE self) { + VALUE r_context, key, value, attribute; + rb_scan_args(argc, argv, "31", &r_context, &key, &value, &attribute); + + Object object(self); + Locker lock(object); + + return Bool::Maybe(object->DefineOwnProperty( + Context(r_context), + *Name(key), + Value(value), + Enum(attribute, v8::None) + )); } - if (handle->IsArray()) { - return Array((v8::Handle)v8::Array::Cast(*handle)); + + VALUE Object::GetPropertyAttributes(VALUE self, VALUE r_context, VALUE key) { + Object object(self); + Locker lock(object); + + return Enum::Maybe(object->GetPropertyAttributes( + Context(r_context), + *Name(key) + )); } - if (handle->IsDate()) { - // return Date(handle); + + VALUE Object::GetOwnPropertyDescriptor(VALUE self, VALUE r_context, VALUE key) { + Object object(self); + Locker lock(object); + + return Value::Maybe(object.getIsolate(), object->GetOwnPropertyDescriptor( + Context(r_context), + *rr::String(key) + )); } - if (handle->IsBooleanObject()) { - // return BooleanObject(handle); + + VALUE Object::GetPropertyNames(VALUE self, VALUE r_context) { + Object object(self); + Locker lock(object); + + return Array::Maybe(object.getIsolate(), object->GetPropertyNames(Context(r_context))); } - if (handle->IsNumberObject()) { - // return NumberObject(handle); + + VALUE Object::GetOwnPropertyNames(VALUE self, VALUE r_context) { + Object object(self); + Locker lock(object); + + return Array::Maybe(object.getIsolate(), object->GetOwnPropertyNames(Context(r_context))); } - if (handle->IsStringObject()) { - // return StringObject(handle); + + VALUE Object::GetPrototype(VALUE self) { + Object object(self); + Locker lock(object); + + return Value(object.getIsolate(), object->GetPrototype()); } - if (handle->IsRegExp()) { - // return RegExp(handle); + + VALUE Object::SetPrototype(VALUE self, VALUE r_context, VALUE prototype) { + Object object(self); + Locker lock(object); + + return Bool::Maybe(object->SetPrototype( + Context(r_context), + *Value(prototype) + )); } - return Ref::operator VALUE(); -} -VALUE Object::GetPropertyNames(VALUE self) { - return Array(Object(self)->GetPropertyNames()); -} + VALUE Object::ObjectProtoToString(VALUE self, VALUE r_context) { + Object object(self); + Locker lock(object); -VALUE Object::GetOwnPropertyNames(VALUE self) { - return Array(Object(self)->GetOwnPropertyNames()); -} + return String::Maybe(object.getIsolate(), object->ObjectProtoToString(Context(r_context))); + } -VALUE Object::GetPrototype(VALUE self) { - return Value(Object(self)->GetPrototype()); -} + VALUE Object::GetConstructorName(VALUE self) { + Object object(self); + Locker lock(object); -VALUE Object::SetPrototype(VALUE self, VALUE prototype) { - return Bool(Object(self)->SetPrototype(Value(prototype))); -} + return String(object.getIsolate(), object->GetConstructorName()); + } -VALUE Object::FindInstanceInPrototypeChain(VALUE self, VALUE impl) { - return Object(Object(self)->FindInstanceInPrototypeChain(FunctionTemplate(impl))); -} + VALUE Object::InternalFieldCount(VALUE self) { + Object object(self); + Locker lock(object); -VALUE Object::ObjectProtoToString(VALUE self) { - return String(Object(self)->ObjectProtoToString()); -} + return INT2FIX(object->InternalFieldCount()); + } -VALUE Object::GetConstructorName(VALUE self) { - return String(Object(self)->GetConstructorName()); -} + VALUE Object::GetInternalField(VALUE self, VALUE index) { + Object object(self); + Locker lock(object); -VALUE Object::InternalFieldCount(VALUE self) { - return INT2FIX(Object(self)->InternalFieldCount()); -} + return Value(object.getIsolate(), object->GetInternalField(NUM2INT(index))); + } -VALUE Object::GetInternalField(VALUE self, VALUE idx) { - return Value(Object(self)->GetInternalField(NUM2INT(idx))); -} + VALUE Object::SetInternalField(VALUE self, VALUE index, VALUE value) { + Object object(self); + Locker lock(object); -VALUE Object::SetInternalField(VALUE self, VALUE idx, VALUE value) { - Void(Object(self)->SetInternalField(NUM2INT(idx), Value(value))); -} + object->SetInternalField(NUM2INT(index), *Value(value)); -VALUE Object::HasOwnProperty(VALUE self, VALUE key) { - return Bool(Object(self)->HasOwnProperty(String(key))); -} + return Qnil; + } -VALUE Object::HasRealNamedProperty(VALUE self, VALUE key) { - return Bool(Object(self)->HasRealNamedProperty(String(key))); -} + VALUE Object::HasOwnProperty(VALUE self, VALUE r_context, VALUE key) { + Object object(self); + Locker lock(object); -VALUE Object::HasRealIndexedProperty(VALUE self, VALUE idx) { - return Bool(Object(self)->HasRealIndexedProperty(UInt32(idx))); -} + return Bool::Maybe(object->HasOwnProperty(Context(r_context), *Name(key))); + } -VALUE Object::HasRealNamedCallbackProperty(VALUE self, VALUE key) { - return Bool(Object(self)->HasRealNamedCallbackProperty(String(key))); -} + VALUE Object::HasRealNamedProperty(VALUE self, VALUE r_context, VALUE key) { + Object object(self); + Locker lock(object); -VALUE Object::GetRealNamedPropertyInPrototypeChain(VALUE self, VALUE key) { - return Value(Object(self)->GetRealNamedPropertyInPrototypeChain(String(key))); -} + return Bool::Maybe(object->HasRealNamedProperty(Context(r_context), *Name(key))); + } -VALUE Object::GetRealNamedProperty(VALUE self, VALUE key) { - return Value(Object(self)->GetRealNamedProperty(String(key))); -} + VALUE Object::HasRealIndexedProperty(VALUE self, VALUE r_context, VALUE index) { + Object object(self); + Locker lock(object); -VALUE Object::HasNamedLookupInterceptor(VALUE self) { - return Bool(Object(self)->HasNamedLookupInterceptor()); -} + return Bool::Maybe(object->HasRealIndexedProperty(Context(r_context), NUM2INT(index))); + } -VALUE Object::HasIndexedLookupInterceptor(VALUE self) { - return Bool(Object(self)->HasIndexedLookupInterceptor()); -} + VALUE Object::HasRealNamedCallbackProperty(VALUE self, VALUE r_context, VALUE key) { + Object object(self); + Locker lock(object); -VALUE Object::TurnOnAccessCheck(VALUE self) { - Void(Object(self)->TurnOnAccessCheck()); -} + return Bool::Maybe(object->HasRealNamedCallbackProperty(Context(r_context), *Name(key))); + } -VALUE Object::GetIdentityHash(VALUE self) { - return INT2FIX(Object(self)->GetIdentityHash()); -} + VALUE Object::GetRealNamedPropertyInPrototypeChain(VALUE self, VALUE r_context, VALUE key) { + Object object(self); + Locker lock(object); -VALUE Object::SetHiddenValue(VALUE self, VALUE key, VALUE value) { - return Bool(Object(self)->SetHiddenValue(String(key), Value(value))); -} + return Value::Maybe(object.getIsolate(), object->GetRealNamedPropertyInPrototypeChain( + Context(r_context), + *Name(key) + )); + } -VALUE Object::GetHiddenValue(VALUE self, VALUE key) { - return Value(Object(self)->GetHiddenValue(String(key))); -} + VALUE Object::GetRealNamedProperty(VALUE self, VALUE r_context, VALUE key) { + Object object(self); + Locker lock(object); -VALUE Object::DeleteHiddenValue(VALUE self, VALUE key) { - return Bool(Object(self)->DeleteHiddenValue(String(key))); -} + return Value::Maybe(object.getIsolate(), object->GetRealNamedProperty( + Context(r_context), + *Name(key) + )); + } -VALUE Object::IsDirty(VALUE self) { - return Bool(Object(self)->IsDirty()); -} + VALUE Object::GetRealNamedPropertyAttributes(VALUE self, VALUE r_context, VALUE key) { + Object object(self); + Locker lock(object); -VALUE Object::Clone(VALUE self) { - return Object(Object(self)->Clone()); -} + return Enum::Maybe(object->GetRealNamedPropertyAttributes( + Context(r_context), + *Name(key) + )); + } -VALUE Object::CreationContext(VALUE self) { - return Context(Object(self)->CreationContext()); -} + VALUE Object::GetRealNamedPropertyAttributesInPrototypeChain(VALUE self, VALUE r_context, VALUE key) { + Object object(self); + Locker lock(object); -VALUE Object::SetIndexedPropertiesToPixelData(VALUE self, VALUE data, VALUE length) { - return not_implemented("SetIndexedPropertiesToPixelData"); -} + return Enum::Maybe(object->GetRealNamedPropertyAttributesInPrototypeChain( + Context(r_context), + *Name(key) + )); + } -VALUE Object::GetIndexedPropertiesPixelData(VALUE self) { - return not_implemented("GetIndexedPropertiesPixelData"); -} + VALUE Object::HasNamedLookupInterceptor(VALUE self) { + Object object(self); + Locker lock(object); -VALUE Object::HasIndexedPropertiesInPixelData(VALUE self) { - return Bool(Object(self)->HasIndexedPropertiesInPixelData()); -} + return Bool(object->HasNamedLookupInterceptor()); + } -VALUE Object::GetIndexedPropertiesPixelDataLength(VALUE self) { - return INT2FIX(Object(self)->GetIndexedPropertiesPixelDataLength()); -} + VALUE Object::HasIndexedLookupInterceptor(VALUE self) { + Object object(self); + Locker lock(object); -VALUE Object::SetIndexedPropertiesToExternalArrayData(VALUE self) { - return not_implemented("SetIndexedPropertiesToExternalArrayData"); -} + return Bool(object->HasIndexedLookupInterceptor()); + } -VALUE Object::HasIndexedPropertiesInExternalArrayData(VALUE self) { - return Bool(Object(self)->HasIndexedPropertiesInExternalArrayData()); -} + VALUE Object::SetHiddenValue(VALUE self, VALUE key, VALUE value) { + Object object(self); + Locker lock(object); -VALUE Object::GetIndexedPropertiesExternalArrayData(VALUE self) { - return not_implemented("GetIndexedPropertiesExternalArrayData"); -} + return Bool(object->SetHiddenValue(String(key), Value(value))); + } -VALUE Object::GetIndexedPropertiesExternalArrayDataType(VALUE self) { - return not_implemented("GetIndexedPropertiesExternalArrayDataType"); -} + VALUE Object::GetHiddenValue(VALUE self, VALUE key) { + Object object(self); + Locker lock(object); -VALUE Object::GetIndexedPropertiesExternalArrayDataLength(VALUE self) { - return INT2FIX(Object(self)->GetIndexedPropertiesExternalArrayDataLength()); -} + return Value(object.getIsolate(), object->GetHiddenValue(String(key))); + } -VALUE Object::IsCallable(VALUE self) { - return Bool(Object(self)->IsCallable()); -} + VALUE Object::DeleteHiddenValue(VALUE self, VALUE key) { + Object object(self); + Locker lock(object); -VALUE Object::CallAsFunction(VALUE self, VALUE recv, VALUE argv) { - return Value(Object(self)->CallAsFunction(Object(recv), RARRAY_LENINT(argv), Value::array(argv))); -} + return Bool(object->DeleteHiddenValue(String(key))); + } -VALUE Object::CallAsConstructor(VALUE self, VALUE argv) { - return Value(Object(self)->CallAsConstructor(RARRAY_LENINT(argv), Value::array(argv))); -} + VALUE Object::Clone(VALUE self) { + Object object(self); + Locker lock(object); + + return Object(object.getIsolate(), object->Clone()); + } + + VALUE Object::CreationContext(VALUE self) { + Object object(self); + Locker lock(object); + + return Context(object->CreationContext()); + } + + VALUE Object::IsCallable(VALUE self) { + Object object(self); + Locker lock(object); + + return Bool(object->IsCallable()); + } + + VALUE Object::CallAsFunction(int argc, VALUE* argv, VALUE self) { + VALUE r_context, recv, r_argv; + rb_scan_args(argc, argv, "30", &r_context, &recv, &r_argv); + + Object object(self); + v8::Isolate* isolate = object.getIsolate(); + Locker lock(isolate); + + std::vector< v8::Handle > vector(Value::convertRubyArray(isolate, r_argv)); + + return Value::Maybe(isolate, object->CallAsFunction( + Context(r_context), + Value(recv), + RARRAY_LENINT(r_argv), + &vector[0] + )); + } + + VALUE Object::CallAsConstructor(VALUE self, VALUE r_context, VALUE argv) { + Object object(self); + v8::Isolate* isolate = object.getIsolate(); + Locker lock(isolate); + + std::vector< v8::Handle > vector(Value::convertRubyArray(isolate, argv)); + + return Value::Maybe(isolate, object->CallAsConstructor( + Context(r_context), + RARRAY_LENINT(argv), + &vector[0] + )); + } + + Object::operator VALUE() { + Isolate isolate(getIsolate()); + Locker lock(isolate); + + if (handle->IsFunction()) { + return Function(isolate, handle.As()); + } + if (handle->IsArray()) { + return Array(isolate, handle.As()); + } + if (handle->IsDate()) { + return Date(isolate, handle.As()); + } + + // TODO: Enable this when the methods are implemented + // + // + // if (handle->IsBooleanObject()) { + // // return BooleanObject(handle); + // } + // + // if (handle->IsNumberObject()) { + // // return NumberObject(handle); + // } + // + // if (handle->IsStringObject()) { + // // return StringObject(handle); + // } + // + // if (handle->IsRegExp()) { + // // return RegExp(handle); + // } + + return Ref::operator VALUE(); + } } diff --git a/ext/v8/object.h b/ext/v8/object.h new file mode 100644 index 00000000..0ed309f2 --- /dev/null +++ b/ext/v8/object.h @@ -0,0 +1,70 @@ +#ifndef RR_OBJECT +#define RR_OBJECT + +namespace rr { + + class Object : public Ref { + public: + static void Init(); + + static VALUE New(VALUE self, VALUE rb_isolate); + static VALUE Set(VALUE self, VALUE r_context, VALUE key, VALUE value); + static VALUE Get(VALUE self, VALUE r_context, VALUE key); + static VALUE GetIdentityHash(VALUE self); + static VALUE Has(VALUE self, VALUE r_context, VALUE key); + static VALUE Delete(VALUE self, VALUE r_context, VALUE key); + + static VALUE SetAccessor(int argc, VALUE* argv, VALUE self); + static VALUE SetAccessorProperty(int argc, VALUE* argv, VALUE self); + + static VALUE CreateDataProperty(VALUE self, VALUE r_context, VALUE key, VALUE value); + static VALUE DefineOwnProperty(int argc, VALUE* argv, VALUE self); + static VALUE GetPropertyAttributes(VALUE self, VALUE r_context, VALUE key); + static VALUE GetOwnPropertyDescriptor(VALUE self, VALUE r_context, VALUE key); + static VALUE GetPropertyNames(VALUE self, VALUE r_context); + static VALUE GetOwnPropertyNames(VALUE self, VALUE r_context); + static VALUE GetPrototype(VALUE self); + static VALUE SetPrototype(VALUE self, VALUE r_context, VALUE prototype); + + static VALUE ObjectProtoToString(VALUE self, VALUE r_context); + static VALUE GetConstructorName(VALUE self); + + static VALUE InternalFieldCount(VALUE self); + static VALUE GetInternalField(VALUE self, VALUE index); + static VALUE SetInternalField(VALUE self, VALUE index, VALUE value); + + static VALUE HasOwnProperty(VALUE self, VALUE r_context, VALUE key); + static VALUE HasRealNamedProperty(VALUE self, VALUE r_context, VALUE key); + static VALUE HasRealIndexedProperty(VALUE self, VALUE r_context, VALUE index); + static VALUE HasRealNamedCallbackProperty(VALUE self, VALUE r_context, VALUE key); + static VALUE GetRealNamedPropertyInPrototypeChain(VALUE self, VALUE r_context, VALUE key); + static VALUE GetRealNamedProperty(VALUE self, VALUE r_context, VALUE key); + static VALUE GetRealNamedPropertyAttributes(VALUE self, VALUE r_context, VALUE key); + static VALUE GetRealNamedPropertyAttributesInPrototypeChain(VALUE self, VALUE r_context, VALUE key); + + static VALUE HasNamedLookupInterceptor(VALUE self); + static VALUE HasIndexedLookupInterceptor(VALUE self); + + static VALUE SetHiddenValue(VALUE self, VALUE key, VALUE value); + static VALUE GetHiddenValue(VALUE self, VALUE key); + static VALUE DeleteHiddenValue(VALUE self, VALUE key); + + static VALUE Clone(VALUE self); + static VALUE CreationContext(VALUE self); + + static VALUE IsCallable(VALUE self); + + static VALUE CallAsFunction(int argc, VALUE* argv, VALUE self); + static VALUE CallAsConstructor(VALUE self, VALUE r_context, VALUE argv); + + inline Object(VALUE value) : Ref(value) {} + inline Object(v8::Isolate* isolate, v8::Handle object) : Ref(isolate, object) {} + + typedef MaybeLocal Maybe; + + operator VALUE(); + }; + +} + +#endif diff --git a/ext/v8/pointer.h b/ext/v8/pointer.h new file mode 100644 index 00000000..30331827 --- /dev/null +++ b/ext/v8/pointer.h @@ -0,0 +1,220 @@ +// -*- mode: c++ -*- +#ifndef RR_POINTER +#define RR_POINTER + +namespace rr { + + /** + * Conversion between a native C/C++ pointer and a Ruby Object. + * + * Use this class When you have a native C/C++ pointer that you want + * to make appear as a Ruby object. It will take care of all the + * book keeping like registering GC callbacks. It is also used to + * access from C/C++ a pointer that you had previously passed to Ruby. + * + * Suppose we have a C++ class `Greeter`. + * + * class Greeter { + * public: + * void sayHello() { + * println("Hello"); + * } + * } + * + * and that we want to pass an instance of that class to the + * following Ruby method: + * + * class Greet + * def self.greet(greeter) + * //we want greeter to wrap our C++ object. + * greeter.say_hello() + * end + * end + * + * We could do that from C by using a `Pointer` specified for + * `Greeter`: + * + * Greeter* greeter = new Greeter(); + * + * // get reference to Ruby `Greet` class from C + * VALUE GreeterClass = rb_define_class("Greet"); + * Pointer native_greeter(greeter); + * + * //now we can pass it to Ruby's `Greeter#new` + * rb_funcall(GreeterClass, rb_intern("greet"), 1, native_greeter); + * + * Once it is passed to Ruby, it is NO LONGER YOURS. It's lifecycle + * now belongs to Ruby, and you must not delete the pointer by + * hand. + * + * If we were to run the example above it would actually fail + * because by default, the Ruby Class of the native pointer is + * actually an opaque `Object`, and there is no method `say_hello` + * on `Object`. In order to make it work, we have to specify which + * class we want Ruby to treat our pointer as. We do that by setting + * the static Pointer::Class variable. + * + * Pointer::Class = rb_define_class("NativeGreeter"); + * + * Now, when we pass `Pointer(new Greeter())` to Ruby code, + * it will appear to have the class `NativeGreeter`. We can now + * define our `say_hello` method on that class in order to call the + * underlying C++ method. We can construct a `Pointer` from + * an instance of `NativeGreeter` and use it to automatically unwrap + * the C++ `Greeter` object and call its methods: + * + * + * static VALUE say_hello(VALUE self) { + * //unwrap the native `Greeter` and call its `sayHello()` + * Pointer(self)->sayHello(); + * return Qnil; + * } + * VALUE NativeGreeter = Pointer::Class; + * rb_define_method(NativeGreeter, "say_hello", (VALUE (*)(...))&say_hello, 0); + * + * Notice how we were able to just call the `sayHello()` method + * directly on the instance of Pointer? That's because it + * will automatically unbox and dereference the pointer so that you + * can automatically forward calls to the underlying object it wraps. + * + * Most of the time The Ruby Racer codebase uses subclassing to + * specify a Pointer. For example, `rr:Isolate ` looks like this: + * + * class Isolate : public Poiter { + * //.... + * } + * + * which means that it can be used in all the cases above without + * any template specifier: + * + * //wrap + * Isolate isolate(new v8::Isolate()); + * + * //unwrap + * VALUE rubyIsolate = getRubyObjectContainingIsolate(); + * Isolate isolate(rubyIsolate); + * + * //Ruby class for Isolate + * Isolate::Class //=> V8::C::Isolate + * + */ + template class Pointer { + public: + /** + * Construct a `Pointer` from a C/C++ pointer. This is normally + * done in order to convert it into a Ruby `VALUE`. E.g. + * + * Greeter* greeter = new Greeter(); + * rb_funcall(Pointer(greeter), rb_intern("to_s")); + */ + inline Pointer(T* t) : pointer(t) {}; + + /** + * Construct a Pointer from a C/C++ pointer that was previously + * passed to Ruby. Use this access and call methods on the + * underlying pointer: + * + * Pointer(rubyValue)->sayHello(); + */ + inline Pointer(VALUE v) { + if (RTEST(v)) { + this->unwrap(v); + } else { + this->pointer = NULL; + } + }; + + /** + * Enable transparent pointer dereferencing via the `*` operator: + * + * *Pointer(new Greeter()).sayHello(); + */ + inline operator T*() { + return pointer; + } + + /** + * Enable transparent pointer dereferencing via the `->` operator: + * + * Pointer(new Greeter())->sayHello(); + */ + inline T* operator ->() { + return pointer; + } + + /** + * Coerce this Pointer into a VALUE. Once this happens, either by + * assigning it to variable of type VALUE, casting it to (VALUE), + * or passing it to a function that takes a VALUE as a parameter, + * this pointer now belongs to Ruby, and you should not `delete` + * it. + */ + inline operator VALUE() { + VALUE RubyClass = Class ? Class : rb_cObject; + return Data_Wrap_Struct(RubyClass, 0, &release, pointer); + } + + /** + * Subclasses implement this to uwrap their particular + * datastructure. For example, the implementation for `Isolate` + * looks like: + * + * void Pointer::unwrap(VALUE value) { + * Data_Get_Struct(value, class v8::Isolate, pointer); + * } + * + * TODO: I would really like to get rid of this and it + * seems like it ought to be templatable, but I couldn't figure + * out a way to get the C++ compiler to play ball. Basically, it + * seems like there ought to be a generic function like: + * + * void inline unwrap(VALUE value) { + * Dat_Get_Struct(value, class T, pointer); + * } + * + * but it wouldn't work for me. + */ + void unwrap(VALUE value); + + + /** + * A static method to delete this pointer. It is implemented as a + * static method so that a function pointer to it can be passed to + * Ruby so that it can be called from Ruby when the object is + * garbage collected. + * TODO: rename to `destroy()` + */ + static void release(T* pointer) { + delete pointer; + } + + /** + * Storage for the Class variable. This is where you write to + * determine what Ruby class this Pointer will have: + * + * Pointer::Class = rb_define_class("NativeGreeter"); + */ + static VALUE Class; + + /** + * See if two `Pointer`s are equal by comparing their memory + * addresses. + * + * TODO: overload the `==` operator + */ + static inline VALUE PointerEquals(VALUE self, VALUE other) { + return Bool(Pointer(self).pointer == Pointer(other).pointer); + } + + protected: + T* pointer; + }; + + /** + * Some C++ template nonsense. + */ + template VALUE Pointer::Class; + +} + +#endif diff --git a/ext/v8/primitive.cc b/ext/v8/primitive.cc index 6ea11860..1f3fe483 100644 --- a/ext/v8/primitive.cc +++ b/ext/v8/primitive.cc @@ -1,8 +1,10 @@ #include "rr.h" namespace rr { + void Primitive::Init() { ClassBuilder("Primitive", Value::Class). store(&Class); } -} \ No newline at end of file + +} diff --git a/ext/v8/primitive.h b/ext/v8/primitive.h new file mode 100644 index 00000000..a3e42442 --- /dev/null +++ b/ext/v8/primitive.h @@ -0,0 +1,16 @@ +#ifndef RR_PRIMITIVE +#define RR_PRIMITIVE + +namespace rr { + + class Primitive: public Ref { + public: + static void Init(); + + inline Primitive(VALUE value) : Ref(value) {} + inline Primitive(v8::Isolate* isolate, v8::Handle primitive) : Ref(isolate, primitive) {} + }; + +} + +#endif diff --git a/ext/v8/property-callback-info.h b/ext/v8/property-callback-info.h new file mode 100644 index 00000000..726a7d25 --- /dev/null +++ b/ext/v8/property-callback-info.h @@ -0,0 +1,104 @@ +// -*- mode: c++ -*- +#ifndef RR_PROPERTY_CALLBACK_INFO_H +#define RR_PROPERTY_CALLBACK_INFO_H + +namespace rr { + + class PropertyCallbackInfo { + public: + + template + class Base : public Wrapper> { + public: + + inline Base(v8::PropertyCallbackInfo info) : + Wrapper>(info) {} + + inline Base(VALUE self) : Wrapper>(self) {} + + static VALUE This(VALUE self) { + Base info(self); + Locker lock(info->GetIsolate()); + return Object(info->GetIsolate(), info->This()); + } + + static VALUE Data(VALUE self) { + Base info(self); + Isolate isolate(info->GetIsolate()); + Locker lock(isolate); + + return PropertyCallback::unwrapData(isolate, info->Data()) ; + } + + static VALUE GetIsolate(VALUE self) { + Base info(self); + return Isolate(info->GetIsolate()); + } + + }; + + class Value : public Base { + public: + + inline Value(v8::PropertyCallbackInfo info) : + Base(info) {} + + inline Value(VALUE self) : Base(self) {} + + static VALUE GetReturnValue(VALUE self) { + Value info(self); + Locker lock(info->GetIsolate()); + return ReturnValue::Value(info->GetReturnValue()); + } + + static inline void Init() { + ClassBuilder("Value", PropertyCallbackInfo::Class, PropertyCallbackInfo::Class). + defineMethod("This", &This). + defineMethod("Data", &Data). + defineMethod("GetIsolate", &GetIsolate). + defineMethod("GetReturnValue", &GetReturnValue). + store(&Class); + } + + }; + + class Void : public Base { + public: + + inline Void(v8::PropertyCallbackInfo info) : + Base(info) {} + + inline Void(VALUE self) : Base(self) {} + + static VALUE GetReturnValue(VALUE self) { + Value info(self); + Locker lock(info->GetIsolate()); + return ReturnValue::Void(info->GetReturnValue()); + } + + static inline void Init() { + ClassBuilder("Void", PropertyCallbackInfo::Class, PropertyCallbackInfo::Class). + defineMethod("This", &This). + defineMethod("Data", &Data). + defineMethod("GetIsolate", &GetIsolate). + defineMethod("GetReturnValue", &GetReturnValue). + store(&Class); + } + + }; + + static VALUE Class; + + static void Init() { + ClassBuilder("PropertyCallbackInfo"). + store(&Class); + + Value::Init(); + Void::Init(); + } + + }; + +} + +#endif /* RR_PROPERTY_CALLBACK_INFO_H */ diff --git a/ext/v8/property-callback.cc b/ext/v8/property-callback.cc new file mode 100644 index 00000000..59f1ccfe --- /dev/null +++ b/ext/v8/property-callback.cc @@ -0,0 +1,77 @@ +#include "rr.h" + +namespace rr { + + v8::Local PropertyCallback::wrapData(v8::Isolate* isolate, VALUE r_getter, VALUE r_setter, VALUE r_data) { + v8::Local holder = v8::Object::New(isolate); + + v8::Local getter_key = v8::String::NewFromUtf8(isolate, "rr::getter"); + v8::Local setter_key = v8::String::NewFromUtf8(isolate, "rr::setter"); + v8::Local data_key = v8::String::NewFromUtf8(isolate, "rr::data"); + + holder->SetHiddenValue(getter_key, External::wrap(isolate, r_getter)); + holder->SetHiddenValue(setter_key, External::wrap(isolate, r_setter)); + holder->SetHiddenValue(data_key, rr::Value(r_data)); + + return holder; + } + + VALUE PropertyCallback::unwrapData(v8::Isolate* isolate, v8::Local value) { + v8::Local holder = v8::Local::Cast(value); + v8::Local data_key = v8::String::NewFromUtf8(isolate, "rr::data"); + + return Value(isolate, holder->GetHiddenValue(data_key)); + } + + VALUE PropertyCallback::unwrapGetter(v8::Isolate* isolate, v8::Local value) { + v8::Local holder = v8::Local::Cast(value); + v8::Local callback_key = v8::String::NewFromUtf8(isolate, "rr::getter"); + + return External::unwrap(holder->GetHiddenValue(callback_key)); + } + + VALUE PropertyCallback::unwrapSetter(v8::Isolate* isolate, v8::Local value) { + v8::Local holder = v8::Local::Cast(value); + v8::Local callback_key = v8::String::NewFromUtf8(isolate, "rr::setter"); + + return External::unwrap(holder->GetHiddenValue(callback_key)); + } + + void PropertyCallback::invokeGetter(v8::Local property, const v8::PropertyCallbackInfo& info) { + v8::Isolate* isolate = info.GetIsolate(); + + VALUE code(unwrapGetter(isolate, info.Data())); + + VALUE rb_property; + if (property->IsSymbol()) { + rb_property = Symbol(isolate, v8::Local::Cast(property)); + } else { + rb_property = String(isolate, property->ToString()); + } + + Unlocker unlock(info.GetIsolate()); + rb_funcall(code, rb_intern("call"), 2, rb_property, (VALUE)PropertyCallbackInfo::Value(info)); + } + + void PropertyCallback::invokeSetter(v8::Local property, v8::Local value, const v8::PropertyCallbackInfo& info) { + v8::Isolate* isolate = info.GetIsolate(); + + VALUE code(unwrapSetter(isolate, info.Data())); + + VALUE rb_property; + if (property->IsSymbol()) { + rb_property = Symbol(isolate, v8::Local::Cast(property)); + } else { + rb_property = String(isolate, property->ToString()); + } + + Unlocker unlock(info.GetIsolate()); + rb_funcall( + code, rb_intern("call"), 3, + rb_property, + (VALUE)Value(isolate, value), + (VALUE)PropertyCallbackInfo::Void(info) + ); + } + +} diff --git a/ext/v8/property-callback.h b/ext/v8/property-callback.h new file mode 100644 index 00000000..92d42497 --- /dev/null +++ b/ext/v8/property-callback.h @@ -0,0 +1,61 @@ +// -*- mode: c++ -*- +#ifndef RR_PROPERTY_CALLBACK_H +#define RR_PROPERTY_CALLBACK_H + +namespace rr { + + class PropertyCallback { + public: + + /** + * Package up the callback data for this object so that it can + * invoke Ruby callables. + * + * Each accessor can have one `v8::Value` associated with it + * that is passed to the `SetAccessor` method. To support this + * same API from ruby, we take the passed data constructor *and* + * the callbacks and store them *both* in a single `v8::Object` + * which we use for the C++ level callback data. + */ + static v8::Local wrapData(v8::Isolate* isolate, VALUE r_getter, VALUE r_setter, VALUE r_data); + + /** + * Get the data packaged in the object. + */ + static VALUE unwrapData(v8::Isolate* isolate, v8::Local holder); + + /** + * Get the getter packaged in the object. + */ + static VALUE unwrapGetter(v8::Isolate* isolate, v8::Local holder); + + /** + * Get the setter packaged in the object. + */ + static VALUE unwrapSetter(v8::Isolate* isolate, v8::Local holder); + + /** + * Call the Ruby code associated with this callback. + * + * Unpack the Ruby code, and the callback data from the C++ + * callback data, and then invoke that code. + * + * Note: This function implements the `v8::AccessorNameGetterCallback` API. + */ + static void invokeGetter(v8::Local property, const v8::PropertyCallbackInfo& info); + + /** + * Call the Ruby code associated with this callback. + * + * Unpack the Ruby code, and the callback data from the C++ + * callback data, and then invoke that code. + * + * Note: This function implements the `v8::AccessorNameSetterCallback` API. + */ + static void invokeSetter(v8::Local property, v8::Local value, const v8::PropertyCallbackInfo& info); + + }; + +} + +#endif /* RR_PROPERTY_CALLBACK_H */ diff --git a/ext/v8/ref.h b/ext/v8/ref.h new file mode 100644 index 00000000..0e053c43 --- /dev/null +++ b/ext/v8/ref.h @@ -0,0 +1,153 @@ +// -*- mode: c++ -*- +#ifndef RR_REF +#define RR_REF + +namespace rr { + /** + * A Reference to a V8 managed object + * + * Uses type coercion to quickly convert from a v8 handle + * to a ruby object and back again. Suppose we have a v8 handle + * that we want to return to Ruby. We can put it into a Ref: + * + * v8::Handle object = v8::Object::New(); + * VALUE val = Ref(object); + * + * this will create a `v8::Persistent` handle for the object + * so that it will not be garbage collected by v8. It then + * stuffs this new persistent handle into a Data_Wrap_Struct + * which can then be passed to Ruby code. When this struct + * is garbage collected by Ruby, it enqueues the corresponding + * v8 handle to be released during v8 gc. + * + * By the same token, you can use Refs to unwrap a Data_Wrap_Struct + * which has been generated in this fashion and call through to + * the underlying v8 methods. Suppose we are passed a VALUE `val` + * wrapping a v8::Object: + * + * Ref object(val); + * object->Get(v8::String::New("foo")); + * + */ + template + class Ref { + public: + struct Holder; + Ref(VALUE value) { + this->value = value; + Holder* holder = unwrapHolder(); + + if (holder) { + this->isolate = holder->data->isolate; + this->handle = v8::Local::New(holder->data->isolate, *holder->cell); + } else { + this->isolate = NULL; + this->handle = v8::Local(); + } + } + + Ref(v8::Isolate* isolate, v8::Local handle) { + this->isolate = isolate; + this->handle = handle; + } + + virtual ~Ref() {} + + /* + * Coerce a Ref into a Ruby VALUE + */ + virtual operator VALUE() const { + if (handle.IsEmpty()) { + return Qnil; + } + + return Data_Wrap_Struct(Class, 0, &destroy, new Holder(isolate, handle)); + } + + /* + * Coerce a Ref into a v8::Local. + */ + inline operator v8::Handle() const { + return handle; + } + + inline v8::Isolate* getIsolate() const { + return isolate; + } + + inline operator v8::Isolate*() const { + return isolate; + } + + static void destroy(Holder* holder) { + delete holder; + } + + /* + * Pointer de-reference operators, this lets you use a ref to + * call through to underlying v8 methods. e.g + * + * Ref(value)->ToString(); + */ + inline v8::Handle operator->() const { return *this; } + inline v8::Handle operator*() const { return *this; } + + struct Holder { + Holder(v8::Isolate* isolate, v8::Handle handle) : + data((Isolate::IsolateData*)isolate->GetData(0)), cell(new v8::Persistent(isolate, handle)) { + Isolate(data).incrementTotalReferences(); + } + + virtual ~Holder() { + Isolate(data).scheduleReleaseObject(cell); + } + + Isolate::IsolateData* data; + v8::Persistent* cell; + }; + + static VALUE Class; + + protected: + Holder* unwrapHolder() const { + if (RTEST(this->value)) { + Holder* holder = NULL; + Data_Get_Struct(this->value, struct Holder, holder); + return holder; + } else { + return NULL; + } + } + + VALUE value; + + v8::Isolate* isolate; + v8::Handle handle; + + public: + template + class array { + public: + inline array(VALUE ary) : argv(ary), vector(RARRAY_LENINT(argv)) { } + + inline operator v8::Handle*() { + for (uint32_t i = 0; i < vector.size(); i++) { + vector[i] = C(rb_ary_entry(argv, i)); + } + + return &vector[0]; + } + + private: + VALUE argv; + std::vector< v8::Handle > vector; + }; + + }; + + template + VALUE Ref::Class; + +} + +#endif diff --git a/ext/v8/return-value.h b/ext/v8/return-value.h new file mode 100644 index 00000000..f2b9f843 --- /dev/null +++ b/ext/v8/return-value.h @@ -0,0 +1,124 @@ +// -*- mode: c++ -*- +#ifndef RR_RETURN_VALUE_H +#define RR_RETURN_VALUE_H + +namespace rr { + + class ReturnValue { + public: + + template + class Base : public Wrapper> { + public: + + Base(v8::ReturnValue value) : Wrapper>(value) {} + Base(VALUE self) : Wrapper>(self) {} + + static VALUE Set(VALUE self, VALUE handle) { + Base ret(self); + Locker lock(ret->GetIsolate()); + v8::Local value((RRType(handle))); + ret->Set(value); + return Qnil; + } + + static VALUE Set_bool(VALUE self, VALUE value) { + Base ret(self); + Locker lock(ret->GetIsolate()); + ret->Set((bool)Bool(value)); + return Qnil; + } + + static VALUE Set_double(VALUE self, VALUE value) { + Base ret(self); + Locker lock(ret->GetIsolate()); + ret->Set(NUM2DBL(value)); + return Qnil; + } + + static VALUE Set_int32_t(VALUE self, VALUE i) { + Base ret(self); + Locker lock(ret->GetIsolate()); + ret->Set(NUM2INT(i)); + return Qnil; + } + + static VALUE Set_uint32_t(VALUE self, VALUE i) { + Base ret(self); + Locker lock(ret->GetIsolate()); + ret->Set(NUM2UINT(i)); + return Qnil; + } + + static VALUE SetNull(VALUE self) { + Base ret(self); + Locker lock(ret->GetIsolate()); + ret->SetNull(); + return Qnil; + } + + static VALUE SetUndefined(VALUE self) { + Base ret(self); + Locker lock(ret->GetIsolate()); + ret->SetUndefined(); + return Qnil; + } + + static VALUE SetEmptyString(VALUE self) { + Base ret(self); + Locker lock(ret->GetIsolate()); + ret->SetEmptyString(); + return Qnil; + } + + static VALUE GetIsolate(VALUE self) { + Base ret(self); + return Isolate(ret->GetIsolate()); + } + }; + + class Value : public Base { + public: + Value(v8::ReturnValue value) : Base(value) {} + Value(VALUE self) : Base(self) {} + + static inline void Init() { + ClassBuilder("Value", ReturnValue::Class, ReturnValue::Class). + defineMethod("Set", &Set). + defineMethod("Set_bool", &Set_bool). + defineMethod("Set_double", &Set_double). + defineMethod("Set_int32_t", &Set_int32_t). + defineMethod("Set_uint32_t", &Set_uint32_t). + defineMethod("SetNull", &SetNull). + defineMethod("SetUndefined", &SetUndefined). + defineMethod("SetEmptyString", &SetEmptyString). + defineMethod("GetIsolate", &GetIsolate). + store(&Class); + } + }; + + class Void : public Base { + public: + + Void(v8::ReturnValue value) : Base(value) {} + Void(VALUE self) : Base(self) {} + + static inline void Init() { + ClassBuilder("Void", ReturnValue::Class, ReturnValue::Class). + defineMethod("GetIsolate", &GetIsolate). + store(&Class); + } + }; + + static VALUE Class; + + static void Init() { + ClassBuilder("ReturnValue"). + store(&Class); + + Value::Init(); + Void::Init(); + } + }; +} +#endif /* RR_RETURN_VALUE_H */ diff --git a/ext/v8/rr.h b/ext/v8/rr.h index 6c76bc09..37a5e4d0 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -1,934 +1,78 @@ #ifndef THE_RUBY_RACER #define THE_RUBY_RACER -#include +#include +#include + #include #include #ifdef HAVE_RUBY_ENCODING_H #include "ruby/encoding.h" #endif -#if !defined(RARRAY_LENINT) -# define RARRAY_LENINT(v) (int)RARRAY_LEN(v) -#endif /* ! defined(RARRAY_LENINT) */ - -#if !defined(SIZET2NUM) -# if SIZEOF_SIZE_T == SIZEOF_LONG -# define SIZET2NUM(n) ULONG2NUM(n) -# else -# define SIZET2NUM(n) ULL2NUM(n) -# endif -#endif /* ! defined(SIZET2NUM) */ - -#if !defined(NUM2SIZET) -# if SIZEOF_SIZE_T == SIZEOF_LONG -# define NUM2SIZET(n) ((size_t)NUM2ULONG(n)) -# else -# define NUM2SIZET(n) ((size_t)NUM2ULL(n)) -# endif -#endif /* ! defined(NUM2SIZET) */ - -namespace rr { - -#define Void(expr) expr; return Qnil; -VALUE not_implemented(const char* message); - -class Equiv { -public: - Equiv(VALUE val) : value(val) {} - inline operator VALUE() {return value;} -protected: - VALUE value; -}; - -class Bool : public Equiv { -public: - Bool(VALUE val) : Equiv(val) {} - Bool(bool b) : Equiv(b ? Qtrue : Qfalse) {} - Bool(v8::Handle b) : Equiv(b->Value() ? Qtrue : Qfalse) {} - inline operator bool() {return RTEST(value);} -}; - -class UInt32 : public Equiv { -public: - UInt32(VALUE val) : Equiv(val) {} - UInt32(uint32_t ui) : Equiv(UINT2NUM(ui)) {} - inline operator uint32_t() {return RTEST(value) ? NUM2UINT(value) : 0;} -}; - -class GC { -public: - class Queue { - public: - Queue(); - void Enqueue(void* phantom); - void* Dequeue(); - private: - struct Node { - Node(void* val ) : value(val), next(NULL) { } - void* value; - Node* next; - }; - Node* first; // for producer only - Node* divider; - Node* last; - }; - static void Finalize(void* phantom); - static void Drain(v8::GCType type, v8::GCCallbackFlags flags); - static void Init(); -}; - -/** -* A V8 Enum -*/ - -template class Enum : public Equiv { -public: - Enum(VALUE value, T defaultValue = 0) : Equiv(value) { - this->defaultValue = defaultValue; - } - inline operator T() { - return (T)(RTEST(value) ? NUM2INT(value) : defaultValue); - } -private: - T defaultValue; -}; - -/** -* A pointer to V8 object managed by Ruby -* -* You deal with V8 objects as either pointers or handles. -* While handles are managed by the V8 garbage collector, pointers -* must be explicitly created and destroyed by your code. -* -* The pointer class provides a handly way to wrap V8 pointers -* into Ruby objects so that they will be deleted when the -* Ruby object is garbage collected. Automatic type coercion is -* used to make wrapping and unwrapping painless. -* -* To create Ruby VALUE: -* -* Pointer ptr(new v8::ScriptOrigin()); -* VALUE value = ptr; //automatically wraps in Data_Wrap_Struct -* -* Conversely, the pointer can be unwrapped from a struct -* created in this way and the underlying methods can be -* invoked: -* -* VALUE value = ...; -* Pointer ptr(value); -* ptr->CallMethod(); -*/ - -template class Pointer { -public: - inline Pointer(T* t) : pointer(t) {}; - inline Pointer(VALUE v) { - if (RTEST(v)) { - this->unwrap(v); - } else { - this->pointer = NULL; - } - }; - inline operator T*() {return pointer;} - inline T* operator ->() {return pointer;} - inline operator VALUE() { - return Data_Wrap_Struct(Class, 0, &release, pointer); - } - void unwrap(VALUE value); - static void release(T* pointer) { - delete pointer; - } - static VALUE Class; -protected: - T* pointer; -}; -template VALUE Pointer::Class; - -/** -* A Reference to a V8 managed object -* -* Uses type coercion to quickly convert from a v8 handle -* to a ruby object and back again. Suppose we have a v8 handle -* that we want to return to Ruby. We can put it into a Ref: -* -* v8::Handle object = v8::Object::New(); -* VALUE val = Ref(object); -* -* this will create a `v8::Persistent` handle for the object -* so that it will not be garbage collected by v8. It then -* stuffs this new persistent handle into a Data_Wrap_Struct -* which can then be passed to Ruby code. When this struct -* is garbage collected by Ruby, it enqueues the corresponding -* v8 handle to be released during v8 gc. -* -* By the same token, you can use Refs to unwrap a Data_Wrap_Struct -* which has been generated in this fashion and call through to -* the underlying v8 methods. Suppose we are passed a VALUE `val` -* wrapping a v8::Object: -* -* Ref object(val); -* object->Get(v8::String::New("foo")); -* -*/ -template class Ref { -public: - Ref(VALUE value) { - this->value = value; - } - Ref(v8::Handle handle, const char* label = "v8::Handle") { - this->handle = handle; - } - virtual ~Ref() {} - /* - * Coerce a Ref into a Ruby VALUE - */ - virtual operator VALUE() const { - return handle.IsEmpty() ? Qnil : Data_Wrap_Struct(Class, 0, &Holder::enqueue, new Holder(handle)); - } - /* - * Coerce a Ref into a v8::Handle. - */ - virtual operator v8::Handle() const { - if (RTEST(this->value)) { - Holder* holder = NULL; - Data_Get_Struct(this->value, class Holder, holder); - return holder->handle; - } else { - return v8::Handle(); - } - } - void dispose() { - Holder* holder = NULL; - Data_Get_Struct(this->value, class Holder, holder); - holder->dispose(); - } - - /* - * Pointer de-reference operators, this lets you use a ref to - * call through to underlying v8 methods. e.g - * - * Ref(value)->ToString(); - */ - inline v8::Handle operator->() const { return *this;} - inline v8::Handle operator*() const {return *this;} - - template class array { - public: - inline array(VALUE ary) : argv(ary), vector(RARRAY_LENINT(argv)) {} - inline operator v8::Handle*() { - for (uint32_t i = 0; i < vector.size(); i++) { - vector[i] = C(rb_ary_entry(argv, i)); - } - return &vector[0]; - } - private: - VALUE argv; - std::vector< v8::Handle > vector; - }; - - class Holder { - friend class Ref; - public: - Holder(v8::Handle handle) { - this->disposed_p = false; - this->handle = v8::Persistent::New(handle); - } - virtual ~Holder() { - this->dispose(); - } - void dispose() { - if (!this->disposed_p) { - handle.Dispose(); - this->disposed_p = true; - } - } - protected: - v8::Persistent handle; - bool disposed_p; - - static void enqueue(Holder* holder) { - GC::Finalize(holder); - } - }; - - VALUE value; - v8::Handle handle; - static VALUE Class; -}; -template VALUE Ref::Class; - -class Backref { -public: - static void Init(); - Backref(VALUE value); - virtual ~Backref(); - VALUE get(); - VALUE set(VALUE value); - v8::Handle toExternal(); - static void release(v8::Persistent handle, void* data); -private: - VALUE storage; - static VALUE Storage; - static ID _new; - static ID object; -}; -class Handles { -public: - static void Init(); - static VALUE HandleScope(int argc, VALUE* argv, VALUE self); -private: - static VALUE SetupAndCall(int* state, VALUE code); - static VALUE DoCall(VALUE code); -}; - -class Phantom { -public: - inline Phantom(void* reference) : holder((Ref::Holder*)reference) {} - inline bool NotNull() { - return this->holder != NULL; - } - inline void destroy() { - delete holder; - } - Ref::Holder* holder; -}; - -class ExtensionConfiguration : public Pointer { -public: - static VALUE initialize(VALUE self, VALUE names); - inline ExtensionConfiguration(v8::ExtensionConfiguration* config) : Pointer(config) {} - inline ExtensionConfiguration(VALUE value) : Pointer(value) {} -}; - -class Context : public Ref { -public: - static void Init(); - static VALUE New(int argc, VALUE argv[], VALUE self); - static VALUE Dispose(VALUE self); - static VALUE Enter(VALUE self); - static VALUE Exit(VALUE self); - static VALUE Global(VALUE self); - static VALUE DetachGlobal(VALUE self); - static VALUE ReattachGlobal(VALUE self, VALUE global); - static VALUE GetEntered(VALUE self); - static VALUE GetCurrent(VALUE self); - static VALUE GetCalling(VALUE self); - static VALUE SetSecurityToken(VALUE self, VALUE token); - static VALUE UseDefaultSecurityToken(VALUE self); - static VALUE GetSecurityToken(VALUE self); - static VALUE HasOutOfMemoryException(VALUE self); - static VALUE InContext(VALUE self); - static VALUE SetEmbedderData(VALUE self, VALUE index, VALUE data); - static VALUE GetEmbedderData(VALUE self, VALUE index); - static VALUE AllowCodeGenerationFromStrings(VALUE self, VALUE allow); - static VALUE IsCodeGenerationFromStringsAllowed(VALUE self); - - inline Context(VALUE value) : Ref(value) {} - inline Context(v8::Handle cxt) : Ref(cxt) {} -}; - -class External: public Ref { -public: - static void Init(); - static VALUE New(VALUE self, VALUE data); - static VALUE Value(VALUE self); - - inline External(VALUE value) : Ref(value) {} - inline External(v8::Handle ext) : Ref(ext) {} - static v8::Handle wrap(VALUE data); - static VALUE unwrap(v8::Handle external); -private: - static void release(v8::Persistent object, void* parameter); - struct Data { - Data(VALUE data); - ~Data(); - VALUE value; - }; -}; - -class ScriptOrigin : public Pointer { -public: - inline ScriptOrigin(v8::ScriptOrigin* o) : Pointer(o) {}; - inline ScriptOrigin(VALUE value) : Pointer(value) {} - - static VALUE initialize(int argc, VALUE argv[], VALUE self); -}; - -class ScriptData : public Pointer { -public: - inline ScriptData(v8::ScriptData* d) : Pointer(d) {}; - inline ScriptData(VALUE value) : Pointer(value) {} - - static VALUE PreCompile(VALUE self, VALUE input, VALUE length); - static VALUE New(VALUE self, VALUE data, VALUE length); - static VALUE Length(VALUE self); - static VALUE Data(VALUE self); - static VALUE HasError(VALUE self); -}; - -class Script : public Ref { -public: - static void Init(); - static VALUE New(int argc, VALUE argv[], VALUE self); - static VALUE Run(VALUE self); - static VALUE RunWithTimeout(VALUE self, VALUE timeout); - - inline Script(VALUE value) : Ref(value) {} - inline Script(v8::Handle script) : Ref(script) {} -}; - -class Value : public Ref { -public: - static void Init(); - static VALUE IsUndefined(VALUE self); - static VALUE IsNull(VALUE self); - static VALUE IsTrue(VALUE self); - static VALUE IsFalse(VALUE self); - static VALUE IsString(VALUE self); - static VALUE IsFunction(VALUE self); - static VALUE IsArray(VALUE self); - static VALUE IsObject(VALUE self); - static VALUE IsBoolean(VALUE self); - static VALUE IsNumber(VALUE self); - static VALUE IsExternal(VALUE self); - static VALUE IsInt32(VALUE self); - static VALUE IsUint32(VALUE self); - static VALUE IsDate(VALUE self); - static VALUE IsBooleanObject(VALUE self); - static VALUE IsNumberObject(VALUE self); - static VALUE IsStringObject(VALUE self); - static VALUE IsNativeError(VALUE self); - static VALUE IsRegExp(VALUE self); - // VALUE ToBoolean(VALUE self); - // VALUE ToNumber(VALUE self); - static VALUE ToString(VALUE self); - static VALUE ToDetailString(VALUE self); - static VALUE ToObject(VALUE self); - // static VALUE ToInteger(VALUE self); - // static VALUE ToUint32(VALUE self); - // static VALUE ToInt32(VALUE self); - // static VALUE ToArrayIndex(VALUE self); - static VALUE BooleanValue(VALUE self); - static VALUE NumberValue(VALUE self); - static VALUE IntegerValue(VALUE self); - static VALUE Uint32Value(VALUE self); - static VALUE Int32Value(VALUE self); - - static VALUE Equals(VALUE self, VALUE other); - static VALUE StrictEquals(VALUE self, VALUE other); - inline Value(VALUE value) : Ref(value) {} - inline Value(v8::Handle value) : Ref(value) {} - virtual operator VALUE(); - virtual operator v8::Handle() const; - static VALUE Empty; -}; - -class Primitive: public Ref { -public: - static void Init(); - inline Primitive(VALUE value) : Ref(value) {} - inline Primitive(v8::Handle primitive) : Ref(primitive) {} -}; - -class String: public Ref { -public: - static void Init(); - static VALUE New(VALUE self, VALUE value); - static VALUE NewSymbol(VALUE self, VALUE string); - static VALUE Utf8Value(VALUE self); - static VALUE Concat(VALUE self, VALUE left, VALUE right); - - inline String(VALUE value) : Ref(value) {} - inline String(v8::Handle string) : Ref(string) {} - virtual operator v8::Handle() const; -}; - -class PropertyAttribute: public Enum { -public: - inline PropertyAttribute(VALUE value) : Enum(value, v8::None) {} -}; -class AccessControl: public Enum { -public: - inline AccessControl(VALUE value) : Enum(value, v8::DEFAULT) {} -}; - -class Accessor { -public: - static void Init(); - Accessor(VALUE get, VALUE set, VALUE data); - Accessor(VALUE get, VALUE set, VALUE query, VALUE deleter, VALUE enumerator, VALUE data); - Accessor(v8::Handle value); - - inline v8::AccessorGetter accessorGetter() {return &AccessorGetter;} - inline v8::AccessorSetter accessorSetter() {return RTEST(set) ? &AccessorSetter : 0;} - - inline v8::NamedPropertyGetter namedPropertyGetter() {return &NamedPropertyGetter;} - inline v8::NamedPropertySetter namedPropertySetter() {return RTEST(set) ? &NamedPropertySetter : 0;} - inline v8::NamedPropertyQuery namedPropertyQuery() {return RTEST(query) ? &NamedPropertyQuery : 0;} - inline v8::NamedPropertyDeleter namedPropertyDeleter() {return RTEST(deleter) ? &NamedPropertyDeleter : 0;} - inline v8::NamedPropertyEnumerator namedPropertyEnumerator() {return RTEST(enumerator) ? &NamedPropertyEnumerator : 0;} - - inline v8::IndexedPropertyGetter indexedPropertyGetter() {return &IndexedPropertyGetter;} - inline v8::IndexedPropertySetter indexedPropertySetter() {return RTEST(set) ? &IndexedPropertySetter : 0;} - inline v8::IndexedPropertyQuery indexedPropertyQuery() {return RTEST(query) ? &IndexedPropertyQuery : 0;} - inline v8::IndexedPropertyDeleter indexedPropertyDeleter() {return RTEST(deleter) ? &IndexedPropertyDeleter : 0;} - inline v8::IndexedPropertyEnumerator indexedPropertyEnumerator() {return RTEST(enumerator) ? &IndexedPropertyEnumerator : 0;} - - operator v8::Handle(); - - class Info { - public: - Info(const v8::AccessorInfo& info); - Info(VALUE value); - static VALUE This(VALUE self); - static VALUE Holder(VALUE self); - static VALUE Data(VALUE self); - operator VALUE(); - inline const v8::AccessorInfo* operator->() {return this->info;} - v8::Handle get(v8::Local property); - v8::Handle set(v8::Local property, v8::Local value); - v8::Handle query(v8::Local property); - v8::Handle remove(v8::Local property); - v8::Handle enumerateNames(); - v8::Handle get(uint32_t index); - v8::Handle set(uint32_t index, v8::Local value); - v8::Handle query(uint32_t index); - v8::Handle remove(uint32_t index); - v8::Handle enumerateIndices(); - - static VALUE Class; - private: - const v8::AccessorInfo* info; - }; - friend class Info; -private: - static v8::Handle AccessorGetter(v8::Local property, const v8::AccessorInfo& info); - static void AccessorSetter(v8::Local property, v8::Local value, const v8::AccessorInfo& info); - - static v8::Handle NamedPropertyGetter(v8::Local property, const v8::AccessorInfo& info); - static v8::Handle NamedPropertySetter(v8::Local property, v8::Local value, const v8::AccessorInfo& info); - static v8::Handle NamedPropertyQuery(v8::Local property, const v8::AccessorInfo& info); - static v8::Handle NamedPropertyDeleter(v8::Local property, const v8::AccessorInfo& info); - static v8::Handle NamedPropertyEnumerator(const v8::AccessorInfo& info); - - static v8::Handle IndexedPropertyGetter(uint32_t index, const v8::AccessorInfo& info); - static v8::Handle IndexedPropertySetter(uint32_t index, v8::Local value, const v8::AccessorInfo& info); - static v8::Handle IndexedPropertyQuery(uint32_t index, const v8::AccessorInfo& info); - static v8::Handle IndexedPropertyDeleter(uint32_t index, const v8::AccessorInfo& info); - static v8::Handle IndexedPropertyEnumerator(const v8::AccessorInfo& info); - - void wrap(v8::Handle wrapper, int index, VALUE value); - VALUE unwrap(v8::Handle wrapper, int index); - VALUE get; - VALUE set; - VALUE query; - VALUE deleter; - VALUE enumerator; - VALUE data; -}; - -class Invocation { -public: - static void Init(); - Invocation(VALUE code, VALUE data); - Invocation(v8::Handle wrapper); - operator v8::InvocationCallback(); - operator v8::Handle(); - static v8::Handle Callback(const v8::Arguments& args); - - class Arguments { - public: - static void Init(); - Arguments(const v8::Arguments& args); - Arguments(VALUE value); - inline const v8::Arguments* operator->() {return this->args;} - inline const v8::Arguments operator*() {return *this->args;} - v8::Handle Call(); - - static VALUE Length(VALUE self); - static VALUE Get(VALUE self, VALUE index); - static VALUE Callee(VALUE self); - static VALUE This(VALUE self); - static VALUE Holder(VALUE self); - static VALUE IsConstructCall(VALUE self); - static VALUE Data(VALUE self); - private: - const v8::Arguments* args; - static VALUE Class; - }; -private: - VALUE code; - VALUE data; - friend class Arguments; -}; - -class Object : public Ref { -public: - static void Init(); - static VALUE New(VALUE self); - static VALUE Set(VALUE self, VALUE key, VALUE value); - static VALUE ForceSet(VALUE self, VALUE key, VALUE value); - static VALUE Get(VALUE self, VALUE key); - static VALUE GetPropertyAttributes(VALUE self, VALUE key); - static VALUE Has(VALUE self, VALUE key); - static VALUE Delete(VALUE self, VALUE key); - static VALUE ForceDelete(VALUE self, VALUE key); - static VALUE SetAccessor(int argc, VALUE* argv, VALUE self); - static VALUE GetPropertyNames(VALUE self); - static VALUE GetOwnPropertyNames(VALUE self); - static VALUE GetPrototype(VALUE self); - static VALUE SetPrototype(VALUE self, VALUE prototype); - static VALUE FindInstanceInPrototypeChain(VALUE self, VALUE impl); - static VALUE ObjectProtoToString(VALUE self); - static VALUE GetConstructorName(VALUE self); - static VALUE InternalFieldCount(VALUE self); - static VALUE GetInternalField(VALUE self, VALUE idx); - static VALUE SetInternalField(VALUE self, VALUE idx, VALUE value); - static VALUE HasOwnProperty(VALUE self, VALUE key); - static VALUE HasRealNamedProperty(VALUE self, VALUE key); - static VALUE HasRealIndexedProperty(VALUE self, VALUE idx); - static VALUE HasRealNamedCallbackProperty(VALUE self, VALUE key); - static VALUE GetRealNamedPropertyInPrototypeChain(VALUE self, VALUE key); - static VALUE GetRealNamedProperty(VALUE self, VALUE key); - static VALUE HasNamedLookupInterceptor(VALUE self); - static VALUE HasIndexedLookupInterceptor(VALUE self); - static VALUE TurnOnAccessCheck(VALUE self); - static VALUE GetIdentityHash(VALUE self); - static VALUE SetHiddenValue(VALUE self, VALUE key, VALUE value); - static VALUE GetHiddenValue(VALUE self, VALUE key); - static VALUE DeleteHiddenValue(VALUE self, VALUE key); - static VALUE IsDirty(VALUE self); - static VALUE Clone(VALUE self); - static VALUE CreationContext(VALUE self); - static VALUE SetIndexedPropertiesToPixelData(VALUE self, VALUE data, VALUE length); - static VALUE GetIndexedPropertiesPixelData(VALUE self); - static VALUE HasIndexedPropertiesInPixelData(VALUE self); - static VALUE GetIndexedPropertiesPixelDataLength(VALUE self); - static VALUE SetIndexedPropertiesToExternalArrayData(VALUE self); - static VALUE HasIndexedPropertiesInExternalArrayData(VALUE self); - static VALUE GetIndexedPropertiesExternalArrayData(VALUE self); - static VALUE GetIndexedPropertiesExternalArrayDataType(VALUE self); - static VALUE GetIndexedPropertiesExternalArrayDataLength(VALUE self); - static VALUE IsCallable(VALUE self); - static VALUE CallAsFunction(VALUE self, VALUE recv, VALUE argv); - static VALUE CallAsConstructor(VALUE self, VALUE argv); - - inline Object(VALUE value) : Ref(value) {} - inline Object(v8::Handle object) : Ref(object) {} - virtual operator VALUE(); - -protected: - VALUE downcast(); -}; - -class Array : public Ref { -public: - static void Init(); - static VALUE New(int argc, VALUE argv[], VALUE self); - static VALUE Length(VALUE self); - static VALUE CloneElementAt(VALUE self, VALUE index); - - inline Array(v8::Handle array) : Ref(array) {} - inline Array(VALUE value) : Ref(value) {} -}; - -class Function : public Ref { -public: - static void Init(); - static VALUE NewInstance(int argc, VALUE argv[], VALUE self); - static VALUE Call(VALUE self, VALUE receiver, VALUE argv); - static VALUE SetName(VALUE self, VALUE name); - static VALUE GetName(VALUE self); - static VALUE GetInferredName(VALUE self); - static VALUE GetScriptLineNumber(VALUE self); - static VALUE GetScriptColumnNumber(VALUE self); - static VALUE GetScriptId(VALUE self); - static VALUE GetScriptOrigin(VALUE self); - - inline Function(VALUE value) : Ref(value) {} - inline Function(v8::Handle function) : Ref(function) {} -}; - -class Date : public Ref { -public: - static void Init(); - static VALUE New(VALUE self, VALUE time); - static VALUE NumberValue(VALUE self); - - inline Date(VALUE value) : Ref(value) {} - inline Date(v8::Handle date) : Ref(date) {} -}; - -class Signature : public Ref { -public: - static void Init(); - static VALUE New(int argc, VALUE argv[], VALUE self); - - inline Signature(v8::Handle sig) : Ref(sig) {} - inline Signature(VALUE value) : Ref(value) {} -}; - -class Template : public Ref { -public: - static void Init(); - static VALUE Set(int argc, VALUE argv[], VALUE self); - inline Template(v8::Handle t) : Ref(t) {} - inline Template(VALUE value) : Ref(value) {} -}; - -class ObjectTemplate : public Ref { -public: - static void Init(); - static VALUE New(VALUE self); - static VALUE NewInstance(VALUE self); - static VALUE SetAccessor(int argc, VALUE argv[], VALUE self); - static VALUE SetNamedPropertyHandler(int argc, VALUE argv[], VALUE self); - static VALUE SetIndexedPropertyHandler(int argc, VALUE argv[], VALUE self); - static VALUE SetCallAsFunctionHandler(int argc, VALUE argv[], VALUE self); - static VALUE MarkAsUndetectable(VALUE self); - static VALUE SetAccessCheckCallbacks(int argc, VALUE argv[], VALUE self); - static VALUE InternalFieldCount(VALUE self); - static VALUE SetInternalFieldCount(VALUE self, VALUE count); - - inline ObjectTemplate(VALUE value) : Ref(value) {} - inline ObjectTemplate(v8::Handle t) : Ref(t) {} -}; - -class FunctionTemplate : public Ref { -public: - static void Init(); - static VALUE New(int argc, VALUE argv[], VALUE self); - static VALUE GetFunction(VALUE self); - static VALUE SetCallHandler(int argc, VALUE argv[], VALUE self); - static VALUE InstanceTemplate(VALUE self); - static VALUE Inherit(VALUE self, VALUE parent); - static VALUE PrototypeTemplate(VALUE self); - static VALUE SetClassName(VALUE self, VALUE name); - static VALUE SetHiddenPrototype(VALUE self, VALUE value); - static VALUE ReadOnlyPrototype(VALUE self); - static VALUE HasInstance(VALUE self, VALUE object); - - inline FunctionTemplate(VALUE value) : Ref(value) {} - inline FunctionTemplate(v8::Handle t) : Ref(t) {} -}; - -class Message : public Ref { -public: - static void Init(); - inline Message(v8::Handle message) : Ref(message) {} - inline Message(VALUE value) : Ref(value) {} - - static VALUE Get(VALUE self); - static VALUE GetSourceLine(VALUE self); - static VALUE GetScriptResourceName(VALUE self); - static VALUE GetScriptData(VALUE self); - static VALUE GetStackTrace(VALUE self); - static VALUE GetLineNumber(VALUE self); - static VALUE GetStartPosition(VALUE self); - static VALUE GetEndPosition(VALUE self); - static VALUE GetStartColumn(VALUE self); - static VALUE GetEndColumn(VALUE self); - static inline VALUE kNoLineNumberInfo(VALUE self) {return INT2FIX(v8::Message::kNoLineNumberInfo);} - static inline VALUE kNoColumnInfo(VALUE self) {return INT2FIX(v8::Message::kNoColumnInfo);} -}; - -class Stack { -public: - static void Init(); - - class Trace : public Ref { - public: - class StackTraceOptions : public Enum { - public: - inline StackTraceOptions(VALUE value) : Enum(value, v8::StackTrace::kOverview) {} - }; - public: - inline Trace(v8::Handle trace) : Ref(trace) {} - inline Trace(VALUE value) : Ref(value) {} - static inline VALUE kLineNumber(VALUE self) {return INT2FIX(v8::StackTrace::kLineNumber);} - static inline VALUE kColumnOffset(VALUE self) {return INT2FIX(v8::StackTrace::kColumnOffset);} - static inline VALUE kScriptName(VALUE self) {return INT2FIX(v8::StackTrace::kScriptName);} - static inline VALUE kFunctionName(VALUE self) {return INT2FIX(v8::StackTrace::kFunctionName);} - static inline VALUE kIsEval(VALUE self) {return INT2FIX(v8::StackTrace::kIsEval);} - static inline VALUE kIsConstructor(VALUE self) {return INT2FIX(v8::StackTrace::kIsConstructor);} - static inline VALUE kScriptNameOrSourceURL(VALUE self) {return INT2FIX(v8::StackTrace::kScriptNameOrSourceURL);} - static inline VALUE kOverview(VALUE self) {return INT2FIX(v8::StackTrace::kOverview);} - static inline VALUE kDetailed(VALUE self) {return INT2FIX(v8::StackTrace::kDetailed);} - - static VALUE GetFrame(VALUE self, VALUE index); - static VALUE GetFrameCount(VALUE self); - static VALUE AsArray(VALUE self); - static VALUE CurrentStackTrace(int argc, VALUE argv[], VALUE self); - }; - class Frame : public Ref { - public: - inline Frame(v8::Handle frame) : Ref(frame) {} - inline Frame(VALUE value) : Ref(value) {} - static VALUE GetLineNumber(VALUE self); - static VALUE GetColumn(VALUE self); - static VALUE GetScriptName(VALUE self); - static VALUE GetScriptNameOrSourceURL(VALUE self); - static VALUE GetFunctionName(VALUE self); - static VALUE IsEval(VALUE self); - static VALUE IsConstructor(VALUE self); - }; -}; - -class TryCatch { -public: - static void Init(); - TryCatch(v8::TryCatch*); - TryCatch(VALUE value); - operator VALUE(); - inline v8::TryCatch* operator->() {return this->impl;} - static VALUE HasCaught(VALUE self); - static VALUE CanContinue(VALUE self); - static VALUE ReThrow(VALUE self); - static VALUE Exception(VALUE self); - static VALUE StackTrace(VALUE self); - static VALUE Message(VALUE self); - static VALUE Reset(VALUE self); - static VALUE SetVerbose(VALUE self, VALUE value); - static VALUE SetCaptureMessage(VALUE self, VALUE value); -private: - static VALUE doTryCatch(int argc, VALUE argv[], VALUE self); - static VALUE setupAndCall(int* state, VALUE code); - static VALUE doCall(VALUE code); - static VALUE Class; - v8::TryCatch* impl; -}; - -class Locker { -public: - static void Init(); - static VALUE StartPreemption(VALUE self, VALUE every_n_ms); - static VALUE StopPreemption(VALUE self); - static VALUE IsLocked(VALUE self); - static VALUE IsActive(VALUE self); - static VALUE doLock(int argc, VALUE* argv, VALUE self); - static VALUE setupLockAndCall(int* state, VALUE code); - static VALUE doLockCall(VALUE code); - static VALUE doUnlock(int argc, VALUE* argv, VALUE self); - static VALUE setupUnlockAndCall(int* state, VALUE code); - static VALUE doUnlockCall(VALUE code); -}; - -class HeapStatistics : public Pointer { -public: - static void Init(); - static VALUE initialize(VALUE self); - static VALUE total_heap_size(VALUE self); - static VALUE total_heap_size_executable(VALUE self); - static VALUE total_physical_size(VALUE self); - static VALUE used_heap_size(VALUE self); - static VALUE heap_size_limit(VALUE self); - - inline HeapStatistics(v8::HeapStatistics* stats) : Pointer(stats) {} - inline HeapStatistics(VALUE value) : Pointer(value) {} -}; - -class ResourceConstraints : Pointer { -public: - static void Init(); - static VALUE initialize(VALUE self); - static VALUE max_young_space_size(VALUE self); - static VALUE set_max_young_space_size(VALUE self, VALUE value); - static VALUE max_old_space_size(VALUE self); - static VALUE set_max_old_space_size(VALUE self, VALUE value); - static VALUE max_executable_size(VALUE self); - static VALUE set_max_executable_size(VALUE self, VALUE value); - - static VALUE SetResourceConstraints(VALUE self, VALUE constraints); - - inline ResourceConstraints(v8::ResourceConstraints* o) : Pointer(o) {}; - inline ResourceConstraints(VALUE value) : Pointer(value) {} -}; - -class Exception { -public: - static void Init(); - static VALUE ThrowException(VALUE self, VALUE exception); - static VALUE RangeError(VALUE self, VALUE message); - static VALUE ReferenceError(VALUE self, VALUE message); - static VALUE SyntaxError(VALUE self, VALUE message); - static VALUE TypeError(VALUE self, VALUE message); - static VALUE Error(VALUE self, VALUE message); -}; - -class Constants { -public: - static void Init(); - static VALUE Undefined(VALUE self); - static VALUE Null(VALUE self); - static VALUE True(VALUE self); - static VALUE False(VALUE self); - -private: - template static VALUE cached(VALUE* storage, v8::Handle value) { - if (!RTEST(*storage)) { - *storage = R(value); - } - return *storage; - } - static VALUE _Undefined; - static VALUE _Null; - static VALUE _True; - static VALUE _False; -}; - -class V8 { -public: - static void Init(); - static VALUE IdleNotification(int argc, VALUE argv[], VALUE self); - static VALUE SetFlagsFromString(VALUE self, VALUE string); - static VALUE SetFlagsFromCommandLine(VALUE self, VALUE args, VALUE remove_flags); - static VALUE AdjustAmountOfExternalAllocatedMemory(VALUE self, VALUE change_in_bytes); - static VALUE PauseProfiler(VALUE self); - static VALUE ResumeProfiler(VALUE self); - static VALUE IsProfilerPaused(VALUE self); - static VALUE GetCurrentThreadId(VALUE self); - static VALUE TerminateExecution(VALUE self, VALUE thread_id); - static VALUE IsExecutionTerminating(VALUE self); - static VALUE Dispose(VALUE self); - static VALUE LowMemoryNotification(VALUE self); - static VALUE ContextDisposedNotification(VALUE self); - - static VALUE SetCaptureStackTraceForUncaughtExceptions(int argc, VALUE argv[], VALUE self); - static VALUE GetHeapStatistics(VALUE self, VALUE statistics_ptr); - static VALUE GetVersion(VALUE self); -}; - -class ClassBuilder { -public: - ClassBuilder() {}; - ClassBuilder(const char* name, VALUE superclass = rb_cObject); - ClassBuilder(const char* name, const char* supername); - ClassBuilder& defineConst(const char* name, VALUE value); - ClassBuilder& defineMethod(const char* name, VALUE (*impl)(int, VALUE*, VALUE)); - ClassBuilder& defineMethod(const char* name, VALUE (*impl)(VALUE)); - ClassBuilder& defineMethod(const char* name, VALUE (*impl)(VALUE, VALUE)); - ClassBuilder& defineMethod(const char* name, VALUE (*impl)(VALUE, VALUE, VALUE)); - ClassBuilder& defineMethod(const char* name, VALUE (*impl)(VALUE, VALUE, VALUE, VALUE)); - ClassBuilder& defineSingletonMethod(const char* name, VALUE (*impl)(int, VALUE*, VALUE)); - ClassBuilder& defineSingletonMethod(const char* name, VALUE (*impl)(VALUE)); - ClassBuilder& defineSingletonMethod(const char* name, VALUE (*impl)(VALUE, VALUE)); - ClassBuilder& defineSingletonMethod(const char* name, VALUE (*impl)(VALUE, VALUE, VALUE)); - ClassBuilder& defineSingletonMethod(const char* name, VALUE (*impl)(VALUE, VALUE, VALUE, VALUE)); - ClassBuilder& defineEnumConst(const char* name, int value); - ClassBuilder& store(VALUE* storage); - inline operator VALUE() {return this->value;} -protected: - VALUE value; -}; - -class ModuleBuilder : public ClassBuilder { -public: - inline ModuleBuilder(const char* name) { - this->value = rb_eval_string(name); - } -}; +inline VALUE not_implemented(const char* message) { + rb_raise(rb_eStandardError, "not yet implemented %s", message); + return Qnil; } +#include "class_builder.h" + +#include "maybe.h" +#include "enum.h" + +#include "equiv.h" +#include "bool.h" +#include "int.h" +#include "uint32_t.h" +#include "pointer.h" +#include "wrapper.h" +#include "isolate.h" + +#include "ref.h" + +#include "v8.h" +#include "locks.h" +#include "handles.h" +#include "context.h" + +#include "value.h" +#include "boolean.h" + +#include "primitive.h" +#include "undefined.h" +#include "null.h" +#include "number.h" +#include "integer.h" + +#include "external.h" +#include "name.h" +// This one is named v8_string to avoid name collisions with C's string.h +#include "rr_string.h" +#include "symbol.h" + +#include "function.h" + +#include "object.h" +#include "date.h" +#include "return-value.h" +#include "property-callback.h" +#include "property-callback-info.h" +#include "array.h" + +#include "script.h" +#include "script-origin.h" +#include "function-callback.h" + +#include "template.h" +#include "signature.h" +#include "function-template.h" +#include "object-template.h" + +#include "stack-frame.h" +#include "stack-trace.h" +#include "message.h" +#include "exception.h" +#include "try-catch.h" + #endif diff --git a/ext/v8/rr_string.cc b/ext/v8/rr_string.cc new file mode 100644 index 00000000..48ebf948 --- /dev/null +++ b/ext/v8/rr_string.cc @@ -0,0 +1,56 @@ +#include "rr.h" + +namespace rr { + + void String::Init() { + ClassBuilder("String", Name::Class). + defineSingletonMethod("NewFromUtf8", &NewFromUtf8). + defineSingletonMethod("Concat", &Concat). + + defineMethod("Utf8Value", &Utf8Value). + + store(&Class); + } + + VALUE String::NewFromUtf8(VALUE StringClass, VALUE rb_isolate, VALUE string) { + Isolate isolate(rb_isolate); + Locker lock(isolate); + + v8::Local v8_string = v8::String::NewFromUtf8(isolate, RSTRING_PTR(string), v8::String::kNormalString, (int)RSTRING_LEN(string)); + + return String(isolate, v8_string); + } + + VALUE String::Utf8Value(VALUE self) { + String string(self); + Locker lock(string.getIsolate()); + + #ifdef HAVE_RUBY_ENCODING_H + return rb_enc_str_new(*v8::String::Utf8Value(*string), string->Utf8Length(), rb_enc_find("utf-8")); + #else + return rb_str_new(*v8::String::Utf8Value(*string), string->Utf8Length()); + #endif + } + + VALUE String::Concat(VALUE self, VALUE left, VALUE right) { + String left_string(left); + Locker lock(left_string.getIsolate()); + + return String(left_string.getIsolate(), v8::String::Concat(left_string, String(right))); + } + + String::operator v8::Handle() const { + Locker lock(getIsolate()); + + switch (TYPE(value)) { + case T_STRING: + return v8::String::NewFromUtf8(getIsolate(), RSTRING_PTR(value), v8::String::kNormalString, (int)RSTRING_LEN(value)); + case T_DATA: + return Ref::operator v8::Handle(); + default: + VALUE string = rb_funcall(value, rb_intern("to_s"), 0); + return v8::String::NewFromUtf8(getIsolate(), RSTRING_PTR(string), v8::String::kNormalString, (int)RSTRING_LEN(string)); + } + } + +} diff --git a/ext/v8/rr_string.h b/ext/v8/rr_string.h new file mode 100644 index 00000000..cc844467 --- /dev/null +++ b/ext/v8/rr_string.h @@ -0,0 +1,24 @@ +#ifndef RR_STRING +#define RR_STRING + +namespace rr { + + class String: public Ref { + public: + static void Init(); + + static VALUE NewFromUtf8(VALUE self, VALUE isolate, VALUE value); + static VALUE Utf8Value(VALUE self); + static VALUE Concat(VALUE self, VALUE left, VALUE right); + + inline String(VALUE value) : Ref(value) {} + inline String(v8::Isolate* isolate, v8::Handle string) : Ref(isolate, string) {} + + virtual operator v8::Handle() const; + + typedef MaybeLocal Maybe; + }; + +} + +#endif diff --git a/ext/v8/script-origin.cc b/ext/v8/script-origin.cc new file mode 100644 index 00000000..c050c738 --- /dev/null +++ b/ext/v8/script-origin.cc @@ -0,0 +1,51 @@ +#include "rr.h" + +namespace rr { + VALUE ScriptOrigin::Class; + void ScriptOrigin::Init() { + ClassBuilder("ScriptOrigin"). + defineSingletonMethod("new", &initialize). + + defineMethod("ResourceName", &ResourceName). + defineMethod("ResourceLineOffset", &ResourceLineOffset). + defineMethod("ResourceColumnOffset", &ResourceColumnOffset). + defineMethod("ScriptID", &ScriptID). + defineMethod("SourceMapUrl", &SourceMapUrl). + + store(&Class); + } + + VALUE ScriptOrigin::initialize(int argc, VALUE argv[], VALUE self) { + Container* container = new Container(); + rb_scan_args(argc, argv, "17", + &container->name, + &container->line_offset, + &container->column_offset, + &container->is_shared_cross_origin, + &container->script_id, + &container->is_embedder_debug_script, + &container->source_map_url, + &container->is_opaque); + return ScriptOrigin(container); + } + + VALUE ScriptOrigin::ResourceName(VALUE self) { + return ScriptOrigin(self).container->name; + } + + VALUE ScriptOrigin::ResourceLineOffset(VALUE self) { + return ScriptOrigin(self).container->line_offset; + } + + VALUE ScriptOrigin::ResourceColumnOffset(VALUE self) { + return ScriptOrigin(self).container->column_offset; + } + + VALUE ScriptOrigin::ScriptID(VALUE self) { + return ScriptOrigin(self).container->script_id; + } + + VALUE ScriptOrigin::SourceMapUrl(VALUE self) { + return ScriptOrigin(self).container->source_map_url; + } +} diff --git a/ext/v8/script-origin.h b/ext/v8/script-origin.h new file mode 100644 index 00000000..5f2ac003 --- /dev/null +++ b/ext/v8/script-origin.h @@ -0,0 +1,103 @@ +// -*- mode: c++ -*- +#ifndef SCRIPT_ORIGIN_H +#define SCRIPT_ORIGIN_H + +namespace rr { + class ScriptOrigin { + struct Container { + inline Container() {} + + Container(VALUE name_, + VALUE line_offset_, + VALUE column_offset_, + VALUE is_shared_cross_origin_, + VALUE script_id_, + VALUE is_embedder_debug_script_, + VALUE source_map_url_, + VALUE is_opaque_) : + name(name_), + line_offset(line_offset_), + column_offset(column_offset_), + is_shared_cross_origin(is_shared_cross_origin_), + script_id(script_id_), + is_embedder_debug_script(is_embedder_debug_script_), + source_map_url(source_map_url_), + is_opaque(is_opaque_) {} + + + VALUE name; + VALUE line_offset; + VALUE column_offset; + VALUE is_shared_cross_origin; //option + VALUE script_id; + VALUE is_embedder_debug_script; //option + VALUE source_map_url; + VALUE is_opaque; //option + + }; + + public: + static void Init(); + + ScriptOrigin(VALUE value) { + Data_Get_Struct(value, struct Container, container); + } + ScriptOrigin(Container* container_) : + container(container_) { + } + ScriptOrigin(v8::Isolate* isolate, v8::ScriptOrigin origin) : + ScriptOrigin(new Container( + Value(isolate, origin.ResourceName()), + Integer(isolate, origin.ResourceLineOffset()), + Integer(isolate, origin.ResourceColumnOffset()), + Bool(origin.Options().IsSharedCrossOrigin()), + Integer(isolate, origin.ScriptID()), + Bool(origin.Options().IsEmbedderDebugScript()), + Value(isolate, origin.SourceMapUrl()), + Bool(origin.Options().IsOpaque()))) { + } + + inline operator v8::ScriptOrigin() { + return v8::ScriptOrigin( + Value(container->name), + Integer(container->line_offset), + Integer(container->column_offset), + Boolean(container->is_shared_cross_origin), + Integer(container->script_id), + Boolean(container->is_embedder_debug_script), + Value(container->source_map_url), + Boolean(container->is_opaque)); + } + + static void mark(Container* container) { + rb_gc_mark(container->name); + rb_gc_mark(container->line_offset); + rb_gc_mark(container->column_offset); + rb_gc_mark(container->script_id); + rb_gc_mark(container->source_map_url); + rb_gc_mark(container->is_shared_cross_origin); + rb_gc_mark(container->is_embedder_debug_script); + rb_gc_mark(container->is_opaque); + } + + static VALUE initialize(int argc, VALUE argv[], VALUE self); + + static void deallocate(Container* container) { + delete container; + } + inline operator VALUE() { + return Data_Wrap_Struct(Class, &mark, &deallocate, container); + } + static VALUE ResourceName(VALUE self); + static VALUE ResourceLineOffset(VALUE self); + static VALUE ResourceColumnOffset(VALUE self); + static VALUE ScriptID(VALUE self); + static VALUE SourceMapUrl(VALUE self); + + static VALUE Class; + private: + Container* container; + }; +} + +#endif /* SCRIPT_ORIGIN_H */ diff --git a/ext/v8/script.cc b/ext/v8/script.cc index f93823c5..fbfd5c4d 100644 --- a/ext/v8/script.cc +++ b/ext/v8/script.cc @@ -1,115 +1,50 @@ #include "rr.h" -#include "pthread.h" -#include "unistd.h" namespace rr { - -void Script::Init() { - ClassBuilder("Script"). - defineSingletonMethod("New", &New). - defineMethod("Run", &Run). - defineMethod("RunWithTimeout", &RunWithTimeout). - store(&Class); - ClassBuilder("ScriptOrigin"). - defineSingletonMethod("new", &ScriptOrigin::initialize). - store(&ScriptOrigin::Class); - ClassBuilder("ScriptData"). - defineSingletonMethod("PreCompile", &ScriptData::PreCompile). - defineSingletonMethod("New", &ScriptData::New). - defineMethod("Length", &ScriptData::Length). - defineMethod("Data", &ScriptData::Data). - defineMethod("HasError", &ScriptData::HasError). - store(&ScriptData::Class); -} - -VALUE ScriptOrigin::initialize(int argc, VALUE argv[], VALUE self) { - VALUE name; VALUE line_offset; VALUE column_offset; - rb_scan_args(argc, argv, "12", &name, &line_offset, &column_offset); - v8::Handle loff = v8::Integer::New(RTEST(line_offset) ? NUM2INT(line_offset) : 0); - v8::Handle coff = v8::Integer::New(RTEST(column_offset) ? NUM2INT(column_offset) : 0); - return ScriptOrigin(new v8::ScriptOrigin(*String(name), loff, coff)); -} - -VALUE ScriptData::PreCompile(VALUE self, VALUE input, VALUE length) { -#ifdef HAVE_RUBY_ENCODING_H - if (!rb_equal(rb_enc_from_encoding(rb_utf8_encoding()), rb_obj_encoding(input))) { - rb_warn("ScriptData::Precompile only accepts UTF-8 encoded source, not: %s", RSTRING_PTR(rb_inspect(rb_obj_encoding(input)))); + void Script::Init() { + ClassBuilder("Script"). + defineSingletonMethod("Compile", &Compile). + + defineMethod("Run", &Run). + // TODO + // defineMethod("RunWithTimeout", &RunWithTimeout). + + store(&Class); + + // TODO + // ClassBuilder("ScriptData"). + // defineSingletonMethod("PreCompile", &ScriptData::PreCompile). + // defineSingletonMethod("New", &ScriptData::New). + // defineMethod("Length", &ScriptData::Length). + // defineMethod("Data", &ScriptData::Data). + // defineMethod("HasError", &ScriptData::HasError). + // store(&ScriptData::Class); } -#endif - return ScriptData(v8::ScriptData::PreCompile(RSTRING_PTR(input), NUM2INT(length))); -} -VALUE ScriptData::New(VALUE self, VALUE data, VALUE length) { - return ScriptData(v8::ScriptData::New(RSTRING_PTR(data), NUM2INT(length))); -} -VALUE ScriptData::Length(VALUE self) { - return ScriptData(self)->Length(); -} -VALUE ScriptData::Data(VALUE self) { - ScriptData data(self); -#ifdef HAVE_RUBY_ENCODING_H - return rb_enc_str_new(data->Data(), data->Length(), rb_enc_find("BINARY")); -#else - return rb_str_new(data->Data(), data->Length()); -#endif -} -VALUE ScriptData::HasError(VALUE self) { - return ScriptData(self)->HasError(); -} -VALUE Script::New(int argc, VALUE argv[], VALUE self) { - VALUE source; VALUE origin; VALUE pre_data; VALUE script_data; - rb_scan_args(argc, argv, "13", &source, &origin, &pre_data, &script_data); - if (argc == 2) { - VALUE filename = origin; - return Script(v8::Script::New(String(source), Value(filename))); - } else { - return Script(v8::Script::New(String(source), ScriptOrigin(origin), ScriptData(pre_data), String(script_data))); - } -} + VALUE Script::Compile(int argc, VALUE argv[], VALUE self) { + VALUE r_source, r_context, r_origin; + rb_scan_args(argc, argv, "21", &r_context, &r_source, &r_origin); -VALUE Script::Run(VALUE self) { - return Value(Script(self)->Run()); -} + Context context(r_context); + Isolate isolate(context.getIsolate()); + Locker lock(isolate); -typedef struct { - v8::Isolate *isolate; - long timeout; -} timeout_data; -void* breaker(void *d) { - timeout_data* data = (timeout_data*)d; - usleep(data->timeout*1000); - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); - v8::V8::TerminateExecution(data->isolate); - return NULL; -} - -VALUE Script::RunWithTimeout(VALUE self, VALUE timeout) { - pthread_t breaker_thread; - timeout_data data; - VALUE rval; - void *res; - - data.isolate = v8::Isolate::GetCurrent(); - data.timeout = NUM2LONG(timeout); - - pthread_create(&breaker_thread, NULL, breaker, &data); - - rval = Value(Script(self)->Run()); - - pthread_cancel(breaker_thread); - pthread_join(breaker_thread, &res); - - return rval; -} + if (RTEST(r_origin)) { + v8::ScriptOrigin origin = ScriptOrigin(r_origin); + v8::MaybeLocal script = v8::Script::Compile(context, String(r_source), &origin); + return Script::Maybe(isolate, script); + } + else { + return Script::Maybe(isolate, v8::Script::Compile(context, String(r_source))); + } + } -template <> void Pointer::unwrap(VALUE value) { - Data_Get_Struct(value, class v8::ScriptData, pointer); -} + VALUE Script::Run(VALUE self, VALUE rb_context) { + Context context(rb_context); + Locker lock(context); -template <> void Pointer::unwrap(VALUE value) { - Data_Get_Struct(value, class v8::ScriptOrigin, pointer); + return Value::Maybe(context, Script(self)->Run(context)); + } } - -} //namespace rr diff --git a/ext/v8/script.h b/ext/v8/script.h new file mode 100644 index 00000000..b8cf46d1 --- /dev/null +++ b/ext/v8/script.h @@ -0,0 +1,21 @@ +// -*- mode: c++ -*- +#ifndef RR_SCRIPT +#define RR_SCRIPT + +#include "rr.h" + +namespace rr { + + class Script : public Ref { + public: + static void Init(); + static VALUE Compile(int argc, VALUE argv[], VALUE self); + static VALUE Run(VALUE self, VALUE context); + // static VALUE RunWithTimeout(VALUE self, VALUE timeout); + + inline Script(VALUE value) : Ref(value) {} + inline Script(v8::Isolate* isolate, v8::Handle script) : Ref(isolate, script) {} + typedef MaybeLocal Maybe; + }; +} +#endif diff --git a/ext/v8/signature.cc b/ext/v8/signature.cc index d227d275..d1eee359 100644 --- a/ext/v8/signature.cc +++ b/ext/v8/signature.cc @@ -1,18 +1,13 @@ #include "rr.h" namespace rr { - void Signature::Init() { - ClassBuilder("Signature"). - defineMethod("New", &New). - store(&Class); - } + VALUE Signature::New(int argc, VALUE argv[], VALUE self) { + VALUE r_isolate; + VALUE r_receiver; + rb_scan_args(argc, argv, "11", &r_isolate, &r_receiver); - VALUE Signature::New(int argc, VALUE args[], VALUE self) { - VALUE receiver; VALUE argv; - rb_scan_args(argc, args, "02", &receiver, &argv); - FunctionTemplate recv(receiver); - int length = RARRAY_LENINT(argv); - FunctionTemplate::array types(argv); - return Signature(v8::Signature::New(recv, length, types)); + Isolate isolate(r_isolate); + Locker lock(isolate); + return Signature(isolate, v8::Signature::New(isolate, FunctionTemplate(r_receiver))); } -} \ No newline at end of file +} diff --git a/ext/v8/signature.h b/ext/v8/signature.h new file mode 100644 index 00000000..802f48bc --- /dev/null +++ b/ext/v8/signature.h @@ -0,0 +1,22 @@ +// -*- mode: c++ -*- +#ifndef RR_SIGNATURE_H +#define RR_SIGNATURE_H + +namespace rr { + class Signature : public Ref { + public: + Signature(VALUE self) : Ref(self) {} + Signature(v8::Isolate* i, v8::Local s) : + Ref(i, s) {} + + static void Init() { + ClassBuilder("Signature"). + defineSingletonMethod("New", &New). + store(&Class); + } + + static VALUE New(int argc, VALUE argv[], VALUE self); + }; +} + +#endif /* RR_SIGNATURE_H */ diff --git a/ext/v8/stack-frame.h b/ext/v8/stack-frame.h new file mode 100644 index 00000000..d683e09a --- /dev/null +++ b/ext/v8/stack-frame.h @@ -0,0 +1,70 @@ +// -*- mode: c++ -*- + +#ifndef RR_STACK_FRAME_H +#define RR_STACK_FRAME_H +#include "rr.h" + +namespace rr { + class StackFrame : public Ref { + public: + StackFrame(VALUE self) : Ref(self) {} + StackFrame(v8::Isolate* isolate, v8::Local frame) : + Ref(isolate, frame) {} + + static VALUE GetLineNumber(VALUE self) { + StackFrame frame(self); + Locker lock(frame); + return INT2FIX(frame->GetLineNumber()); + } + static VALUE GetColumn(VALUE self) { + StackFrame frame(self); + Locker lock(frame); + return INT2FIX(frame->GetColumn()); + } + static VALUE GetScriptId(VALUE self) { + StackFrame frame(self); + Locker lock(frame); + return INT2FIX(frame->GetScriptId()); + } + static VALUE GetScriptName(VALUE self) { + StackFrame frame(self); + Locker lock(frame); + return String(frame, frame->GetScriptName()); + } + static VALUE GetScriptNameOrSourceURL(VALUE self) { + StackFrame frame(self); + Locker lock(frame); + return String(frame, frame->GetScriptNameOrSourceURL()); + } + static VALUE GetFunctionName(VALUE self) { + StackFrame frame(self); + Locker lock(frame); + return String(frame, frame->GetFunctionName()); + } + static VALUE IsEval(VALUE self) { + StackFrame frame(self); + Locker lock(frame); + return Bool(frame->IsEval()); + } + static VALUE IsConstructor(VALUE self) { + StackFrame frame(self); + Locker lock(frame); + return Bool(frame->IsConstructor()); + } + + static inline void Init() { + ClassBuilder("StackFrame"). + defineMethod("GetLineNumber", &GetLineNumber). + defineMethod("GetColumn", &GetColumn). + defineMethod("GetScriptId", &GetScriptId). + defineMethod("GetScriptName", &GetScriptName). + defineMethod("GetScriptNameOrSourceURL", &GetScriptNameOrSourceURL). + defineMethod("GetFunctionName", &GetFunctionName). + defineMethod("IsEval", &IsEval). + defineMethod("IsConstructor", &IsConstructor). + store(&Class); + } + }; +} + +#endif /* RR_STACK_FRAME_H */ diff --git a/ext/v8/stack-trace.h b/ext/v8/stack-trace.h new file mode 100644 index 00000000..8d6c14eb --- /dev/null +++ b/ext/v8/stack-trace.h @@ -0,0 +1,70 @@ +// -*- mode: c++ -*- +#ifndef RR_STACK_TRACE_H +#define RR_STACK_TRACE_H +#include "rr.h" + +namespace rr { + class StackTrace : public Ref { + public: + StackTrace(VALUE self) : Ref(self) {} + StackTrace(v8::Isolate* isolate, v8::Local trace) : + Ref(isolate, trace) {} + + static VALUE GetFrame(VALUE self, VALUE offset) { + StackTrace stack(self); + Locker lock(stack); + + return StackFrame(stack, stack->GetFrame(NUM2INT(offset))); + } + + static VALUE GetFrameCount(VALUE self) { + StackTrace stack(self); + Locker lock(stack); + + return INT2FIX(stack->GetFrameCount()); + } + + static VALUE AsArray(VALUE self) { + StackTrace stack(self); + Locker lock(stack); + + return Array(stack, stack->AsArray()); + } + + static VALUE CurrentStackTrace(VALUE self, VALUE rb_isolate, VALUE frame_limit, VALUE options) { + Isolate isolate(rb_isolate); + Locker lock(isolate); + + return StackTrace( + isolate, + v8::StackTrace::CurrentStackTrace( + isolate, + NUM2UINT(frame_limit), + Enum(options, v8::StackTrace::kOverview))); + } + + inline static void Init() { + ClassBuilder("StackTrace"). + defineMethod("GetFrame", &GetFrame). + defineMethod("GetFrameCount", &GetFrameCount). + defineMethod("AsArray", &AsArray). + defineSingletonMethod("CurrentStackTrace", &CurrentStackTrace). + + defineConstMethod("kLineNumber", INT2FIX(v8::StackTrace::kLineNumber)). + defineConstMethod("kColumnOffset", INT2FIX(v8::StackTrace::kColumnOffset)). + defineConstMethod("kScriptName", INT2FIX(v8::StackTrace::kScriptName)). + defineConstMethod("kFunctionName", INT2FIX(v8::StackTrace::kFunctionName)). + defineConstMethod("kIsEval", INT2FIX(v8::StackTrace::kIsEval)). + defineConstMethod("kIsConstructor", INT2FIX(v8::StackTrace::kIsConstructor)). + defineConstMethod("kScriptNameOrSourceURL", INT2FIX(v8::StackTrace::kScriptNameOrSourceURL)). + defineConstMethod("kScriptId", INT2FIX(v8::StackTrace::kScriptId)). + defineConstMethod("kExposeFramesAcrossSecurityOrigins", INT2FIX(v8::StackTrace::kExposeFramesAcrossSecurityOrigins)). + defineConstMethod("kOverview", INT2FIX(v8::StackTrace::kOverview)). + defineConstMethod("kDetailed", INT2FIX(v8::StackTrace::kDetailed)). + store(&Class); + } + }; + +} + +#endif /* RR_STACK_TRACE_H */ diff --git a/ext/v8/stack.cc b/ext/v8/stack.cc deleted file mode 100644 index 1f095583..00000000 --- a/ext/v8/stack.cc +++ /dev/null @@ -1,76 +0,0 @@ -#include "rr.h" - -namespace rr { - void Stack::Init() { - ClassBuilder("StackTrace"). - defineSingletonMethod("kLineNumber", &Trace::kLineNumber). - defineSingletonMethod("kColumnOffset", &Trace::kColumnOffset). - defineSingletonMethod("kScriptName", &Trace::kScriptName). - defineSingletonMethod("kFunctionName", &Trace::kFunctionName). - defineSingletonMethod("kIsEval", &Trace::kIsEval). - defineSingletonMethod("kIsConstructor", &Trace::kIsConstructor). - defineSingletonMethod("kScriptNameOrSourceURL", &Trace::kScriptNameOrSourceURL). - defineSingletonMethod("kOverview", &Trace::kOverview). - defineSingletonMethod("kDetailed", &Trace::kDetailed). - defineSingletonMethod("CurrentStackTrace", &Trace::CurrentStackTrace). - defineMethod("GetFrame", &Trace::GetFrame). - defineMethod("GetFrameCount", &Trace::GetFrameCount). - defineMethod("AsArray", &Trace::AsArray). - store(&Trace::Class); - ClassBuilder("StackFrame"). - defineMethod("GetLineNumber", &Frame::GetLineNumber). - defineMethod("GetColumn", &Frame::GetColumn). - defineMethod("GetScriptName", &Frame::GetScriptName). - defineMethod("GetScriptNameOrSourceURL", &Frame::GetScriptNameOrSourceURL). - defineMethod("GetFunctionName", &Frame::GetFunctionName). - defineMethod("IsEval", &Frame::IsEval). - defineMethod("IsConstructor", &Frame::IsConstructor). - store(&Frame::Class); - } - - VALUE Stack::Trace::GetFrame(VALUE self, VALUE index) { - return Frame(Trace(self)->GetFrame(NUM2UINT(index))); - } - - VALUE Stack::Trace::GetFrameCount(VALUE self) { - return INT2FIX(Trace(self)->GetFrameCount()); - } - - VALUE Stack::Trace::AsArray(VALUE self) { - return Array(Trace(self)->AsArray()); - } - - VALUE Stack::Trace::CurrentStackTrace(int argc, VALUE argv[], VALUE self) { - VALUE frame_limit; VALUE options; - rb_scan_args(argc, argv, "11", &frame_limit, &options); - return Trace(v8::StackTrace::CurrentStackTrace(NUM2INT(frame_limit), StackTraceOptions(options))); - } - - VALUE Stack::Frame::GetLineNumber(VALUE self) { - return INT2FIX(Frame(self)->GetLineNumber()); - } - - VALUE Stack::Frame::GetColumn(VALUE self) { - return INT2FIX(Frame(self)->GetColumn()); - } - - VALUE Stack::Frame::GetScriptName(VALUE self) { - return String(Frame(self)->GetScriptName()); - } - - VALUE Stack::Frame::GetScriptNameOrSourceURL(VALUE self) { - return String(Frame(self)->GetScriptNameOrSourceURL()); - } - - VALUE Stack::Frame::GetFunctionName(VALUE self) { - return String(Frame(self)->GetFunctionName()); - } - - VALUE Stack::Frame::IsEval(VALUE self) { - return Bool(Frame(self)->IsEval()); - } - - VALUE Stack::Frame::IsConstructor(VALUE self) { - return Bool(Frame(self)->IsConstructor()); - } -} \ No newline at end of file diff --git a/ext/v8/string.cc b/ext/v8/string.cc deleted file mode 100644 index a75ead40..00000000 --- a/ext/v8/string.cc +++ /dev/null @@ -1,47 +0,0 @@ -#include "rr.h" - -namespace rr { - -void String::Init() { - ClassBuilder("String", Primitive::Class). - defineSingletonMethod("New", &New). - defineSingletonMethod("NewSymbol", &NewSymbol). - defineSingletonMethod("Concat", &Concat). - defineMethod("Utf8Value", &Utf8Value). - store(&Class); -} - -VALUE String::New(VALUE StringClass, VALUE string) { - return String(v8::String::New(RSTRING_PTR(string), (int)RSTRING_LEN(string))); -} - -VALUE String::NewSymbol(VALUE self, VALUE string) { - return String(v8::String::NewSymbol(RSTRING_PTR(string), (int)RSTRING_LEN(string))); -} - -VALUE String::Utf8Value(VALUE self) { - String str(self); - #ifdef HAVE_RUBY_ENCODING_H - return rb_enc_str_new(*v8::String::Utf8Value(*str), str->Utf8Length(), rb_enc_find("utf-8")); - #else - return rb_str_new(*v8::String::Utf8Value(*str), str->Utf8Length()); - #endif -} - -VALUE String::Concat(VALUE self, VALUE left, VALUE right) { - return String(v8::String::Concat(String(left), String(right))); -} - -String::operator v8::Handle() const { - switch (TYPE(value)) { - case T_STRING: - return v8::String::New(RSTRING_PTR(value), (int)RSTRING_LEN(value)); - case T_DATA: - return Ref::operator v8::Handle(); - default: - VALUE string = rb_funcall(value, rb_intern("to_s"), 0); - return v8::String::New(RSTRING_PTR(string), (int)RSTRING_LEN(string)); - } -} - -} //namespace rr \ No newline at end of file diff --git a/ext/v8/symbol.cc b/ext/v8/symbol.cc new file mode 100644 index 00000000..539fe216 --- /dev/null +++ b/ext/v8/symbol.cc @@ -0,0 +1,74 @@ +#include "rr.h" + +namespace rr { + void Symbol::Init() { + ClassBuilder("Symbol", Name::Class). + defineSingletonMethod("New", &New). + defineSingletonMethod("For", &For). + defineSingletonMethod("ForApi", &ForApi). + defineSingletonMethod("GetIterator", &GetIterator). + defineSingletonMethod("GetUnscopables", &GetUnscopables). + defineSingletonMethod("GetToStringTag", &GetToStringTag). + defineMethod("Name", &Name). + + store(&Class); + } + + VALUE Symbol::New(int argc, VALUE argv[], VALUE self) { + VALUE rb_isolate, rb_name; + rb_scan_args(argc, argv, "11", &rb_isolate, &rb_name); + + Isolate isolate(rb_isolate); + Locker lock(isolate); + v8::HandleScope handle_scope(isolate); + + if (RTEST(rb_name)) { + return Symbol(isolate, v8::Symbol::New(isolate, String(rb_name))); + } else { + return Symbol(isolate, v8::Symbol::New(isolate)); + } + } + + VALUE Symbol::For(VALUE self, VALUE rb_isolate, VALUE name) { + Isolate isolate(rb_isolate); + Locker lock(isolate); + + return Symbol(isolate, v8::Symbol::For(isolate, String(name))); + } + + VALUE Symbol::ForApi(VALUE self, VALUE rb_isolate, VALUE name) { + Isolate isolate(rb_isolate); + Locker lock(isolate); + + return Symbol(isolate, v8::Symbol::ForApi(isolate, String(name))); + } + + VALUE Symbol::GetIterator(VALUE self, VALUE rb_isolate) { + Isolate isolate(rb_isolate); + Locker lock(isolate); + + return Symbol(isolate, v8::Symbol::GetIterator(isolate)); + } + + VALUE Symbol::GetUnscopables(VALUE self, VALUE rb_isolate) { + Isolate isolate(rb_isolate); + Locker lock(isolate); + + return Symbol(isolate, v8::Symbol::GetUnscopables(isolate)); + } + + VALUE Symbol::GetToStringTag(VALUE self, VALUE rb_isolate) { + Isolate isolate(rb_isolate); + Locker lock(isolate); + + return Symbol(isolate, v8::Symbol::GetToStringTag(isolate)); + } + + VALUE Symbol::Name(VALUE self) { + Symbol symbol(self); + Isolate isolate(symbol.getIsolate()); + Locker lock(isolate); + + return Value(isolate, symbol->Name()); + } +} diff --git a/ext/v8/symbol.h b/ext/v8/symbol.h new file mode 100644 index 00000000..75016619 --- /dev/null +++ b/ext/v8/symbol.h @@ -0,0 +1,26 @@ +// -*- mode: c++ -*- +#ifndef SYMBOL_H +#define SYMBOL_H + +#include "rr.h" + +namespace rr { + class Symbol : public Ref { + public: + static void Init(); + + static VALUE Name(VALUE self); + static VALUE New(int argc, VALUE argv[], VALUE self); + static VALUE For(VALUE self, VALUE isolate, VALUE name); + static VALUE ForApi(VALUE self, VALUE isolate, VALUE name); + static VALUE GetIterator(VALUE self, VALUE isolate); + static VALUE GetUnscopables(VALUE self, VALUE isolate); + static VALUE GetToStringTag(VALUE self, VALUE isolate); + + Symbol(VALUE self) : Ref(self) {} + Symbol(v8::Isolate* isolate, v8::Local symbol) : + Ref(isolate, symbol) {} + }; +} + +#endif /* SYMBOL_H */ diff --git a/ext/v8/template.cc b/ext/v8/template.cc deleted file mode 100644 index e82642d6..00000000 --- a/ext/v8/template.cc +++ /dev/null @@ -1,175 +0,0 @@ -#include "rr.h" - -namespace rr { - void Template::Init() { - ClassBuilder("Template"). - defineMethod("Set", &Set); - ObjectTemplate::Init(); - FunctionTemplate::Init(); - } - - VALUE Template::Set(int argc, VALUE argv[], VALUE self) { - VALUE name; VALUE value; VALUE attributes; - rb_scan_args(argc, argv, "21", &name, &value, &attributes); - Void(Template(self)->Set(*String(name), *Value(value), PropertyAttribute(attributes))); - } - - void ObjectTemplate::Init() { - ClassBuilder("ObjectTemplate", "Template"). - defineSingletonMethod("New", &New). - defineMethod("NewInstance", &NewInstance). - defineMethod("SetAccessor", &SetAccessor). - defineMethod("SetNamedPropertyHandler", &SetNamedPropertyHandler). - defineMethod("SetIndexedPropertyHandler", &SetIndexedPropertyHandler). - defineMethod("SetCallAsFunctionHandler", &SetCallAsFunctionHandler). - defineMethod("MarkAsUndetectable", &MarkAsUndetectable). - defineMethod("SetAccessCheckCallbacks", &SetAccessCheckCallbacks). - defineMethod("InternalFieldCount", &InternalFieldCount). - defineMethod("SetInternalFieldCount", &SetInternalFieldCount). - store(&Class); - } - - VALUE ObjectTemplate::New(VALUE self) { - return ObjectTemplate(v8::ObjectTemplate::New()); - } - - VALUE ObjectTemplate::NewInstance(VALUE self) { - return Object(ObjectTemplate(self)->NewInstance()); - } - - VALUE ObjectTemplate::SetAccessor(int argc, VALUE argv[], VALUE self) { - VALUE name; VALUE get; VALUE set; VALUE data; VALUE settings; VALUE attribs; - rb_scan_args(argc, argv, "24", &name, &get, &set, &data, &settings, &attribs); - Accessor accessor(get, set, data); - ObjectTemplate(self)->SetAccessor( - String(name), - accessor.accessorGetter(), - accessor.accessorSetter(), - accessor, - AccessControl(settings), - PropertyAttribute(attribs) - ); - Void(); - } - - VALUE ObjectTemplate::SetNamedPropertyHandler(int argc, VALUE argv[], VALUE self) { - VALUE get; VALUE set; VALUE query; VALUE deleter; VALUE enumerator; VALUE data; - rb_scan_args(argc, argv, "15", &get, &set, &query, &deleter, &enumerator, &data); - Accessor accessor(get,set,query,deleter,enumerator,data); - ObjectTemplate(self)->SetNamedPropertyHandler( - accessor.namedPropertyGetter(), - accessor.namedPropertySetter(), - accessor.namedPropertyQuery(), - accessor.namedPropertyDeleter(), - accessor.namedPropertyEnumerator(), - accessor - ); - Void(); - } - - VALUE ObjectTemplate::SetIndexedPropertyHandler(int argc, VALUE argv[], VALUE self) { - VALUE get; VALUE set; VALUE query; VALUE deleter; VALUE enumerator; VALUE data; - rb_scan_args(argc, argv, "15", &get, &set, &query, &deleter, &enumerator, &data); - Accessor accessor(get,set,query,deleter,enumerator,data); - ObjectTemplate(self)->SetIndexedPropertyHandler( - accessor.indexedPropertyGetter(), - accessor.indexedPropertySetter(), - accessor.indexedPropertyQuery(), - accessor.indexedPropertyDeleter(), - accessor.indexedPropertyEnumerator(), - accessor - ); - Void(); - } - - VALUE ObjectTemplate::SetCallAsFunctionHandler(int argc, VALUE argv[], VALUE self) { - VALUE callback; VALUE data; - rb_scan_args(argc, argv, "11", &callback, &data); - Invocation invocation(callback, data); - Void(ObjectTemplate(self)->SetCallAsFunctionHandler(invocation, invocation)); - } - - VALUE ObjectTemplate::MarkAsUndetectable(VALUE self) { - Void(ObjectTemplate(self)->MarkAsUndetectable()); - } - - - VALUE ObjectTemplate::SetAccessCheckCallbacks(int argc, VALUE argv[], VALUE self) { - VALUE named_handler; VALUE indexed_handler; VALUE data; VALUE turned_on_by_default; - rb_scan_args(argc, argv, "22", &named_handler, &indexed_handler, &data, &turned_on_by_default); - return not_implemented("ObjectTemplate::SetAccessCheckCallbacks"); - } - - VALUE ObjectTemplate::InternalFieldCount(VALUE self) { - return INT2FIX(ObjectTemplate(self)->InternalFieldCount()); - } - - VALUE ObjectTemplate::SetInternalFieldCount(VALUE self, VALUE count) { - Void(ObjectTemplate(self)->SetInternalFieldCount(NUM2INT(count))); - } - - void FunctionTemplate::Init() { - ClassBuilder("FunctionTemplate", "Template"). - defineSingletonMethod("New", &New). - defineMethod("GetFunction", &GetFunction). - defineMethod("SetCallHandler", &SetCallHandler). - defineMethod("InstanceTemplate", &InstanceTemplate). - defineMethod("Inherit", &Inherit). - defineMethod("PrototypeTemplate", &PrototypeTemplate). - defineMethod("SetClassName", &SetClassName). - defineMethod("SetHiddenPrototype", &SetHiddenPrototype). - defineMethod("ReadOnlyPrototype", &ReadOnlyPrototype). - defineMethod("HasInstance", &HasInstance). - store(&Class); - } - - VALUE FunctionTemplate::New(int argc, VALUE argv[], VALUE self) { - VALUE code; VALUE data; VALUE signature; - rb_scan_args(argc, argv, "03", &code, &data, &signature); - if (RTEST(code)) { - Invocation invocation(code, data); - return FunctionTemplate(v8::FunctionTemplate::New(invocation, invocation, Signature(signature))); - } else { - return FunctionTemplate(v8::FunctionTemplate::New()); - } - } - - VALUE FunctionTemplate::GetFunction(VALUE self) { - return Function(FunctionTemplate(self)->GetFunction()); - } - - VALUE FunctionTemplate::SetCallHandler(int argc, VALUE argv[], VALUE self) { - VALUE code; VALUE data; - rb_scan_args(argc, argv, "11", &code, &data); - Invocation invocation(code, data); - Void(FunctionTemplate(self)->SetCallHandler(invocation, invocation)); - } - - VALUE FunctionTemplate::InstanceTemplate(VALUE self) { - return ObjectTemplate(FunctionTemplate(self)->InstanceTemplate()); - } - - VALUE FunctionTemplate::Inherit(VALUE self, VALUE parent) { - Void(FunctionTemplate(self)->Inherit(FunctionTemplate(parent))); - } - - VALUE FunctionTemplate::PrototypeTemplate(VALUE self) { - return ObjectTemplate(FunctionTemplate(self)->PrototypeTemplate()); - } - - VALUE FunctionTemplate::SetClassName(VALUE self, VALUE name) { - Void(FunctionTemplate(self)->SetClassName(String(name))); - } - - VALUE FunctionTemplate::SetHiddenPrototype(VALUE self, VALUE value) { - Void(FunctionTemplate(self)->SetHiddenPrototype(Bool(value))); - } - - VALUE FunctionTemplate::ReadOnlyPrototype(VALUE self) { - Void(FunctionTemplate(self)->ReadOnlyPrototype()); - } - - VALUE FunctionTemplate::HasInstance(VALUE self, VALUE object) { - return Bool(FunctionTemplate(self)->HasInstance(Value(object))); - } -} \ No newline at end of file diff --git a/ext/v8/template.h b/ext/v8/template.h new file mode 100644 index 00000000..600b4f25 --- /dev/null +++ b/ext/v8/template.h @@ -0,0 +1,39 @@ +// -*- mode: c++ -*- +#ifndef RR_TEMPLATE_H +#define RR_TEMPLATE_H + +namespace rr { + class Template : public Ref { + public: + Template(VALUE t) : Ref(t) {} + + inline static void Init() { + ClassBuilder("Template"). + defineMethod("Set", &Set). + defineMethod("SetAccessorProperty", &SetAccessorProperty). + store(&Class); + } + + static VALUE Set(int argc, VALUE argv[], VALUE self) { + VALUE name, value, r_attributes; + rb_scan_args(argc, argv, "21", &name, &value, &r_attributes); + v8::PropertyAttribute attributes(v8::PropertyAttribute::None); + if (RTEST(r_attributes)) { + attributes = (v8::PropertyAttribute)NUM2INT(r_attributes); + } + v8::Local val = Value(value); + Template(self)->Set(Name(name), val, attributes); + return Qnil; + } + + static VALUE SetAccessorProperty(int argc, VALUE argv[], VALUE self) { + VALUE r_name, r_getter, setter, attributes, access; + rb_scan_args(argc, argv, "14", &r_name, &r_getter, &setter, &attributes, &access); + Template(self)->SetAccessorProperty(Name(r_name)); + return Qnil; + } + }; +} + + +#endif /* RR_TEMPLATE_H */ diff --git a/ext/v8/try-catch.cc b/ext/v8/try-catch.cc new file mode 100644 index 00000000..b5f12421 --- /dev/null +++ b/ext/v8/try-catch.cc @@ -0,0 +1,5 @@ +#include "rr.h" + +namespace rr { + VALUE TryCatch::Class; +} diff --git a/ext/v8/try-catch.h b/ext/v8/try-catch.h new file mode 100644 index 00000000..cd51fa2e --- /dev/null +++ b/ext/v8/try-catch.h @@ -0,0 +1,192 @@ +// -*- mode: c++ -*- + +#ifndef RR_TRY_CATCH_H +#define RR_TRY_CATCH_H +#include "rr.h" + +namespace rr { + /** + * V8::C::TryCatch is both a class and a method. + * + * Use the method to evaluate a block of Ruby code from within a + * v8::TryCatch. The block will receive an instance of the + * `V8::C::TryCatch` class as its argument E.g. + * + * V8::C::TryCatch(isolate) do |trycatch| + * trycatch.class #=> V8::C::TryCatch + * cxt.eval('throw new Error()') + * trycatch.HasCaught() #=> true + * end + * + * Note that the `trycatch` value yielded to the block must never be + * used outside the block since it references the `v8::TryCatch` C++ + * object which is allocated on the stack. See `doTryCatch` and + * `call` for the implementation of the block parameters. + * + * Note: Ideally, we'd like to make the `Wrapper` class as a super + * class to handle the wrapping and unwrapping of the TryCatch, but + * that would require introducing pass-by-reference semantics to the + * wrapper template (since v8::TryCatch disables the copy + * constructor). Satisfying the compiler proved too challenging for + * this poor implementer. + */ + class TryCatch { + public: + TryCatch(VALUE self) : container(Container::unwrap(self)) {} + TryCatch(v8::Isolate* isolate, v8::TryCatch& trycatch) : + container(new Container(isolate, trycatch)) {} + + static VALUE HasCaught(VALUE self) { + TryCatch tc(self); + Locker lock(tc); + return Bool(tc->HasCaught()); + } + static VALUE CanContinue(VALUE self) { + TryCatch tc(self); + Locker lock(tc); + return Bool(tc->CanContinue()); + } + static VALUE HasTerminated(VALUE self) { + TryCatch tc(self); + Locker lock(tc); + return Bool(tc->HasTerminated()); + } + static VALUE ReThrow(VALUE self) { + TryCatch tc(self); + Locker lock(tc); + return Value(tc, tc->ReThrow()); + } + static VALUE Exception(VALUE self) { + TryCatch tc(self); + Locker lock(tc); + return Value(tc, tc->Exception()); + } + static VALUE StackTrace(VALUE self, VALUE context) { + TryCatch tc(self); + Locker lock(tc); + return Value::Maybe(tc, tc->StackTrace(Context(context))); + } + static VALUE Message(VALUE self) { + TryCatch tc(self); + Locker lock(tc); + return rr::Message(tc, tc->Message()); + } + static VALUE Reset(VALUE self) { + TryCatch tc(self); + Locker lock(tc); + tc->Reset(); + return Qnil; + } + static VALUE SetVerbose(VALUE self, VALUE value) { + TryCatch tc(self); + Locker lock(tc); + tc->SetVerbose(Bool(value)); + return Qnil; + } + static VALUE SetCaptureMessage(VALUE self, VALUE value) { + TryCatch tc(self); + Locker lock(tc); + tc->SetCaptureMessage(Bool(value)); + return Qnil; + } + + /** + * Implements the V8::C::TryCatch(isolate) method. This yields the + * trycatch to the passed ruby code. + */ + static VALUE doTryCatch(int argc, VALUE argv[], VALUE self) { + if (!rb_block_given_p()) { + return Qnil; + } + int state; + VALUE isolate, code, result; + rb_scan_args(argc, argv, "10&", &isolate, &code); + + // allocate the trycatch within its own scope, so that its + // destructor is called, even if we're forced to `rb_jmp_tag` + // out of this function because of a ruby exception. + // the actual `V8::C::TryCatch` object is stored on the code in + // the `_v8_trycatch` variable, because rb_protect only accepts + // a single argument. + { + v8::TryCatch trycatch; + rb_iv_set(code, "_v8_trycatch", TryCatch(Isolate(isolate), trycatch)); + result = rb_protect(&call, code, &state); + rb_iv_set(code, "_v8_trycatch", Qnil); + } + if (state != 0) { + // ruby exception. bail! + rb_jump_tag(state); + } + return result; + } + /** + * Actually invokes the passed ruby block. The instance of + * `V8::C::TryCatch` is pulled off of the `_v8_trycatch` instance variable. + */ + static VALUE call(VALUE code) { + return rb_funcall(code, rb_intern("call"), 1, rb_iv_get(code, "_v8_trycatch")); + } + + + // What follows is a copy of the `Wrapper` template, that uses + // pass-by-reference semantics so that the actual `v8::TryCatch` + // object that is allocated on the C++ stack is the same object + // used everywhere. Ideally, we'd like to share the + // implementation, but I couldn't figure out how. + static VALUE Class; + + struct Container { + Container(v8::Isolate* isolate_, v8::TryCatch& tc) : isolate(isolate_), trycatch(tc) {} + + inline VALUE wrap() { + return Data_Wrap_Struct(Class, 0, &destroy, this); + } + + static inline Container* unwrap(VALUE object) { + Container* container; + Data_Get_Struct(object, struct Container, container); + return container; + } + + static void destroy(Container* container) { + delete container; + } + v8::Isolate* isolate; + v8::TryCatch& trycatch; + }; + + inline v8::TryCatch* operator ->() { + return &container->trycatch; + } + + inline operator v8::Isolate*() { + return container->isolate; + } + + inline operator VALUE() { + return container->wrap(); + } + static inline void Init() { + ClassBuilder("TryCatch"). + defineMethod("HasCaught", &HasCaught). + defineMethod("CanContinue", &CanContinue). + defineMethod("HasTerminated", &HasTerminated). + defineMethod("ReThrow", &ReThrow). + defineMethod("Exception", &Exception). + defineMethod("StackTrace", &StackTrace). + defineMethod("Message", &Message). + defineMethod("Reset", &Reset). + defineMethod("SetVerbose", &SetVerbose). + defineMethod("SetCaptureMessage", &SetCaptureMessage). + + store(&Class); + rb_define_singleton_method(rb_eval_string("V8::C"), "TryCatch", (VALUE (*)(...))&doTryCatch, -1); + } + + Container* container; + }; +} + + +#endif /* RR_TRY_CATCH_H */ diff --git a/ext/v8/trycatch.cc b/ext/v8/trycatch.cc deleted file mode 100644 index 1f092988..00000000 --- a/ext/v8/trycatch.cc +++ /dev/null @@ -1,87 +0,0 @@ -#include "rr.h" - -namespace rr { - VALUE TryCatch::Class; - - void TryCatch::Init() { - ClassBuilder("TryCatch"). - defineMethod("HasCaught", &HasCaught). - defineMethod("CanContinue", &CanContinue). - defineMethod("ReThrow", &ReThrow). - defineMethod("Exception", &Exception). - defineMethod("StackTrace", &StackTrace). - defineMethod("Message", &Message). - defineMethod("Reset", &Reset). - defineMethod("SetVerbose", &SetVerbose). - defineMethod("SetCaptureMessage", &SetCaptureMessage). - store(&Class); - VALUE v8 = rb_define_module("V8"); - VALUE c = rb_define_module_under(v8, "C"); - rb_define_singleton_method(c, "TryCatch", (VALUE (*)(...))&doTryCatch, -1); - } - - TryCatch::TryCatch(v8::TryCatch* impl) { - this->impl = impl; - } - TryCatch::TryCatch(VALUE value) { - Data_Get_Struct(value, class v8::TryCatch, impl); - } - - TryCatch::operator VALUE() { - return Data_Wrap_Struct(Class, 0, 0, impl); - } - - VALUE TryCatch::HasCaught(VALUE self) { - return Bool(TryCatch(self)->HasCaught()); - } - VALUE TryCatch::CanContinue(VALUE self) { - return Bool(TryCatch(self)->CanContinue()); - } - VALUE TryCatch::ReThrow(VALUE self) { - return Value(TryCatch(self)->ReThrow()); - } - VALUE TryCatch::Exception(VALUE self) { - return Value(TryCatch(self)->Exception()); - } - VALUE TryCatch::StackTrace(VALUE self) { - return Value(TryCatch(self)->StackTrace()); - } - VALUE TryCatch::Message(VALUE self) { - return rr::Message(TryCatch(self)->Message()); - } - VALUE TryCatch::Reset(VALUE self) { - Void(TryCatch(self)->Reset()); - } - VALUE TryCatch::SetVerbose(VALUE self, VALUE value) { - Void(TryCatch(self)->SetVerbose(Bool(value))); - } - VALUE TryCatch::SetCaptureMessage(VALUE self, VALUE value) { - Void(TryCatch(self)->SetCaptureMessage(Bool(value))); - } - - VALUE TryCatch::doTryCatch(int argc, VALUE argv[], VALUE self) { - if (!rb_block_given_p()) { - return Qnil; - } - int state = 0; - VALUE code; - rb_scan_args(argc,argv,"00&", &code); - VALUE result = setupAndCall(&state, code); - if (state != 0) { - rb_jump_tag(state); - } - return result; - } - - VALUE TryCatch::setupAndCall(int* state, VALUE code) { - v8::TryCatch trycatch; - rb_iv_set(code, "_v8_trycatch", TryCatch(&trycatch)); - VALUE result = rb_protect(&doCall, code, state); - rb_iv_set(code, "_v8_trycatch", Qnil); - return result; - } - - VALUE TryCatch::doCall(VALUE code) { - return rb_funcall(code, rb_intern("call"), 1, rb_iv_get(code, "_v8_trycatch")); - } -} \ No newline at end of file diff --git a/ext/v8/uint32_t.h b/ext/v8/uint32_t.h new file mode 100644 index 00000000..5c3beb3b --- /dev/null +++ b/ext/v8/uint32_t.h @@ -0,0 +1,46 @@ +// -*- mode: c++ -*- +#ifndef RR_UINT32_H +#define RR_UINT32_H + + +namespace rr { + /** + * Converts between Ruby `Number` and the C/C++ `uint32_t`. + * + * This allows you to easily pass in `uint32_t` values whenever a + * Ruby VALUE is expected (such as a method call) E.g. + * + * uint_32_t myInt = 5; + * rb_funcall(Uint32_t(myInt), rb_intern("to_s")); //=> + * + * It also converts a Ruby `VALUE` into its corresponding + * `uint32_t`: + * + * uint_32_t myInt = Uint32_t(rb_eval_string("5")); //=> 5 + * + * Like all `Equiv`s, it stores itself internally as a Ruby `VALUE` + */ + class Uint32_t : public Equiv { + public: + /** + * Construct a Uint32_t from a Ruby `VALUE` + */ + Uint32_t(VALUE val) : Equiv(val) {} + + /** + * Construct a Uint32_t from a `uint32_t` by converting it into its + * corresponding `VALUE`. + */ + Uint32_t(uint32_t ui) : Equiv(UINT2NUM(ui)) {} + + /** + * Coerce the Ruby `VALUE` into a `uint32_t`. + */ + inline operator uint32_t() { + return RTEST(value) ? NUM2UINT(value) : 0; + } + }; + +} + +#endif diff --git a/ext/v8/undefined.h b/ext/v8/undefined.h new file mode 100644 index 00000000..addea976 --- /dev/null +++ b/ext/v8/undefined.h @@ -0,0 +1,18 @@ +// -*- mode: c++ -*- +#ifndef RR_UNDEFINED_H +#define RR_UNDEFINED_H + +namespace rr { + class Undefined : public Ref { + public: + Undefined(v8::Isolate* isolate, v8::Local undefined) : + Ref(isolate, undefined) {} + + static inline void Init() { + ClassBuilder("Undefined", Primitive::Class). + store(&Class); + } + }; +} + +#endif /* RR_UNDEFINED_H */ diff --git a/ext/v8/v8.cc b/ext/v8/v8.cc index 6357ca68..4bdc60d5 100644 --- a/ext/v8/v8.cc +++ b/ext/v8/v8.cc @@ -2,86 +2,48 @@ namespace rr { -void V8::Init() { - ClassBuilder("V8"). - defineSingletonMethod("IdleNotification", &IdleNotification). - defineSingletonMethod("SetFlagsFromString", &SetFlagsFromString). - defineSingletonMethod("SetFlagsFromCommandLine", &SetFlagsFromCommandLine). - defineSingletonMethod("PauseProfiler", &PauseProfiler). - defineSingletonMethod("ResumeProfiler", &ResumeProfiler). - defineSingletonMethod("IsProfilerPaused", &IsProfilerPaused). - defineSingletonMethod("GetCurrentThreadId", &GetCurrentThreadId). - defineSingletonMethod("TerminateExecution", &TerminateExecution). - defineSingletonMethod("IsExecutionTerminating", &IsExecutionTerminating). - defineSingletonMethod("Dispose", &Dispose). - defineSingletonMethod("LowMemoryNotification", &LowMemoryNotification). - defineSingletonMethod("AdjustAmountOfExternalAllocatedMemory", &AdjustAmountOfExternalAllocatedMemory). - defineSingletonMethod("ContextDisposedNotification", &ContextDisposedNotification). - defineSingletonMethod("SetCaptureStackTraceForUncaughtExceptions", &SetCaptureStackTraceForUncaughtExceptions). - defineSingletonMethod("GetHeapStatistics", &GetHeapStatistics). - defineSingletonMethod("GetVersion", &GetVersion); -} + v8::Platform* V8::v8_platform = NULL; + + void V8::Init() { + // Initialize V8. + v8::V8::InitializeICU(); + v8_platform = v8::platform::CreateDefaultPlatform(); + v8::V8::InitializePlatform(v8_platform); + v8::V8::Initialize(); -VALUE V8::IdleNotification(int argc, VALUE argv[], VALUE self) { - VALUE hint; - rb_scan_args(argc, argv, "01", &hint); - if (RTEST(hint)) { - return Bool(v8::V8::IdleNotification(NUM2INT(hint))); - } else { - return Bool(v8::V8::IdleNotification()); + ClassBuilder("V8"). + // defineSingletonMethod("IdleNotification", &IdleNotification). + // defineSingletonMethod("SetFlagsFromString", &SetFlagsFromString). + // defineSingletonMethod("SetFlagsFromCommandLine", &SetFlagsFromCommandLine). + // defineSingletonMethod("PauseProfiler", &PauseProfiler). + // defineSingletonMethod("ResumeProfiler", &ResumeProfiler). + // defineSingletonMethod("IsProfilerPaused", &IsProfilerPaused). + // defineSingletonMethod("GetCurrentThreadId", &GetCurrentThreadId). + // defineSingletonMethod("TerminateExecution", &TerminateExecution). + // defineSingletonMethod("IsExecutionTerminating", &IsExecutionTerminating). + defineSingletonMethod("Dispose", &Dispose). + // defineSingletonMethod("LowMemoryNotification", &LowMemoryNotification). + // defineSingletonMethod("AdjustAmountOfExternalAllocatedMemory", &AdjustAmountOfExternalAllocatedMemory). + // defineSingletonMethod("ContextDisposedNotification", &ContextDisposedNotification). + // defineSingletonMethod("SetCaptureStackTraceForUncaughtExceptions", &SetCaptureStackTraceForUncaughtExceptions). + // defineSingletonMethod("GetHeapStatistics", &GetHeapStatistics). + defineSingletonMethod("GetVersion", &GetVersion); } -} -VALUE V8::SetFlagsFromString(VALUE self, VALUE string) { - Void(v8::V8::SetFlagsFromString(RSTRING_PTR(string), (int)RSTRING_LEN(string))); -} -VALUE V8::SetFlagsFromCommandLine(VALUE self, VALUE args, VALUE remove_flags) { - int argc = RARRAY_LENINT(args); - char* argv[argc]; - for(int i = 0; i < argc; i++) { - argv[i] = RSTRING_PTR(rb_ary_entry(args, i)); + + VALUE V8::Dispose(VALUE self) { + v8::V8::Dispose(); + v8::V8::ShutdownPlatform(); + + if (v8_platform) { + delete v8_platform; + v8_platform = NULL; + } + + return Qnil; } - Void(v8::V8::SetFlagsFromCommandLine(&argc, argv, Bool(remove_flags))); -} -VALUE V8::AdjustAmountOfExternalAllocatedMemory(VALUE self, VALUE change_in_bytes) { - return SIZET2NUM(v8::V8::AdjustAmountOfExternalAllocatedMemory(NUM2SIZET(change_in_bytes))); -} -VALUE V8::PauseProfiler(VALUE self) { - Void(v8::V8::PauseProfiler()); -} -VALUE V8::ResumeProfiler(VALUE self) { - Void(v8::V8::ResumeProfiler()); -} -VALUE V8::IsProfilerPaused(VALUE self) { - return Bool(v8::V8::IsProfilerPaused()); -} -VALUE V8::GetCurrentThreadId(VALUE self) { - return INT2FIX(v8::V8::GetCurrentThreadId()); -} -VALUE V8::TerminateExecution(VALUE self, VALUE thread_id) { - Void(v8::V8::TerminateExecution(NUM2INT(thread_id))); -} -VALUE V8::IsExecutionTerminating(VALUE self) { - return Bool(v8::V8::IsExecutionTerminating()); -} -VALUE V8::Dispose(VALUE self) { - Void(v8::V8::Dispose()); -} -VALUE V8::LowMemoryNotification(VALUE self) { - Void(v8::V8::LowMemoryNotification()); -} -VALUE V8::ContextDisposedNotification(VALUE self) { - return INT2FIX(v8::V8::ContextDisposedNotification()); -} -VALUE V8::SetCaptureStackTraceForUncaughtExceptions(int argc, VALUE argv[], VALUE self) { - VALUE should_capture; VALUE frame_limit; VALUE options; - rb_scan_args(argc, argv, "12", &should_capture, &frame_limit, &options); - int limit = RTEST(frame_limit) ? NUM2INT(frame_limit) : 10; - Void(v8::V8::SetCaptureStackTraceForUncaughtExceptions(Bool(should_capture), limit, Stack::Trace::StackTraceOptions(options))); -} -VALUE V8::GetHeapStatistics(VALUE self, VALUE statistics_ptr) { - Void(v8::V8::GetHeapStatistics(HeapStatistics(statistics_ptr))); -} -VALUE V8::GetVersion(VALUE self) { - return rb_str_new2(v8::V8::GetVersion()); + + VALUE V8::GetVersion(VALUE self) { + return rb_str_new2(v8::V8::GetVersion()); + } + } -} \ No newline at end of file diff --git a/ext/v8/v8.h b/ext/v8/v8.h new file mode 100644 index 00000000..a8f5ca55 --- /dev/null +++ b/ext/v8/v8.h @@ -0,0 +1,25 @@ +namespace rr { + class V8 { + public: + static v8::Platform* v8_platform; + + static void Init(); + // static VALUE IdleNotification(int argc, VALUE argv[], VALUE self); + // static VALUE SetFlagsFromString(VALUE self, VALUE string); + // static VALUE SetFlagsFromCommandLine(VALUE self, VALUE args, VALUE remove_flags); + // static VALUE AdjustAmountOfExternalAllocatedMemory(VALUE self, VALUE change_in_bytes); + // static VALUE PauseProfiler(VALUE self); + // static VALUE ResumeProfiler(VALUE self); + // static VALUE IsProfilerPaused(VALUE self); + // static VALUE GetCurrentThreadId(VALUE self); + // static VALUE TerminateExecution(VALUE self, VALUE thread_id); + // static VALUE IsExecutionTerminating(VALUE self); + static VALUE Dispose(VALUE self); + // static VALUE LowMemoryNotification(VALUE self); + // static VALUE ContextDisposedNotification(VALUE self); + + // static VALUE SetCaptureStackTraceForUncaughtExceptions(int argc, VALUE argv[], VALUE self); + // static VALUE GetHeapStatistics(VALUE self, VALUE statistics_ptr); + static VALUE GetVersion(VALUE self); + }; +} diff --git a/ext/v8/value.cc b/ext/v8/value.cc index af2a955a..ed59380e 100644 --- a/ext/v8/value.cc +++ b/ext/v8/value.cc @@ -2,219 +2,230 @@ namespace rr { -VALUE Value::Empty; - -void Value::Init() { - Empty = rb_eval_string("Object.new"); - ClassBuilder("Value"). - defineConst("Empty", Empty). - defineMethod("IsUndefined", &IsUndefined). - defineMethod("IsNull", &IsNull). - defineMethod("IsTrue", &IsTrue). - defineMethod("IsFalse", &IsFalse). - defineMethod("IsString", &IsString). - defineMethod("IsFunction", &IsFunction). - defineMethod("IsArray", &IsArray). - defineMethod("IsObject", &IsObject). - defineMethod("IsBoolean", &IsBoolean). - defineMethod("IsNumber", &IsNumber). - defineMethod("IsExternal", &IsExternal). - defineMethod("IsInt32", &IsInt32). - defineMethod("IsUint32", &IsUint32). - defineMethod("IsDate", &IsDate). - defineMethod("IsBooleanObject", &IsBooleanObject). - defineMethod("IsNumberObject", &IsNumberObject). - defineMethod("IsStringObject", &IsStringObject). - defineMethod("IsNativeError", &IsNativeError). - defineMethod("IsRegExp", &IsRegExp). - defineMethod("ToString", &ToString). - defineMethod("ToDetailString", &ToDetailString). - defineMethod("ToObject", &ToObject). - defineMethod("BooleanValue", &BooleanValue). - defineMethod("NumberValue", &NumberValue). - defineMethod("IntegerValue", &IntegerValue). - defineMethod("Uint32Value", &Uint32Value). - defineMethod("IntegerValue", &IntegerValue). - defineMethod("Equals", &Equals). - defineMethod("StrictEquals", &StrictEquals) - .store(&Class); - rb_gc_register_address(&Empty); -} + void Value::Init() { + ClassBuilder("Value"). + defineMethod("IsUndefined", &IsUndefined). + defineMethod("IsNull", &IsNull). + defineMethod("IsTrue", &IsTrue). + defineMethod("IsFalse", &IsFalse). + defineMethod("IsBoolean", &IsBoolean). + defineMethod("IsString", &IsString). + defineMethod("IsFunction", &IsFunction). + // defineMethod("IsArray", &IsArray). + defineMethod("IsObject", &IsObject). + defineMethod("IsNumber", &IsNumber). + defineMethod("IsExternal", &IsExternal). + defineMethod("IsInt32", &IsInt32). + defineMethod("IsUint32", &IsUint32). + // defineMethod("IsDate", &IsDate). + // defineMethod("IsBooleanObject", &IsBooleanObject). + // defineMethod("IsNumberObject", &IsNumberObject). + // defineMethod("IsStringObject", &IsStringObject). + defineMethod("IsNativeError", &IsNativeError). + // defineMethod("IsRegExp", &IsRegExp). + defineMethod("ToString", &ToString). + // defineMethod("ToDetailString", &ToDetailString). + // defineMethod("ToObject", &ToObject). + defineMethod("BooleanValue", &BooleanValue). + // defineMethod("NumberValue", &NumberValue). + // defineMethod("IntegerValue", &IntegerValue). + // defineMethod("Uint32Value", &Uint32Value). + // defineMethod("IntegerValue", &IntegerValue). + defineMethod("Equals", &Equals). + defineMethod("StrictEquals", &StrictEquals). - VALUE Value::IsUndefined(VALUE self) { - return Bool(Value(self)->IsUndefined()); - } - VALUE Value::IsNull(VALUE self) { - return Bool(Value(self)->IsNull()); - } - VALUE Value::IsTrue(VALUE self) { - return Bool(Value(self)->IsTrue()); - } - VALUE Value::IsFalse(VALUE self) { - return Bool(Value(self)->IsFalse()); - } - VALUE Value::IsString(VALUE self) { - return Bool(Value(self)->IsString()); - } - VALUE Value::IsFunction(VALUE self) { - return Bool(Value(self)->IsFunction()); - } - VALUE Value::IsArray(VALUE self) { - return Bool(Value(self)->IsArray()); - } - VALUE Value::IsObject(VALUE self) { - return Bool(Value(self)->IsObject()); - } - VALUE Value::IsBoolean(VALUE self) { - return Bool(Value(self)->IsBoolean()); - } - VALUE Value::IsNumber(VALUE self) { - return Bool(Value(self)->IsNumber()); - } - VALUE Value::IsExternal(VALUE self) { - return Bool(Value(self)->IsExternal()); - } - VALUE Value::IsInt32(VALUE self) { - return Bool(Value(self)->IsInt32()); - } - VALUE Value::IsUint32(VALUE self) { - return Bool(Value(self)->IsUint32()); - } - VALUE Value::IsDate(VALUE self) { - return Bool(Value(self)->IsDate()); - } - VALUE Value::IsBooleanObject(VALUE self) { - return Bool(Value(self)->IsBooleanObject()); - } - VALUE Value::IsNumberObject(VALUE self) { - return Bool(Value(self)->IsNumberObject()); - } - VALUE Value::IsStringObject(VALUE self) { - return Bool(Value(self)->IsStringObject()); - } - VALUE Value::IsNativeError(VALUE self) { - return Bool(Value(self)->IsNativeError()); - } - VALUE Value::IsRegExp(VALUE self) { - return Bool(Value(self)->IsRegExp()); - } - - // VALUE Value::ToBoolean(VALUE self) { - // return Boolean(Value(self)->ToBoolean()); - // } - - // VALUE Value::ToNumber(VALUE self) { - // return Number(Value(self)->ToNumber()); - // } - VALUE Value::ToString(VALUE self) { - return String(Value(self)->ToString()); - } - - VALUE Value::ToDetailString(VALUE self) { - return String(Value(self)->ToDetailString()); - } - - VALUE Value::ToObject(VALUE self) { - return Object(Value(self)->ToObject()); - } - - // VALUE Value::ToInteger(VALUE self) { - // return Integer(Value(self)->ToInteger()); - // } - - // VALUE Value::ToUint32(VALUE self) { - // return Uint32(Value(self)->ToUint32()); - // } - - // VALUE Value::ToInt32(VALUE self) { - // return Int32(Value(self)->ToInt32()); - // } - - -// VALUE Value::ToArrayIndex(VALUE self) { -// return Uint32(Value(self)->ToArrayIndex()); -// } - -VALUE Value::BooleanValue(VALUE self) { - return Bool(Value(self)->BooleanValue()); -} -VALUE Value::NumberValue(VALUE self) { - return rb_float_new(Value(self)->NumberValue()); -} -VALUE Value::IntegerValue(VALUE self) { - return INT2NUM(Value(self)->IntegerValue()); -} -VALUE Value::Uint32Value(VALUE self) { - return UINT2NUM(Value(self)->Uint32Value()); -} -VALUE Value::Int32Value(VALUE self) { - return INT2FIX(Value(self)->Int32Value()); -} + store(&Class); + } -VALUE Value::Equals(VALUE self, VALUE other) { - return Bool(Value(self)->Equals(Value(other))); -} + VALUE Value::IsUndefined(VALUE self) { + Value value(self); + Locker lock(value.getIsolate()); -VALUE Value::StrictEquals(VALUE self, VALUE other) { - return Bool(Value(self)->StrictEquals(Value(other))); -} + return Bool(value->IsUndefined()); + } + + VALUE Value::IsNull(VALUE self) { + Value value(self); + Locker lock(value.getIsolate()); + + return Bool(value->IsNull()); + } + + VALUE Value::IsTrue(VALUE self) { + Value value(self); + Locker lock(value.getIsolate()); + + return Bool(value->IsTrue()); + } + + VALUE Value::IsFalse(VALUE self) { + Value value(self); + Locker lock(value.getIsolate()); + + return Bool(value->IsFalse()); + } + + VALUE Value::IsBoolean(VALUE self) { + Value value(self); + Locker lock(value); -Value::operator VALUE() { - if (handle.IsEmpty() || handle->IsUndefined() || handle->IsNull()) { - return Qnil; + return Bool(value->IsBoolean()); } - if (handle->IsTrue()) { - return Qtrue; + + VALUE Value::IsString(VALUE self) { + Value value(self); + Locker lock(value.getIsolate()); + + return Bool(value->IsString()); } - if (handle->IsFalse()) { - return Qfalse; + + VALUE Value::IsFunction(VALUE self) { + Value value(self); + Locker lock(value.getIsolate()); + + return Bool(value->IsFunction()); } - if (handle->IsExternal()) { - return External((v8::Handle)v8::External::Cast(*handle)); + + VALUE Value::IsObject(VALUE self) { + Value value(self); + Locker lock(value.getIsolate()); + + return Bool(value->IsObject()); } - if (handle->IsUint32()) { - return UInt32(handle->Uint32Value()); + + VALUE Value::IsExternal(VALUE self) { + Value value(self); + Locker lock(value.getIsolate()); + + return Bool(value->IsExternal()); } - if (handle->IsInt32()) { - return INT2FIX(handle->Int32Value()); + + VALUE Value::IsNumber(VALUE self) { + Value value(self); + Locker lock(value); + return Bool(value->IsNumber()); } - if (handle->IsBoolean()) { - return handle->BooleanValue() ? Qtrue : Qfalse; + + VALUE Value::IsInt32(VALUE self) { + Value value(self); + Locker lock(value.getIsolate()); + + return Bool(value->IsInt32()); } - if (handle->IsNumber()) { - return rb_float_new(handle->NumberValue()); + + VALUE Value::IsUint32(VALUE self) { + Value value(self); + Locker lock(value.getIsolate()); + + return Bool(value->IsUint32()); } - if (handle->IsString()) { - return String(handle->ToString()); + + VALUE Value::IsNativeError(VALUE self) { + Value value(self); + Locker lock(value); + + return Bool(value->IsNativeError()); } - if (handle->IsDate()) { - return Date((v8::Handle)v8::Date::Cast(*handle)); + + VALUE Value::ToString(VALUE self) { + Value value(self); + Locker lock(value.getIsolate()); + + return String(value.getIsolate(), value->ToString()); } - if (handle->IsObject()) { - return Object(handle->ToObject()); + + VALUE Value::BooleanValue(VALUE self, VALUE r_context) { + Value value(self); + Locker lock(value); + + return Bool::Maybe(value->BooleanValue(Context(r_context))); } - return Ref::operator VALUE(); -} -Value::operator v8::Handle() const { - if (rb_equal(value,Empty)) { - return v8::Handle(); + VALUE Value::Equals(VALUE self, VALUE other) { + Value value(self); + Locker lock(value.getIsolate()); + + return Bool(value->Equals(Value(other))); + } + + VALUE Value::StrictEquals(VALUE self, VALUE other) { + Value value(self); + Locker lock(value.getIsolate()); + + return Bool(value->StrictEquals(Value(other))); + } + + Value::operator VALUE() { + if (handle.IsEmpty()) { + return Qnil; + } + + if (handle->IsUndefined()) { + return Undefined(isolate, handle); + } + + if (handle->IsNull()) { + return Null(isolate, handle); + } + + if (handle->IsBoolean()) { + return Boolean(isolate, handle.As()); + } + + if (handle->IsExternal()) { + return External(isolate, handle); + } + + if (handle->IsUint32()) { + return Uint32(isolate, handle); + } + + if (handle->IsInt32()) { + return Int32(isolate, handle); + } + + if (handle->IsNumber()) { + return Number(isolate, handle); } + + if (handle->IsString()) { + return String(isolate, handle.As()); + } + + if (handle->IsSymbol()) { + return Symbol(isolate, handle.As()); + } + + if (handle->IsName()) { + return Name(isolate, handle.As()); + } + + if (handle->IsFunction()) { + return Function(isolate, v8::Handle::Cast(handle)); + } + + if (handle->IsObject()) { + return Object(isolate, handle->ToObject()); + } + return Ref::operator VALUE(); + } + + v8::Handle Value::rubyObjectToHandle(v8::Isolate* isolate, VALUE value) { switch (TYPE(value)) { case T_FIXNUM: - return v8::Integer::New(NUM2INT(value)); + return v8::Integer::New(isolate, NUM2INT(value)); case T_FLOAT: - return v8::Number::New(NUM2DBL(value)); + return v8::Number::New(isolate, NUM2DBL(value)); case T_STRING: - return v8::String::New(RSTRING_PTR(value), (int)RSTRING_LEN(value)); + return v8::String::NewFromUtf8(isolate, RSTRING_PTR(value), v8::String::kNormalString, (int)RSTRING_LEN(value)); case T_NIL: - return v8::Null(); + return v8::Null(isolate); case T_TRUE: - return v8::True(); + return v8::True(isolate); case T_FALSE: - return v8::False(); + return v8::False(isolate); case T_DATA: - return Ref::operator v8::Handle(); + return Value(value); case T_OBJECT: case T_CLASS: case T_ICLASS: @@ -231,9 +242,20 @@ Value::operator v8::Handle() const { case T_NODE: default: rb_warn("unknown conversion to V8 for: %s", RSTRING_PTR(rb_inspect(value))); - return v8::String::New("Undefined Conversion"); + return v8::String::NewFromUtf8(isolate, "Undefined Conversion"); } - return v8::Undefined(); + return v8::Undefined(isolate); + } + + std::vector< v8::Handle > Value::convertRubyArray(v8::Isolate* isolate, VALUE value) { + std::vector< v8::Handle > vector(RARRAY_LENINT(value)); + + for (uint32_t i = 0; i < vector.size(); i++) { + vector[i] = Value::rubyObjectToHandle(isolate, rb_ary_entry(value, i)); + } + + return vector; + } + } -} \ No newline at end of file diff --git a/ext/v8/value.h b/ext/v8/value.h new file mode 100644 index 00000000..b1f48a8f --- /dev/null +++ b/ext/v8/value.h @@ -0,0 +1,58 @@ +// -*- mode: c++ -*- +#ifndef RR_VALUE +#define RR_VALUE + +namespace rr { + + class Value : public Ref { + public: + typedef MaybeLocal Maybe; + + static void Init(); + + static VALUE IsUndefined(VALUE self); + static VALUE IsNull(VALUE self); + static VALUE IsTrue(VALUE self); + static VALUE IsFalse(VALUE self); + static VALUE IsString(VALUE self); + static VALUE IsFunction(VALUE self); + // static VALUE IsArray(VALUE self); + static VALUE IsObject(VALUE self); + static VALUE IsBoolean(VALUE self); + static VALUE IsNumber(VALUE self); + static VALUE IsExternal(VALUE self); + static VALUE IsInt32(VALUE self); + static VALUE IsUint32(VALUE self); + // static VALUE IsDate(VALUE self); + // static VALUE IsBooleanObject(VALUE self); + // static VALUE IsNumberObject(VALUE self); + // static VALUE IsStringObject(VALUE self); + static VALUE IsNativeError(VALUE self); + // static VALUE IsRegExp(VALUE self); + static VALUE ToString(VALUE self); + // static VALUE ToDetailString(VALUE self); + // static VALUE ToObject(VALUE self); + static VALUE BooleanValue(VALUE self, VALUE context); + // static VALUE NumberValue(VALUE self); + // static VALUE IntegerValue(VALUE self); + // static VALUE Uint32Value(VALUE self); + // static VALUE Int32Value(VALUE self); + + static VALUE ToRubyObject(VALUE self); + static VALUE FromRubyObject(VALUE self, VALUE isolate, VALUE value); + + static VALUE Equals(VALUE self, VALUE other); + static VALUE StrictEquals(VALUE self, VALUE other); + + inline Value(VALUE value) : Ref(value) {} + inline Value(v8::Isolate* isolate, v8::Handle value) : Ref(isolate, value) {} + + operator VALUE(); + static v8::Handle rubyObjectToHandle(v8::Isolate* isolate, VALUE value); + + static std::vector< v8::Handle > convertRubyArray(v8::Isolate* isolate, VALUE value); + }; + +} + +#endif diff --git a/ext/v8/vendor/concurrentqueue.h b/ext/v8/vendor/concurrentqueue.h new file mode 100644 index 00000000..d3f35f27 --- /dev/null +++ b/ext/v8/vendor/concurrentqueue.h @@ -0,0 +1,3542 @@ +// Provides a C++11 implementation of a multi-producer, multi-consumer lock-free queue. +// An overview, including benchmark results, is provided here: +// http://moodycamel.com/blog/2014/a-fast-general-purpose-lock-free-queue-for-c++ +// The full design is also described in excruciating detail at: +// http://moodycamel.com/blog/2014/detailed-design-of-a-lock-free-queue + + +// Simplified BSD license: +// Copyright (c) 2013-2015, Cameron Desrochers. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// - Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// - Redistributions in binary form must reproduce the above copyright notice, this list of +// conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +#pragma once + +#if defined(__GNUC__) +// Disable -Wconversion warnings (spuriously triggered when Traits::size_t and +// Traits::index_t are set to < 32 bits, causing integer promotion, causing warnings +// upon assigning any computed values) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" + +#ifdef MCDBGQ_USE_RELACY +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" +#endif +#endif + +#ifdef MCDBGQ_USE_RELACY +#include "relacy/relacy_std.hpp" +#include "relacy_shims.h" +// We only use malloc/free anyway, and the delete macro messes up `= delete` method declarations. +// We'll override the default trait malloc ourselves without a macro. +#undef new +#undef delete +#undef malloc +#undef free +#else +#include // Requires C++11. Sorry VS2010. +#include +#endif +#include +#include +#include +#include +#include +#include +#include // for CHAR_BIT +#include +#include // for __WINPTHREADS_VERSION if on MinGW-w64 w/ POSIX threading + +// Platform-specific definitions of a numeric thread ID type and an invalid value +#if defined(MCDBGQ_USE_RELACY) +namespace moodycamel { namespace details { + typedef std::uint32_t thread_id_t; + static const thread_id_t invalid_thread_id = 0xFFFFFFFFU; + static const thread_id_t invalid_thread_id2 = 0xFFFFFFFEU; + static inline thread_id_t thread_id() { return rl::thread_index(); } +} } +#elif defined(_WIN32) || defined(__WINDOWS__) || defined(__WIN32__) +// No sense pulling in windows.h in a header, we'll manually declare the function +// we use and rely on backwards-compatibility for this not to break +extern "C" __declspec(dllimport) unsigned long __stdcall GetCurrentThreadId(void); +namespace moodycamel { namespace details { + static_assert(sizeof(unsigned long) == sizeof(std::uint32_t), "Expected size of unsigned long to be 32 bits on Windows"); + typedef std::uint32_t thread_id_t; + static const thread_id_t invalid_thread_id = 0; // See http://blogs.msdn.com/b/oldnewthing/archive/2004/02/23/78395.aspx + static const thread_id_t invalid_thread_id2 = 0xFFFFFFFFU; // Not technically guaranteed to be invalid, but is never used in practice. Note that all Win32 thread IDs are presently multiples of 4. + static inline thread_id_t thread_id() { return static_cast(::GetCurrentThreadId()); } +} } +#else +// Use a nice trick from this answer: http://stackoverflow.com/a/8438730/21475 +// In order to get a numeric thread ID in a platform-independent way, we use a thread-local +// static variable's address as a thread identifier :-) +#if defined(__GNUC__) || defined(__INTEL_COMPILER) +#define MOODYCAMEL_THREADLOCAL __thread +#elif defined(_MSC_VER) +#define MOODYCAMEL_THREADLOCAL __declspec(thread) +#else +// Assume C++11 compliant compiler +#define MOODYCAMEL_THREADLOCAL thread_local +#endif +namespace moodycamel { namespace details { + typedef std::uintptr_t thread_id_t; + static const thread_id_t invalid_thread_id = 0; // Address can't be nullptr + static const thread_id_t invalid_thread_id2 = 1; // Member accesses off a null pointer are also generally invalid. Plus it's not aligned. + static inline thread_id_t thread_id() { static MOODYCAMEL_THREADLOCAL int x; return reinterpret_cast(&x); } +} } +#endif + +// Exceptions +#ifndef MOODYCAMEL_EXCEPTIONS_ENABLED +#if (defined(_MSC_VER) && defined(_CPPUNWIND)) || (defined(__GNUC__) && defined(__EXCEPTIONS)) || (!defined(_MSC_VER) && !defined(__GNUC__)) +#define MOODYCAMEL_EXCEPTIONS_ENABLED +#define MOODYCAMEL_TRY try +#define MOODYCAMEL_CATCH(...) catch(__VA_ARGS__) +#define MOODYCAMEL_RETHROW throw +#define MOODYCAMEL_THROW(expr) throw (expr) +#else +#define MOODYCAMEL_TRY if (true) +#define MOODYCAMEL_CATCH(...) else if (false) +#define MOODYCAMEL_RETHROW +#define MOODYCAMEL_THROW(expr) +#endif +#endif + +#ifndef MOODYCAMEL_NOEXCEPT +#if !defined(MOODYCAMEL_EXCEPTIONS_ENABLED) +#define MOODYCAMEL_NOEXCEPT +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) true +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) true +#elif defined(_MSC_VER) && defined(_NOEXCEPT) && _MSC_VER < 1800 +// VS2012's std::is_nothrow_[move_]constructible is broken and returns true when it shouldn't :-( +// We have to assume *all* non-trivial constructors may throw on VS2012! +#define MOODYCAMEL_NOEXCEPT _NOEXCEPT +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) (std::is_rvalue_reference::value && std::is_move_constructible::value ? std::is_trivially_move_constructible::value : std::is_trivially_copy_constructible::value) +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) ((std::is_rvalue_reference::value && std::is_move_assignable::value ? std::is_trivially_move_assignable::value || std::is_nothrow_move_assignable::value : std::is_trivially_copy_assignable::value || std::is_nothrow_copy_assignable::value) && MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr)) +#elif defined(_MSC_VER) && defined(_NOEXCEPT) && _MSC_VER < 1900 +#define MOODYCAMEL_NOEXCEPT _NOEXCEPT +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) (std::is_rvalue_reference::value && std::is_move_constructible::value ? std::is_trivially_move_constructible::value || std::is_nothrow_move_constructible::value : std::is_trivially_copy_constructible::value || std::is_nothrow_copy_constructible::value) +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) ((std::is_rvalue_reference::value && std::is_move_assignable::value ? std::is_trivially_move_assignable::value || std::is_nothrow_move_assignable::value : std::is_trivially_copy_assignable::value || std::is_nothrow_copy_assignable::value) && MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr)) +#else +#define MOODYCAMEL_NOEXCEPT noexcept +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) noexcept(expr) +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) noexcept(expr) +#endif +#endif + +#ifndef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED +#ifdef MCDBGQ_USE_RELACY +#define MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED +#else +//// VS2013 doesn't support `thread_local`, and MinGW-w64 w/ POSIX threading has a crippling bug: http://sourceforge.net/p/mingw-w64/bugs/445 +//// g++ <=4.7 doesn't support thread_local either +//#if (!defined(_MSC_VER) || _MSC_VER >= 1900) && (!defined(__MINGW32__) && !defined(__MINGW64__) || !defined(__WINPTHREADS_VERSION)) && (!defined(__GNUC__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) +//// Assume `thread_local` is fully supported in all other C++11 compilers/runtimes +//#define MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED +//#endif +#endif +#endif + +// VS2012 doesn't support deleted functions. +// In this case, we declare the function normally but don't define it. A link error will be generated if the function is called. +#ifndef MOODYCAMEL_DELETE_FUNCTION +#if defined(_MSC_VER) && _MSC_VER < 1800 +#define MOODYCAMEL_DELETE_FUNCTION +#else +#define MOODYCAMEL_DELETE_FUNCTION = delete +#endif +#endif + +// Compiler-specific likely/unlikely hints +namespace moodycamel { namespace details { +#if defined(__GNUC__) + inline bool likely(bool x) { return __builtin_expect((x), true); } + inline bool unlikely(bool x) { return __builtin_expect((x), false); } +#else + inline bool likely(bool x) { return x; } + inline bool unlikely(bool x) { return x; } +#endif +} } + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG +#include "internal/concurrentqueue_internal_debug.h" +#endif + +namespace moodycamel { +namespace details { + template + struct const_numeric_max { + static_assert(std::is_integral::value, "const_numeric_max can only be used with integers"); + static const T value = std::numeric_limits::is_signed + ? (static_cast(1) << (sizeof(T) * CHAR_BIT - 1)) - static_cast(1) + : static_cast(-1); + }; +} + +// Default traits for the ConcurrentQueue. To change some of the +// traits without re-implementing all of them, inherit from this +// struct and shadow the declarations you wish to be different; +// since the traits are used as a template type parameter, the +// shadowed declarations will be used where defined, and the defaults +// otherwise. +struct ConcurrentQueueDefaultTraits +{ + // General-purpose size type. std::size_t is strongly recommended. + typedef std::size_t size_t; + + // The type used for the enqueue and dequeue indices. Must be at least as + // large as size_t. Should be significantly larger than the number of elements + // you expect to hold at once, especially if you have a high turnover rate; + // for example, on 32-bit x86, if you expect to have over a hundred million + // elements or pump several million elements through your queue in a very + // short space of time, using a 32-bit type *may* trigger a race condition. + // A 64-bit int type is recommended in that case, and in practice will + // prevent a race condition no matter the usage of the queue. Note that + // whether the queue is lock-free with a 64-int type depends on the whether + // std::atomic is lock-free, which is platform-specific. + typedef std::size_t index_t; + + // Internally, all elements are enqueued and dequeued from multi-element + // blocks; this is the smallest controllable unit. If you expect few elements + // but many producers, a smaller block size should be favoured. For few producers + // and/or many elements, a larger block size is preferred. A sane default + // is provided. Must be a power of 2. + static const size_t BLOCK_SIZE = 32; + + // For explicit producers (i.e. when using a producer token), the block is + // checked for being empty by iterating through a list of flags, one per element. + // For large block sizes, this is too inefficient, and switching to an atomic + // counter-based approach is faster. The switch is made for block sizes strictly + // larger than this threshold. + static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = 32; + + // How many full blocks can be expected for a single explicit producer? This should + // reflect that number's maximum for optimal performance. Must be a power of 2. + static const size_t EXPLICIT_INITIAL_INDEX_SIZE = 32; + + // How many full blocks can be expected for a single implicit producer? This should + // reflect that number's maximum for optimal performance. Must be a power of 2. + static const size_t IMPLICIT_INITIAL_INDEX_SIZE = 32; + + // The initial size of the hash table mapping thread IDs to implicit producers. + // Note that the hash is resized every time it becomes half full. + // Must be a power of two, and either 0 or at least 1. If 0, implicit production + // (using the enqueue methods without an explicit producer token) is disabled. + static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = 32; + + // Controls the number of items that an explicit consumer (i.e. one with a token) + // must consume before it causes all consumers to rotate and move on to the next + // internal queue. + static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = 256; + + // The maximum number of elements (inclusive) that can be enqueued to a sub-queue. + // Enqueue operations that would cause this limit to be surpassed will fail. Note + // that this limit is enforced at the block level (for performance reasons), i.e. + // it's rounded up to the nearest block size. + static const size_t MAX_SUBQUEUE_SIZE = details::const_numeric_max::value; + + +#ifndef MCDBGQ_USE_RELACY + // Memory allocation can be customized if needed. + // malloc should return nullptr on failure, and handle alignment like std::malloc. + static inline void* malloc(size_t size) { return std::malloc(size); } + static inline void free(void* ptr) { return std::free(ptr); } +#else + // Debug versions when running under the Relacy race detector (ignore + // these in user code) + static inline void* malloc(size_t size) { return rl::rl_malloc(size, $); } + static inline void free(void* ptr) { return rl::rl_free(ptr, $); } +#endif +}; + + +// When producing or consuming many elements, the most efficient way is to: +// 1) Use one of the bulk-operation methods of the queue with a token +// 2) Failing that, use the bulk-operation methods without a token +// 3) Failing that, create a token and use that with the single-item methods +// 4) Failing that, use the single-parameter methods of the queue +// Having said that, don't create tokens willy-nilly -- ideally there should be +// a maximum of one token per thread (of each kind). +struct ProducerToken; +struct ConsumerToken; + +template class ConcurrentQueue; +template class BlockingConcurrentQueue; +class ConcurrentQueueTests; + + +namespace details +{ + struct ConcurrentQueueProducerTypelessBase + { + ConcurrentQueueProducerTypelessBase* next; + std::atomic inactive; + ProducerToken* token; + + ConcurrentQueueProducerTypelessBase() + : inactive(false), token(nullptr) + { + } + }; + + template struct _hash_32_or_64 { + static inline std::uint32_t hash(std::uint32_t h) + { + // MurmurHash3 finalizer -- see https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp + // Since the thread ID is already unique, all we really want to do is propagate that + // uniqueness evenly across all the bits, so that we can use a subset of the bits while + // reducing collisions significantly + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + return h ^ (h >> 16); + } + }; + template<> struct _hash_32_or_64<1> { + static inline std::uint64_t hash(std::uint64_t h) + { + h ^= h >> 33; + h *= 0xff51afd7ed558ccd; + h ^= h >> 33; + h *= 0xc4ceb9fe1a85ec53; + return h ^ (h >> 33); + } + }; + template struct hash_32_or_64 : public _hash_32_or_64<(size > 4)> { }; + + static inline size_t hash_thread_id(thread_id_t id) + { + static_assert(sizeof(thread_id_t) <= 8, "Expected a platform where thread IDs are at most 64-bit values"); + return static_cast(hash_32_or_64::hash(id)); + } + + template + static inline bool circular_less_than(T a, T b) + { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4554) +#endif + static_assert(std::is_integral::value && !std::numeric_limits::is_signed, "circular_less_than is intended to be used only with unsigned integer types"); + return static_cast(a - b) > static_cast(static_cast(1) << static_cast(sizeof(T) * CHAR_BIT - 1)); +#ifdef _MSC_VER +#pragma warning(pop) +#endif + } + + template + static inline char* align_for(char* ptr) + { + const std::size_t alignment = std::alignment_of::value; + return ptr + (alignment - (reinterpret_cast(ptr) % alignment)) % alignment; + } + + template + static inline T ceil_to_pow_2(T x) + { + static_assert(std::is_integral::value && !std::numeric_limits::is_signed, "ceil_to_pow_2 is intended to be used only with unsigned integer types"); + + // Adapted from http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 + --x; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + for (std::size_t i = 1; i < sizeof(T); i <<= 1) { + x |= x >> (i << 3); + } + ++x; + return x; + } + + template + static inline void swap_relaxed(std::atomic& left, std::atomic& right) + { + T temp = std::move(left.load(std::memory_order_relaxed)); + left.store(std::move(right.load(std::memory_order_relaxed)), std::memory_order_relaxed); + right.store(std::move(temp), std::memory_order_relaxed); + } + + template + static inline T const& nomove(T const& x) + { + return x; + } + + template + struct nomove_if + { + template + static inline T const& eval(T const& x) + { + return x; + } + }; + + template<> + struct nomove_if + { + template + static inline auto eval(U&& x) + -> decltype(std::forward(x)) + { + return std::forward(x); + } + }; + + template + static inline auto deref_noexcept(It& it) MOODYCAMEL_NOEXCEPT -> decltype(*it) + { + return *it; + } + +#if defined(__APPLE__) || defined(__clang__) || !defined(__GNUC__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) + template struct is_trivially_destructible : std::is_trivially_destructible { }; +#else + template struct is_trivially_destructible : std::has_trivial_destructor { }; +#endif + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED +#ifdef MCDBGQ_USE_RELACY + typedef RelacyThreadExitListener ThreadExitListener; + typedef RelacyThreadExitNotifier ThreadExitNotifier; +#else + struct ThreadExitListener + { + typedef void (*callback_t)(void*); + callback_t callback; + void* userData; + + ThreadExitListener* next; // reserved for use by the ThreadExitNotifier + }; + + + class ThreadExitNotifier + { + public: + static void subscribe(ThreadExitListener* listener) + { + auto& tlsInst = instance(); + listener->next = tlsInst.tail; + tlsInst.tail = listener; + } + + static void unsubscribe(ThreadExitListener* listener) + { + auto& tlsInst = instance(); + ThreadExitListener** prev = &tlsInst.tail; + for (auto ptr = tlsInst.tail; ptr != nullptr; ptr = ptr->next) { + if (ptr == listener) { + *prev = ptr->next; + break; + } + prev = &ptr->next; + } + } + + private: + ThreadExitNotifier() : tail(nullptr) { } + ThreadExitNotifier(ThreadExitNotifier const&) MOODYCAMEL_DELETE_FUNCTION; + ThreadExitNotifier& operator=(ThreadExitNotifier const&) MOODYCAMEL_DELETE_FUNCTION; + + ~ThreadExitNotifier() + { + // This thread is about to exit, let everyone know! + assert(this == &instance() && "If this assert fails, you likely have a buggy compiler! Change the preprocessor conditions such that MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED is no longer defined."); + for (auto ptr = tail; ptr != nullptr; ptr = ptr->next) { + ptr->callback(ptr->userData); + } + } + + // Thread-local + static inline ThreadExitNotifier& instance() + { + static thread_local ThreadExitNotifier notifier; + return notifier; + } + + private: + ThreadExitListener* tail; + }; +#endif +#endif + + template struct static_is_lock_free_num { enum { value = 0 }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_CHAR_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_SHORT_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_INT_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_LONG_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_LLONG_LOCK_FREE }; }; + template struct static_is_lock_free : static_is_lock_free_num::type> { }; + template<> struct static_is_lock_free { enum { value = ATOMIC_BOOL_LOCK_FREE }; }; + template struct static_is_lock_free { enum { value = ATOMIC_POINTER_LOCK_FREE }; }; +} + + +struct ProducerToken +{ + template + explicit ProducerToken(ConcurrentQueue& queue); + + template + explicit ProducerToken(BlockingConcurrentQueue& queue); + + explicit ProducerToken(ProducerToken&& other) MOODYCAMEL_NOEXCEPT + : producer(other.producer) + { + other.producer = nullptr; + if (producer != nullptr) { + producer->token = this; + } + } + + inline ProducerToken& operator=(ProducerToken&& other) MOODYCAMEL_NOEXCEPT + { + swap(other); + return *this; + } + + void swap(ProducerToken& other) MOODYCAMEL_NOEXCEPT + { + std::swap(producer, other.producer); + if (producer != nullptr) { + producer->token = this; + } + if (other.producer != nullptr) { + other.producer->token = &other; + } + } + + // A token is always valid unless: + // 1) Memory allocation failed during construction + // 2) It was moved via the move constructor + // (Note: assignment does a swap, leaving both potentially valid) + // 3) The associated queue was destroyed + // Note that if valid() returns true, that only indicates + // that the token is valid for use with a specific queue, + // but not which one; that's up to the user to track. + inline bool valid() const { return producer != nullptr; } + + ~ProducerToken() + { + if (producer != nullptr) { + producer->token = nullptr; + producer->inactive.store(true, std::memory_order_release); + } + } + + // Disable copying and assignment + ProducerToken(ProducerToken const&) MOODYCAMEL_DELETE_FUNCTION; + ProducerToken& operator=(ProducerToken const&) MOODYCAMEL_DELETE_FUNCTION; + +private: + template friend class ConcurrentQueue; + friend class ConcurrentQueueTests; + +protected: + details::ConcurrentQueueProducerTypelessBase* producer; +}; + + +struct ConsumerToken +{ + template + explicit ConsumerToken(ConcurrentQueue& q); + + template + explicit ConsumerToken(BlockingConcurrentQueue& q); + + explicit ConsumerToken(ConsumerToken&& other) MOODYCAMEL_NOEXCEPT + : initialOffset(other.initialOffset), lastKnownGlobalOffset(other.lastKnownGlobalOffset), itemsConsumedFromCurrent(other.itemsConsumedFromCurrent), currentProducer(other.currentProducer), desiredProducer(other.desiredProducer) + { + } + + inline ConsumerToken& operator=(ConsumerToken&& other) MOODYCAMEL_NOEXCEPT + { + swap(other); + return *this; + } + + void swap(ConsumerToken& other) MOODYCAMEL_NOEXCEPT + { + std::swap(initialOffset, other.initialOffset); + std::swap(lastKnownGlobalOffset, other.lastKnownGlobalOffset); + std::swap(itemsConsumedFromCurrent, other.itemsConsumedFromCurrent); + std::swap(currentProducer, other.currentProducer); + std::swap(desiredProducer, other.desiredProducer); + } + + // Disable copying and assignment + ConsumerToken(ConsumerToken const&) MOODYCAMEL_DELETE_FUNCTION; + ConsumerToken& operator=(ConsumerToken const&) MOODYCAMEL_DELETE_FUNCTION; + +private: + template friend class ConcurrentQueue; + friend class ConcurrentQueueTests; + +private: // but shared with ConcurrentQueue + std::uint32_t initialOffset; + std::uint32_t lastKnownGlobalOffset; + std::uint32_t itemsConsumedFromCurrent; + details::ConcurrentQueueProducerTypelessBase* currentProducer; + details::ConcurrentQueueProducerTypelessBase* desiredProducer; +}; + +// Need to forward-declare this swap because it's in a namespace. +// See http://stackoverflow.com/questions/4492062/why-does-a-c-friend-class-need-a-forward-declaration-only-in-other-namespaces +template +inline void swap(typename ConcurrentQueue::ImplicitProducerKVP& a, typename ConcurrentQueue::ImplicitProducerKVP& b) MOODYCAMEL_NOEXCEPT; + + +template +class ConcurrentQueue +{ +public: + typedef ::moodycamel::ProducerToken producer_token_t; + typedef ::moodycamel::ConsumerToken consumer_token_t; + + typedef typename Traits::index_t index_t; + typedef typename Traits::size_t size_t; + + static const size_t BLOCK_SIZE = static_cast(Traits::BLOCK_SIZE); + static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = static_cast(Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD); + static const size_t EXPLICIT_INITIAL_INDEX_SIZE = static_cast(Traits::EXPLICIT_INITIAL_INDEX_SIZE); + static const size_t IMPLICIT_INITIAL_INDEX_SIZE = static_cast(Traits::IMPLICIT_INITIAL_INDEX_SIZE); + static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = static_cast(Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE); + static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = static_cast(Traits::EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE); +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4307) // + integral constant overflow (that's what the ternary expression is for!) +#pragma warning(disable: 4309) // static_cast: Truncation of constant value +#endif + static const size_t MAX_SUBQUEUE_SIZE = (details::const_numeric_max::value - static_cast(Traits::MAX_SUBQUEUE_SIZE) < BLOCK_SIZE) ? details::const_numeric_max::value : ((static_cast(Traits::MAX_SUBQUEUE_SIZE) + (BLOCK_SIZE - 1)) / BLOCK_SIZE * BLOCK_SIZE); +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + static_assert(!std::numeric_limits::is_signed && std::is_integral::value, "Traits::size_t must be an unsigned integral type"); + static_assert(!std::numeric_limits::is_signed && std::is_integral::value, "Traits::index_t must be an unsigned integral type"); + static_assert(sizeof(index_t) >= sizeof(size_t), "Traits::index_t must be at least as wide as Traits::size_t"); + static_assert((BLOCK_SIZE > 1) && !(BLOCK_SIZE & (BLOCK_SIZE - 1)), "Traits::BLOCK_SIZE must be a power of 2 (and at least 2)"); + static_assert((EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD > 1) && !(EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD & (EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD - 1)), "Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD must be a power of 2 (and greater than 1)"); + static_assert((EXPLICIT_INITIAL_INDEX_SIZE > 1) && !(EXPLICIT_INITIAL_INDEX_SIZE & (EXPLICIT_INITIAL_INDEX_SIZE - 1)), "Traits::EXPLICIT_INITIAL_INDEX_SIZE must be a power of 2 (and greater than 1)"); + static_assert((IMPLICIT_INITIAL_INDEX_SIZE > 1) && !(IMPLICIT_INITIAL_INDEX_SIZE & (IMPLICIT_INITIAL_INDEX_SIZE - 1)), "Traits::IMPLICIT_INITIAL_INDEX_SIZE must be a power of 2 (and greater than 1)"); + static_assert((INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) || !(INITIAL_IMPLICIT_PRODUCER_HASH_SIZE & (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE - 1)), "Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE must be a power of 2"); + static_assert(INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0 || INITIAL_IMPLICIT_PRODUCER_HASH_SIZE >= 1, "Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE must be at least 1 (or 0 to disable implicit enqueueing)"); + +public: + // Creates a queue with at least `capacity` element slots; note that the + // actual number of elements that can be inserted without additional memory + // allocation depends on the number of producers and the block size (e.g. if + // the block size is equal to `capacity`, only a single block will be allocated + // up-front, which means only a single producer will be able to enqueue elements + // without an extra allocation -- blocks aren't shared between producers). + // This method is not thread safe -- it is up to the user to ensure that the + // queue is fully constructed before it starts being used by other threads (this + // includes making the memory effects of construction visible, possibly with a + // memory barrier). + explicit ConcurrentQueue(size_t capacity = 6 * BLOCK_SIZE) + : producerListTail(nullptr), + producerCount(0), + initialBlockPoolIndex(0), + nextExplicitConsumerId(0), + globalExplicitConsumerOffset(0) + { + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + populate_initial_implicit_producer_hash(); + populate_initial_block_list(capacity / BLOCK_SIZE + ((capacity & (BLOCK_SIZE - 1)) == 0 ? 0 : 1)); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + // Track all the producers using a fully-resolved typed list for + // each kind; this makes it possible to debug them starting from + // the root queue object (otherwise wacky casts are needed that + // don't compile in the debugger's expression evaluator). + explicitProducers.store(nullptr, std::memory_order_relaxed); + implicitProducers.store(nullptr, std::memory_order_relaxed); +#endif + } + + // Computes the correct amount of pre-allocated blocks for you based + // on the minimum number of elements you want available at any given + // time, and the maximum concurrent number of each type of producer. + ConcurrentQueue(size_t minCapacity, size_t maxExplicitProducers, size_t maxImplicitProducers) + : producerListTail(nullptr), + producerCount(0), + initialBlockPoolIndex(0), + nextExplicitConsumerId(0), + globalExplicitConsumerOffset(0) + { + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + populate_initial_implicit_producer_hash(); + size_t blocks = ((((minCapacity + BLOCK_SIZE - 1) / BLOCK_SIZE) - 1) * (maxExplicitProducers + 1) + 2 * (maxExplicitProducers + maxImplicitProducers)) * BLOCK_SIZE; + populate_initial_block_list(blocks); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + explicitProducers.store(nullptr, std::memory_order_relaxed); + implicitProducers.store(nullptr, std::memory_order_relaxed); +#endif + } + + // Note: The queue should not be accessed concurrently while it's + // being deleted. It's up to the user to synchronize this. + // This method is not thread safe. + ~ConcurrentQueue() + { + // Destroy producers + auto ptr = producerListTail.load(std::memory_order_relaxed); + while (ptr != nullptr) { + auto next = ptr->next_prod(); + if (ptr->token != nullptr) { + ptr->token->producer = nullptr; + } + destroy(ptr); + ptr = next; + } + + // Destroy implicit producer hash tables + if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE != 0) { + auto hash = implicitProducerHash.load(std::memory_order_relaxed); + while (hash != nullptr) { + auto prev = hash->prev; + if (prev != nullptr) { // The last hash is part of this object and was not allocated dynamically + for (size_t i = 0; i != hash->capacity; ++i) { + hash->entries[i].~ImplicitProducerKVP(); + } + hash->~ImplicitProducerHash(); + Traits::free(hash); + } + hash = prev; + } + } + + // Destroy global free list + auto block = freeList.head_unsafe(); + while (block != nullptr) { + auto next = block->freeListNext.load(std::memory_order_relaxed); + if (block->dynamicallyAllocated) { + destroy(block); + } + block = next; + } + + // Destroy initial free list + destroy_array(initialBlockPool, initialBlockPoolSize); + } + + // Disable copying and copy assignment + ConcurrentQueue(ConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; + ConcurrentQueue& operator=(ConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; + + // Moving is supported, but note that it is *not* a thread-safe operation. + // Nobody can use the queue while it's being moved, and the memory effects + // of that move must be propagated to other threads before they can use it. + // Note: When a queue is moved, its tokens are still valid but can only be + // used with the destination queue (i.e. semantically they are moved along + // with the queue itself). + ConcurrentQueue(ConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT + : producerListTail(other.producerListTail.load(std::memory_order_relaxed)), + producerCount(other.producerCount.load(std::memory_order_relaxed)), + initialBlockPoolIndex(other.initialBlockPoolIndex.load(std::memory_order_relaxed)), + initialBlockPool(other.initialBlockPool), + initialBlockPoolSize(other.initialBlockPoolSize), + freeList(std::move(other.freeList)), + nextExplicitConsumerId(other.nextExplicitConsumerId.load(std::memory_order_relaxed)), + globalExplicitConsumerOffset(other.globalExplicitConsumerOffset.load(std::memory_order_relaxed)) + { + // Move the other one into this, and leave the other one as an empty queue + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + populate_initial_implicit_producer_hash(); + swap_implicit_producer_hashes(other); + + other.producerListTail.store(nullptr, std::memory_order_relaxed); + other.producerCount.store(0, std::memory_order_relaxed); + other.nextExplicitConsumerId.store(0, std::memory_order_relaxed); + other.globalExplicitConsumerOffset.store(0, std::memory_order_relaxed); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + explicitProducers.store(other.explicitProducers.load(std::memory_order_relaxed), std::memory_order_relaxed); + other.explicitProducers.store(nullptr, std::memory_order_relaxed); + implicitProducers.store(other.implicitProducers.load(std::memory_order_relaxed), std::memory_order_relaxed); + other.implicitProducers.store(nullptr, std::memory_order_relaxed); +#endif + + other.initialBlockPoolIndex.store(0, std::memory_order_relaxed); + other.initialBlockPoolSize = 0; + other.initialBlockPool = nullptr; + + reown_producers(); + } + + inline ConcurrentQueue& operator=(ConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT + { + return swap_internal(other); + } + + // Swaps this queue's state with the other's. Not thread-safe. + // Swapping two queues does not invalidate their tokens, however + // the tokens that were created for one queue must be used with + // only the swapped queue (i.e. the tokens are tied to the + // queue's movable state, not the object itself). + inline void swap(ConcurrentQueue& other) MOODYCAMEL_NOEXCEPT + { + swap_internal(other); + } + +private: + ConcurrentQueue& swap_internal(ConcurrentQueue& other) + { + if (this == &other) { + return *this; + } + + details::swap_relaxed(producerListTail, other.producerListTail); + details::swap_relaxed(producerCount, other.producerCount); + details::swap_relaxed(initialBlockPoolIndex, other.initialBlockPoolIndex); + std::swap(initialBlockPool, other.initialBlockPool); + std::swap(initialBlockPoolSize, other.initialBlockPoolSize); + freeList.swap(other.freeList); + details::swap_relaxed(nextExplicitConsumerId, other.nextExplicitConsumerId); + details::swap_relaxed(globalExplicitConsumerOffset, other.globalExplicitConsumerOffset); + + swap_implicit_producer_hashes(other); + + reown_producers(); + other.reown_producers(); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + details::swap_relaxed(explicitProducers, other.explicitProducers); + details::swap_relaxed(implicitProducers, other.implicitProducers); +#endif + + return *this; + } + +public: + // Enqueues a single item (by copying it). + // Allocates memory if required. Only fails if memory allocation fails (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, + // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(T const& item) + { + if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + return inner_enqueue(item); + } + + // Enqueues a single item (by moving it, if possible). + // Allocates memory if required. Only fails if memory allocation fails (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, + // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(T&& item) + { + if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + return inner_enqueue(std::move(item)); + } + + // Enqueues a single item (by copying it) using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails (or + // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(producer_token_t const& token, T const& item) + { + return inner_enqueue(token, item); + } + + // Enqueues a single item (by moving it, if possible) using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails (or + // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(producer_token_t const& token, T&& item) + { + return inner_enqueue(token, std::move(item)); + } + + // Enqueues several items. + // Allocates memory if required. Only fails if memory allocation fails (or + // implicit production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE + // is 0, or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Note: Use std::make_move_iterator if the elements should be moved instead of copied. + // Thread-safe. + template + bool enqueue_bulk(It itemFirst, size_t count) + { + if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + return inner_enqueue_bulk(std::forward(itemFirst), count); + } + + // Enqueues several items using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails + // (or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + bool enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + return inner_enqueue_bulk(token, std::forward(itemFirst), count); + } + + // Enqueues a single item (by copying it). + // Does not allocate memory. Fails if not enough room to enqueue (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE + // is 0). + // Thread-safe. + inline bool try_enqueue(T const& item) + { + if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + return inner_enqueue(item); + } + + // Enqueues a single item (by moving it, if possible). + // Does not allocate memory (except for one-time implicit producer). + // Fails if not enough room to enqueue (or implicit production is + // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). + // Thread-safe. + inline bool try_enqueue(T&& item) + { + if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + return inner_enqueue(std::move(item)); + } + + // Enqueues a single item (by copying it) using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Thread-safe. + inline bool try_enqueue(producer_token_t const& token, T const& item) + { + return inner_enqueue(token, item); + } + + // Enqueues a single item (by moving it, if possible) using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Thread-safe. + inline bool try_enqueue(producer_token_t const& token, T&& item) + { + return inner_enqueue(token, std::move(item)); + } + + // Enqueues several items. + // Does not allocate memory (except for one-time implicit producer). + // Fails if not enough room to enqueue (or implicit production is + // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + bool try_enqueue_bulk(It itemFirst, size_t count) + { + if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + return inner_enqueue_bulk(std::forward(itemFirst), count); + } + + // Enqueues several items using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + bool try_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + return inner_enqueue_bulk(token, std::forward(itemFirst), count); + } + + + + // Attempts to dequeue from the queue. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + bool try_dequeue(U& item) + { + // Instead of simply trying each producer in turn (which could cause needless contention on the first + // producer), we score them heuristically. + size_t nonEmptyCount = 0; + ProducerBase* best = nullptr; + size_t bestSize = 0; + for (auto ptr = producerListTail.load(std::memory_order_acquire); nonEmptyCount < 3 && ptr != nullptr; ptr = ptr->next_prod()) { + auto size = ptr->size_approx(); + if (size > 0) { + if (size > bestSize) { + bestSize = size; + best = ptr; + } + ++nonEmptyCount; + } + } + + // If there was at least one non-empty queue but it appears empty at the time + // we try to dequeue from it, we need to make sure every queue's been tried + if (nonEmptyCount > 0) { + if (details::likely(best->dequeue(item))) { + return true; + } + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + if (ptr != best && ptr->dequeue(item)) { + return true; + } + } + } + return false; + } + + // Attempts to dequeue from the queue. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // This differs from the try_dequeue(item) method in that this one does + // not attempt to reduce contention by interleaving the order that producer + // streams are dequeued from. So, using this method can reduce overall throughput + // under contention, but will give more predictable results in single-threaded + // consumer scenarios. This is mostly only useful for internal unit tests. + // Never allocates. Thread-safe. + template + bool try_dequeue_non_interleaved(U& item) + { + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + if (ptr->dequeue(item)) { + return true; + } + } + return false; + } + + // Attempts to dequeue from the queue using an explicit consumer token. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + bool try_dequeue(consumer_token_t& token, U& item) + { + // The idea is roughly as follows: + // Every 256 items from one producer, make everyone rotate (increase the global offset) -> this means the highest efficiency consumer dictates the rotation speed of everyone else, more or less + // If you see that the global offset has changed, you must reset your consumption counter and move to your designated place + // If there's no items where you're supposed to be, keep moving until you find a producer with some items + // If the global offset has not changed but you've run out of items to consume, move over from your current position until you find an producer with something in it + + if (token.desiredProducer == nullptr || token.lastKnownGlobalOffset != globalExplicitConsumerOffset.load(std::memory_order_relaxed)) { + if (!update_current_producer_after_rotation(token)) { + return false; + } + } + + // If there was at least one non-empty queue but it appears empty at the time + // we try to dequeue from it, we need to make sure every queue's been tried + if (static_cast(token.currentProducer)->dequeue(item)) { + if (++token.itemsConsumedFromCurrent == EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE) { + globalExplicitConsumerOffset.fetch_add(1, std::memory_order_relaxed); + } + return true; + } + + auto tail = producerListTail.load(std::memory_order_acquire); + auto ptr = static_cast(token.currentProducer)->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + while (ptr != static_cast(token.currentProducer)) { + if (ptr->dequeue(item)) { + token.currentProducer = ptr; + token.itemsConsumedFromCurrent = 1; + return true; + } + ptr = ptr->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + } + return false; + } + + // Attempts to dequeue several elements from the queue. + // Returns the number of items actually dequeued. + // Returns 0 if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + size_t try_dequeue_bulk(It itemFirst, size_t max) + { + size_t count = 0; + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + count += ptr->dequeue_bulk(itemFirst, max - count); + if (count == max) { + break; + } + } + return count; + } + + // Attempts to dequeue several elements from the queue using an explicit consumer token. + // Returns the number of items actually dequeued. + // Returns 0 if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + size_t try_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max) + { + if (token.desiredProducer == nullptr || token.lastKnownGlobalOffset != globalExplicitConsumerOffset.load(std::memory_order_relaxed)) { + if (!update_current_producer_after_rotation(token)) { + return false; + } + } + + size_t count = static_cast(token.currentProducer)->dequeue_bulk(itemFirst, max); + if (count == max) { + if ((token.itemsConsumedFromCurrent += static_cast(max)) >= EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE) { + globalExplicitConsumerOffset.fetch_add(1, std::memory_order_relaxed); + } + return max; + } + token.itemsConsumedFromCurrent += static_cast(count); + max -= count; + + auto tail = producerListTail.load(std::memory_order_acquire); + auto ptr = static_cast(token.currentProducer)->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + while (ptr != static_cast(token.currentProducer)) { + auto dequeued = ptr->dequeue_bulk(itemFirst, max); + count += dequeued; + if (dequeued != 0) { + token.currentProducer = ptr; + token.itemsConsumedFromCurrent = static_cast(dequeued); + } + if (dequeued == max) { + break; + } + max -= dequeued; + ptr = ptr->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + } + return count; + } + + + + // Attempts to dequeue from a specific producer's inner queue. + // If you happen to know which producer you want to dequeue from, this + // is significantly faster than using the general-case try_dequeue methods. + // Returns false if the producer's queue appeared empty at the time it + // was checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline bool try_dequeue_from_producer(producer_token_t const& producer, U& item) + { + return static_cast(producer.producer)->dequeue(item); + } + + // Attempts to dequeue several elements from a specific producer's inner queue. + // Returns the number of items actually dequeued. + // If you happen to know which producer you want to dequeue from, this + // is significantly faster than using the general-case try_dequeue methods. + // Returns 0 if the producer's queue appeared empty at the time it + // was checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline size_t try_dequeue_bulk_from_producer(producer_token_t const& producer, It itemFirst, size_t max) + { + return static_cast(producer.producer)->dequeue_bulk(itemFirst, max); + } + + + // Returns an estimate of the total number of elements currently in the queue. This + // estimate is only accurate if the queue has completely stabilized before it is called + // (i.e. all enqueue and dequeue operations have completed and their memory effects are + // visible on the calling thread, and no further operations start while this method is + // being called). + // Thread-safe. + size_t size_approx() const + { + size_t size = 0; + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + size += ptr->size_approx(); + } + return size; + } + + + // Returns true if the underlying atomic variables used by + // the queue are lock-free (they should be on most platforms). + // Thread-safe. + static bool is_lock_free() + { + return + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2; + } + + +private: + friend struct ProducerToken; + friend struct ConsumerToken; + friend struct ExplicitProducer; + friend class ConcurrentQueueTests; + + enum AllocationMode { CanAlloc, CannotAlloc }; + + + /////////////////////////////// + // Queue methods + /////////////////////////////// + + template + inline bool inner_enqueue(producer_token_t const& token, U&& element) + { + return static_cast(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue(std::forward(element)); + } + + template + inline bool inner_enqueue(U&& element) + { + auto producer = get_or_add_implicit_producer(); + return producer == nullptr ? false : producer->ConcurrentQueue::ImplicitProducer::template enqueue(std::forward(element)); + } + + template + inline bool inner_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + return static_cast(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue_bulk(std::forward(itemFirst), count); + } + + template + inline bool inner_enqueue_bulk(It itemFirst, size_t count) + { + auto producer = get_or_add_implicit_producer(); + return producer == nullptr ? false : producer->ConcurrentQueue::ImplicitProducer::template enqueue_bulk(std::forward(itemFirst), count); + } + + inline bool update_current_producer_after_rotation(consumer_token_t& token) + { + // Ah, there's been a rotation, figure out where we should be! + auto tail = producerListTail.load(std::memory_order_acquire); + if (token.desiredProducer == nullptr && tail == nullptr) { + return false; + } + auto prodCount = producerCount.load(std::memory_order_relaxed); + auto globalOffset = globalExplicitConsumerOffset.load(std::memory_order_relaxed); + if (details::unlikely(token.desiredProducer == nullptr)) { + // Aha, first time we're dequeueing anything. + // Figure out our local position + // Note: offset is from start, not end, but we're traversing from end -- subtract from count first + std::uint32_t offset = prodCount - 1 - (token.initialOffset % prodCount); + token.desiredProducer = tail; + for (std::uint32_t i = 0; i != offset; ++i) { + token.desiredProducer = static_cast(token.desiredProducer)->next_prod(); + if (token.desiredProducer == nullptr) { + token.desiredProducer = tail; + } + } + } + + std::uint32_t delta = globalOffset - token.lastKnownGlobalOffset; + if (delta >= prodCount) { + delta = delta % prodCount; + } + for (std::uint32_t i = 0; i != delta; ++i) { + token.desiredProducer = static_cast(token.desiredProducer)->next_prod(); + if (token.desiredProducer == nullptr) { + token.desiredProducer = tail; + } + } + + token.lastKnownGlobalOffset = globalOffset; + token.currentProducer = token.desiredProducer; + token.itemsConsumedFromCurrent = 0; + return true; + } + + + /////////////////////////// + // Free list + /////////////////////////// + + template + struct FreeListNode + { + FreeListNode() : freeListRefs(0), freeListNext(nullptr) { } + + std::atomic freeListRefs; + std::atomic freeListNext; + }; + + // A simple CAS-based lock-free free list. Not the fastest thing in the world under heavy contention, but + // simple and correct (assuming nodes are never freed until after the free list is destroyed), and fairly + // speedy under low contention. + template // N must inherit FreeListNode or have the same fields (and initialization of them) + struct FreeList + { + FreeList() : freeListHead(nullptr) { } + FreeList(FreeList&& other) : freeListHead(other.freeListHead.load(std::memory_order_relaxed)) { other.freeListHead.store(nullptr, std::memory_order_relaxed); } + void swap(FreeList& other) { details::swap_relaxed(freeListHead, other.freeListHead); } + + FreeList(FreeList const&) MOODYCAMEL_DELETE_FUNCTION; + FreeList& operator=(FreeList const&) MOODYCAMEL_DELETE_FUNCTION; + + inline void add(N* node) + { +#if MCDBGQ_NOLOCKFREE_FREELIST + debug::DebugLock lock(mutex); +#endif + // We know that the should-be-on-freelist bit is 0 at this point, so it's safe to + // set it using a fetch_add + if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST, std::memory_order_acq_rel) == 0) { + // Oh look! We were the last ones referencing this node, and we know + // we want to add it to the free list, so let's do it! + add_knowing_refcount_is_zero(node); + } + } + + inline N* try_get() + { +#if MCDBGQ_NOLOCKFREE_FREELIST + debug::DebugLock lock(mutex); +#endif + auto head = freeListHead.load(std::memory_order_acquire); + while (head != nullptr) { + auto prevHead = head; + auto refs = head->freeListRefs.load(std::memory_order_relaxed); + if ((refs & REFS_MASK) == 0 || !head->freeListRefs.compare_exchange_strong(refs, refs + 1, std::memory_order_acquire, std::memory_order_relaxed)) { + head = freeListHead.load(std::memory_order_acquire); + continue; + } + + // Good, reference count has been incremented (it wasn't at zero), which means we can read the + // next and not worry about it changing between now and the time we do the CAS + auto next = head->freeListNext.load(std::memory_order_relaxed); + if (freeListHead.compare_exchange_strong(head, next, std::memory_order_acquire, std::memory_order_relaxed)) { + // Yay, got the node. This means it was on the list, which means shouldBeOnFreeList must be false no + // matter the refcount (because nobody else knows it's been taken off yet, it can't have been put back on). + assert((head->freeListRefs.load(std::memory_order_relaxed) & SHOULD_BE_ON_FREELIST) == 0); + + // Decrease refcount twice, once for our ref, and once for the list's ref + head->freeListRefs.fetch_add(-2, std::memory_order_release); + return head; + } + + // OK, the head must have changed on us, but we still need to decrease the refcount we increased. + // Note that we don't need to release any memory effects, but we do need to ensure that the reference + // count decrement happens-after the CAS on the head. + refs = prevHead->freeListRefs.fetch_add(-1, std::memory_order_acq_rel); + if (refs == SHOULD_BE_ON_FREELIST + 1) { + add_knowing_refcount_is_zero(prevHead); + } + } + + return nullptr; + } + + // Useful for traversing the list when there's no contention (e.g. to destroy remaining nodes) + N* head_unsafe() const { return freeListHead.load(std::memory_order_relaxed); } + + private: + inline void add_knowing_refcount_is_zero(N* node) + { + // Since the refcount is zero, and nobody can increase it once it's zero (except us, and we run + // only one copy of this method per node at a time, i.e. the single thread case), then we know + // we can safely change the next pointer of the node; however, once the refcount is back above + // zero, then other threads could increase it (happens under heavy contention, when the refcount + // goes to zero in between a load and a refcount increment of a node in try_get, then back up to + // something non-zero, then the refcount increment is done by the other thread) -- so, if the CAS + // to add the node to the actual list fails, decrease the refcount and leave the add operation to + // the next thread who puts the refcount back at zero (which could be us, hence the loop). + auto head = freeListHead.load(std::memory_order_relaxed); + while (true) { + node->freeListNext.store(head, std::memory_order_relaxed); + node->freeListRefs.store(1, std::memory_order_release); + if (!freeListHead.compare_exchange_strong(head, node, std::memory_order_release, std::memory_order_relaxed)) { + // Hmm, the add failed, but we can only try again when the refcount goes back to zero + if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST - 1, std::memory_order_release) == 1) { + continue; + } + } + return; + } + } + + private: + // Implemented like a stack, but where node order doesn't matter (nodes are inserted out of order under contention) + std::atomic freeListHead; + + static const std::uint32_t REFS_MASK = 0x7FFFFFFF; + static const std::uint32_t SHOULD_BE_ON_FREELIST = 0x80000000; + +#if MCDBGQ_NOLOCKFREE_FREELIST + debug::DebugMutex mutex; +#endif + }; + + + /////////////////////////// + // Block + /////////////////////////// + + enum InnerQueueContext { implicit_context = 0, explicit_context = 1 }; + + struct Block + { + Block() + : elementsCompletelyDequeued(0), freeListRefs(0), freeListNext(nullptr), shouldBeOnFreeList(false), dynamicallyAllocated(true) + { +#if MCDBGQ_TRACKMEM + owner = nullptr; +#endif + } + + template + inline bool is_empty() const + { + if (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Check flags + for (size_t i = 0; i < BLOCK_SIZE; ++i) { + if (!emptyFlags[i].load(std::memory_order_relaxed)) { + return false; + } + } + + // Aha, empty; make sure we have all other memory effects that happened before the empty flags were set + std::atomic_thread_fence(std::memory_order_acquire); + return true; + } + else { + // Check counter + if (elementsCompletelyDequeued.load(std::memory_order_relaxed) == BLOCK_SIZE) { + std::atomic_thread_fence(std::memory_order_acquire); + return true; + } + assert(elementsCompletelyDequeued.load(std::memory_order_relaxed) <= BLOCK_SIZE); + return false; + } + } + + // Returns true if the block is now empty (does not apply in explicit context) + template + inline bool set_empty(index_t i) + { + if (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Set flag + assert(!emptyFlags[BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1))].load(std::memory_order_relaxed)); + emptyFlags[BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1))].store(true, std::memory_order_release); + return false; + } + else { + // Increment counter + auto prevVal = elementsCompletelyDequeued.fetch_add(1, std::memory_order_release); + assert(prevVal < BLOCK_SIZE); + return prevVal == BLOCK_SIZE - 1; + } + } + + // Sets multiple contiguous item statuses to 'empty' (assumes no wrapping and count > 0). + // Returns true if the block is now empty (does not apply in explicit context). + template + inline bool set_many_empty(index_t i, size_t count) + { + if (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Set flags + std::atomic_thread_fence(std::memory_order_release); + i = BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1)) - count + 1; + for (size_t j = 0; j != count; ++j) { + assert(!emptyFlags[i + j].load(std::memory_order_relaxed)); + emptyFlags[i + j].store(true, std::memory_order_relaxed); + } + return false; + } + else { + // Increment counter + auto prevVal = elementsCompletelyDequeued.fetch_add(count, std::memory_order_release); + assert(prevVal + count <= BLOCK_SIZE); + return prevVal + count == BLOCK_SIZE; + } + } + + template + inline void set_all_empty() + { + if (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Set all flags + for (size_t i = 0; i != BLOCK_SIZE; ++i) { + emptyFlags[i].store(true, std::memory_order_relaxed); + } + } + else { + // Reset counter + elementsCompletelyDequeued.store(BLOCK_SIZE, std::memory_order_relaxed); + } + } + + template + inline void reset_empty() + { + if (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Reset flags + for (size_t i = 0; i != BLOCK_SIZE; ++i) { + emptyFlags[i].store(false, std::memory_order_relaxed); + } + } + else { + // Reset counter + elementsCompletelyDequeued.store(0, std::memory_order_relaxed); + } + } + + inline T* operator[](index_t idx) MOODYCAMEL_NOEXCEPT { return reinterpret_cast(elements) + static_cast(idx & static_cast(BLOCK_SIZE - 1)); } + inline T const* operator[](index_t idx) const MOODYCAMEL_NOEXCEPT { return reinterpret_cast(elements) + static_cast(idx & static_cast(BLOCK_SIZE - 1)); } + + public: + Block* next; + std::atomic elementsCompletelyDequeued; + std::atomic emptyFlags[BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD ? BLOCK_SIZE : 1]; + private: + char elements[sizeof(T) * BLOCK_SIZE]; + public: + std::atomic freeListRefs; + std::atomic freeListNext; + std::atomic shouldBeOnFreeList; + bool dynamicallyAllocated; // Perhaps a better name for this would be 'isNotPartOfInitialBlockPool' + +#if MCDBGQ_TRACKMEM + void* owner; +#endif + }; + + +#if MCDBGQ_TRACKMEM +public: + struct MemStats; +private: +#endif + + /////////////////////////// + // Producer base + /////////////////////////// + + struct ProducerBase : public details::ConcurrentQueueProducerTypelessBase + { + ProducerBase(ConcurrentQueue* parent, bool isExplicit) : + tailIndex(0), + headIndex(0), + dequeueOptimisticCount(0), + dequeueOvercommit(0), + tailBlock(nullptr), + isExplicit(isExplicit), + parent(parent) + { + } + + virtual ~ProducerBase() { }; + + template + inline bool dequeue(U& element) + { + if (isExplicit) { + return static_cast(this)->dequeue(element); + } + else { + return static_cast(this)->dequeue(element); + } + } + + template + inline size_t dequeue_bulk(It& itemFirst, size_t max) + { + if (isExplicit) { + return static_cast(this)->dequeue_bulk(itemFirst, max); + } + else { + return static_cast(this)->dequeue_bulk(itemFirst, max); + } + } + + inline ProducerBase* next_prod() const { return static_cast(next); } + + inline size_t size_approx() const + { + auto tail = tailIndex.load(std::memory_order_relaxed); + auto head = headIndex.load(std::memory_order_relaxed); + return details::circular_less_than(head, tail) ? static_cast(tail - head) : 0; + } + + inline index_t getTail() const { return tailIndex.load(std::memory_order_relaxed); } + protected: + std::atomic tailIndex; // Where to enqueue to next + std::atomic headIndex; // Where to dequeue from next + + std::atomic dequeueOptimisticCount; + std::atomic dequeueOvercommit; + + Block* tailBlock; + + public: + bool isExplicit; + ConcurrentQueue* parent; + + protected: +#if MCDBGQ_TRACKMEM + friend struct MemStats; +#endif + }; + + + /////////////////////////// + // Explicit queue + /////////////////////////// + + struct ExplicitProducer : public ProducerBase + { + explicit ExplicitProducer(ConcurrentQueue* parent) : + ProducerBase(parent, true), + blockIndex(nullptr), + pr_blockIndexSlotsUsed(0), + pr_blockIndexSize(EXPLICIT_INITIAL_INDEX_SIZE >> 1), + pr_blockIndexFront(0), + pr_blockIndexEntries(nullptr), + pr_blockIndexRaw(nullptr) + { + size_t poolBasedIndexSize = details::ceil_to_pow_2(parent->initialBlockPoolSize) >> 1; + if (poolBasedIndexSize > pr_blockIndexSize) { + pr_blockIndexSize = poolBasedIndexSize; + } + + new_block_index(0); // This creates an index with double the number of current entries, i.e. EXPLICIT_INITIAL_INDEX_SIZE + } + + ~ExplicitProducer() + { + // Destruct any elements not yet dequeued. + // Since we're in the destructor, we can assume all elements + // are either completely dequeued or completely not (no halfways). + if (this->tailBlock != nullptr) { // Note this means there must be a block index too + // First find the block that's partially dequeued, if any + Block* halfDequeuedBlock = nullptr; + if ((this->headIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)) != 0) { + // The head's not on a block boundary, meaning a block somewhere is partially dequeued + // (or the head block is the tail block and was fully dequeued, but the head/tail are still not on a boundary) + size_t i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & (pr_blockIndexSize - 1); + while (details::circular_less_than(pr_blockIndexEntries[i].base + BLOCK_SIZE, this->headIndex.load(std::memory_order_relaxed))) { + i = (i + 1) & (pr_blockIndexSize - 1); + } + assert(details::circular_less_than(pr_blockIndexEntries[i].base, this->headIndex.load(std::memory_order_relaxed))); + halfDequeuedBlock = pr_blockIndexEntries[i].block; + } + + // Start at the head block (note the first line in the loop gives us the head from the tail on the first iteration) + auto block = this->tailBlock; + do { + block = block->next; + if (block->ConcurrentQueue::Block::template is_empty()) { + continue; + } + + size_t i = 0; // Offset into block + if (block == halfDequeuedBlock) { + i = static_cast(this->headIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)); + } + + // Walk through all the items in the block; if this is the tail block, we need to stop when we reach the tail index + auto lastValidIndex = (this->tailIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)) == 0 ? BLOCK_SIZE : static_cast(this->tailIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)); + while (i != BLOCK_SIZE && (block != this->tailBlock || i != lastValidIndex)) { + (*block)[i++]->~T(); + } + } while (block != this->tailBlock); + } + + // Destroy all blocks that we own + if (this->tailBlock != nullptr) { + auto block = this->tailBlock; + do { + auto next = block->next; + if (block->dynamicallyAllocated) { + destroy(block); + } + block = next; + } while (block != this->tailBlock); + } + + // Destroy the block indices + auto header = static_cast(pr_blockIndexRaw); + while (header != nullptr) { + auto prev = static_cast(header->prev); + header->~BlockIndexHeader(); + Traits::free(header); + header = prev; + } + } + + template + inline bool enqueue(U&& element) + { + index_t currentTailIndex = this->tailIndex.load(std::memory_order_relaxed); + index_t newTailIndex = 1 + currentTailIndex; + if ((currentTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + // We reached the end of a block, start a new one + auto startBlock = this->tailBlock; + auto originalBlockIndexSlotsUsed = pr_blockIndexSlotsUsed; + if (this->tailBlock != nullptr && this->tailBlock->next->ConcurrentQueue::Block::template is_empty()) { + // We can re-use the block ahead of us, it's empty! + this->tailBlock = this->tailBlock->next; + this->tailBlock->ConcurrentQueue::Block::template reset_empty(); + + // We'll put the block on the block index (guaranteed to be room since we're conceptually removing the + // last block from it first -- except instead of removing then adding, we can just overwrite). + // Note that there must be a valid block index here, since even if allocation failed in the ctor, + // it would have been re-attempted when adding the first block to the queue; since there is such + // a block, a block index must have been successfully allocated. + } + else { + // Whatever head value we see here is >= the last value we saw here (relatively), + // and <= its current value. Since we have the most recent tail, the head must be + // <= to it. + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + if (!details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) + || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head))) { + // We can't enqueue in another block because there's not enough leeway -- the + // tail could surpass the head by the time the block fills up! (Or we'll exceed + // the size limit, if the second part of the condition was true.) + return false; + } + // We're going to need a new block; check that the block index has room + if (pr_blockIndexRaw == nullptr || pr_blockIndexSlotsUsed == pr_blockIndexSize) { + // Hmm, the circular block index is already full -- we'll need + // to allocate a new index. Note pr_blockIndexRaw can only be nullptr if + // the initial allocation failed in the constructor. + + if (allocMode == CannotAlloc || !new_block_index(pr_blockIndexSlotsUsed)) { + return false; + } + } + + // Insert a new block in the circular linked list + auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); + if (newBlock == nullptr) { + return false; + } +#if MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template reset_empty(); + if (this->tailBlock == nullptr) { + newBlock->next = newBlock; + } + else { + newBlock->next = this->tailBlock->next; + this->tailBlock->next = newBlock; + } + this->tailBlock = newBlock; + ++pr_blockIndexSlotsUsed; + } + + if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (nullptr) T(std::forward(element)))) { + // The constructor may throw. We want the element not to appear in the queue in + // that case (without corrupting the queue): + MOODYCAMEL_TRY { + new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); + } + MOODYCAMEL_CATCH (...) { + // Revert change to the current block, but leave the new block available + // for next time + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? this->tailBlock : startBlock; + MOODYCAMEL_RETHROW; + } + } + else { + (void)startBlock; + (void)originalBlockIndexSlotsUsed; + } + + // Add block to block index + auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; + entry.base = currentTailIndex; + entry.block = this->tailBlock; + blockIndex.load(std::memory_order_relaxed)->front.store(pr_blockIndexFront, std::memory_order_release); + pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); + + if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (nullptr) T(std::forward(element)))) { + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + } + + // Enqueue + new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); + + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + + template + bool dequeue(U& element) + { + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + if (details::circular_less_than(this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit, tail)) { + // Might be something to dequeue, let's give it a try + + // Note that this if is purely for performance purposes in the common case when the queue is + // empty and the values are eventually consistent -- we may enter here spuriously. + + // Note that whatever the values of overcommit and tail are, they are not going to change (unless we + // change them) and must be the same value at this point (inside the if) as when the if condition was + // evaluated. + + // We insert an acquire fence here to synchronize-with the release upon incrementing dequeueOvercommit below. + // This ensures that whatever the value we got loaded into overcommit, the load of dequeueOptisticCount in + // the fetch_add below will result in a value at least as recent as that (and therefore at least as large). + // Note that I believe a compiler (signal) fence here would be sufficient due to the nature of fetch_add (all + // read-modify-write operations are guaranteed to work on the latest value in the modification order), but + // unfortunately that can't be shown to be correct using only the C++11 standard. + // See http://stackoverflow.com/questions/18223161/what-are-the-c11-memory-ordering-guarantees-in-this-corner-case + std::atomic_thread_fence(std::memory_order_acquire); + + // Increment optimistic counter, then check if it went over the boundary + auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(1, std::memory_order_relaxed); + + // Note that since dequeueOvercommit must be <= dequeueOptimisticCount (because dequeueOvercommit is only ever + // incremented after dequeueOptimisticCount -- this is enforced in the `else` block below), and since we now + // have a version of dequeueOptimisticCount that is at least as recent as overcommit (due to the release upon + // incrementing dequeueOvercommit and the acquire above that synchronizes with it), overcommit <= myDequeueCount. + assert(overcommit <= myDequeueCount); + + // Note that we reload tail here in case it changed; it will be the same value as before or greater, since + // this load is sequenced after (happens after) the earlier load above. This is supported by read-read + // coherency (as defined in the standard), explained here: http://en.cppreference.com/w/cpp/atomic/memory_order + tail = this->tailIndex.load(std::memory_order_acquire); + if (details::likely(details::circular_less_than(myDequeueCount - overcommit, tail))) { + // Guaranteed to be at least one element to dequeue! + + // Get the index. Note that since there's guaranteed to be at least one element, this + // will never exceed tail. We need to do an acquire-release fence here since it's possible + // that whatever condition got us to this point was for an earlier enqueued element (that + // we already see the memory effects for), but that by the time we increment somebody else + // has incremented it, and we need to see the memory effects for *that* element, which is + // in such a case is necessarily visible on the thread that incremented it in the first + // place with the more current condition (they must have acquired a tail that is at least + // as recent). + auto index = this->headIndex.fetch_add(1, std::memory_order_acq_rel); + + + // Determine which block the element is in + + auto localBlockIndex = blockIndex.load(std::memory_order_acquire); + auto localBlockIndexHead = localBlockIndex->front.load(std::memory_order_acquire); + + // We need to be careful here about subtracting and dividing because of index wrap-around. + // When an index wraps, we need to preserve the sign of the offset when dividing it by the + // block size (in order to get a correct signed block count offset in all cases): + auto headBase = localBlockIndex->entries[localBlockIndexHead].base; + auto blockBaseIndex = index & ~static_cast(BLOCK_SIZE - 1); + auto offset = static_cast(static_cast::type>(blockBaseIndex - headBase) / BLOCK_SIZE); + auto block = localBlockIndex->entries[(localBlockIndexHead + offset) & (localBlockIndex->size - 1)].block; + + // Dequeue + auto& el = *((*block)[index]); + if (!MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, element = std::move(el))) { + // Make sure the element is still fully dequeued and destroyed even if the assignment + // throws + struct Guard { + Block* block; + index_t index; + + ~Guard() + { + (*block)[index]->~T(); + block->ConcurrentQueue::Block::template set_empty(index); + } + } guard = { block, index }; + + element = std::move(el); + } + else { + element = std::move(el); + el.~T(); + block->ConcurrentQueue::Block::template set_empty(index); + } + + return true; + } + else { + // Wasn't anything to dequeue after all; make the effective dequeue count eventually consistent + this->dequeueOvercommit.fetch_add(1, std::memory_order_release); // Release so that the fetch_add on dequeueOptimisticCount is guaranteed to happen before this write + } + } + + return false; + } + + template + bool enqueue_bulk(It itemFirst, size_t count) + { + // First, we need to make sure we have enough room to enqueue all of the elements; + // this means pre-allocating blocks and putting them in the block index (but only if + // all the allocations succeeded). + index_t startTailIndex = this->tailIndex.load(std::memory_order_relaxed); + auto startBlock = this->tailBlock; + auto originalBlockIndexFront = pr_blockIndexFront; + auto originalBlockIndexSlotsUsed = pr_blockIndexSlotsUsed; + + Block* firstAllocatedBlock = nullptr; + + // Figure out how many blocks we'll need to allocate, and do so + size_t blockBaseDiff = ((startTailIndex + count - 1) & ~static_cast(BLOCK_SIZE - 1)) - ((startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1)); + index_t currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + if (blockBaseDiff > 0) { + // Allocate as many blocks as possible from ahead + while (blockBaseDiff > 0 && this->tailBlock != nullptr && this->tailBlock->next != firstAllocatedBlock && this->tailBlock->next->ConcurrentQueue::Block::template is_empty()) { + blockBaseDiff -= static_cast(BLOCK_SIZE); + currentTailIndex += static_cast(BLOCK_SIZE); + + this->tailBlock = this->tailBlock->next; + firstAllocatedBlock = firstAllocatedBlock == nullptr ? this->tailBlock : firstAllocatedBlock; + + auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; + entry.base = currentTailIndex; + entry.block = this->tailBlock; + pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); + } + + // Now allocate as many blocks as necessary from the block pool + while (blockBaseDiff > 0) { + blockBaseDiff -= static_cast(BLOCK_SIZE); + currentTailIndex += static_cast(BLOCK_SIZE); + + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + bool full = !details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head)); + if (pr_blockIndexRaw == nullptr || pr_blockIndexSlotsUsed == pr_blockIndexSize || full) { + if (allocMode == CannotAlloc || full || !new_block_index(originalBlockIndexSlotsUsed)) { + // Failed to allocate, undo changes (but keep injected blocks) + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + return false; + } + + // pr_blockIndexFront is updated inside new_block_index, so we need to + // update our fallback value too (since we keep the new index even if we + // later fail) + originalBlockIndexFront = originalBlockIndexSlotsUsed; + } + + // Insert a new block in the circular linked list + auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); + if (newBlock == nullptr) { + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + return false; + } + +#if MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template set_all_empty(); + if (this->tailBlock == nullptr) { + newBlock->next = newBlock; + } + else { + newBlock->next = this->tailBlock->next; + this->tailBlock->next = newBlock; + } + this->tailBlock = newBlock; + firstAllocatedBlock = firstAllocatedBlock == nullptr ? this->tailBlock : firstAllocatedBlock; + + ++pr_blockIndexSlotsUsed; + + auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; + entry.base = currentTailIndex; + entry.block = this->tailBlock; + pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); + } + + // Excellent, all allocations succeeded. Reset each block's emptiness before we fill them up, and + // publish the new block index front + auto block = firstAllocatedBlock; + while (true) { + block->ConcurrentQueue::Block::template reset_empty(); + if (block == this->tailBlock) { + break; + } + block = block->next; + } + + if (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst)))) { + blockIndex.load(std::memory_order_relaxed)->front.store((pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release); + } + } + + // Enqueue, one block at a time + index_t newTailIndex = startTailIndex + static_cast(count); + currentTailIndex = startTailIndex; + auto endBlock = this->tailBlock; + this->tailBlock = startBlock; + assert((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr || count == 0); + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0 && firstAllocatedBlock != nullptr) { + this->tailBlock = firstAllocatedBlock; + } + while (true) { + auto stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(newTailIndex, stopIndex)) { + stopIndex = newTailIndex; + } + if (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst)))) { + while (currentTailIndex != stopIndex) { + new ((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++); + } + } + else { + MOODYCAMEL_TRY { + while (currentTailIndex != stopIndex) { + // Must use copy constructor even if move constructor is available + // because we may have to revert if there's an exception. + // Sorry about the horrible templated next line, but it was the only way + // to disable moving *at compile time*, which is important because a type + // may only define a (noexcept) move constructor, and so calls to the + // cctor will not compile, even if they are in an if branch that will never + // be executed + new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if<(bool)!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst)); + ++currentTailIndex; + ++itemFirst; + } + } + MOODYCAMEL_CATCH (...) { + // Oh dear, an exception's been thrown -- destroy the elements that + // were enqueued so far and revert the entire bulk operation (we'll keep + // any allocated blocks in our linked list for later, though). + auto constructedStopIndex = currentTailIndex; + auto lastBlockEnqueued = this->tailBlock; + + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + + if (!details::is_trivially_destructible::value) { + auto block = startBlock; + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + block = firstAllocatedBlock; + } + currentTailIndex = startTailIndex; + while (true) { + auto stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(constructedStopIndex, stopIndex)) { + stopIndex = constructedStopIndex; + } + while (currentTailIndex != stopIndex) { + (*block)[currentTailIndex++]->~T(); + } + if (block == lastBlockEnqueued) { + break; + } + block = block->next; + } + } + MOODYCAMEL_RETHROW; + } + } + + if (this->tailBlock == endBlock) { + assert(currentTailIndex == newTailIndex); + break; + } + this->tailBlock = this->tailBlock->next; + } + + if (!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst))) && firstAllocatedBlock != nullptr) { + blockIndex.load(std::memory_order_relaxed)->front.store((pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release); + } + + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + + template + size_t dequeue_bulk(It& itemFirst, size_t max) + { + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + auto desiredCount = static_cast(tail - (this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit)); + if (details::circular_less_than(0, desiredCount)) { + desiredCount = desiredCount < max ? desiredCount : max; + std::atomic_thread_fence(std::memory_order_acquire); + + auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(desiredCount, std::memory_order_relaxed); + assert(overcommit <= myDequeueCount); + + tail = this->tailIndex.load(std::memory_order_acquire); + auto actualCount = static_cast(tail - (myDequeueCount - overcommit)); + if (details::circular_less_than(0, actualCount)) { + actualCount = desiredCount < actualCount ? desiredCount : actualCount; + if (actualCount < desiredCount) { + this->dequeueOvercommit.fetch_add(desiredCount - actualCount, std::memory_order_release); + } + + // Get the first index. Note that since there's guaranteed to be at least actualCount elements, this + // will never exceed tail. + auto firstIndex = this->headIndex.fetch_add(actualCount, std::memory_order_acq_rel); + + // Determine which block the first element is in + auto localBlockIndex = blockIndex.load(std::memory_order_acquire); + auto localBlockIndexHead = localBlockIndex->front.load(std::memory_order_acquire); + + auto headBase = localBlockIndex->entries[localBlockIndexHead].base; + auto firstBlockBaseIndex = firstIndex & ~static_cast(BLOCK_SIZE - 1); + auto offset = static_cast(static_cast::type>(firstBlockBaseIndex - headBase) / BLOCK_SIZE); + auto indexIndex = (localBlockIndexHead + offset) & (localBlockIndex->size - 1); + + // Iterate the blocks and dequeue + auto index = firstIndex; + do { + auto firstIndexInBlock = index; + auto endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + auto block = localBlockIndex->entries[indexIndex].block; + if (MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, details::deref_noexcept(itemFirst) = std::move((*(*block)[index])))) { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst++ = std::move(el); + el.~T(); + ++index; + } + } + else { + MOODYCAMEL_TRY { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst = std::move(el); + ++itemFirst; + el.~T(); + ++index; + } + } + MOODYCAMEL_CATCH (...) { + // It's too late to revert the dequeue, but we can make sure that all + // the dequeued objects are properly destroyed and the block index + // (and empty count) are properly updated before we propagate the exception + do { + block = localBlockIndex->entries[indexIndex].block; + while (index != endIndex) { + (*block)[index++]->~T(); + } + block->ConcurrentQueue::Block::template set_many_empty(firstIndexInBlock, static_cast(endIndex - firstIndexInBlock)); + indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1); + + firstIndexInBlock = index; + endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + } while (index != firstIndex + actualCount); + + MOODYCAMEL_RETHROW; + } + } + block->ConcurrentQueue::Block::template set_many_empty(firstIndexInBlock, static_cast(endIndex - firstIndexInBlock)); + indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1); + } while (index != firstIndex + actualCount); + + return actualCount; + } + else { + // Wasn't anything to dequeue after all; make the effective dequeue count eventually consistent + this->dequeueOvercommit.fetch_add(desiredCount, std::memory_order_release); + } + } + + return 0; + } + + private: + struct BlockIndexEntry + { + index_t base; + Block* block; + }; + + struct BlockIndexHeader + { + size_t size; + std::atomic front; // Current slot (not next, like pr_blockIndexFront) + BlockIndexEntry* entries; + void* prev; + }; + + + bool new_block_index(size_t numberOfFilledSlotsToExpose) + { + auto prevBlockSizeMask = pr_blockIndexSize - 1; + + // Create the new block + pr_blockIndexSize <<= 1; + auto newRawPtr = static_cast(Traits::malloc(sizeof(BlockIndexHeader) + std::alignment_of::value - 1 + sizeof(BlockIndexEntry) * pr_blockIndexSize)); + if (newRawPtr == nullptr) { + pr_blockIndexSize >>= 1; // Reset to allow graceful retry + return false; + } + + auto newBlockIndexEntries = reinterpret_cast(details::align_for(newRawPtr + sizeof(BlockIndexHeader))); + + // Copy in all the old indices, if any + size_t j = 0; + if (pr_blockIndexSlotsUsed != 0) { + auto i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & prevBlockSizeMask; + do { + newBlockIndexEntries[j++] = pr_blockIndexEntries[i]; + i = (i + 1) & prevBlockSizeMask; + } while (i != pr_blockIndexFront); + } + + // Update everything + auto header = new (newRawPtr) BlockIndexHeader; + header->size = pr_blockIndexSize; + header->front.store(numberOfFilledSlotsToExpose - 1, std::memory_order_relaxed); + header->entries = newBlockIndexEntries; + header->prev = pr_blockIndexRaw; // we link the new block to the old one so we can free it later + + pr_blockIndexFront = j; + pr_blockIndexEntries = newBlockIndexEntries; + pr_blockIndexRaw = newRawPtr; + blockIndex.store(header, std::memory_order_release); + + return true; + } + + private: + std::atomic blockIndex; + + // To be used by producer only -- consumer must use the ones in referenced by blockIndex + size_t pr_blockIndexSlotsUsed; + size_t pr_blockIndexSize; + size_t pr_blockIndexFront; // Next slot (not current) + BlockIndexEntry* pr_blockIndexEntries; + void* pr_blockIndexRaw; + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + public: + ExplicitProducer* nextExplicitProducer; + private: +#endif + +#if MCDBGQ_TRACKMEM + friend struct MemStats; +#endif + }; + + + ////////////////////////////////// + // Implicit queue + ////////////////////////////////// + + struct ImplicitProducer : public ProducerBase + { + ImplicitProducer(ConcurrentQueue* parent) : + ProducerBase(parent, false), + nextBlockIndexCapacity(IMPLICIT_INITIAL_INDEX_SIZE), + blockIndex(nullptr) + { + new_block_index(); + } + + ~ImplicitProducer() + { + // Note that since we're in the destructor we can assume that all enqueue/dequeue operations + // completed already; this means that all undequeued elements are placed contiguously across + // contiguous blocks, and that only the first and last remaining blocks can be only partially + // empty (all other remaining blocks must be completely full). + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + // Unregister ourselves for thread termination notification + if (!this->inactive.load(std::memory_order_relaxed)) { + details::ThreadExitNotifier::unsubscribe(&threadExitListener); + } +#endif + + // Destroy all remaining elements! + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto index = this->headIndex.load(std::memory_order_relaxed); + Block* block = nullptr; + assert(index == tail || details::circular_less_than(index, tail)); + bool forceFreeLastBlock = index != tail; // If we enter the loop, then the last (tail) block will not be freed + while (index != tail) { + if ((index & static_cast(BLOCK_SIZE - 1)) == 0 || block == nullptr) { + if (block != nullptr && block->dynamicallyAllocated) { + // Free the old block + this->parent->destroy(block); + } + + block = get_block_index_entry_for_index(index)->value.load(std::memory_order_relaxed); + } + + ((*block)[index])->~T(); + ++index; + } + // Even if the queue is empty, there's still one block that's not on the free list + // (unless the head index reached the end of it, in which case the tail will be poised + // to create a new block). + if (this->tailBlock != nullptr && (forceFreeLastBlock || (tail & static_cast(BLOCK_SIZE - 1)) != 0) && this->tailBlock->dynamicallyAllocated) { + this->parent->destroy(this->tailBlock); + } + + // Destroy block index + auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); + if (localBlockIndex != nullptr) { + for (size_t i = 0; i != localBlockIndex->capacity; ++i) { + localBlockIndex->index[i]->~BlockIndexEntry(); + } + do { + auto prev = localBlockIndex->prev; + localBlockIndex->~BlockIndexHeader(); + Traits::free(localBlockIndex); + localBlockIndex = prev; + } while (localBlockIndex != nullptr); + } + } + + template + inline bool enqueue(U&& element) + { + index_t currentTailIndex = this->tailIndex.load(std::memory_order_relaxed); + index_t newTailIndex = 1 + currentTailIndex; + if ((currentTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + // We reached the end of a block, start a new one + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + if (!details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head))) { + return false; + } +#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + // Find out where we'll be inserting this block in the block index + BlockIndexEntry* idxEntry; + if (!insert_block_index_entry(idxEntry, currentTailIndex)) { + return false; + } + + // Get ahold of a new block + auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); + if (newBlock == nullptr) { + rewind_block_index_tail(); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + return false; + } +#if MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template reset_empty(); + + if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (nullptr) T(std::forward(element)))) { + // May throw, try to insert now before we publish the fact that we have this new block + MOODYCAMEL_TRY { + new ((*newBlock)[currentTailIndex]) T(std::forward(element)); + } + MOODYCAMEL_CATCH (...) { + rewind_block_index_tail(); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + this->parent->add_block_to_free_list(newBlock); + MOODYCAMEL_RETHROW; + } + } + + // Insert the new block into the index + idxEntry->value.store(newBlock, std::memory_order_relaxed); + + this->tailBlock = newBlock; + + if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (nullptr) T(std::forward(element)))) { + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + } + + // Enqueue + new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); + + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + + template + bool dequeue(U& element) + { + // See ExplicitProducer::dequeue for rationale and explanation + index_t tail = this->tailIndex.load(std::memory_order_relaxed); + index_t overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + if (details::circular_less_than(this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit, tail)) { + std::atomic_thread_fence(std::memory_order_acquire); + + index_t myDequeueCount = this->dequeueOptimisticCount.fetch_add(1, std::memory_order_relaxed); + assert(overcommit <= myDequeueCount); + tail = this->tailIndex.load(std::memory_order_acquire); + if (details::likely(details::circular_less_than(myDequeueCount - overcommit, tail))) { + index_t index = this->headIndex.fetch_add(1, std::memory_order_acq_rel); + + // Determine which block the element is in + auto entry = get_block_index_entry_for_index(index); + + // Dequeue + auto block = entry->value.load(std::memory_order_relaxed); + auto& el = *((*block)[index]); + + if (!MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, element = std::move(el))) { +#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + // Note: Acquiring the mutex with every dequeue instead of only when a block + // is released is very sub-optimal, but it is, after all, purely debug code. + debug::DebugLock lock(producer->mutex); +#endif + struct Guard { + Block* block; + index_t index; + BlockIndexEntry* entry; + ConcurrentQueue* parent; + + ~Guard() + { + (*block)[index]->~T(); + if (block->ConcurrentQueue::Block::template set_empty(index)) { + entry->value.store(nullptr, std::memory_order_relaxed); + parent->add_block_to_free_list(block); + } + } + } guard = { block, index, entry, this->parent }; + + element = std::move(el); + } + else { + element = std::move(el); + el.~T(); + + if (block->ConcurrentQueue::Block::template set_empty(index)) { + { +#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + // Add the block back into the global free pool (and remove from block index) + entry->value.store(nullptr, std::memory_order_relaxed); + } + this->parent->add_block_to_free_list(block); // releases the above store + } + } + + return true; + } + else { + this->dequeueOvercommit.fetch_add(1, std::memory_order_release); + } + } + + return false; + } + + template + bool enqueue_bulk(It itemFirst, size_t count) + { + // First, we need to make sure we have enough room to enqueue all of the elements; + // this means pre-allocating blocks and putting them in the block index (but only if + // all the allocations succeeded). + + // Note that the tailBlock we start off with may not be owned by us any more; + // this happens if it was filled up exactly to the top (setting tailIndex to + // the first index of the next block which is not yet allocated), then dequeued + // completely (putting it on the free list) before we enqueue again. + + index_t startTailIndex = this->tailIndex.load(std::memory_order_relaxed); + auto startBlock = this->tailBlock; + Block* firstAllocatedBlock = nullptr; + auto endBlock = this->tailBlock; + + // Figure out how many blocks we'll need to allocate, and do so + size_t blockBaseDiff = ((startTailIndex + count - 1) & ~static_cast(BLOCK_SIZE - 1)) - ((startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1)); + index_t currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + if (blockBaseDiff > 0) { +#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + do { + blockBaseDiff -= static_cast(BLOCK_SIZE); + currentTailIndex += static_cast(BLOCK_SIZE); + + // Find out where we'll be inserting this block in the block index + BlockIndexEntry* idxEntry; + Block* newBlock; + bool indexInserted = false; + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + bool full = !details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head)); + if (full || !(indexInserted = insert_block_index_entry(idxEntry, currentTailIndex)) || (newBlock = this->parent->ConcurrentQueue::template requisition_block()) == nullptr) { + // Index allocation or block allocation failed; revert any other allocations + // and index insertions done so far for this operation + if (indexInserted) { + rewind_block_index_tail(); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + } + currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + for (auto block = firstAllocatedBlock; block != nullptr; block = block->next) { + currentTailIndex += static_cast(BLOCK_SIZE); + idxEntry = get_block_index_entry_for_index(currentTailIndex); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + rewind_block_index_tail(); + } + this->parent->add_blocks_to_free_list(firstAllocatedBlock); + this->tailBlock = startBlock; + + return false; + } + +#if MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template reset_empty(); + newBlock->next = nullptr; + + // Insert the new block into the index + idxEntry->value.store(newBlock, std::memory_order_relaxed); + + // Store the chain of blocks so that we can undo if later allocations fail, + // and so that we can find the blocks when we do the actual enqueueing + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr) { + assert(this->tailBlock != nullptr); + this->tailBlock->next = newBlock; + } + this->tailBlock = newBlock; + endBlock = newBlock; + firstAllocatedBlock = firstAllocatedBlock == nullptr ? newBlock : firstAllocatedBlock; + } while (blockBaseDiff > 0); + } + + // Enqueue, one block at a time + index_t newTailIndex = startTailIndex + static_cast(count); + currentTailIndex = startTailIndex; + this->tailBlock = startBlock; + assert((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr || count == 0); + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0 && firstAllocatedBlock != nullptr) { + this->tailBlock = firstAllocatedBlock; + } + while (true) { + auto stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(newTailIndex, stopIndex)) { + stopIndex = newTailIndex; + } + if (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst)))) { + while (currentTailIndex != stopIndex) { + new ((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++); + } + } + else { + MOODYCAMEL_TRY { + while (currentTailIndex != stopIndex) { + new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if<(bool)!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst)); + ++currentTailIndex; + ++itemFirst; + } + } + MOODYCAMEL_CATCH (...) { + auto constructedStopIndex = currentTailIndex; + auto lastBlockEnqueued = this->tailBlock; + + if (!details::is_trivially_destructible::value) { + auto block = startBlock; + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + block = firstAllocatedBlock; + } + currentTailIndex = startTailIndex; + while (true) { + auto stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(constructedStopIndex, stopIndex)) { + stopIndex = constructedStopIndex; + } + while (currentTailIndex != stopIndex) { + (*block)[currentTailIndex++]->~T(); + } + if (block == lastBlockEnqueued) { + break; + } + block = block->next; + } + } + + currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + for (auto block = firstAllocatedBlock; block != nullptr; block = block->next) { + currentTailIndex += static_cast(BLOCK_SIZE); + auto idxEntry = get_block_index_entry_for_index(currentTailIndex); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + rewind_block_index_tail(); + } + this->parent->add_blocks_to_free_list(firstAllocatedBlock); + this->tailBlock = startBlock; + MOODYCAMEL_RETHROW; + } + } + + if (this->tailBlock == endBlock) { + assert(currentTailIndex == newTailIndex); + break; + } + this->tailBlock = this->tailBlock->next; + } + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + + template + size_t dequeue_bulk(It& itemFirst, size_t max) + { + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + auto desiredCount = static_cast(tail - (this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit)); + if (details::circular_less_than(0, desiredCount)) { + desiredCount = desiredCount < max ? desiredCount : max; + std::atomic_thread_fence(std::memory_order_acquire); + + auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(desiredCount, std::memory_order_relaxed); + assert(overcommit <= myDequeueCount); + + tail = this->tailIndex.load(std::memory_order_acquire); + auto actualCount = static_cast(tail - (myDequeueCount - overcommit)); + if (details::circular_less_than(0, actualCount)) { + actualCount = desiredCount < actualCount ? desiredCount : actualCount; + if (actualCount < desiredCount) { + this->dequeueOvercommit.fetch_add(desiredCount - actualCount, std::memory_order_release); + } + + // Get the first index. Note that since there's guaranteed to be at least actualCount elements, this + // will never exceed tail. + auto firstIndex = this->headIndex.fetch_add(actualCount, std::memory_order_acq_rel); + + // Iterate the blocks and dequeue + auto index = firstIndex; + BlockIndexHeader* localBlockIndex; + auto indexIndex = get_block_index_index_for_index(index, localBlockIndex); + do { + auto blockStartIndex = index; + auto endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + + auto entry = localBlockIndex->index[indexIndex]; + auto block = entry->value.load(std::memory_order_relaxed); + if (MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, details::deref_noexcept(itemFirst) = std::move((*(*block)[index])))) { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst++ = std::move(el); + el.~T(); + ++index; + } + } + else { + MOODYCAMEL_TRY { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst = std::move(el); + ++itemFirst; + el.~T(); + ++index; + } + } + MOODYCAMEL_CATCH (...) { + do { + entry = localBlockIndex->index[indexIndex]; + block = entry->value.load(std::memory_order_relaxed); + while (index != endIndex) { + (*block)[index++]->~T(); + } + + if (block->ConcurrentQueue::Block::template set_many_empty(blockStartIndex, static_cast(endIndex - blockStartIndex))) { +#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + entry->value.store(nullptr, std::memory_order_relaxed); + this->parent->add_block_to_free_list(block); + } + indexIndex = (indexIndex + 1) & (localBlockIndex->capacity - 1); + + blockStartIndex = index; + endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + } while (index != firstIndex + actualCount); + + MOODYCAMEL_RETHROW; + } + } + if (block->ConcurrentQueue::Block::template set_many_empty(blockStartIndex, static_cast(endIndex - blockStartIndex))) { + { +#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + // Note that the set_many_empty above did a release, meaning that anybody who acquires the block + // we're about to free can use it safely since our writes (and reads!) will have happened-before then. + entry->value.store(nullptr, std::memory_order_relaxed); + } + this->parent->add_block_to_free_list(block); // releases the above store + } + indexIndex = (indexIndex + 1) & (localBlockIndex->capacity - 1); + } while (index != firstIndex + actualCount); + + return actualCount; + } + else { + this->dequeueOvercommit.fetch_add(desiredCount, std::memory_order_release); + } + } + + return 0; + } + + private: + // The block size must be > 1, so any number with the low bit set is an invalid block base index + static const index_t INVALID_BLOCK_BASE = 1; + + struct BlockIndexEntry + { + std::atomic key; + std::atomic value; + }; + + struct BlockIndexHeader + { + size_t capacity; + std::atomic tail; + BlockIndexEntry* entries; + BlockIndexEntry** index; + BlockIndexHeader* prev; + }; + + template + inline bool insert_block_index_entry(BlockIndexEntry*& idxEntry, index_t blockStartIndex) + { + auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); // We're the only writer thread, relaxed is OK + auto newTail = (localBlockIndex->tail.load(std::memory_order_relaxed) + 1) & (localBlockIndex->capacity - 1); + idxEntry = localBlockIndex->index[newTail]; + if (idxEntry->key.load(std::memory_order_relaxed) == INVALID_BLOCK_BASE || + idxEntry->value.load(std::memory_order_relaxed) == nullptr) { + + idxEntry->key.store(blockStartIndex, std::memory_order_relaxed); + localBlockIndex->tail.store(newTail, std::memory_order_release); + return true; + } + + // No room in the old block index, try to allocate another one! + if (allocMode == CannotAlloc || !new_block_index()) { + return false; + } + localBlockIndex = blockIndex.load(std::memory_order_relaxed); + newTail = (localBlockIndex->tail.load(std::memory_order_relaxed) + 1) & (localBlockIndex->capacity - 1); + idxEntry = localBlockIndex->index[newTail]; + assert(idxEntry->key.load(std::memory_order_relaxed) == INVALID_BLOCK_BASE); + idxEntry->key.store(blockStartIndex, std::memory_order_relaxed); + localBlockIndex->tail.store(newTail, std::memory_order_release); + return true; + } + + inline void rewind_block_index_tail() + { + auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); + localBlockIndex->tail.store((localBlockIndex->tail.load(std::memory_order_relaxed) - 1) & (localBlockIndex->capacity - 1), std::memory_order_relaxed); + } + + inline BlockIndexEntry* get_block_index_entry_for_index(index_t index) const + { + BlockIndexHeader* localBlockIndex; + auto idx = get_block_index_index_for_index(index, localBlockIndex); + return localBlockIndex->index[idx]; + } + + inline size_t get_block_index_index_for_index(index_t index, BlockIndexHeader*& localBlockIndex) const + { +#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + index &= ~static_cast(BLOCK_SIZE - 1); + localBlockIndex = blockIndex.load(std::memory_order_acquire); + auto tail = localBlockIndex->tail.load(std::memory_order_acquire); + auto tailBase = localBlockIndex->index[tail]->key.load(std::memory_order_relaxed); + assert(tailBase != INVALID_BLOCK_BASE); + // Note: Must use division instead of shift because the index may wrap around, causing a negative + // offset, whose negativity we want to preserve + auto offset = static_cast(static_cast::type>(index - tailBase) / BLOCK_SIZE); + size_t idx = (tail + offset) & (localBlockIndex->capacity - 1); + assert(localBlockIndex->index[idx]->key.load(std::memory_order_relaxed) == index && localBlockIndex->index[idx]->value.load(std::memory_order_relaxed) != nullptr); + return idx; + } + + bool new_block_index() + { + auto prev = blockIndex.load(std::memory_order_relaxed); + size_t prevCapacity = prev == nullptr ? 0 : prev->capacity; + auto entryCount = prev == nullptr ? nextBlockIndexCapacity : prevCapacity; + auto raw = static_cast(Traits::malloc( + sizeof(BlockIndexHeader) + + std::alignment_of::value - 1 + sizeof(BlockIndexEntry) * entryCount + + std::alignment_of::value - 1 + sizeof(BlockIndexEntry*) * nextBlockIndexCapacity)); + if (raw == nullptr) { + return false; + } + + auto header = new (raw) BlockIndexHeader; + auto entries = reinterpret_cast(details::align_for(raw + sizeof(BlockIndexHeader))); + auto index = reinterpret_cast(details::align_for(reinterpret_cast(entries) + sizeof(BlockIndexEntry) * entryCount)); + if (prev != nullptr) { + auto prevTail = prev->tail.load(std::memory_order_relaxed); + auto prevPos = prevTail; + size_t i = 0; + do { + prevPos = (prevPos + 1) & (prev->capacity - 1); + index[i++] = prev->index[prevPos]; + } while (prevPos != prevTail); + assert(i == prevCapacity); + } + for (size_t i = 0; i != entryCount; ++i) { + new (entries + i) BlockIndexEntry; + entries[i].key.store(INVALID_BLOCK_BASE, std::memory_order_relaxed); + index[prevCapacity + i] = entries + i; + } + header->prev = prev; + header->entries = entries; + header->index = index; + header->capacity = nextBlockIndexCapacity; + header->tail.store((prevCapacity - 1) & (nextBlockIndexCapacity - 1), std::memory_order_relaxed); + + blockIndex.store(header, std::memory_order_release); + + nextBlockIndexCapacity <<= 1; + + return true; + } + + private: + size_t nextBlockIndexCapacity; + std::atomic blockIndex; + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + public: + details::ThreadExitListener threadExitListener; + private: +#endif + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + public: + ImplicitProducer* nextImplicitProducer; + private: +#endif + +#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + mutable debug::DebugMutex mutex; +#endif +#if MCDBGQ_TRACKMEM + friend struct MemStats; +#endif + }; + + + ////////////////////////////////// + // Block pool manipulation + ////////////////////////////////// + + void populate_initial_block_list(size_t blockCount) + { + initialBlockPoolSize = blockCount; + if (initialBlockPoolSize == 0) { + initialBlockPool = nullptr; + return; + } + + initialBlockPool = create_array(blockCount); + if (initialBlockPool == nullptr) { + initialBlockPoolSize = 0; + } + for (size_t i = 0; i < initialBlockPoolSize; ++i) { + initialBlockPool[i].dynamicallyAllocated = false; + } + } + + inline Block* try_get_block_from_initial_pool() + { + if (initialBlockPoolIndex.load(std::memory_order_relaxed) >= initialBlockPoolSize) { + return nullptr; + } + + auto index = initialBlockPoolIndex.fetch_add(1, std::memory_order_relaxed); + + return index < initialBlockPoolSize ? (initialBlockPool + index) : nullptr; + } + + inline void add_block_to_free_list(Block* block) + { +#if MCDBGQ_TRACKMEM + block->owner = nullptr; +#endif + freeList.add(block); + } + + inline void add_blocks_to_free_list(Block* block) + { + while (block != nullptr) { + auto next = block->next; + add_block_to_free_list(block); + block = next; + } + } + + inline Block* try_get_block_from_free_list() + { + return freeList.try_get(); + } + + // Gets a free block from one of the memory pools, or allocates a new one (if applicable) + template + Block* requisition_block() + { + auto block = try_get_block_from_initial_pool(); + if (block != nullptr) { + return block; + } + + block = try_get_block_from_free_list(); + if (block != nullptr) { + return block; + } + + if (canAlloc == CanAlloc) { + return create(); + } + + return nullptr; + } + + +#if MCDBGQ_TRACKMEM + public: + struct MemStats { + size_t allocatedBlocks; + size_t usedBlocks; + size_t freeBlocks; + size_t ownedBlocksExplicit; + size_t ownedBlocksImplicit; + size_t implicitProducers; + size_t explicitProducers; + size_t elementsEnqueued; + size_t blockClassBytes; + size_t queueClassBytes; + size_t implicitBlockIndexBytes; + size_t explicitBlockIndexBytes; + + friend class ConcurrentQueue; + + private: + static MemStats getFor(ConcurrentQueue* q) + { + MemStats stats = { 0 }; + + stats.elementsEnqueued = q->size_approx(); + + auto block = q->freeList.head_unsafe(); + while (block != nullptr) { + ++stats.allocatedBlocks; + ++stats.freeBlocks; + block = block->freeListNext.load(std::memory_order_relaxed); + } + + for (auto ptr = q->producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + bool implicit = dynamic_cast(ptr) != nullptr; + stats.implicitProducers += implicit ? 1 : 0; + stats.explicitProducers += implicit ? 0 : 1; + + if (implicit) { + auto prod = static_cast(ptr); + stats.queueClassBytes += sizeof(ImplicitProducer); + auto head = prod->headIndex.load(std::memory_order_relaxed); + auto tail = prod->tailIndex.load(std::memory_order_relaxed); + auto hash = prod->blockIndex.load(std::memory_order_relaxed); + if (hash != nullptr) { + for (size_t i = 0; i != hash->capacity; ++i) { + if (hash->index[i]->key.load(std::memory_order_relaxed) != ImplicitProducer::INVALID_BLOCK_BASE && hash->index[i]->value.load(std::memory_order_relaxed) != nullptr) { + ++stats.allocatedBlocks; + ++stats.ownedBlocksImplicit; + } + } + stats.implicitBlockIndexBytes += hash->capacity * sizeof(typename ImplicitProducer::BlockIndexEntry); + for (; hash != nullptr; hash = hash->prev) { + stats.implicitBlockIndexBytes += sizeof(typename ImplicitProducer::BlockIndexHeader) + hash->capacity * sizeof(typename ImplicitProducer::BlockIndexEntry*); + } + } + for (; details::circular_less_than(head, tail); head += BLOCK_SIZE) { + //auto block = prod->get_block_index_entry_for_index(head); + ++stats.usedBlocks; + } + } + else { + auto prod = static_cast(ptr); + stats.queueClassBytes += sizeof(ExplicitProducer); + auto tailBlock = prod->tailBlock; + bool wasNonEmpty = false; + if (tailBlock != nullptr) { + auto block = tailBlock; + do { + ++stats.allocatedBlocks; + if (!block->ConcurrentQueue::Block::template is_empty() || wasNonEmpty) { + ++stats.usedBlocks; + wasNonEmpty = wasNonEmpty || block != tailBlock; + } + ++stats.ownedBlocksExplicit; + block = block->next; + } while (block != tailBlock); + } + auto index = prod->blockIndex.load(std::memory_order_relaxed); + while (index != nullptr) { + stats.explicitBlockIndexBytes += sizeof(typename ExplicitProducer::BlockIndexHeader) + index->size * sizeof(typename ExplicitProducer::BlockIndexEntry); + index = static_cast(index->prev); + } + } + } + + auto freeOnInitialPool = q->initialBlockPoolIndex.load(std::memory_order_relaxed) >= q->initialBlockPoolSize ? 0 : q->initialBlockPoolSize - q->initialBlockPoolIndex.load(std::memory_order_relaxed); + stats.allocatedBlocks += freeOnInitialPool; + stats.freeBlocks += freeOnInitialPool; + + stats.blockClassBytes = sizeof(Block) * stats.allocatedBlocks; + stats.queueClassBytes += sizeof(ConcurrentQueue); + + return stats; + } + }; + + // For debugging only. Not thread-safe. + MemStats getMemStats() + { + return MemStats::getFor(this); + } + private: + friend struct MemStats; +#endif + + + ////////////////////////////////// + // Producer list manipulation + ////////////////////////////////// + + ProducerBase* recycle_or_create_producer(bool isExplicit) + { + bool recycled; + return recycle_or_create_producer(isExplicit, recycled); + } + + ProducerBase* recycle_or_create_producer(bool isExplicit, bool& recycled) + { +#if MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugLock lock(implicitProdMutex); +#endif + // Try to re-use one first + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + if (ptr->inactive.load(std::memory_order_relaxed) && ptr->isExplicit == isExplicit) { + bool expected = true; + if (ptr->inactive.compare_exchange_strong(expected, /* desired */ false, std::memory_order_acquire, std::memory_order_relaxed)) { + // We caught one! It's been marked as activated, the caller can have it + recycled = true; + return ptr; + } + } + } + + recycled = false; + return add_producer(isExplicit ? static_cast(create(this)) : create(this)); + } + + ProducerBase* add_producer(ProducerBase* producer) + { + // Handle failed memory allocation + if (producer == nullptr) { + return nullptr; + } + + producerCount.fetch_add(1, std::memory_order_relaxed); + + // Add it to the lock-free list + auto prevTail = producerListTail.load(std::memory_order_relaxed); + do { + producer->next = prevTail; + } while (!producerListTail.compare_exchange_weak(prevTail, producer, std::memory_order_release, std::memory_order_relaxed)); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + if (producer->isExplicit) { + auto prevTailExplicit = explicitProducers.load(std::memory_order_relaxed); + do { + static_cast(producer)->nextExplicitProducer = prevTailExplicit; + } while (!explicitProducers.compare_exchange_weak(prevTailExplicit, static_cast(producer), std::memory_order_release, std::memory_order_relaxed)); + } + else { + auto prevTailImplicit = implicitProducers.load(std::memory_order_relaxed); + do { + static_cast(producer)->nextImplicitProducer = prevTailImplicit; + } while (!implicitProducers.compare_exchange_weak(prevTailImplicit, static_cast(producer), std::memory_order_release, std::memory_order_relaxed)); + } +#endif + + return producer; + } + + void reown_producers() + { + // After another instance is moved-into/swapped-with this one, all the + // producers we stole still think their parents are the other queue. + // So fix them up! + for (auto ptr = producerListTail.load(std::memory_order_relaxed); ptr != nullptr; ptr = ptr->next_prod()) { + ptr->parent = this; + } + } + + + ////////////////////////////////// + // Implicit producer hash + ////////////////////////////////// + + struct ImplicitProducerKVP + { + std::atomic key; + ImplicitProducer* value; // No need for atomicity since it's only read by the thread that sets it in the first place + + ImplicitProducerKVP() { } + + ImplicitProducerKVP(ImplicitProducerKVP&& other) MOODYCAMEL_NOEXCEPT + { + key.store(other.key.load(std::memory_order_relaxed), std::memory_order_relaxed); + value = other.value; + } + + inline ImplicitProducerKVP& operator=(ImplicitProducerKVP&& other) MOODYCAMEL_NOEXCEPT + { + swap(other); + return *this; + } + + inline void swap(ImplicitProducerKVP& other) MOODYCAMEL_NOEXCEPT + { + if (this != &other) { + details::swap_relaxed(key, other.key); + std::swap(value, other.value); + } + } + }; + + template + friend void moodycamel::swap(typename ConcurrentQueue::ImplicitProducerKVP&, typename ConcurrentQueue::ImplicitProducerKVP&) MOODYCAMEL_NOEXCEPT; + + struct ImplicitProducerHash + { + size_t capacity; + ImplicitProducerKVP* entries; + ImplicitProducerHash* prev; + }; + + inline void populate_initial_implicit_producer_hash() + { + if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return; + + implicitProducerHashCount.store(0, std::memory_order_relaxed); + auto hash = &initialImplicitProducerHash; + hash->capacity = INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; + hash->entries = &initialImplicitProducerHashEntries[0]; + for (size_t i = 0; i != INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; ++i) { + initialImplicitProducerHashEntries[i].key.store(details::invalid_thread_id, std::memory_order_relaxed); + } + hash->prev = nullptr; + implicitProducerHash.store(hash, std::memory_order_relaxed); + } + + void swap_implicit_producer_hashes(ConcurrentQueue& other) + { + if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return; + + // Swap (assumes our implicit producer hash is initialized) + initialImplicitProducerHashEntries.swap(other.initialImplicitProducerHashEntries); + initialImplicitProducerHash.entries = &initialImplicitProducerHashEntries[0]; + other.initialImplicitProducerHash.entries = &other.initialImplicitProducerHashEntries[0]; + + details::swap_relaxed(implicitProducerHashCount, other.implicitProducerHashCount); + + details::swap_relaxed(implicitProducerHash, other.implicitProducerHash); + if (implicitProducerHash.load(std::memory_order_relaxed) == &other.initialImplicitProducerHash) { + implicitProducerHash.store(&initialImplicitProducerHash, std::memory_order_relaxed); + } + else { + ImplicitProducerHash* hash; + for (hash = implicitProducerHash.load(std::memory_order_relaxed); hash->prev != &other.initialImplicitProducerHash; hash = hash->prev) { + continue; + } + hash->prev = &initialImplicitProducerHash; + } + if (other.implicitProducerHash.load(std::memory_order_relaxed) == &initialImplicitProducerHash) { + other.implicitProducerHash.store(&other.initialImplicitProducerHash, std::memory_order_relaxed); + } + else { + ImplicitProducerHash* hash; + for (hash = other.implicitProducerHash.load(std::memory_order_relaxed); hash->prev != &initialImplicitProducerHash; hash = hash->prev) { + continue; + } + hash->prev = &other.initialImplicitProducerHash; + } + } + + // Only fails (returns nullptr) if memory allocation fails + ImplicitProducer* get_or_add_implicit_producer() + { + // Note that since the data is essentially thread-local (key is thread ID), + // there's a reduced need for fences (memory ordering is already consistent + // for any individual thread), except for the current table itself. + + // Start by looking for the thread ID in the current and all previous hash tables. + // If it's not found, it must not be in there yet, since this same thread would + // have added it previously to one of the tables that we traversed. + + // Code and algorithm adapted from http://preshing.com/20130605/the-worlds-simplest-lock-free-hash-table + +#if MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugLock lock(implicitProdMutex); +#endif + + auto id = details::thread_id(); + auto hashedId = details::hash_thread_id(id); + + auto mainHash = implicitProducerHash.load(std::memory_order_acquire); + for (auto hash = mainHash; hash != nullptr; hash = hash->prev) { + // Look for the id in this hash + auto index = hashedId; + while (true) { // Not an infinite loop because at least one slot is free in the hash table + index &= hash->capacity - 1; + + auto probedKey = hash->entries[index].key.load(std::memory_order_relaxed); + if (probedKey == id) { + // Found it! If we had to search several hashes deep, though, we should lazily add it + // to the current main hash table to avoid the extended search next time. + // Note there's guaranteed to be room in the current hash table since every subsequent + // table implicitly reserves space for all previous tables (there's only one + // implicitProducerHashCount). + auto value = hash->entries[index].value; + if (hash != mainHash) { + index = hashedId; + while (true) { + index &= mainHash->capacity - 1; + probedKey = mainHash->entries[index].key.load(std::memory_order_relaxed); + auto empty = details::invalid_thread_id; +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + auto reusable = details::invalid_thread_id2; + if ((probedKey == empty && mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_relaxed)) || + (probedKey == reusable && mainHash->entries[index].key.compare_exchange_strong(reusable, id, std::memory_order_acquire))) { +#else + if ((probedKey == empty && mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_relaxed))) { +#endif + mainHash->entries[index].value = value; + break; + } + ++index; + } + } + + return value; + } + if (probedKey == details::invalid_thread_id) { + break; // Not in this hash table + } + ++index; + } + } + + // Insert! + auto newCount = 1 + implicitProducerHashCount.fetch_add(1, std::memory_order_relaxed); + while (true) { + if (newCount >= (mainHash->capacity >> 1) && !implicitProducerHashResizeInProgress.test_and_set(std::memory_order_acquire)) { + // We've acquired the resize lock, try to allocate a bigger hash table. + // Note the acquire fence synchronizes with the release fence at the end of this block, and hence when + // we reload implicitProducerHash it must be the most recent version (it only gets changed within this + // locked block). + mainHash = implicitProducerHash.load(std::memory_order_acquire); + if (newCount >= (mainHash->capacity >> 1)) { + auto newCapacity = mainHash->capacity << 1; + while (newCount >= (newCapacity >> 1)) { + newCapacity <<= 1; + } + auto raw = static_cast(Traits::malloc(sizeof(ImplicitProducerHash) + std::alignment_of::value - 1 + sizeof(ImplicitProducerKVP) * newCapacity)); + if (raw == nullptr) { + // Allocation failed + implicitProducerHashCount.fetch_add(-1, std::memory_order_relaxed); + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + return nullptr; + } + + auto newHash = new (raw) ImplicitProducerHash; + newHash->capacity = newCapacity; + newHash->entries = reinterpret_cast(details::align_for(raw + sizeof(ImplicitProducerHash))); + for (size_t i = 0; i != newCapacity; ++i) { + new (newHash->entries + i) ImplicitProducerKVP; + newHash->entries[i].key.store(details::invalid_thread_id, std::memory_order_relaxed); + } + newHash->prev = mainHash; + implicitProducerHash.store(newHash, std::memory_order_release); + implicitProducerHashResizeInProgress.clear(std::memory_order_release); + mainHash = newHash; + } + else { + implicitProducerHashResizeInProgress.clear(std::memory_order_release); + } + } + + // If it's < three-quarters full, add to the old one anyway so that we don't have to wait for the next table + // to finish being allocated by another thread (and if we just finished allocating above, the condition will + // always be true) + if (newCount < (mainHash->capacity >> 1) + (mainHash->capacity >> 2)) { + bool recycled; + auto producer = static_cast(recycle_or_create_producer(false, recycled)); + if (producer == nullptr) { + implicitProducerHashCount.fetch_add(-1, std::memory_order_relaxed); + return nullptr; + } + if (recycled) { + implicitProducerHashCount.fetch_add(-1, std::memory_order_relaxed); + } + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + producer->threadExitListener.callback = &ConcurrentQueue::implicit_producer_thread_exited_callback; + producer->threadExitListener.userData = producer; + details::ThreadExitNotifier::subscribe(&producer->threadExitListener); +#endif + + auto index = hashedId; + while (true) { + index &= mainHash->capacity - 1; + auto probedKey = mainHash->entries[index].key.load(std::memory_order_relaxed); + + auto empty = details::invalid_thread_id; +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + auto reusable = details::invalid_thread_id2; + if ((probedKey == empty && mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_relaxed)) || + (probedKey == reusable && mainHash->entries[index].key.compare_exchange_strong(reusable, id, std::memory_order_acquire))) { +#else + if ((probedKey == empty && mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_relaxed))) { +#endif + mainHash->entries[index].value = producer; + break; + } + ++index; + } + return producer; + } + + // Hmm, the old hash is quite full and somebody else is busy allocating a new one. + // We need to wait for the allocating thread to finish (if it succeeds, we add, if not, + // we try to allocate ourselves). + mainHash = implicitProducerHash.load(std::memory_order_acquire); + } + } + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + void implicit_producer_thread_exited(ImplicitProducer* producer) + { + // Remove from thread exit listeners + details::ThreadExitNotifier::unsubscribe(&producer->threadExitListener); + + // Remove from hash +#if MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugLock lock(implicitProdMutex); +#endif + auto hash = implicitProducerHash.load(std::memory_order_acquire); + assert(hash != nullptr); // The thread exit listener is only registered if we were added to a hash in the first place + auto id = details::thread_id(); + auto hashedId = details::hash_thread_id(id); + details::thread_id_t probedKey; + + // We need to traverse all the hashes just in case other threads aren't on the current one yet and are + // trying to add an entry thinking there's a free slot (because they reused a producer) + for (; hash != nullptr; hash = hash->prev) { + auto index = hashedId; + do { + index &= hash->capacity - 1; + probedKey = hash->entries[index].key.load(std::memory_order_relaxed); + if (probedKey == id) { + hash->entries[index].key.store(details::invalid_thread_id2, std::memory_order_release); + break; + } + ++index; + } while (probedKey != details::invalid_thread_id); // Can happen if the hash has changed but we weren't put back in it yet, or if we weren't added to this hash in the first place + } + + // Mark the queue as being recyclable + producer->inactive.store(true, std::memory_order_release); + } + + static void implicit_producer_thread_exited_callback(void* userData) + { + auto producer = static_cast(userData); + auto queue = producer->parent; + queue->implicit_producer_thread_exited(producer); + } +#endif + + ////////////////////////////////// + // Utility functions + ////////////////////////////////// + + template + static inline U* create_array(size_t count) + { + assert(count > 0); + auto p = static_cast(Traits::malloc(sizeof(U) * count)); + if (p == nullptr) { + return nullptr; + } + + for (size_t i = 0; i != count; ++i) { + new (p + i) U(); + } + return p; + } + + template + static inline void destroy_array(U* p, size_t count) + { + if (p != nullptr) { + assert(count > 0); + for (size_t i = count; i != 0; ) { + (p + --i)->~U(); + } + Traits::free(p); + } + } + + template + static inline U* create() + { + auto p = Traits::malloc(sizeof(U)); + return p != nullptr ? new (p) U : nullptr; + } + + template + static inline U* create(A1&& a1) + { + auto p = Traits::malloc(sizeof(U)); + return p != nullptr ? new (p) U(std::forward(a1)) : nullptr; + } + + template + static inline void destroy(U* p) + { + if (p != nullptr) { + p->~U(); + } + Traits::free(p); + } + +private: + std::atomic producerListTail; + std::atomic producerCount; + + std::atomic initialBlockPoolIndex; + Block* initialBlockPool; + size_t initialBlockPoolSize; + +#if !MCDBGQ_USEDEBUGFREELIST + FreeList freeList; +#else + debug::DebugFreeList freeList; +#endif + + std::atomic implicitProducerHash; + std::atomic implicitProducerHashCount; // Number of slots logically used + ImplicitProducerHash initialImplicitProducerHash; + std::array initialImplicitProducerHashEntries; + std::atomic_flag implicitProducerHashResizeInProgress; + + std::atomic nextExplicitConsumerId; + std::atomic globalExplicitConsumerOffset; + +#if MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugMutex implicitProdMutex; +#endif + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + std::atomic explicitProducers; + std::atomic implicitProducers; +#endif +}; + + +template +ProducerToken::ProducerToken(ConcurrentQueue& queue) + : producer(queue.recycle_or_create_producer(true)) +{ + if (producer != nullptr) { + producer->token = this; + } +} + +template +ProducerToken::ProducerToken(BlockingConcurrentQueue& queue) + : producer(reinterpret_cast*>(&queue)->recycle_or_create_producer(true)) +{ + if (producer != nullptr) { + producer->token = this; + } +} + +template +ConsumerToken::ConsumerToken(ConcurrentQueue& queue) + : itemsConsumedFromCurrent(0), currentProducer(nullptr), desiredProducer(nullptr) +{ + initialOffset = queue.nextExplicitConsumerId.fetch_add(1, std::memory_order_release); + lastKnownGlobalOffset = -1; +} + +template +ConsumerToken::ConsumerToken(BlockingConcurrentQueue& queue) + : itemsConsumedFromCurrent(0), currentProducer(nullptr), desiredProducer(nullptr) +{ + initialOffset = reinterpret_cast*>(&queue)->nextExplicitConsumerId.fetch_add(1, std::memory_order_release); + lastKnownGlobalOffset = -1; +} + +template +inline void swap(ConcurrentQueue& a, ConcurrentQueue& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +inline void swap(ProducerToken& a, ProducerToken& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +inline void swap(ConsumerToken& a, ConsumerToken& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +template +inline void swap(typename ConcurrentQueue::ImplicitProducerKVP& a, typename ConcurrentQueue::ImplicitProducerKVP& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +} + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif diff --git a/ext/v8/wrapper.h b/ext/v8/wrapper.h new file mode 100644 index 00000000..b6e81075 --- /dev/null +++ b/ext/v8/wrapper.h @@ -0,0 +1,113 @@ +// -*- mode: c++ -*- +#ifndef RR_WRAPPER_H +#define RR_WRAPPER_H + +namespace rr { + + /** + * A wrapper provides the ability to create a temporary reference to + * a stack-allocated value so that it can be passed to ruby. This is + * normally the case for a callback parameter, where you will be + * calling into Ruby from C++. Ruby should not call methods on this + * object outside the scope of the function call. E.g. + * + * class MyObjectWrapper : public Wrapper { + * // [...] + * }; + * + * MyObject foo; + * rb_funcall(object, rb_intern("count"), 1, MyObjectWrapper(foo)); + * // ruby should not use anymore, it might crash + * + * Note: This seems dangerous in practice, but since it is only ever + * used by the low-level api, wrapper objects do not escape into the + * wild, and so aren't used beyond their normal lifetime on the C++ + * stack. + * + * We may add a destructor that invalidates the pointer, + * and would throw an exception if you tried to use this object at + * some after it leaves scope. + */ + template + class Wrapper { + public: + /** + * The Ruby Class of this Wrapper + */ + static VALUE Class; + + /** + * Package up this wrapper so that it can be accessed from + * Ruby. Use this just before passing to a ruby call. E.g. + * + * MyObject object; + * rb_funcall(thing, rb_intern("take"), 1, MyObjectWrapper(object)) + */ + Wrapper(T content) : container(new Container(content)) {} + + /** + * Access the underlying object when it has been passed from Ruby + * to a C++ function. E.g. + * + * SomeFunction(VALUE self) { + * MyObjectWrapper object(self); + * } + */ + Wrapper(VALUE self) : container(Container::unwrap(self)) {} + + /** + * Access the wrapped value via pointer dereference: + * + * MyObjectWrapper object(self); + * object->SomeMethod(); + */ + inline T* operator ->() { + return &container->content; + } + + /** + * Convert this Wrapper into a Ruby VALUE so that it can be + * transparently passed to methods expecting a VALUE. This means + * you can do things like: + * + * MyObjectWrapper wrapper(self); + * rb_inspect(wrapper); + * + * Even though MyObjectWrapper is not a VALUE + */ + inline operator VALUE() { + return Container::wrap(container, Class); + } + + /** + * This is the struct that will be held inside the Ruby T_DATA + * object. It mainly serves to keep a reference to the stack value. + */ + struct Container { + + Container(T content_) : content(content_) {} + + static inline VALUE wrap(Container* container, VALUE rb_class) { + return Data_Wrap_Struct(rb_class, 0, &destroy, container); + } + + static inline Container* unwrap(VALUE object) { + Container* container; + Data_Get_Struct(object, struct Container, container); + return container; + } + + static void destroy(Container* container) { + delete container; + } + T content; + }; + + Container* container; + }; + + template + VALUE Wrapper::Class; +} + +#endif /* RR_WRAPPER_H */ diff --git a/lib/v8.rb b/lib/v8.rb index 892550a4..69e50e5b 100644 --- a/lib/v8.rb +++ b/lib/v8.rb @@ -1,30 +1,30 @@ require "v8/version" -require 'v8/weak' require 'v8/init' -require 'v8/error' -require 'v8/stack' +require 'v8/isolate' +require 'v8/context' +# require 'v8/error' +# require 'v8/stack' require 'v8/conversion/fundamental' -require 'v8/conversion/indentity' -require 'v8/conversion/reference' -require 'v8/conversion/primitive' -require 'v8/conversion/code' -require 'v8/conversion/class' -require 'v8/conversion/object' -require 'v8/conversion/time' -require 'v8/conversion/hash' -require 'v8/conversion/array' -require 'v8/conversion/proc' -require 'v8/conversion/method' -require 'v8/conversion/symbol' -require 'v8/conversion/string' -require 'v8/conversion/fixnum' +# require 'v8/conversion/reference' +# require 'v8/conversion/primitive' +# require 'v8/conversion/code' +# require 'v8/conversion/class' +# require 'v8/conversion/object' +# require 'v8/conversion/time' +# require 'v8/conversion/hash' +# require 'v8/conversion/array' +# require 'v8/conversion/proc' +# require 'v8/conversion/method' +# require 'v8/conversion/symbol' +# require 'v8/conversion/string' +# require 'v8/conversion/fixnum' require 'v8/conversion' -require 'v8/access/names' -require 'v8/access/indices' -require 'v8/access/invocation' -require 'v8/access' -require 'v8/context' +# require 'v8/access/names' +# require 'v8/access/indices' +# require 'v8/access/invocation' +# require 'v8/access' require 'v8/object' -require 'v8/array' +require 'v8/date' +# require 'v8/array' require 'v8/function' diff --git a/lib/v8/c.rb b/lib/v8/c.rb new file mode 100644 index 00000000..287dbf35 --- /dev/null +++ b/lib/v8/c.rb @@ -0,0 +1,2 @@ +require 'v8/weak' +require 'v8/c/maybe' diff --git a/lib/v8/c/maybe.rb b/lib/v8/c/maybe.rb new file mode 100644 index 00000000..8ebe5b2c --- /dev/null +++ b/lib/v8/c/maybe.rb @@ -0,0 +1,78 @@ +module V8 + module C + ## + # Some V8 API methods return a `Maybe` value that force you to check + # if the operation failed or not before accessing the underlying + # value. This implements the `v8::Maybe` and `v8::MaybeLocal` apis, + # and is returned by those low-level C methods: + # + # maybe = object.Get(context, key) + # if maybe.IsJust() + # puts maybe.class #=> V8::C::Maybe::Just + # puts "the value is #{maybe.FromJust()}" + # else + # puts maybe.class #=> V8::C::Maybe::Nothing + # puts "operation failed!" + # end + # + # Note: Instances of `V8::C::Maybe` are only ever created by the C + # extension, and should not ever be instantiated from Ruby code. + class Maybe + + ## + # If true, then this is an instance of `V8::C::Just`, and it does + # wrap a value + def IsJust() + false + end + + ## + # If true, then this is an instance of `V8::C::Nothing`, and it + # does not contain a value. Any attempt to access the value will + # raise an error. + def IsNothing() + false + end + + ## + # A Maybe that *does* contain a value. + class Just < Maybe + def initialize(value) + @value = value + end + + def IsJust() + true + end + + def FromJust() + @value + end + end + + ## + # A Maybe that *does* not contain any value + class Nothing < Maybe + + def IsNothing() + true + end + + def FromJust() + fail PendingExceptionError, "no value was available because an exception is pending in this context" + end + end + + ## + # Singleton instance of Nothing. + # + # We only need one instance of Nothing because there is no + # additional state apart from its nothingness. + def self.nothing + @nothing ||= Nothing.new + end + end + + class PendingExceptionError < StandardError;end + end +end diff --git a/lib/v8/context.rb b/lib/v8/context.rb index 05f362f0..0267b176 100644 --- a/lib/v8/context.rb +++ b/lib/v8/context.rb @@ -26,7 +26,7 @@ module V8 # cxt.eval('num') # => 5 # end class Context - include V8::Error::Try + # include V8::Error::Try # @!attribute [r] conversion # @return [V8::Conversion] conversion behavior for this context @@ -44,6 +44,10 @@ class Context # @return [Number] maximum execution time in milliseconds for scripts executed in this context attr_reader :timeout + # @!attribute [r] isolate + # @return [V8::Isolate] + attr_reader :isolate + # Creates a new context. # # If passed the `:with` option, that object will be used as @@ -62,20 +66,24 @@ class Context # * :with scope serves as the global scope of the new context # @yield [V8::Context] the newly created context def initialize(options = {}) - @conversion = Conversion.new - @access = Access.new - @timeout = options[:timeout] - if global = options[:with] - Context.new.enter do - global_template = global.class.to_template.InstanceTemplate() - @native = V8::C::Context::New(nil, global_template) - end - enter {link global, @native.Global()} - else - V8::C::Locker() do - @native = V8::C::Context::New() - end + @isolate = options[:isolate] || V8::Isolate.new + V8::C::HandleScope(@isolate.native) do + @native = V8::C::Context::New(@isolate.native) end + @conversion = Conversion.new + # @access = Access.new + # @timeout = options[:timeout] + # if global = options[:with] + # Context.new.enter do + # global_template = global.class.to_template.InstanceTemplate() + # @native = V8::C::Context::New(nil, global_template) + # end + # enter {link global, @native.Global()} + # else + # V8::C::Locker() do + # @native = V8::C::Context::New() + # end + # end yield self if block_given? end @@ -92,12 +100,11 @@ def eval(source, filename = '', line = 1) source = source.read end enter do - script = try { V8::C::Script::New(source.to_s, filename.to_s) } - if @timeout - to_ruby try {script.RunWithTimeout(@timeout)} - else - to_ruby try {script.Run()} - end + src = V8::C::String::NewFromUtf8(@isolate.native, source.to_s) + filename = V8::C::String::NewFromUtf8(@isolate.native, filename.to_s) + origin = V8::C::ScriptOrigin.new(filename); + script = V8::C::Script::Compile(@native, src, origin).FromJust() + to_ruby script.Run(@native).FromJust() end end @@ -107,7 +114,7 @@ def eval(source, filename = '', line = 1) # @return [Object] value the value at `key` def [](key) enter do - to_ruby(@native.Global().Get(to_v8(key))) + to_ruby(@native.Global().Get(@cnative, to_v8(key)).FromJust()) end end @@ -117,26 +124,11 @@ def [](key) # @param [Object] value the value to bind def []=(key, value) enter do - @native.Global().Set(to_v8(key), to_v8(value)) + @native.Global().Set(@native, to_v8(key), to_v8(value)) end return value end - # Destroy this context and release any internal references it may - # contain to embedded Ruby objects. - # - # A disposed context may never again be used for anything, and all - # objects created with it will become unusable. - def dispose - return unless @native - @native.Dispose() - @native = nil - V8::C::V8::ContextDisposedNotification() - def self.enter - fail "cannot enter a context which has already been disposed" - end - end - # Returns this context's global object. This will be a `V8::Object` # if no scope was provided or just an `Object` if a Ruby object # is serving as the global scope. @@ -165,7 +157,7 @@ def to_ruby(v8_object) # @return [V8::C::Object] to pass to V8 # @see V8::Conversion for customizing and extending this mechanism def to_v8(ruby_object) - @conversion.to_v8(ruby_object) + @conversion.to_v8(self, ruby_object) end # Marks a Ruby object and a v8 C++ Object as being the same. In other @@ -241,14 +233,12 @@ def self.current=(context) def lock_scope_and_enter current = Context.current Context.current = self - V8::C::Locker() do - V8::C::HandleScope() do - begin - @native.Enter() - yield if block_given? - ensure - @native.Exit() - end + V8::C::HandleScope(@isolate.native) do + begin + @native.Enter() + yield if block_given? + ensure + @native.Exit() end end ensure diff --git a/lib/v8/conversion.rb b/lib/v8/conversion.rb index 3c2518bd..6fb3af9b 100644 --- a/lib/v8/conversion.rb +++ b/lib/v8/conversion.rb @@ -1,36 +1,167 @@ - +## +# An extensible conversion mechanism for converting objects to and +# from their V8 representations. +# +# The Ruby Racer has two levels of representation for JavaScript +# objects: the low-level C++ objects which are just thin wrappers +# native counterparts. These are the objects in the `V8::C::*` +# namespace. It also has high-level Ruby objects which can +# either be instances of `V8::Object`, `V8::Date`, or just plain Ruby +# objects that have representations in the JavaScript runtime. +# +# The conversion object held by the context captures this transition +# from one object type to the other. It is implemented as a "middleware" +# stack where at the base is the `Fundamental` conversion which does +# basic conversion and identity mapping. +# +# In order to "extend" or override the conversion mechanism, you can +# extend this object to add behaviors. For example, the following +# extension will add a `__fromRuby__` property to every ruby object +# that is embedded into this context. +# +# module TagRubyObjects +# def to_v8(context, ruby_object) +# super.tap do |v8_object| +# v8_object.Set(V8::C::String::NewFromUtf8("__fromRuby__"), V8::C::Boolean::New(true)) +# end +# end +# end +# context.conversion.extend TagRubyObjects +# +# @see V8::Conversion::Fundamental for the basic conversion operation. class V8::Conversion include Fundamental - include Identity + + ## + # Convert a low-level instance of `V8::C::Value` or one of its + # subclasses into a Ruby object. + # + # The `Fundamental` conversion will call `v8_object.to_ruby`, but + # any number of middlewares can be inserted between then. + # + # @param [V8::C::Value] the object to convert + # @return [Object] the corresponding Ruby value def to_ruby(v8_object) super v8_object end - def to_v8(ruby_object) - super ruby_object + ## + # Convert a Ruby Object into a low-level `V8::C::Value` or one of + # its subclasses. Note here that things like `V8::Object` are + # considered Ruby objects and *not* V8 objects. So, for example, the + # fundamental conversion for `V8::Object` will return a + # `V8::C::Object` + # + # The `Fundamental` conversion will call + # `ruby_object.to_v8(context)` optionally storing the result in an + # identity map in the case where the result is a `V8::C::Object` + # + # @param [V8::Context] the Ruby context in the conversion happens + # @param [Object] Ruby object to convert + # @return [V8::C::Value] the v8 representation + def to_v8(context, ruby_object) + super context, ruby_object end end -for type in [TrueClass, FalseClass, NilClass, Float] do - type.class_eval do - include V8::Conversion::Primitive + +## +# The folowing represent the default conversions from instances of +# `V8::C::Value` into their Ruby counterparts. +module V8::C + class String + def to_ruby + self.Utf8Value() + end + end + + class Number + def to_ruby + self.Value() + end + end + + class Undefined + def to_ruby + nil + end + end + + class Boolean + def to_ruby + IsTrue() + end + end + + class Object + def to_ruby + ::V8::Object.new self + end + end + + class Date + def to_ruby + ::V8::Date.new self + end + end + + class Function + def to_ruby + ::V8::Function.new self + end + end + + class Value + def to_v8(context) + self + end end end -for type in [Class, Object, Array, Hash, String, Symbol, Time, Proc, Method, Fixnum] do - type.class_eval do - include V8::Conversion.const_get(type.name) +## +# The following are the default conversions from Ruby objects into +# low-level C++ objects. +class String + def to_v8(context) + V8::C::String::NewFromUtf8(context.isolate.native, self) end end -class UnboundMethod - include V8::Conversion::Method +class Fixnum + def to_v8(context) + V8::C::Integer::New(context.isolate.native, self) + end end -for type in [:Object, :String, :Date] do - V8::C::const_get(type).class_eval do - include V8::Conversion::const_get("Native#{type}") +class Symbol + def to_v8(context) + isolate = context.isolate.native + V8::C::Symbol::For(context.isolate.native, V8::C::String::NewFromUtf8(isolate, to_s)) end end +class Object + def to_v8(context) + V8::C::Object::New(context.isolate.native) + end +end + +class Proc + def to_v8(context) + isolate = context.isolate.native + callback = lambda do |info| + args = [] + arity = info.Length() + if self.arity > -1 + arity = self.arity + end + arity.times do |i| + args << context.to_ruby(info[i]) + end + result = context.to_v8 self.call(*args) + info.GetReturnValue().Set(result) + end + V8::C::Function::New(isolate, callback) + end +end diff --git a/lib/v8/conversion/fundamental.rb b/lib/v8/conversion/fundamental.rb index a1ec5dae..9c9afd7d 100644 --- a/lib/v8/conversion/fundamental.rb +++ b/lib/v8/conversion/fundamental.rb @@ -1,11 +1,88 @@ +require 'v8/weak' + class V8::Conversion + ## + # This is the fundamental conversion for from Ruby objects into + # `V8::C::Value`s and vice-versa. For instances of `V8::C::Object`, + # the result is stored in an identity map, so that subsequent + # conversions return the same object both to and from ruby. + # + # It sits at the top of the conversion "middleware stack" module Fundamental + ## + # Convert a low-level `V8::C::Value` into a Ruby object, + # optionally storing the result in an identity map so that it can + # be re-used for subsequent conversions. + # + # @param [V8::C::Value] low-level C++ object. + # @return [Object] Ruby object counterpart def to_ruby(v8_object) - v8_object.to_ruby + # Only objects can be reliably identified. If this is an + # object, then we want to see if there is already a ruby object + # associated with it. + if v8_object.kind_of? V8::C::Object + if rb_object = v8_idmap[v8_object.GetIdentityHash()] + # it was in the id map, so return the existing instance. + rb_object + else + # it was not in the id map, so we run the default conversion + # and store it in the id map + v8_object.to_ruby.tap do |object| + equate object, v8_object + end + end + else + # not an object, just do the default conversion + v8_object.to_ruby + end + end + + ## + # Convert a Ruby object into a low-level C++ `V8::C::Value`. + # + # First it checks to see if there is an entry in the id map for + # this object. Otherwise, it will run the default conversion. + def to_v8(context, ruby_object) + if v8_object = rb_idmap[ruby_object.object_id] + v8_object + else + v8_object = ruby_object.to_v8(context) + if v8_object.kind_of? V8::C::Object + v8_object.tap do + equate ruby_object, v8_object + end + else + v8_object + end + end + end + + ## + # Mark a ruby object and a low-level V8 C++ object as being + # equivalent in this context. + # + # After being equated the two objects are like mirrors of each + # other, where one exists in the Ruby world, and the other exists + # in the V8 world. It's all very through the looking glass. + # + # Whenever `ruby_object` is reflected into the V8 runtime, then + # `v8_object` will be used in its stead, and whenever `v8_object` + # is reflected into the Ruby runtime, then `ruby_object` will be + # used in *its* stead. + # + # @param [Object] the ruby object + # @param [V8::C::Value] the v8 object + def equate(ruby_object, v8_object) + v8_idmap[v8_object.GetIdentityHash()] = ruby_object + rb_idmap[ruby_object.object_id] = v8_object + end + + def v8_idmap + @v8_idmap ||= V8::Weak::WeakValueMap.new end - def to_v8(ruby_object) - ruby_object.to_v8 + def rb_idmap + @ruby_idmap ||= V8::Weak::WeakValueMap.new end end -end \ No newline at end of file +end diff --git a/lib/v8/conversion/indentity.rb b/lib/v8/conversion/indentity.rb deleted file mode 100644 index f566f950..00000000 --- a/lib/v8/conversion/indentity.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'ref' - -class V8::Conversion - module Identity - def to_ruby(v8_object) - if v8_object.class <= V8::C::Object - v8_idmap[v8_object.GetIdentityHash()] || super(v8_object) - else - super(v8_object) - end - end - - def to_v8(ruby_object) - return super(ruby_object) if ruby_object.is_a?(String) || ruby_object.is_a?(Primitive) - rb_idmap[ruby_object.object_id] || super(ruby_object) - end - - def equate(ruby_object, v8_object) - v8_idmap[v8_object.GetIdentityHash()] = ruby_object - rb_idmap[ruby_object.object_id] = v8_object - end - - def v8_idmap - @v8_idmap ||= V8::Weak::WeakValueMap.new - end - - def rb_idmap - @ruby_idmap ||= V8::Weak::WeakValueMap.new - end - end -end \ No newline at end of file diff --git a/lib/v8/conversion/primitive.rb b/lib/v8/conversion/primitive.rb deleted file mode 100644 index 8602d9ae..00000000 --- a/lib/v8/conversion/primitive.rb +++ /dev/null @@ -1,7 +0,0 @@ -class V8::Conversion - module Primitive - def to_v8 - return self - end - end -end \ No newline at end of file diff --git a/lib/v8/date.rb b/lib/v8/date.rb new file mode 100644 index 00000000..0fc4e766 --- /dev/null +++ b/lib/v8/date.rb @@ -0,0 +1,7 @@ +module V8 + class Date < V8::Object + def value_of + @context.to_ruby @native.ValueOf() + end + end +end diff --git a/lib/v8/function.rb b/lib/v8/function.rb index c1cbbb4b..4a366944 100644 --- a/lib/v8/function.rb +++ b/lib/v8/function.rb @@ -1,5 +1,5 @@ class V8::Function < V8::Object - include V8::Error::Try + #include V8::Error::Try def initialize(native = nil) super do @@ -10,7 +10,7 @@ def initialize(native = nil) def methodcall(this, *args) @context.enter do this ||= @context.native.Global() - @context.to_ruby try {native.Call(@context.to_v8(this), args.map {|a| @context.to_v8 a})} + @context.to_ruby native.Call(@context.to_v8(this), args.map {|a| @context.to_v8 a}) end end @@ -22,7 +22,7 @@ def call(*args) def new(*args) @context.enter do - @context.to_ruby try {native.NewInstance(args.map {|a| @context.to_v8 a})} + @context.to_ruby native.NewInstance(args.map {|a| @context.to_v8 a}) end end -end \ No newline at end of file +end diff --git a/lib/v8/isolate.rb b/lib/v8/isolate.rb new file mode 100644 index 00000000..b294885a --- /dev/null +++ b/lib/v8/isolate.rb @@ -0,0 +1,58 @@ +module V8 + ## + # A completely encapsulated JavaScript virtual machine. No state of + # any kind is shared between isolates. + # + # The isolate is the primany API for understanding and configuring + # the runtime profile of the JavaScript interpreter. For example, it + # is through the isolate that you can take heap and stack samples + # for memory and cpu profiling. It is also through the isolate that + # you can place restraints on the amount of memory and CPU that your + # JavaScript execution will consume. + # + # An isolate can have many exceutions contexts that each have their + # own scopes, and so significant resource savings can be had by + # reusing the same isolate for multiple contexts. + class Isolate + + # @!attribute [r] native + # @return [V8::C::Isolate] the underlying C++ object + attr_reader :native + + ## + # Create an new isolate. + def initialize() + @native = V8::C::Isolate::New() + end + + ## + # Terminates any currently executing JavaScript in this Isolate. + # + # This will throw an uncatchable exception inside the JavaScript + # code which will forcibly unwind the stack. + # + # @return [NilClass] nil + def terminate_execution! + @native.TerminateExecution() + end + + ## + # Returns true if execution is terminating, but there are still + # JavaScript frames left on the stack. + # + # @return [TrueClass | FalseClass] whether isolate is terminating execution + def execution_terminating? + @native.IsExecutionTerminating() + end + + ## + # Cancels any scheduled termination of JavaScript. Not really sure + # why you would want to do this, but if you figure out a use-case, + # would love for you to document it here. + # + # @return [NilClass] nil + def cancel_terminate_execution! + @native.CancelTerminateExecution() + end + end +end diff --git a/lib/v8/object.rb b/lib/v8/object.rb index 71f6281b..95e7c29b 100644 --- a/lib/v8/object.rb +++ b/lib/v8/object.rb @@ -1,17 +1,15 @@ class V8::Object include Enumerable attr_reader :native - alias_method :to_v8, :native def initialize(native = nil) @context = V8::Context.current or fail "tried to initialize a #{self.class} without being in an entered V8::Context" - @native = block_given? ? yield : native || V8::C::Object::New() - @context.link self, @native + @native = native || V8::C::Object::New(@context.isolate.native) end def [](key) @context.enter do - @context.to_ruby @native.Get(@context.to_v8(key)) + @context.to_ruby @native.Get(@context.native, @context.to_v8(key.to_s)).FromJust() end end @@ -52,6 +50,10 @@ def to_s end end + def to_v8(context) + native + end + def respond_to?(method) super or self[method] != nil end @@ -76,4 +78,4 @@ def method_missing(name, *args, &block) raise ArgumentError, "wrong number of arguments (#{args.length} for 0)" unless args.empty? end end -end \ No newline at end of file +end diff --git a/lib/v8/retained_objects.rb b/lib/v8/retained_objects.rb new file mode 100644 index 00000000..0dbc898b --- /dev/null +++ b/lib/v8/retained_objects.rb @@ -0,0 +1,29 @@ +module V8 + class RetainedObjects + def initialize + @counts = {} + end + + def add(object) + if @counts[object] + @counts[object] += 1 + else + @counts[object] = 1 + end + end + + def remove(object) + if count = @counts[object] + if count <= 1 + @counts.delete object + else + @counts[object] -= 1 + end + end + end + + def retaining?(object) + !!@counts[object] + end + end +end diff --git a/spec/c/array_spec.rb b/spec/c/array_spec.rb index aab69762..fe7d58b2 100644 --- a/spec/c/array_spec.rb +++ b/spec/c/array_spec.rb @@ -1,19 +1,36 @@ -require 'spec_helper' +require 'c_spec_helper' describe V8::C::Array do requires_v8_context - it "can store and retrieve a value" do - o = V8::C::Object::New() - a = V8::C::Array::New() - a.Length().should eql 0 - a.Set(0, o) - a.Length().should eql 1 - a.Get(0).Equals(o).should be_true + it 'can store and retrieve a value' do + o = V8::C::Object::New(@isolate) + a = V8::C::Array::New(@isolate) + + expect(a.Length).to eq 0 + + a.Set(@ctx, 0, o) + expect(a.Length).to eq 1 + + expect(a.Get(@ctx, 0)).to v8_eq o end - it "can be initialized with a length" do - a = V8::C::Array::New(5) - a.Length().should eql 5 + it 'can be initialized with a length' do + a = V8::C::Array::New(@isolate, 5) + expect(a.Length).to eq 5 + end + + it 'can clone its elements' do + o = V8::C::Object::New(@isolate) + key = V8::C::String::NewFromUtf8(@isolate, "key") + o.Set(@ctx, key, key) + a = V8::C::Array::New(@isolate) + + a.Set(@ctx, 0, o) + + object = a.CloneElementAt(@ctx, 0) + expect(object).to be_successful + expect(object.FromJust().Get(@ctx, key)).to be_successful + expect(object.FromJust().Get(@ctx, key).FromJust().Utf8Value()).to eql "key" end end diff --git a/spec/c/constants_spec.rb b/spec/c/constants_spec.rb deleted file mode 100644 index 46837bf6..00000000 --- a/spec/c/constants_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'spec_helper' - -describe V8::C do - requires_v8_context - - it "has constant methods for Undefined, Null, True and False" do - [:Undefined, :Null, :True, :False].each do |name| - constant = V8::C.send(name) - constant.should_not be_nil - V8::C.send(name).should be constant - end - end - - it "has a value for the Empty handle" do - V8::C::Value::Empty.should_not be_nil - V8::C::Value::Empty.should be V8::C::Value::Empty - end - - it "can access the V8 version" do - V8::C::V8::GetVersion().should match /^3\.16/ - end -end diff --git a/spec/c/context_spec.rb b/spec/c/context_spec.rb new file mode 100644 index 00000000..02291403 --- /dev/null +++ b/spec/c/context_spec.rb @@ -0,0 +1,15 @@ +require 'c_spec_helper' + +describe V8::C::Context do + let(:isolate) { V8::C::Isolate::New() } + let(:context) { V8::C::Context::New(isolate) } + around do |example| + V8::C::HandleScope(isolate) do + example.run + end + end + + it "can be instantiated" do + expect(context).to be + end +end diff --git a/spec/c/enum_spec.rb b/spec/c/enum_spec.rb new file mode 100644 index 00000000..06b7fdf1 --- /dev/null +++ b/spec/c/enum_spec.rb @@ -0,0 +1,21 @@ +require 'c_spec_helper' + +describe V8::C do + describe 'AccessControl' do + it 'has all enum values' do + expect(V8::C::AccessControl.const_defined? 'DEFAULT').to be true + expect(V8::C::AccessControl.const_defined? 'ALL_CAN_READ').to be true + expect(V8::C::AccessControl.const_defined? 'ALL_CAN_WRITE').to be true + expect(V8::C::AccessControl.const_defined? 'PROHIBITS_OVERWRITING').to be true + end + end + + describe 'PropertyAttribute' do + it 'has all enum values' do + expect(V8::C::PropertyAttribute.const_defined? 'None').to be true + expect(V8::C::PropertyAttribute.const_defined? 'ReadOnly').to be true + expect(V8::C::PropertyAttribute.const_defined? 'DontEnum').to be true + expect(V8::C::PropertyAttribute.const_defined? 'DontDelete').to be true + end + end +end diff --git a/spec/c/exception_spec.rb b/spec/c/exception_spec.rb deleted file mode 100644 index 18512371..00000000 --- a/spec/c/exception_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'spec_helper' - -describe V8::C::Exception do - requires_v8_context - - it "can be thrown from Ruby" do - t = V8::C::FunctionTemplate::New(method(:explode)) - @cxt.Global().Set("explode", t.GetFunction()) - script = V8::C::Script::New(<<-JS, '') - (function() { - try { - explode() - } catch (e) { - return e.message - } - })() - JS - result = script.Run() - result.should_not be_nil - result.should be_kind_of(V8::C::String) - result.Utf8Value().should eql 'did not pay syntax' - end - - def explode(arguments) - error = V8::C::Exception::SyntaxError('did not pay syntax') - V8::C::ThrowException(error) - end -end diff --git a/spec/c/external_spec.rb b/spec/c/external_spec.rb index 2553028e..e3372450 100644 --- a/spec/c/external_spec.rb +++ b/spec/c/external_spec.rb @@ -1,11 +1,23 @@ -require 'spec_helper' +require 'c_spec_helper' describe V8::C::External do - requires_v8_context + let(:isolate) { V8::C::Isolate::New() } + let(:value) { @external::Value() } + around { |example| V8::C::HandleScope(isolate) { example.run } } - it "can store and retrieve a value" do - o = Object.new - external = V8::C::External::New(o) - external.Value().should be(o) + before do + Object.new.tap do |object| + @object_id = object.object_id + @external = V8::C::External::New(isolate, object) + end + end + + it "exists" do + expect(@external).to be + end + + it "can retrieve the ruby object out from V8 land" do + expect(value).to be + expect(value.object_id).to eql @object_id end end diff --git a/spec/c/function_spec.rb b/spec/c/function_spec.rb index 17835bc8..1bce3b0a 100644 --- a/spec/c/function_spec.rb +++ b/spec/c/function_spec.rb @@ -1,48 +1,101 @@ -require 'spec_helper' +require 'c_spec_helper' describe V8::C::Function do requires_v8_context - it "can be called" do - fn = run '(function() {return "foo"})' - fn.Call(@cxt.Global(), []).Utf8Value().should eql "foo" + it "has a script origin" do + fn = run '(function() { return "foo" })' + origin = fn.GetScriptOrigin() + expect(origin.ResourceName().ToString().Utf8Value()).to eql 'undefined' + expect(origin.ResourceLineOffset().Value()).to eql 0 + expect(origin.ResourceColumnOffset().Value()).to eql 0 end - it "can be called with arguments and context" do + it 'can be called' do + fn = run '(function() { return "foo" })' + expect(fn.Call(@ctx.Global, []).Utf8Value).to eq 'foo' + end + + it 'can be called with arguments and context' do fn = run '(function(one, two, three) {this.one = one; this.two = two; this.three = three})' - one = V8::C::Object::New() - two = V8::C::Object::New() - fn.Call(@cxt.Global(), [one, two, 3]) - @cxt.Global().Get("one").should eql one - @cxt.Global().Get("two").should eql two - @cxt.Global().Get("three").should eql 3 + + one = V8::C::Object.New(@isolate) + two = V8::C::Object.New(@isolate) + + fn.Call(@ctx.Global, [one, two, 3]) + + expect(@ctx.Global.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'one'))).to strict_eq one + expect(@ctx.Global.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'two'))).to strict_eq two + expect(@ctx.Global.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'three')).FromJust().Value()).to eq 3 end - it "can be called as a constructor" do + it 'can be called as a constructor' do fn = run '(function() {this.foo = "foo"})' - fn.NewInstance().Get(V8::C::String::New('foo')).Utf8Value().should eql "foo" + expect(fn.NewInstance.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'foo')).FromJust().Utf8Value).to eq 'foo' end - it "can be called as a constructor with arguments" do + it 'can be called as a constructor with arguments' do fn = run '(function(foo) {this.foo = foo})' - object = fn.NewInstance([V8::C::String::New("bar")]) - object.Get(V8::C::String::New('foo')).Utf8Value().should eql "bar" + object = fn.NewInstance([V8::C::String.NewFromUtf8(@isolate, 'bar')]) + + expect(object.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'foo')).FromJust().Utf8Value).to eq 'bar' end - it "doesn't kill the world if invoking it throws a javascript exception" do - V8::C::TryCatch() do - fn = run '(function() { throw new Error("boom!")})' - fn.Call(@cxt.Global(), []) - fn.NewInstance([]) + describe "with a callback" do + + class FunctionCallback + attr_reader :length, :callee, :this, :is_construct_call, :data + def initialize(isolate) + @isolate = isolate + end + def call(info) + @length = info.Length() + @callee = info.Callee() + @this = info.This() + @is_construct_call = info.IsConstructCall() + @data = info.Data() + + ohai = V8::C::String::NewFromUtf8(@isolate, "ohai ") + arg = info[0].ToString() + result = V8::C::String::Concat(ohai, arg) + info.GetReturnValue().Set(result) + end + end + + let(:data) { V8::C::Object::New(@isolate) } + let(:callback) { FunctionCallback.new @isolate } + let(:fn) { V8::C::Function::New(@isolate, callback, data) } + + before do + expect(fn.Call(@ctx.Global(), ["world"]).Utf8Value()).to eql "ohai world" + end + + + it "has all of the function callback info available" do + expect(callback.length).to eql 1 + expect(callback.callee.GetIdentityHash()).to eql fn.GetIdentityHash() + expect(callback.this.GetIdentityHash()).to eql @ctx.Global().GetIdentityHash() + expect(callback.is_construct_call).to be false + expect(callback.data).not_to be_nil + expect(callback.data).to strict_eq data end end + # TODO + # it 'doesn\'t kill the world if invoking it throws a javascript exception' do + # V8::C::TryCatch do + # fn = run '(function() { throw new Error("boom!")})' + # fn.Call(@ctx.Global(), []) + # fn.NewInstance([]) + # end + # end + def run(source) - source = V8::C::String::New(source.to_s) - filename = V8::C::String::New("") - script = V8::C::Script::New(source, filename) - result = script.Run() - result.kind_of?(V8::C::String) ? result.Utf8Value() : result + source = V8::C::String.NewFromUtf8(@isolate, source.to_s) + script = V8::C::Script.Compile(@ctx, source) + result = script.FromJust().Run(@ctx) + checked = result.FromJust() + checked.kind_of?(V8::C::String) ? checked.Utf8Value : checked end end diff --git a/spec/c/function_template_spec.rb b/spec/c/function_template_spec.rb new file mode 100644 index 00000000..2f39ef4e --- /dev/null +++ b/spec/c/function_template_spec.rb @@ -0,0 +1,35 @@ +require 'c_spec_helper' + +describe V8::C::FunctionTemplate do + requires_v8_context + let(:function) { template.GetFunction(@ctx).FromJust() } + + describe "New()" do + describe "with no arguments" do + let(:template) { V8::C::FunctionTemplate::New(@isolate) } + it "creates an empty template" do + expect(template).to be + end + it "has an empty function" do + expect(function).to be + expect(function.Call(@ctx.Global(), []).StrictEquals(@ctx.Global())).to be true + end + end + describe "with a function callback" do + let(:data) { V8::C::Integer::New(@isolate, 42) } + let(:signature) { V8::C::Signature::New(@isolate) } + let(:template) { V8::C::FunctionTemplate::New(@isolate, @callback, data, @signature, 0) } + before do + @callback = ->(info) do + @data = info.Data() + end + function.Call(@ctx.Global(), []) + end + + it "does something" do + expect(@data).not_to be_nil + expect(@data.Value()).to be 42 + end + end + end +end diff --git a/spec/c/handles_spec.rb b/spec/c/handles_spec.rb index 4882403c..604a1541 100644 --- a/spec/c/handles_spec.rb +++ b/spec/c/handles_spec.rb @@ -1,31 +1,22 @@ -require 'spec_helper' +require 'c_spec_helper' -describe "setting up handles scopes" do - around(:each) do |example| - V8::C::Locker() do - cxt = V8::C::Context::New() - begin - cxt.Enter() - example.run - ensure - cxt.Exit() +describe 'handles' do + describe '#HandleScope' do + let(:isolate) { V8::C::Isolate.New } + + it 'can allocate handle scopes' do + V8::C::HandleScope(isolate) do end end - end - it "can allocate handle scopes" do - V8::C::HandleScope() do - V8::C::Object::New() - end.class.should eql V8::C::Object - end - - it "isn't the end of the world if a ruby exception is raised inside a HandleScope" do - begin - V8::C::HandleScope() do - raise "boom!" + it 'isn\'t the end of the world if a ruby exception is raised inside a HandleScope' do + begin + V8::C::HandleScope(isolate) do + raise 'boom!' + end + rescue StandardError => e + expect(e.message).to eq 'boom!' end - rescue StandardError => e - e.message.should eql "boom!" end end end diff --git a/spec/c/isolate_spec.rb b/spec/c/isolate_spec.rb new file mode 100644 index 00000000..61656dca --- /dev/null +++ b/spec/c/isolate_spec.rb @@ -0,0 +1,9 @@ +require 'c_spec_helper' + +describe V8::C::Isolate do + let(:isolate) { V8::C::Isolate::New() } + + it 'can create a new isolate' do + expect(isolate).to be + end +end diff --git a/spec/c/locker_spec.rb b/spec/c/locker_spec.rb deleted file mode 100644 index 3f7e163d..00000000 --- a/spec/c/locker_spec.rb +++ /dev/null @@ -1,36 +0,0 @@ -require 'spec_helper' - -describe V8::C::Locker do - it "can lock and unlock the VM" do - V8::C::Locker::IsLocked().should be_false - V8::C::Locker() do - V8::C::Locker::IsLocked().should be_true - V8::C::Unlocker() do - V8::C::Locker::IsLocked().should be_false - end - end - V8::C::Locker::IsLocked().should be_false - end - - it "properly unlocks if an exception is thrown inside a lock block" do - begin - V8::C::Locker() do - raise "boom!" - end - rescue - V8::C::Locker::IsLocked().should be_false - end - end - - it "properly re-locks if an exception is thrown inside an un-lock block" do - V8::C::Locker() do - begin - V8::C::Unlocker() do - raise "boom!" - end - rescue - V8::C::Locker::IsLocked().should be_true - end - end - end -end diff --git a/spec/c/number_spec.rb b/spec/c/number_spec.rb new file mode 100644 index 00000000..31a274f2 --- /dev/null +++ b/spec/c/number_spec.rb @@ -0,0 +1,38 @@ +require 'c_spec_helper' + +describe "V8::C::Number pyramid" do + requires_v8_context + + describe V8::C::Number do + let(:number) { V8::C::Number::New @isolate, 25.325 } + it "sees that value" do + expect(number.Value()).to eql 25.325 + end + it "is a kind of primitive" do + expect(number).to be_kind_of V8::C::Primitive + end + end + describe V8::C::Integer do + let(:int) { V8::C::Integer::New @isolate, 5 } + + it "is has a value of 5" do + expect(int.Value()).to eql 5 + end + + it "is a Uint32" do + expect(int.IsUint32()).to be_truthy + expect(int).to be_kind_of V8::C::Uint32 + end + end + describe V8::C::Int32 do + let(:int32) { V8::C::Integer::New @isolate, -5 } + + it "has a value of -5" do + expect(int32.Value()).to eql -5 + end + + it "is an Int32" do + expect(int32).to be_kind_of V8::C::Int32 + end + end +end diff --git a/spec/c/object_spec.rb b/spec/c/object_spec.rb index 58649896..1a43b478 100644 --- a/spec/c/object_spec.rb +++ b/spec/c/object_spec.rb @@ -1,47 +1,439 @@ -require 'spec_helper' +require 'c_spec_helper' describe V8::C::Object do requires_v8_context - it "can store and retrieve a value" do - o = V8::C::Object::New() - key = V8::C::String::New("foo") - value = V8::C::String::New("bar") - o.Set(key, value) - o.Get(key).Utf8Value().should eql "bar" - end - - it "can retrieve all property names" do - o = V8::C::Object::New() - o.Set(V8::C::String::New("foo"), V8::C::String::New("bar")) - o.Set(V8::C::String::New("baz"), V8::C::String::New("bang")) - names = o.GetPropertyNames() - names.Length().should eql 2 - names.Get(0).Utf8Value().should eql "foo" - names.Get(1).Utf8Value().should eql "baz" - end - it "can set an accessor from ruby" do - o = V8::C::Object::New() - property = V8::C::String::New("statement") - callback_data = V8::C::String::New("I am Legend") - left = V8::C::String::New("Yo! ") - getter = proc do |name, info| - info.This().StrictEquals(o).should be_true - info.Holder().StrictEquals(o).should be_true - V8::C::String::Concat(left, info.Data()) - end - setter = proc do |name, value, info| - left = value - end - o.SetAccessor(property, getter, setter, callback_data) - o.Get(property).Utf8Value().should eql "Yo! I am Legend" - o.Set(property, V8::C::String::New("Bro! ")) - o.Get(property).Utf8Value().should eql "Bro! I am Legend" - end - it "always returns the same ruby object for the same V8 object" do - one = V8::C::Object::New() - two = V8::C::Object::New() - one.Set("two", two) - one.Get("two").should be two + it 'can create a new object' do + expect(V8::C::Object.New(@isolate)).to be + end + + it 'can store and retrieve a value' do + o = V8::C::Object.New(@isolate) + key = V8::C::String.NewFromUtf8(@isolate, 'foo') + value = V8::C::String.NewFromUtf8(@isolate, 'bar') + + expect(o.Set(@ctx, key, value)).to be_successful + expect(o.Get(@ctx, key)).to strict_eq value + end + + it 'can determine if a key has been set' do + o = V8::C::Object.New(@isolate) + key = V8::C::String.NewFromUtf8(@isolate, 'foo') + + expect(o.Set(@ctx, key, key)).to be_successful + expect(o.Has(@ctx, key)).to be_successful + end + + it 'can delete keys' do + o = V8::C::Object.New(@isolate) + key = V8::C::String.NewFromUtf8(@isolate, 'foo') + + expect(o.Set(@ctx, key, key)).to be_successful + expect(o.Delete(@ctx, key)).to be_successful + expect(o.Has(@ctx, key)).to eq_just false + end + + describe '#SetAccessor' do + it 'can set getters' do + o = V8::C::Object.New(@isolate) + key = V8::C::String.NewFromUtf8(@isolate, 'foo') + + data = V8::C::String.NewFromUtf8(@isolate, 'data') + get_value = V8::C::String.NewFromUtf8(@isolate, 'bar') + + get_name = nil + get_data = nil + + getter = proc do |name, info| + get_name = name + get_data = info.Data + + info.GetReturnValue.Set(get_value) + end + + expect(o.SetAccessor(@ctx, key, getter, nil, data)).to be_successful + expect(o.Get(@ctx, key)).to strict_eq get_value + + expect(get_name).to v8_eq key + expect(get_data).to strict_eq data + end + + it 'can set setters' do + o = V8::C::Object.New(@isolate) + key = V8::C::String.NewFromUtf8(@isolate, 'foo') + data = V8::C::String.NewFromUtf8(@isolate, 'data') + + set_value = nil + set_data = nil + + setter = proc do |name, value, info| + set_data = info.Data + + set_value = value + end + + expect(o.SetAccessor(@ctx, key, proc { }, setter, data)).to be_successful + + expect(o.Set(@ctx, key, data)).to be_successful + + expect(set_data).to strict_eq data + expect(set_value).to strict_eq data + end + end + + describe '#SetAccessorProperty' do + it 'can set getters' do + o = V8::C::Object.New(@isolate) + key = V8::C::String.NewFromUtf8(@isolate, 'foo') + + get_value = V8::C::String.NewFromUtf8(@isolate, 'bar') + getter = V8::C::Function.New(@isolate, proc { |info| info.GetReturnValue.Set(get_value) }) + + o.SetAccessorProperty(@ctx, key, getter) + expect(o.Get(@ctx, key)).to strict_eq get_value + end + + it 'can set setters' do + o = V8::C::Object.New(@isolate) + key = V8::C::String.NewFromUtf8(@isolate, 'foo') + + set_value = nil + + getter = V8::C::Function.New(@isolate, proc { }) + setter = V8::C::Function.New(@isolate, proc { |info| set_value = info[0] }) + + o.SetAccessorProperty(@ctx, key, getter, setter) + expect(o.Set(@ctx, key, key)).to be_successful + expect(set_value).to strict_eq key + end + end + + describe '#CreateDataProperty' do + it 'can set the property' do + o = V8::C::Object.New(@isolate) + key = V8::C::String.NewFromUtf8(@isolate, 'foo') + data = V8::C::String.NewFromUtf8(@isolate, 'data') + + expect(o.CreateDataProperty(@ctx, key, data)).to be_successful + expect(o.Get(@ctx, key)).to be_successful + end + end + + describe '#DefineOwnProperty' do + it 'can set the property' do + o = V8::C::Object.New(@isolate) + key = V8::C::String.NewFromUtf8(@isolate, 'foo') + data = V8::C::String.NewFromUtf8(@isolate, 'data') + + expect(o.DefineOwnProperty(@ctx, key, data)).to be_successful + expect(o.Get(@ctx, key)).to be_successful + end + end + + describe '#GetPropertyAttributes' do + it 'can get the set attributes' do + o = V8::C::Object.New(@isolate) + key = V8::C::String.NewFromUtf8(@isolate, 'foo') + data = V8::C::String.NewFromUtf8(@isolate, 'data') + + expect(o.DefineOwnProperty(@ctx, key, data, V8::C::PropertyAttribute::DontEnum)).to be_successful + expect(o.GetPropertyAttributes(@ctx, key)).to eq_just V8::C::PropertyAttribute::DontEnum + end + end + + describe '#GetOwnPropertyDescriptor' do + it 'can read the descriptor of a data property' do + o = V8::C::Object.New(@isolate) + key = V8::C::String.NewFromUtf8(@isolate, 'foo') + data = V8::C::String.NewFromUtf8(@isolate, 'data') + + expect(o.DefineOwnProperty(@ctx, key, data, V8::C::PropertyAttribute::DontEnum)).to be_successful + + maybe = o.GetOwnPropertyDescriptor(@ctx, key) + expect(maybe).to be_successful + + descriptor = maybe.FromJust + value = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'value')) + writable = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'writable')) + get = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'get')) + set = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'set')) + configurable = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'configurable')) + enumerable = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'enumerable')) + + expect(value).to strict_eq data + + expect(writable).to be_successful + expect(writable.FromJust.Value).to be true + + expect(get).to be_successful + expect(get.FromJust).to be_a V8::C::Undefined + + expect(set).to be_successful + expect(set.FromJust).to be_a V8::C::Undefined + + expect(configurable).to be_successful + expect(configurable.FromJust.Value).to be true + + expect(enumerable).to be_successful + expect(enumerable.FromJust.Value).to be false + end + + it 'can read the descriptor of an accessor property' do + o = V8::C::Object.New(@isolate) + key = V8::C::String.NewFromUtf8(@isolate, 'foo') + data = V8::C::String.NewFromUtf8(@isolate, 'data') + + getter = V8::C::Function.New @isolate, proc { }, V8::C::Object::New(@isolate) + setter = V8::C::Function.New @isolate, proc { }, V8::C::Object::New(@isolate) + + o.SetAccessorProperty(@ctx, key, getter, setter) + + maybe = o.GetOwnPropertyDescriptor(@ctx, key) + expect(maybe).to be_successful + + descriptor = maybe.FromJust + value = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'value')) + writable = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'writable')) + get = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'get')) + set = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'set')) + configurable = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'configurable')) + enumerable = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'enumerable')) + + expect(value).to be_successful + expect(value.FromJust).to be_a V8::C::Undefined + expect(writable).to be_successful + + expect(get).to strict_eq getter + expect(set).to strict_eq setter + + expect(configurable).to be_successful + expect(configurable.FromJust.Value).to be true + + expect(enumerable).to be_successful + expect(enumerable.FromJust.Value).to be true + end + end + + describe '#GetPropertyNames' do + it 'can get array of attribute names' do + o = V8::C::Object.New(@isolate) + key_one = V8::C::String.NewFromUtf8(@isolate, 'foo 1') + key_two = V8::C::String.NewFromUtf8(@isolate, 'foo 2') + data = V8::C::String.NewFromUtf8(@isolate, 'data') + + expect(o.DefineOwnProperty(@ctx, key_one, data)).to be_successful + expect(o.DefineOwnProperty(@ctx, key_two, data)).to be_successful + + names = o.GetPropertyNames(@ctx) + expect(names).to be_successful + + expect(names.FromJust.Get(@ctx, 0)).to v8_eq key_one + expect(names.FromJust.Get(@ctx, 1)).to v8_eq key_two + end + end + + describe '#GetOwnPropertyNames' do + it 'can get array of attribute names' do + o = V8::C::Object.New(@isolate) + key_one = V8::C::String.NewFromUtf8(@isolate, 'foo 1') + key_two = V8::C::String.NewFromUtf8(@isolate, 'foo 2') + data = V8::C::String.NewFromUtf8(@isolate, 'data') + + expect(o.DefineOwnProperty(@ctx, key_one, data)).to be_successful + expect(o.DefineOwnProperty(@ctx, key_two, data)).to be_successful + + names = o.GetOwnPropertyNames(@ctx) + expect(names).to be_successful + + expect(names.FromJust.Get(@ctx, 0)).to v8_eq key_one + expect(names.FromJust.Get(@ctx, 1)).to v8_eq key_two + end + end + + context 'with prototypes' do + let(:o) { V8::C::Object.New(@isolate) } + let(:prototype) { V8::C::Object.New(@isolate) } + let(:prototype_key) { V8::C::String.NewFromUtf8(@isolate, 'foo') } + let(:o_key) { V8::C::String.NewFromUtf8(@isolate, 'bar') } + + before do + expect(prototype.Set(@ctx, prototype_key, prototype_key)).to be_successful + expect(o.Set(@ctx, o_key, o_key)).to be_successful + + expect(o.SetPrototype(@ctx, prototype)).to be_successful + end + + it 'can set the object prototype' do + expect(o.Get(@ctx, prototype_key)).to strict_eq prototype_key + end + + it 'can enumerate only own properties' do + names = o.GetOwnPropertyNames(@ctx) + + expect(names).to be_successful + expect(names.FromJust.Get(@ctx, 0)).to v8_eq o_key + + has_own = o.HasOwnProperty(@ctx, o_key) + expect(has_own.IsJust).to be true + expect(has_own.FromJust).to be true + + has_own = o.HasOwnProperty(@ctx, prototype_key) + expect(has_own.IsJust).to be true + expect(has_own.FromJust).to be false + end + + it 'can enumerate all properties' do + names = o.GetPropertyNames(@ctx) + + expect(names).to be_successful + expect(names.FromJust.Get(@ctx, 0)).to v8_eq o_key + expect(names.FromJust.Get(@ctx, 1)).to v8_eq prototype_key + end + end + + describe '#ObjectProtoToString' do + it 'can return a string representation of simple objects' do + o = V8::C::Object.New(@isolate) + str = o.ObjectProtoToString(@ctx) + + expect(str).to be_successful + expect(str.FromJust.Utf8Value).to eq '[object Object]' + end + + it 'can return a string representation of simple objects' do + a = V8::C::Array.New(@isolate) + + str = a.ObjectProtoToString(@ctx) + expect(str).to be_successful + expect(str.FromJust.Utf8Value).to eq '[object Array]' + end + end + + describe '#GetConstructorName' do + it 'can return the constructor name of an array' do + a = V8::C::Array.New(@isolate) + + expect(a.GetConstructorName().Utf8Value).to eq 'Array' + end + end + + context 'with internal fields' do + let(:o) do + template = V8::C::ObjectTemplate.New(@isolate) + template.SetInternalFieldCount(2) + + template.NewInstance(@ctx).FromJust + end + + let(:value_one) { V8::C::String.NewFromUtf8(@isolate, 'value 1') } + let(:value_two) { V8::C::String.NewFromUtf8(@isolate, 'value 2') } + + before do + o.SetInternalField(0, value_one) + o.SetInternalField(1, value_two) + end + + it 'can get and set internal fields' do + expect(o.GetInternalField(0)).to strict_eq value_one + expect(o.GetInternalField(1)).to strict_eq value_two + end + + it 'can get the internal field count' do + expect(o.InternalFieldCount).to eq 2 + end + end + + context 'with hidden values' do + let(:o) { V8::C::Object.New @isolate } + + let(:key_one) { V8::C::String.NewFromUtf8(@isolate, 'key 1') } + let(:key_two) { V8::C::String.NewFromUtf8(@isolate, 'key 2') } + let(:value_one) { V8::C::String.NewFromUtf8(@isolate, 'value 1') } + let(:value_two) { V8::C::String.NewFromUtf8(@isolate, 'value 2') } + + before do + o.SetHiddenValue(key_one, value_one) + o.SetHiddenValue(key_two, value_two) + end + + it 'can get and set hidden values' do + expect(o.GetHiddenValue(key_one)).to strict_eq value_one + expect(o.GetHiddenValue(key_two)).to strict_eq value_two + end + + it 'can delete hidden values' do + expect(o.DeleteHiddenValue(key_one)).to be true + expect(o.GetHiddenValue(key_one)).to be nil + end + end + + describe '#Clone' do + let(:o) { V8::C::Object.New @isolate } + + let(:key) { V8::C::String.NewFromUtf8(@isolate, 'key') } + let(:value) { V8::C::String.NewFromUtf8(@isolate, 'value') } + + before do + o.Set(@ctx, key, value) + end + + it 'can clone an object' do + clone = o.Clone + + expect(clone).to_not strict_eq o + + expect(clone.Get(@ctx, key)).to strict_eq value + end + end + + describe '#CreationContext' do + it 'returns a context' do + o = V8::C::Object.New @isolate + expect(o.CreationContext).to be_a V8::C::Context + end + end + + describe '#IsCallable' do + it 'returns false for plain objects' do + o = V8::C::Object.New @isolate + expect(o.IsCallable).to be false + end + + it 'returns true for functions' do + fn = V8::C::Function.New @isolate, proc { }, V8::C::Object::New(@isolate) + expect(fn.IsCallable).to be true + end + end + + describe 'callable objects' do + let(:value) { V8::C::String.NewFromUtf8(@isolate, 'value') } + let(:one) { V8::C::String.NewFromUtf8(@isolate, 'one') } + let(:two) { V8::C::String.NewFromUtf8(@isolate, 'two') } + + let(:fn) do + source = '(function(one, two) {this.one = one; this.two = two; return this;})' + source = V8::C::String.NewFromUtf8(@isolate, source.to_s) + script = V8::C::Script.Compile(@ctx, source) + result = script.FromJust.Run(@ctx) + + result.FromJust + end + + it 'can be called as functions' do + object = V8::C::Object::New(@isolate) + result = fn.CallAsFunction(@ctx, object, [one, two]).FromJust + + expect(result).to strict_eq object + expect(object.Get(@ctx, one)).to strict_eq one + expect(object.Get(@ctx, two)).to strict_eq two + end + + it 'can be called as constructors' do + result = fn.CallAsConstructor(@ctx, [one, two]).FromJust + + expect(result).to be_a V8::C::Object + expect(result.Get(@ctx, one)).to strict_eq one + expect(result.Get(@ctx, two)).to strict_eq two + end end end diff --git a/spec/c/object_template_spec.rb b/spec/c/object_template_spec.rb new file mode 100644 index 00000000..a62e7801 --- /dev/null +++ b/spec/c/object_template_spec.rb @@ -0,0 +1,10 @@ +require 'c_spec_helper' + +describe V8::C::ObjectTemplate do + requires_v8_context + let(:template) { V8::C::ObjectTemplate::New(@isolate) } + + it "can be used to create object instances" do + expect(template.NewInstance(@ctx).FromJust()).to be_instance_of V8::C::Object + end +end diff --git a/spec/c/script_origin_spec.rb b/spec/c/script_origin_spec.rb new file mode 100644 index 00000000..0a85771e --- /dev/null +++ b/spec/c/script_origin_spec.rb @@ -0,0 +1,38 @@ +require 'c_spec_helper' + +describe V8::C::ScriptOrigin do + requires_v8_context + + describe "with only a name" do + let(:origin) { V8::C::ScriptOrigin.new V8::C::String::NewFromUtf8(@isolate, "bob.js") } + + it "it hase a resource name" do + expect(origin.ResourceName().Utf8Value).to eql "bob.js" + end + + it "has nil for all the other values" do + expect(origin.ResourceLineOffset()).to be_nil + expect(origin.ResourceColumnOffset()).to be_nil + expect(origin.ScriptID()).to be_nil + expect(origin.SourceMapUrl()).to be_nil + end + end + + describe "with all the other options" do + let(:origin) do + V8::C::ScriptOrigin.new( + V8::C::String::NewFromUtf8(@isolate, "bob.js"), 5, 25, + true, 100, true, + V8::C::String::NewFromUtf8(@isolate, "http://foo/bob.js.map"), + false + ) + end + it "maps the correct values" do + expect(origin.ResourceName().Utf8Value()).to eql 'bob.js' + expect(origin.ResourceLineOffset()).to eql 5 + expect(origin.ResourceColumnOffset()).to eql 25 + expect(origin.ScriptID()).to eql 100 + expect(origin.SourceMapUrl().Utf8Value()).to eql "http://foo/bob.js.map" + end + end +end diff --git a/spec/c/script_spec.rb b/spec/c/script_spec.rb index db1a8b72..0609f3cf 100644 --- a/spec/c/script_spec.rb +++ b/spec/c/script_spec.rb @@ -1,30 +1,35 @@ -# encoding: UTF-8 -require 'spec_helper' +require 'c_spec_helper' describe V8::C::Script do requires_v8_context - it "can run a script and return a polymorphic result" do - source = V8::C::String::New("(new Array())") - filename = V8::C::String::New("") - script = V8::C::Script::New(source, filename) - result = script.Run() - result.should be_kind_of V8::C::Array - end - - it "can accept precompiled script data" do - source = "7 * 6" - name = V8::C::String::New("") + it 'can run a script and return a polymorphic result' do + source = V8::C::String::NewFromUtf8(@isolate, "(new Array())") + name = V8::C::String::NewFromUtf8(@isolate, "a/file.js") origin = V8::C::ScriptOrigin.new(name) - data = V8::C::ScriptData::PreCompile(source, source.length) - data.HasError().should be_false - script = V8::C::Script::New(V8::C::String::New(source), origin, data) - script.Run().should eql 42 - end + script = V8::C::Script::Compile(@ctx, source, origin) + expect(script.IsJust()).to be true - it "can detect errors in the script data" do - source = "^ = ;" - data = V8::C::ScriptData::PreCompile(source, source.length) - data.HasError().should be_true + result = script.FromJust().Run(@ctx) + expect(result.IsJust()).to be true + expect(result.FromJust()).to be_an V8::C::Array end + + # TODO + # it 'can accept precompiled script data' do + # source = "7 * 6" + # name = V8::C::String::New("") + # origin = V8::C::ScriptOrigin.new(name) + # data = V8::C::ScriptData::PreCompile(source, source.length) + # data.HasError().should be_false + # script = V8::C::Script::New(V8::C::String::New(source), origin, data) + # script.Run().should eql 42 + # end + + # TODO + # it 'can detect errors in the script data' do + # source = "^ = ;" + # data = V8::C::ScriptData::PreCompile(source, source.length) + # data.HasError().should be_true + # end end diff --git a/spec/c/stack_trace_spec.rb b/spec/c/stack_trace_spec.rb new file mode 100644 index 00000000..0ac22d70 --- /dev/null +++ b/spec/c/stack_trace_spec.rb @@ -0,0 +1,18 @@ +require 'c_spec_helper' + +describe V8::C::StackTrace do + let(:const) { V8::C::StackTrace } + it "defines the stack trace description constants" do + expect(const::kLineNumber).to eql 1 + expect(const::kColumnOffset).to eql 3 + expect(const::kScriptName).to eql 4 + expect(const::kFunctionName).to eql 8 + expect(const::kIsEval).to eql 16 + expect(const::kIsConstructor).to eql 32 + expect(const::kScriptNameOrSourceURL).to eql 64 + expect(const::kScriptId).to eql 128 + expect(const::kExposeFramesAcrossSecurityOrigins).to eql 256 + expect(const::kOverview).to eql 15 + expect(const::kDetailed).to eql 127 + end +end diff --git a/spec/c/string_spec.rb b/spec/c/string_spec.rb index 75184e4f..e7773287 100644 --- a/spec/c/string_spec.rb +++ b/spec/c/string_spec.rb @@ -1,18 +1,29 @@ -require 'spec_helper' +require 'c_spec_helper' describe V8::C::String do requires_v8_context - it "can hold Unicode values outside the Basic Multilingual Plane" do - string = V8::C::String::New("\u{100000}") - string.Utf8Value().should eql "\u{100000}" + it 'is a primitive' do + expect(V8::C::String.NewFromUtf8(@isolate, 'test')).to be_a V8::C::Primitive end - it "can naturally translate ruby strings into v8 strings" do - V8::C::String::Concat(V8::C::String::New("Hello "), "World").Utf8Value().should eql "Hello World" + it 'can create new strings' do + expect(V8::C::String.NewFromUtf8(@isolate, 'test')).to be end - it "can naturally translate ruby objects into v8 strings" do - V8::C::String::Concat(V8::C::String::New("forty two is "), 42).Utf8Value().should eql "forty two is 42" + it 'can be converted to a ruby string' do + expect(V8::C::String.NewFromUtf8(@isolate, 'test').Utf8Value).to eq 'test' + end + + it 'can hold Unicode values outside the Basic Multilingual Plane' do + string = V8::C::String.NewFromUtf8(@isolate, "\u{100000}") + expect(string.Utf8Value).to eq "\u{100000}" + end + + it 'can concatenate strings' do + string_one = V8::C::String.NewFromUtf8(@isolate, 'Hello ') + string_two = V8::C::String.NewFromUtf8(@isolate, 'World!') + + expect(V8::C::String.Concat(string_one, string_two).Utf8Value).to eq 'Hello World!' end end diff --git a/spec/c/symbol_spec.rb b/spec/c/symbol_spec.rb new file mode 100644 index 00000000..a229f701 --- /dev/null +++ b/spec/c/symbol_spec.rb @@ -0,0 +1,53 @@ +require 'c_spec_helper' + +describe V8::C::Symbol do + requires_v8_context + + describe "without a description" do + let(:symbol) { V8::C::Symbol::New(@isolate)} + + it "exists" do + expect(symbol).to be + end + + it "has an identity hash" do + expect(symbol.GetIdentityHash()).to be + end + end + + describe "with a description" do + let(:description) { V8::C::String::NewFromUtf8(@isolate, "bob") } + let(:symbol) { V8::C::Symbol::New(@isolate, description) } + + it "has a name" do + expect(symbol.Name().Utf8Value()).to eql "bob" + end + end + + describe "from symbol registries" do + let(:key) { V8::C::String::NewFromUtf8(@isolate, "mysym") } + let(:global) { V8::C::Symbol::For(@isolate, key) } + let(:api) { V8::C::Symbol::ForApi(@isolate, key) } + + it "always retrieves the same value for a given key" do + expect(V8::C::Symbol::For(@isolate, key).StrictEquals(global)).to be true + expect(V8::C::Symbol::ForApi(@isolate, key).StrictEquals(api)).to be true + end + + it "returns different symbols for different registries" do + expect(global).to_not strict_eq api + end + end + + describe "well-known symbols" do + it "GetIterator" do + expect(V8::C::Symbol::GetIterator(@isolate)).to be_kind_of V8::C::Symbol + end + it "GetUnscopables" do + expect(V8::C::Symbol::GetUnscopables(@isolate)).to be_kind_of V8::C::Symbol + end + it "GetToStringTag" do + expect(V8::C::Symbol::GetToStringTag(@isolate)).to be_kind_of V8::C::Symbol + end + end +end diff --git a/spec/c/template_spec.rb b/spec/c/template_spec.rb index f1da9b33..6004c409 100644 --- a/spec/c/template_spec.rb +++ b/spec/c/template_spec.rb @@ -1,31 +1,33 @@ -require 'spec_helper' +require 'c_spec_helper' describe V8::C::Template do requires_v8_context - describe V8::C::FunctionTemplate do - it "can be created with no arguments" do - t = V8::C::FunctionTemplate::New() - t.GetFunction().Call(@cxt.Global(),[]).StrictEquals(@cxt.Global()).should be_true + let(:key) { V8::C::String::NewFromUtf8(@isolate, "key") } + let(:value) { V8::C::Integer::New(@isolate, 23) } + let(:template) { V8::C::ObjectTemplate::New(@isolate) } + + describe "Set()" do + + describe "without property attributes" do + before do + template.Set(key, value) + end + it "places those values on all new instances" do + object = template.NewInstance(@ctx).FromJust() + expect(object.Get(@ctx, key).FromJust().Value).to be 23 + end end - it "can be created with a callback" do - receiver = V8::C::Object::New() - f = nil - callback = lambda do |arguments| - arguments.Length().should be(2) - arguments[0].Utf8Value().should eql 'one' - arguments[1].Utf8Value().should eql 'two' - arguments.Callee().StrictEquals(f).should be_true - arguments.This().StrictEquals(receiver).should be_true - arguments.Holder().StrictEquals(receiver).should be_true - arguments.IsConstructCall().should be_false - arguments.Data().Value().should be(42) - V8::C::String::New("result") + describe "with property attributes" do + before do + template.Set(key, value, V8::C::PropertyAttribute::ReadOnly) + @object = template.NewInstance(@ctx).FromJust() + @object.Set(@ctx, key, V8::C::Integer::New(@isolate, 25)) + end + it "applies those property attributes" do + expect(@object.Get(@ctx, key).FromJust().Value).to be 23 end - t = V8::C::FunctionTemplate::New(callback, V8::C::External::New(42)) - f = t.GetFunction() - f.Call(receiver, [V8::C::String::New('one'), V8::C::String::New('two')]).Utf8Value().should eql "result" end end end diff --git a/spec/c/try_catch_spec.rb b/spec/c/try_catch_spec.rb new file mode 100644 index 00000000..d33ccf09 --- /dev/null +++ b/spec/c/try_catch_spec.rb @@ -0,0 +1,78 @@ +require 'c_spec_helper' + +describe V8::C::TryCatch do + requires_v8_context + + before do + @isolate.SetCaptureStackTraceForUncaughtExceptions(true, 99, V8::C::StackTrace::kDetailed) + end + + it "can catch javascript exceptions" do + V8::C::TryCatch(@isolate) do |trycatch| + source = V8::C::String::NewFromUtf8(@isolate, <<-JS) + function one() { + two() + } + function two() { + three() + } + function three() { + boom() + } + function boom() { + throw new Error('boom!') + } + eval('one()') + JS + + + filename = V8::C::String::NewFromUtf8(@isolate, "") + origin = V8::C::ScriptOrigin.new(filename) + script = V8::C::Script::Compile(@ctx, source, origin) + expect(script).to be_successful + result = script.FromJust().Run(@ctx) + trycatch.HasCaught().should be_truthy + trycatch.CanContinue().should be_truthy + exception = trycatch.Exception() + exception.should_not be_nil + exception.IsNativeError().should be_truthy + expect(trycatch.StackTrace(@ctx)).to be_successful + trace = trycatch.StackTrace(@ctx).FromJust() + trace.Utf8Value().should match /boom.*three.*two.*one/m + message = trycatch.Message(); + message.should_not be_nil + message.Get().Utf8Value().should eql "Uncaught Error: boom!" + expect(message.GetSourceLine(@cxt)).to be_successful + message.GetSourceLine(@cxt).FromJust().Utf8Value().should eql " throw new Error('boom!')" + message.GetScriptResourceName().Utf8Value().should eql "" + expect(message.GetLineNumber(@cxt)).to be_successful + expect(message.GetLineNumber(@cxt)).to eq_just 11 + + stack = message.GetStackTrace() + stack.should_not be_nil + stack.GetFrameCount().should eql 6 + frame = stack.GetFrame(0) + frame.GetLineNumber().should eql 11 + frame.GetColumn().should eql 15 + frame.GetScriptName().Utf8Value().should eql "" + frame.IsEval().should be_falsey + frame.IsConstructor().should be_falsey + end + end + + it "sees JavaScript exceptions thrown from Ruby" do + V8::C::TryCatch(@isolate) do |trycatch| + message = V8::C::String::NewFromUtf8(@isolate, "boom!") + @isolate.ThrowException(V8::C::Exception::Error(message)) + expect(trycatch.HasCaught).to be_truthy + end + end + + it "won't die on a ruby exception" do + expect { + V8::C::TryCatch(@isolate) do |trycatch| + fail "boom!" + end + }.to raise_error RuntimeError, "boom!" + end +end diff --git a/spec/c/trycatch_spec.rb b/spec/c/trycatch_spec.rb deleted file mode 100644 index d9ec06e9..00000000 --- a/spec/c/trycatch_spec.rb +++ /dev/null @@ -1,52 +0,0 @@ -require 'spec_helper' - -describe V8::C::External do - requires_v8_context - - it "can catch javascript exceptions" do - V8::C::V8::SetCaptureStackTraceForUncaughtExceptions(true, 99, V8::C::StackTrace::kDetailed) - V8::C::TryCatch() do |trycatch| - source = V8::C::String::New(<<-JS) - function one() { - two() - } - function two() { - three() - } - function three() { - boom() - } - function boom() { - throw new Error('boom!') - } - eval('one()') - JS - filename = V8::C::String::New("") - script = V8::C::Script::New(source, filename) - result = script.Run() - trycatch.HasCaught().should be_true - trycatch.CanContinue().should be_true - exception = trycatch.Exception() - exception.should_not be_nil - exception.IsNativeError().should be_true - trycatch.StackTrace().Utf8Value().should match /boom.*three.*two.*one/m - message = trycatch.Message(); - message.should_not be_nil - message.Get().Utf8Value().should eql "Uncaught Error: boom!" - message.GetSourceLine().Utf8Value().should eql " throw new Error('boom!')" - message.GetScriptResourceName().Utf8Value().should eql "" - message.GetLineNumber().should eql 11 - stack = message.GetStackTrace() - stack.should_not be_nil - stack.GetFrameCount().should eql 6 - frame = stack.GetFrame(0) - frame.GetLineNumber().should eql 11 - frame.GetColumn().should eql 15 - frame.GetScriptName().Utf8Value().should eql "" - frame.GetScriptNameOrSourceURL().Utf8Value().should eql "" - frame.IsEval().should be_false - stack.GetFrame(4).IsEval().should be_true - frame.IsConstructor().should be_false - end - end -end diff --git a/spec/c/v8_spec.rb b/spec/c/v8_spec.rb new file mode 100644 index 00000000..65c0a189 --- /dev/null +++ b/spec/c/v8_spec.rb @@ -0,0 +1,7 @@ +require 'c_spec_helper' + +describe V8::C::V8 do + it 'can say its version' do + expect(V8::C::V8.GetVersion).to be_a String + end +end diff --git a/spec/c/value_spec.rb b/spec/c/value_spec.rb new file mode 100644 index 00000000..ee825301 --- /dev/null +++ b/spec/c/value_spec.rb @@ -0,0 +1,48 @@ +require 'c_spec_helper' + +describe V8::C::Value do + requires_v8_context + + describe "Boolean" do + let(:t) { V8::C::Boolean::New @isolate, true } + let(:f) { V8::C::Boolean::New @isolate, false } + + it "knows true is true, and false is false" do + expect(t.IsTrue()).to be true + expect(t.IsFalse()).to be false + expect(f.IsTrue()).to be false + expect(f.IsFalse()).to be true + expect(t.IsBoolean()).to be true + expect(f.IsBoolean()).to be true + end + + it "has a BooleanValue" do + expect(t.BooleanValue(@ctx).FromJust()).to be true + expect(f.BooleanValue(@ctx).FromJust()).to be false + end + end + + describe "String" do + let(:string) { V8::C::String::NewFromUtf8(@isolate, "value")} + it "is a string" do + expect(string.IsString()).to be true + end + end + + describe "Number" do + let(:int) { V8::C::Integer::New(@isolate, -10) } + let(:num) { V8::C::Number::New(@isolate, 5.5) } + let(:uint) { V8::C::Integer::NewFromUnsigned(@isolate, 10) } + + it "recognizes integer types" do + expect(int.IsNumber).to be true + expect(int.IsInt32).to be true + expect(int.IsUint32).to be false + expect(num.IsNumber()).to be true + expect(num.IsInt32).to be false + expect(uint.IsNumber).to be true + expect(uint.IsUint32).to be true + end + end + +end diff --git a/spec/c_spec_helper.rb b/spec/c_spec_helper.rb new file mode 100644 index 00000000..be0779b2 --- /dev/null +++ b/spec/c_spec_helper.rb @@ -0,0 +1,3 @@ +require 'v8/init' + +Dir["#{File.dirname(__FILE__)}/support/*.rb"].each { |helper| require_relative helper } diff --git a/spec/mem/blunt_spec.rb b/spec/mem/blunt_spec.rb index 7c5a0582..a3f346e7 100644 --- a/spec/mem/blunt_spec.rb +++ b/spec/mem/blunt_spec.rb @@ -7,7 +7,7 @@ end #allocate a single context to make sure that v8 loads its snapshot and #we pay the overhead. - V8::Context.new + V8::Isolate.new @start_memory = process_memory GC.stress = true end @@ -15,23 +15,27 @@ after do GC.stress = false end + + it "can create 3 isolates in a row" do + 3.times { V8::Isolate.new } + end + it "won't increase process memory by more than 50% no matter how many contexts we create" do - 500.times do - V8::Context.new - run_v8_gc + 250.times do + isolate = V8::Context.new.isolate.native + isolate.IdleNotificationDeadline(0.1) end - process_memory.should <= @start_memory * 1.5 + expect(process_memory).to be <= @start_memory * 1.5 end it "can eval simple value passing statements repeatedly without significantly increasing memory" do - V8::C::Locker() do - cxt = V8::Context.new + V8::Context.new do |cxt| 500.times do cxt.eval('7 * 6') - run_v8_gc + cxt.isolate.native.IdleNotificationDeadline(0.1) end end - process_memory.should <= @start_memory * 1.1 + expect(process_memory).to be <= @start_memory * 1.1 end def process_memory @@ -39,4 +43,3 @@ def process_memory end end - diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 41785c81..d2b1abbd 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,41 +1 @@ require 'v8' - -def run_v8_gc - V8::C::V8::LowMemoryNotification() - while !V8::C::V8::IdleNotification() do - end -end - -def rputs(msg) - puts "
#{ERB::Util.h(msg)}
" - $stdout.flush -end - -module V8ContextHelpers - module GroupMethods - def requires_v8_context - around(:each) do |example| - bootstrap_v8_context(&example) - end - end - end - - def bootstrap_v8_context - V8::C::Locker() do - V8::C::HandleScope() do - @cxt = V8::C::Context::New() - begin - @cxt.Enter() - yield - ensure - @cxt.Exit() - end - end - end - end -end - -RSpec.configure do |c| - c.include V8ContextHelpers - c.extend V8ContextHelpers::GroupMethods -end diff --git a/spec/support/context.rb b/spec/support/context.rb new file mode 100644 index 00000000..faba9fbd --- /dev/null +++ b/spec/support/context.rb @@ -0,0 +1,28 @@ +module V8ContextHelpers + module GroupMethods + def requires_v8_context + around(:each) do |example| + bootstrap_v8_context(&example) + end + end + end + + def bootstrap_v8_context + @isolate = V8::C::Isolate.New + + V8::C::HandleScope(@isolate) do + @ctx = V8::C::Context::New(@isolate) + begin + @ctx.Enter + yield + ensure + @ctx.Exit + end + end + end +end + +RSpec.configure do |c| + c.include V8ContextHelpers + c.extend V8ContextHelpers::GroupMethods +end diff --git a/spec/support/v8_matchers.rb b/spec/support/v8_matchers.rb new file mode 100644 index 00000000..3ed2fb22 --- /dev/null +++ b/spec/support/v8_matchers.rb @@ -0,0 +1,45 @@ +module V8Matchers + extend RSpec::Matchers::DSL + + matcher :eq_just do |expected| + match do |maybe| + return false unless maybe.IsJust + + maybe.FromJust == expected + end + end + + matcher :be_successful do + match do |maybe| + return false unless maybe.IsJust + + maybe.FromJust + end + end + + matcher :strict_eq do |expected| + match do |object| + if object.respond_to? 'IsJust' + return false unless object.IsJust + object = object.FromJust + end + + object.StrictEquals(expected) + end + end + + matcher :v8_eq do |expected| + match do |object| + if object.respond_to? 'IsJust' + return false unless object.IsJust + object = object.FromJust + end + + object.Equals(expected) + end + end +end + +RSpec.configure do |c| + c.include V8Matchers +end diff --git a/spec/v8/context_spec.rb b/spec/v8/context_spec.rb index 850dcf0a..3116d484 100644 --- a/spec/v8/context_spec.rb +++ b/spec/v8/context_spec.rb @@ -1,19 +1,1037 @@ +# -*- encoding: utf-8 -*- require 'spec_helper' -describe V8::Context do - it "can be disposed of" do - cxt = V8::Context.new - cxt.enter do - cxt['object'] = V8::Object.new +describe "V8::Context" do + + describe "Basic Evaluation", :compat => '0.1.0' do + + before do + @cxt = V8::Context.new + end + + it "can evaluate some javascript" do + @cxt.eval("5 + 3").should == 8 + end + + it "can pass back null to ruby" do + @cxt.eval("null").should be_nil + end + + it "can pass back undefined to ruby" do + @cxt.eval("this.undefined").should be_nil + end + + it "can pass the empty string back to ruby" do + @cxt.eval("''").should == "" + end + + it "can pass doubles back to ruby" do + @cxt.eval("2.5").should == 2.5 + end + + it "can pass fixed numbers back to ruby" do + @cxt.eval("1").should == 1 + end + + it "can pass boolean values back to ruby" do + @cxt.eval("true").should be(true) + @cxt.eval("false").should be(false) + end + + it "treats nil and the empty string as the same thing when it comes to eval" do + @cxt.eval(nil).should == @cxt.eval('') + end + + it "can pass back strings to ruby" do + @cxt['foo'] = "Hello World" + @cxt.eval("foo").should == "Hello World" + end + + it "can pass back very long strings to ruby" do + lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis faucibus, diam vel pellentesque aliquet, nisl sapien molestie eros, vitae vehicula libero massa vel neque. Phasellus tempor pharetra ipsum vel venenatis. Quisque vitae nisl vitae quam mattis pellentesque et in sapien. Sed at lectus quis eros pharetra feugiat non ac neque. Vivamus lacus eros, feugiat at volutpat at, viverra id nisl. Vivamus ac dolor eleifend libero venenatis pharetra ut iaculis arcu. Donec neque nibh, vehicula non porta a, consectetur eu erat. Sed eleifend, metus vel euismod placerat, lectus lectus sollicitudin nisl, ac elementum sem quam nec dolor. In hac habitasse platea dictumst. Proin vitae suscipit orci. Suspendisse a ipsum vel lorem tempus scelerisque et vitae neque. Proin sodales, tellus sit amet consequat cursus, odio massa ultricies enim, eu fermentum velit lectus in lacus. Quisque eu porttitor diam. Nunc felis purus, facilisis non tristique ac, pulvinar nec nulla. Duis dolor risus, egestas nec tristique ac, ullamcorper cras amet." + @cxt.eval("'#{lorem}'").should == lorem end - cxt.dispose() - lambda {cxt.eval('1 + 1')}.should raise_error - lambda {cxt['object']}.should raise_error + it "correctly handles translating strings that have non-standard characters" do + @cxt['utf8'] = "Σὲ γνωρίζω ἀπὸ τὴν κόψη" + @cxt['utf8'].should == "Σὲ γνωρίζω ἀπὸ τὴν κόψη" + @cxt.eval('var nativeUtf8 = "Σὲ γνωρίζω ἀπὸ τὴν κόψη"') + @cxt['nativeUtf8'].should == "Σὲ γνωρίζω ἀπὸ τὴν κόψη" + end + + it "translates JavaScript dates objects" do + now = Time.now + @cxt.eval('new Date()').tap do |time| + expect(time.getDate().to_i).to eql now.day + expect(time.getFullYear().to_i).to eql now.year + expect(time.getMonth().to_i).to eql now.month - 1 + expect(time.getMinutes().to_i).to eql now.min + expect(time.getSeconds().to_i).to eql now.sec + end + end + + it "can pass objects back to ruby" do + @cxt.eval("({foo: 'bar', baz: 'bang', '5': 5, embedded: {badda: 'bing'}})").tap do |object| + object.should_not be_nil + object['foo'].should == 'bar' + object['baz'].should == 'bang' + object['5'].should == 5 + object['embedded'].tap do |embedded| + embedded.should_not be_nil + embedded['badda'].should == 'bing' + end + end + end + + it "can pass int properties to ruby", :compat => '0.2.1' do + @cxt.eval("({ 4: '4', 5: 5, '6': true })").tap do |object| + object[ 4 ].should == '4' + object['4'].should == '4' + object[ 5 ].should == 5 + object['5'].should == 5 + object['6'].should == true + end + end + + it "unwraps ruby objects returned by embedded ruby code to maintain referential integrity" do + Object.new.tap do |o| + @cxt['get'] = lambda {o} + @cxt.eval('get()').should be(o) + end + end + + it "always returns the same ruby object for a single javascript object" do + obj = @cxt.eval('obj = {}') + obj.should be(@cxt['obj']) + @cxt.eval('obj').should be(@cxt['obj']) + @cxt['obj'].should be(@cxt['obj']) + end + + # xit "converts arrays to javascript" do + # @cxt['a'] = [1,2,4] + # @cxt.eval('var sum = 0;for (var i = 0; i < a.length; i++) {sum += a[i]}; sum').should == 7 + # @cxt.eval('[1,2,3]').tap do |a| + # a.length.should == 3 + # a.to_a.should == [1,2,3] + # end + # end + + # xit "can iterate over arrays" do + # @cxt['a'] = @cxt.eval('[{num: 1},{num:2},{num:3},{num: 4}]') + # a = @cxt['a'] + # a.inject(0) do |sum, item| + # sum + item['num'] + # end.should == 10 + + # end + + # xit "converts ruby hashes to javascript objects" do + # @cxt['h'] = {:foo => 'bar', :baz => 'bang', :bar => {'hello' => 'world'}} + # @cxt['h']['foo'].should == 'bar' + # @cxt['h']['baz'].should == 'bang' + # @cxt['h']['bar']['hello'].should == 'world' + # end end - it "can be disposed of any number of times" do - cxt = V8::Context.new - 10.times {cxt.dispose()} + describe "Calling Ruby Code From Within Javascript" do + + before(:each) do +# @class = Class.new +# @instance = @class.new + @cxt = V8::Context.new +# @cxt['o'] = @instance + end + + it "can embed a closure into a context and call it" do + @cxt["say"] = lambda { |*args| args[-2] * args[-1] } + @cxt.eval("say('Hello', 2)").should == "HelloHello" + end + + it "recognizes the same closure embedded into the same context as the same function object" do + @cxt['say'] = @cxt['declare'] = lambda { |*args| args } + @cxt.eval('say == declare').should be(true) + @cxt.eval('say === declare').should be(true) + end + +# xit "translates ruby Array to Javascript Array" do +# class_eval do +# def ruby_array +# [] +# end +# end +# evaljs('o.ruby_array instanceof Array').should == true +# end + +# xit "translates ruby Time to Javascript Date" do +# now = Time.now +# class_eval do +# def ruby_time +# @now +# end +# end +# @instance.instance_variable_set(:@now, now) +# evaljs('o.ruby_time instanceof Date').should == true +# evaljs('o.ruby_time.getFullYear()').should == now.year +# evaljs('o.ruby_time.getMonth() + 1').should == now.month +# evaljs('o.ruby_time.getDate()').should == now.day +# evaljs('o.ruby_time.getMinutes()').should == now.min +# evaljs('o.ruby_time.getSeconds()').should == now.sec +# end + +# xit "translates ruby true to Javascript true" do +# class_eval do +# def bool +# true +# end +# end +# evaljs('o.bool === true').should == true +# end + +# xit "translates ruby false to Javascript false" do +# class_eval do +# def bool +# false +# end +# end +# evaljs('o.bool === false').should == true +# end + +# xit "can embed a ruby object into a context and call its methods" do +# class_eval do +# def say_hello(to) +# "Hello #{to}!" +# end +# end +# evaljs('o.say_hello("Gracie")').should == "Hello Gracie!" +# end + +# xit "recognizes object method as the same function" do +# @class.class_eval do +# def foo(*args); args; end +# end +# @cxt['obj'] = @class.new +# @cxt.eval('obj.foo === obj.foo').should be(true) +# end + +# xit "recognizes functions on objects of the same class being equal", :compat => '0.2.0' do +# @class.class_eval do +# def foo(*args); args; end +# self +# end +# @cxt['one'] = @class.new +# @cxt['two'] = @class.new +# @cxt.eval('one.foo == two.foo').should be(true) +# end + +# xit "recognizes functions on objects of the same class being the same", :compat => '0.2.1' do +# @class.class_eval do +# def foo(*args); args; end +# self +# end +# @cxt['one'] = @class.new +# @cxt['two'] = @class.new +# @cxt.eval('one.foo === two.foo').should be(true) +# #TODO: nice to have, but a bit tricky. +# # @cxt.eval('one.foo === one.constructor.prototype.foo').should be(true) +# end + +# xit "fails without the correct context passed to an object function", :compat => '0.2.0' do +# @class.class_eval do +# def foo(*args); args; end +# end +# @cxt['obj'] = @class.new +# @cxt.eval('var foo = obj.foo;') +# lambda { @cxt.eval('foo()') }.should raise_error +# end + +# xit "can call a bound ruby method" do +# five = class_eval do +# def initialize(lhs) +# @lhs = lhs +# end +# def times(rhs) +# @lhs * rhs +# end +# new(5) +# end + +# @cxt['timesfive'] = five.method(:times) +# @cxt.eval('timesfive(3)').should == 15 +# end + +# xit "reports object's type being object", :compat => '0.1.1' do +# @cxt.eval('typeof( o )').should == 'object' +# end + +# xit "reports object method type as function", :compat => '0.1.1' do +# @class.class_eval do +# def foo(*args); args; end +# end +# @cxt.eval('typeof o.foo ').should == 'function' +# end + +# xit "reports wrapped class as of type function", :compat => '0.1.1' do +# @cxt['RObject'] = Object +# @cxt.eval('typeof(RObject)').should == 'function' +# end + + it "truncates lambda arguments passed in to match the arity of the function", :compat => '0.4.2' do + @cxt['testing'] = lambda { |arg| arg } + lambda { + @cxt.eval('testing(1,2,3)') + }.should_not raise_error + + @cxt['testing'] = lambda { } + lambda { + @cxt.eval('testing(1,2,3)') + }.should_not raise_error + end + +# xit "truncates method arguments passed in to match the arity of the function", :compat => '0.4.3' do +# @instance.instance_eval do +# def foo(arg); arg; end +# def bar(*args); args; end +# end + +# lambda { +# @cxt.eval('o.foo(1,2,3)').should == 1 +# }.should_not raise_error +# lambda { +# @cxt.eval('o.bar(1,2,3)').to_a.should == [ 1, 2, 3 ] +# }.should_not raise_error +# end + +# xit "fills in missing arguments with nils to match the arity of the function", :compat => '0.4.3' do +# @instance.instance_eval do +# def foo(a1, a2, a3); [a1, a2, a3]; end +# end + +# lambda { +# @cxt.eval('o.foo(1)').to_a.should == [ 1, nil, nil ] +# @cxt.eval('o.foo()').to_a.should == [ nil, nil, nil ] +# }.should_not raise_error +# end + +# describe "Default Ruby Object Access" do + +# xit "can call public locally defined ruby methods" do +# class_eval do +# def voo(str) +# "voo#{str}" +# end +# end +# evaljs("o.voo('doo')").should == "voodoo" +# end + +# xit "reports ruby methods that do not exist as undefined" do +# V8::Context.new(:with => Object.new) do |cxt| +# cxt.eval('this.foobar').should be nil +# end +# end + +# xit "can access methods defined in an object's superclass" do +# o = Class.new.class_eval do +# attr_accessor :foo +# def foo +# @foo ||= "FOO" +# end +# Class.new(self) +# end.new +# V8::Context.new(:with => o) do |cxt| +# cxt.eval('this.foo').should == 'FOO' +# cxt.eval('this.foo = "bar!"') +# cxt.eval('this.foo').should == "bar!" +# end +# end + +# xit "allows a ruby object to intercept property access with []" do +# Class.new.class_eval do +# def [](val) +# "FOO" +# end +# V8::Context.new(:with => new) do |cxt| +# cxt.eval('this.foo').should == "FOO" +# cxt.eval('this.bar').should == "FOO" +# end +# end +# end + +# xit "allows a ruby object to intercept property setting with []=" do +# Class.new.class_eval do +# def initialize +# @properties = {} +# end +# def [](name) +# @properties[name] +# end +# def []=(name, value) +# @properties[name] = value +# end + +# V8::Context.new(:with => new) do |cxt| +# cxt.eval('this.foo = "bar"').should == "bar" +# cxt.eval('this.foo').should == "bar" +# end +# end +# end + +# xit "allows a ruby object which intercepts property access to take a pass on intercepting the property", :compat => '0.4.0' do +# klass = Class.new do +# def initialize +# @attrs = {} +# end +# def [](name) +# name =~ /foo/ ? @attrs[name] : yield +# end +# def []=(name, value) +# name =~ /foo/ ? @attrs[name] = "#{value}-diddly" : yield +# end +# end + +# V8::Context.new do |cxt| +# cxt['foo'] = klass.new +# cxt.eval('typeof foo.bar').should == 'undefined' +# cxt.eval('foo.bar = "baz"') +# cxt.eval('foo.bar').should == 'baz' +# cxt.eval('foo.foobar').should == nil +# cxt.eval('foo.foobar = "baz"') +# cxt.eval('foo.foobar').should == "baz-diddly" +# end +# end + +# xit "allows a ruby object to take a pass on intercepting an indexed property", :compat => '0.4.0' do +# klass = Class.new do +# def initialize +# @arr = [] +# end +# def [](i) +# i >= 5 ? @arr[i] : yield +# end +# def []=(i, value) +# i >= 5 ? @arr[i] = "#{value}-diddly" : yield +# end +# end + +# V8::Context.new do |cxt| +# cxt['obj'] = klass.new +# cxt.eval('typeof obj[1]').should == 'undefined' +# cxt.eval('obj[1] = "foo"') +# cxt.eval('obj[1]').should == "foo" +# cxt.eval('obj[5] = "foo"').should == "foo" +# cxt.eval('obj[5]').should == "foo-diddly" +# end +# end + +# xit "does not make the [] and []= methods visible or enumerable by default", :compat => '0.4.1' do +# klass = Class.new do +# def [](name); name; end +# def []=(name, value); name && value; end +# end + +# V8::Context.new do |cxt| +# cxt['o'] = klass.new +# cxt.eval('o["[]"]').should == nil +# cxt.eval('o["[]="]').should == nil +# cxt.eval('a = new Array(); for (var i in o) a.push(i);') +# cxt['a'].length.should == 0 +# end +# end + +# xit "doesn't kill the whole process if a dynamic interceptor or setter throws an exception" do +# klass = Class.new do +# def [](name) +# raise "BOOM #{name}!" +# end +# def []=(name, value) +# raise "Bam #{name} = #{value} !" +# end +# end + +# V8::Context.new do |cxt| +# cxt['foo'] = klass.new +# lambda { +# cxt.eval('foo.bar') +# }.should raise_error +# lambda { +# cxt.eval('foo.bar = "baz"') +# }.should raise_error +# end +# end + +# xit "doesn't kill the whole process if reader or accessor throws an exception" do +# klass = Class.new do +# def foo +# raise "NO GET 4 U!" +# end +# def foo=(val) +# raise "NO SET 4 U!" +# end +# end + +# cxt = V8::Context.new(:with => klass.new) +# lambda { +# cxt.eval(this.foo) +# }.should raise_error +# lambda { +# cxt.eval("this.foo = 'bar'") +# }.should raise_error +# end + +# xit "allows access to methods defined on an objects included/extended modules (class)" do +# modul = Module.new do +# attr_accessor :foo +# def foo +# @foo ||= "FOO" +# end +# end +# klass = Class.new do +# include modul +# end + +# V8::Context.new(:with => klass.new) do |cxt| +# cxt.eval('this.foo').should == "FOO" +# cxt.eval('this.foo = "bar!"') +# cxt.eval('this.foo').should == "bar!" +# end +# end + +# xit "allows access to methods defined on an objects included/extended modules (instance)" do +# modul = Module.new do +# attr_accessor :foo +# def foo +# @foo ||= "FOO" +# end +# self +# end + +# obj = Object.new; obj.extend(modul) +# V8::Context.new(:with => obj) do |cxt| +# cxt.eval('this.foo').should == "FOO" +# cxt.eval('this.foo = "bar!"') +# cxt.eval('this.foo').should == "bar!" +# end +# end + +# xit "allows access to public singleton methods" do +# obj = Object.new +# class << obj; attr_accessor :foo; end +# def obj.foo; @foo ||= "FOO"; end + +# V8::Context.new(:with => obj) do |cxt| +# cxt.eval("this.foo").should == "FOO" +# cxt.eval('this.foo = "bar!"') +# cxt.eval('this.foo').should == "bar!" +# end +# end + +# xit "does not allow access to methods defined on Object and above", :compat => '0.5.0' do +# klass = Class.new do +# def foo; "FOO"; end +# end + +# V8::Context.new(:with => klass.new) do |cxt| +# for method in Object.public_instance_methods +# cxt.eval("this['#{method}']").should be nil +# end +# end +# end + +# xit "hides methods derived from Object, Kernel, etc...", :compat => '0.5.0' do +# evaljs("o.to_s").should be nil # Object +# evaljs("o.puts").should be nil # Kernel +# end + +# xit "does not allow to call a ruby constructor, unless that constructor has been directly embedded", :compat => '0.5.0' do +# klass = Class.new +# @cxt['obj'] = klass.new +# lambda { +# @cxt.eval('new (obj.constructor)()') +# }.should raise_js_error +# end + +# describe "with an integer index" do + +# xit "allows accessing indexed properties via the []() method" do +# class_eval do +# def [](i) +# "foo" * i +# end +# end +# evaljs("o[3]").should == "foofoofoo" +# end + +# xit "allows setting indexed properties via the []=() method" do +# class_eval do +# def [](i) +# @storage ||= [] +# @storage[i] +# end +# def []=(i, val) +# @storage ||= [] +# @storage[i] = val +# end +# end +# evaljs("o[3] = 'three'").should == 'three' +# evaljs("o[3]").should == 'three' +# end + +# xit "doesn't kill the whole process if indexed interceptors throw exceptions" do +# class_eval do +# def [](idx) +# raise "No Indexed [#{idx}] For You!" +# end +# def []=(idx, value) +# raise "No Indexed [#{idx}] = #{value} For You!" +# end +# end +# lambda { +# evaljs("o[1] = 'boo'") +# }.should raise_error +# lambda { +# evaljs("o[1]") +# }.should raise_error +# end + +# #TODO: I'm not sure this is warranted +# #it "will enumerate indexed properties if a length property is provided" +# end + +# end + +# xit "will see a method that appears after the wrapper was first created" do +# @cxt['o'] = @instance +# class_eval do +# def whiz(str) +# "whiz#{str}!" +# end +# end +# @cxt.eval("o.whiz('bang')").should == "whizbang!" +# end + +# xit "treats ruby methods that have an arity of 0 as javascript properties by default" do +# class_eval do +# def property +# "flan!" +# end +# end +# evaljs('o.property').should == 'flan!' +# end + +# xit "will call ruby accesssor function when setting a property from javascript" do +# class_eval do +# def dollars +# @dollars +# end + +# def dollars=(amount) +# @dollars = amount +# end +# end +# evaljs('o.dollars = 50') +# @instance.dollars.should == 50 +# end + +# xit "will accept expando properties by default for properties on ruby object that are not implemented in ruby" do +# evaljs('o.five = 5; o.five').should == 5 +# end + +# xit "it silently fails to replace properties which are defined on ruby objects but which are read-only" do +# class_eval do +# def bar +# "baz" +# end +# end +# evaljs('o.bar = "bing"; o.bar').should == "baz" +# end + +# def evaljs(str) +# @cxt.eval(str) +# end + +# def class_eval(&body) +# @class.class_eval &body +# end + + end + + describe "Calling JavaScript Code From Within Ruby" do + + before(:each) do + @cxt = V8::Context.new + end + + it "allows you to capture a reference to a javascript function and call it" do + f = @cxt.eval('(function add(lhs, rhs) {return lhs + rhs})') + expect(f.call 1,2).to eql 3 + end + + it "can path the 'this' object into a function as context with methodcall()" do + obj = @cxt.eval('({num: 5})') + times = @cxt.eval('(function times(num) {return this.num * num})') + expect(times.methodcall obj, 5).to eql 25 + end + + it "unwraps objects that are backed by javascript objects to pass their native equivalents" do + @cxt.eval('obj = {foo: "bar"}') + f = @cxt.eval('(function() {return this == obj})') + expect(f.methodcall @cxt['obj']).to be true + end + + it "can invoke a javacript constructor and return the new object reflected into ruby" do + wrapper = @cxt.eval('(function Wrapper(value) {this.value = value})') + wrapper.new(5)['value'].should == 5 + end + + it "can call a javascript method directly from a ruby object" do + obj = @cxt.eval('Object').new + expect(obj).to respond_to(:toString) + expect(obj.toString()).to eql '[object Object]' + end + + it "can access properties defined on a javascript object through ruby" do + obj = @cxt.eval('({ str: "bar", num: 5 })') + expect(obj.str).to eql "bar" + expect(obj.num).to eql 5 + end + +# xit "can set properties on the javascript object via ruby setter methods" do +# obj = @cxt.eval('({ str: "bar", num: 5 })') +# obj.str = "baz" +# obj.str.should == "baz" +# obj.double = proc {|i| i * 2} +# obj.double.call(2).should == 4 +# obj.array = 1,2,3 +# obj.array.to_a.should == [1,2,3] +# obj.array = [1,2,3] +# obj.array.to_a.should == [1,2,3] +# end + + it "is an error to try and pass parameters to a property" do + obj = @cxt.eval('({num: 1})') + expect { obj.num(5) }.to raise_error(ArgumentError) + end + end + +# describe "Setting up the Host Environment", :compat => '0.1.0' do + +# before(:each) do +# @cxt = V8::Context.new +# end + +# xit "can eval javascript with a given ruby object as the scope." do +# scope = Class.new.class_eval do +# def plus(lhs, rhs) +# lhs + rhs +# end + +# def minus(lhs, rhs) +# lhs - rhs +# end + +# new +# end + +# V8::Context.new(:with => scope) do |cxt| +# cxt.eval("plus(1,2)", "test").should == 3 +# cxt.eval("minus(10, 20)", "test").should == -10 +# cxt.eval("this").should be(scope) +# end +# end + +# xit "can directly embed ruby values into javascript" do +# @cxt["bar"] = 9 +# @cxt['foo'] = "bar" +# @cxt['num'] = 3.14 +# @cxt['trU'] = true +# @cxt['falls'] = false +# @cxt.eval("bar + 10").should == 19 +# @cxt.eval('foo').should == "bar" +# @cxt.eval('num').should == 3.14 +# @cxt.eval('trU').should be(true) +# @cxt.eval('falls').should be(false) +# end + +# xit "has the global object available as a javascript value" do +# @cxt['foo'] = 'bar' +# @cxt.scope.should_not be(nil) +# @cxt.scope.should respond_to :'[]' +# @cxt.scope['foo'].should == 'bar' +# end + +# xit "will treat class objects as constructors by default" do +# @cxt[:MyClass] = Class.new do +# attr_reader :one, :two +# def initialize(one, two) +# @one, @two = one, two +# end +# end +# @cxt.eval('new MyClass(1,2).one').should == 1 +# @cxt.eval('new MyClass(1,2).two').should == 2 +# end + +# xit "exposes class properties as javascript properties on the corresponding constructor" do +# @cxt[:MyClass] = Class.new do +# def self.foo(*args) +# args.inspect +# end +# end +# @cxt.eval('MyClass.foo').should_not be nil +# @cxt.eval('MyClass.foo()').should == "[]" +# end + +# xit "unwraps reflected ruby constructor objects into their underlying ruby classes" do +# @cxt['RubyObject'] = Object +# @cxt['RubyObject'].should be(Object) +# end + +# xit "honors the instanceof operator for ruby instances when compared to their reflected constructors" do +# @cxt['RubyObject'] = Object +# @cxt['rb_object'] = Object.new +# @cxt.eval('rb_object instanceof RubyObject').should be(true) +# @cxt.eval('new RubyObject() instanceof RubyObject').should be(true) +# @cxt.eval('new RubyObject() instanceof Array').should be(false) +# @cxt.eval('new RubyObject() instanceof Object').should be(true) +# end + +# xit "reports constructor function as being an instance of Function", :compat => '0.1.1' do +# @cxt['RubyObject'] = Object +# @cxt.eval('RubyObject instanceof Function').should be(true) +# end + +# xit "respects ruby's inheritance chain with the instanceof operator", :compat => '0.1.1' do +# @cxt['Class1'] = klass1 = Class.new(Object) +# @cxt['Class2'] = klass2 = Class.new(klass1) +# @cxt['obj1'] = klass1.new +# @cxt['obj2'] = klass2.new + +# @cxt.eval('obj1 instanceof Class1').should == true +# @cxt.eval('obj1 instanceof Class2').should == false +# @cxt.eval('obj2 instanceof Class2').should == true +# @cxt.eval('obj2 instanceof Class1').should == true +# end + +# xit "unwraps instances created by a native constructor when passing them back to ruby" do +# @cxt['RubyClass'] = Class.new do +# def definitely_a_product_of_this_one_off_class? +# true +# end +# end +# @cxt.eval('new RubyClass()').should be_definitely_a_product_of_this_one_off_class +# end +# end + +# describe "Loading javascript source into the interpreter", :compat => '0.1.0' do + +# xit "can take an IO object in the eval method instead of a string" do +# source = StringIO.new(<<-EOJS) +# /* +# * we want to have a fairly verbose function so that we can be assured tha +# * we overflow the buffer size so that we see that the reader is chunking +# * it's payload in at least several fragments. +# * +# * That's why we're wasting space here +# */ +# function five() { +# return 5 +# } +# foo = 'bar' +# five(); +# EOJS +# V8::Context.new do |cxt| +# cxt.eval(source, "StringIO").should == 5 +# cxt['foo'].should == "bar" +# end +# end + +# xit "can load a file into the runtime" do +# filename = Pathname(__FILE__).dirname.join("fixtures/loadme.js").to_s +# V8::Context.new.load(filename).should == "I am Legend" +# end +# end + +# describe "A Javascript Object Reflected Into Ruby", :compat => '0.1.0' do + +# before(:each) do +# @cxt = V8::Context.new +# @o = @cxt.eval("o = new Object(); o") +# end + +# def evaljs(js) +# @cxt.eval(js) +# end + +# xit "can have its properties manipulated via ruby style [] hash access" do +# @o["foo"] = 'bar' +# evaljs('o.foo').should == "bar" +# evaljs('o.blue = "blam"') +# @o["blue"].should == "blam" +# end + +# xit "doesn't matter if you use a symbol or a string to set a value" do +# @o[:foo] = "bar" +# @o['foo'].should == "bar" +# @o['baz'] = "bang" +# @o[:baz].should == "bang" +# end + +# xit "returns nil when the value is null, null, or not defined" do +# @o[:foo].should be_nil +# end + +# xit "traverses the prototype chain when hash accessing properties from the ruby object" do +# V8::Context.new do |cxt| +# cxt.eval(< 'bar', "bang" => 'baz', 5 => 'flip'} +# end +# end +# end + +# describe "Exception Handling", :compat => '0.1.0' do + +# xit "raises javascript exceptions as ruby exceptions" do +# lambda { +# V8::Context.new.eval('foo') +# }.should raise_js_error +# end + +# xit "can handle syntax errors" do +# lambda { +# V8::Context.eval('does not compiles') +# }.should raise_error +# end + +# xit "will allow exceptions to pass through multiple languages boundaries (i.e. js -> rb -> js -> rb)" do +# V8::Context.new do |cxt| +# cxt['one'] = lambda do +# cxt.eval('two()', 'one.js') +# end +# cxt['two'] = lambda do +# cxt.eval('three()', 'two.js') +# end +# cxt['three'] = lambda do +# cxt.eval('throw "BOOM!"', "three.js") +# end +# lambda { +# cxt['one'].call(cxt.scope) +# }.should raise_error {|e| +# #TODO: assert something about the contents of the stack? +# #--cowboyd 05/25/2010 +# } +# end +# end + +# xit "translates ruby exceptions into javascript exceptions if they are thrown from code called xit javascript", :compat => '0.3.0' do +# V8::Context.new do |cxt| +# cxt['boom'] = lambda { raise "BOOM!" } +# cxt.eval('( function() { try { boom() } catch (e) { return e.message } } )()').should == 'BOOM!' +# end +# end + +# xit "allows javascript to catch ScriptError", :compat => '0.4.4' do +# V8::Context.new do |cxt| +# cxt['boom'] = lambda { raise ScriptError, "BOOM!" } +# cxt.eval('( function() { try { boom() } catch (e) { return e.message } } )()').should == 'BOOM!' +# end +# end + +# xit "will not let JavaScript catch other errors such as a SystemExit and fatal", :compat => '0.4.4' do +# V8::Context.new do |cxt| +# cxt['boom'] = lambda { raise Exception, "ByE!" } +# expect {cxt.eval('( function() { try { boom() } catch (e) { return e.message } } )()')}.should raise_error Exception +# end +# end + +# xit "propagates javascript thrown values into the ruby side", :compat => '0.4.5' do +# V8::Context.new do |cxt| +# begin +# cxt.eval('throw 42') +# rescue => e +# e.should respond_to(:value) +# e.value.should == 42 +# else +# fail "not raised !" +# end +# begin +# cxt.eval("throw { foo: 'bar' }") +# rescue => e +# e.should respond_to(:value) +# e.value.should respond_to(:'[]') +# e.value['foo'].should == 'bar' +# else +# fail "not raised !" +# end +# end +# end + +# xit "propagates a javascript thrown error from a function", :compat => '0.4.6' do +# context = V8::Context.new +# context.eval 'function bar() { throw new Error("bar"); }' +# begin +# context['bar'].call +# rescue Exception => e +# e.should be_a defined?(V8::Error) ? V8::Error : StandardError +# else +# fail "function bar() not raised !" +# end +# end + +# xit "propagates a javascript thrown error from a constructor", :compat => '0.4.6' do +# context = V8::Context.new +# context.eval 'Foo = function() { throw new Error("Foo"); };' +# begin +# context['Foo'].new +# rescue Exception => e +# e.should be_a defined?(V8::Error) ? V8::Error : StandardError +# else +# fail "not raised !" +# end +# end + +# end + +# describe "A Ruby class reflected into JavaScript", :compat => '0.6.0' do + +# xit "will extend instances of the class when properties are added to the corresponding JavaScript constructor's prototype" do +# klass = Class.new +# V8::Context.new do |cxt| +# cxt['RubyObject'] = klass +# cxt.eval('RubyObject.prototype.foo = function() {return "bar"}') +# cxt['o'] = klass.new +# cxt.eval('o.foo()').should == "bar" +# end +# end + +# xit"will extend instances of subclasses when properties are added to the corresponding JavaScript constructor's prototype" do +# superclass = Class.new +# subclass = Class.new(superclass) +# V8::Context.new do |cxt| +# cxt['SuperClass'] = superclass +# cxt['SubClass'] = subclass +# cxt['o'] = subclass.new +# cxt.eval('SuperClass.prototype.foo = function() {return "bar"}') +# cxt.eval('o.foo()').should == "bar" +# end +# end + +# end + + private + + def raise_js_error + defined?(V8::Error) ? raise_error(V8::Error) : raise_error end + end diff --git a/spec/v8/isolate_spec.rb b/spec/v8/isolate_spec.rb new file mode 100644 index 00000000..3589fd8b --- /dev/null +++ b/spec/v8/isolate_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' + +describe V8::Isolate do + let(:isolate) { V8::Isolate.new } + let(:context) { V8::Context.new isolate: isolate } + + it "allows you to terminate javascript execution" do + context['doTerminate'] = lambda { isolate.terminate_execution! } + expect(isolate).to_not be_execution_terminating + expect { + context.eval <<-JS +function doesTerminate() { + doTerminate(); +} +function returnFive() { + return 5; +} +doesTerminate(); +returnFive(); +JS + }.to raise_error V8::C::PendingExceptionError + end + + it "allows you to cancel terminated javascript executing" do + context['doTerminate'] = lambda do + isolate.terminate_execution! + isolate.cancel_terminate_execution! + end + result = context.eval <<-JS +function doesTerminate() { + doTerminate(); +} +function returnFive() { + return 5; +} +doesTerminate(); +returnFive(); +JS + expect(result).to eql 5 + end + + +end diff --git a/spec/v8/retained_objects_spec.rb b/spec/v8/retained_objects_spec.rb new file mode 100644 index 00000000..06a3f338 --- /dev/null +++ b/spec/v8/retained_objects_spec.rb @@ -0,0 +1,47 @@ +require 'v8/retained_objects' + +describe V8::RetainedObjects do + let(:object) { Object.new } + let(:objects) { V8::RetainedObjects.new } + + it "knows that something isn't retained" do + expect(objects).not_to be_retaining object + end + + describe "adding a reference to an object" do + before do + objects.add(object) + end + + it "is now retained" do + expect(objects).to be_retaining object + end + + describe "removing the reference" do + before do + objects.remove(object) + end + it "is no longer retained" do + expect(objects).to_not be_retaining object + end + end + describe "adding another reference and then removing" do + before do + objects.add(object) + objects.remove(object) + end + it "is still retained" do + expect(objects).to be_retaining object + end + + describe "removing one more time" do + before do + objects.remove(object) + end + it "is no longer retained" do + expect(objects).to_not be_retaining object + end + end + end + end +end diff --git a/therubyracer.gemspec b/therubyracer.gemspec index 5a6ace7d..bf67f9b5 100644 --- a/therubyracer.gemspec +++ b/therubyracer.gemspec @@ -18,5 +18,5 @@ Gem::Specification.new do |gem| gem.license = 'MIT' gem.add_dependency 'ref' - gem.add_dependency 'libv8', '~> 3.16.14.0' + gem.add_dependency 'libv8', '~> 4.5.95.0' end