From 44d61f8c2a96b2a1607588a96f94dc04de31916e Mon Sep 17 00:00:00 2001 From: Brian Ford Date: Thu, 21 Aug 2008 23:25:18 -0700 Subject: [PATCH] Added Time C++ class and primitives. --- kernel/bootstrap/time.rb | 28 ++++++++------- kernel/core/time.rb | 42 ++++++++++------------ rakelib/vm.rake | 1 + vm/builtin/object.cpp | 3 ++ vm/builtin/time.cpp | 76 ++++++++++++++++++++++++++++++++++++++++ vm/builtin/time.hpp | 35 ++++++++++++++++++ vm/globals.hpp | 4 ++- vm/object_types.hpp | 1 + vm/objects.cpp | 14 +++++++- vm/test/test_objects.hpp | 11 ++++++ vm/test/test_time.hpp | 61 ++++++++++++++++++++++++++++++++ vm/test/test_vm.hpp | 2 +- 12 files changed, 240 insertions(+), 38 deletions(-) create mode 100644 vm/builtin/time.cpp create mode 100644 vm/builtin/time.hpp create mode 100644 vm/test/test_time.hpp diff --git a/kernel/bootstrap/time.rb b/kernel/bootstrap/time.rb index e9da341f1b..9965ac306b 100644 --- a/kernel/bootstrap/time.rb +++ b/kernel/bootstrap/time.rb @@ -1,22 +1,26 @@ class Time - def self.gettimeofday - Ruby.primitive :gettimeofday - raise PrimitiveFailure, "primitive failed" + def self.allocate + Ruby.primitive :time_allocate + raise PrimitiveFailure, "Time.allocate primitive failed" end - + + def gettimeofday + Ruby.primitive :time_gettimeofday + raise PrimitiveFailure, "Time#gettimeofday primitive failed" + end + def time_switch(sec, to_gmt) Ruby.primitive :time_switch - raise PrimitiveFailure, "primitive failed" + raise PrimitiveFailure, "Time#time_switch primitive failed" end - + def time_mktime(sec, min, hour, mday, mon, year, usec, isdst, from_gmt) - Ruby.primitive :mktime + Ruby.primitive :time_mktime raise ArgumentError, "time out of range" end - - def __strftime(tm, format) - Ruby.primitive :strftime - raise PrimitiveFailure, "primitive failed" + + def __strftime__(tm, format) + Ruby.primitive :time_strftime + raise PrimitiveFailure, "Time#__strftime__ primitive failed" end - end diff --git a/kernel/core/time.rb b/kernel/core/time.rb index 71c302803e..d6b191a50f 100644 --- a/kernel/core/time.rb +++ b/kernel/core/time.rb @@ -54,16 +54,6 @@ class Time :usec => 1, } - def initialize - @timeval = Time.gettimeofday - - # this flag specifies whether this Time instance represents - # local time or GMT. it is independent of the actual time zone. - @is_gmt = false - - @tm = time_switch(@timeval.first, false) - end - #-- # TODO: doesn't load nsec or ivars #++ @@ -101,8 +91,11 @@ def self._load(data) #++ def _dump(limit = nil) - tm = time_switch @timeval.first, true - year = tm[TM_FIELDS[:year]] + tm = @tm + is_gmt = @is_gmt + + time_switch true + year = @tm[TM_FIELDS[:year]] if (year & 0xffff) != year then raise ArgumentError, "year too big to marshal: #{year}" @@ -110,17 +103,20 @@ def _dump(limit = nil) gmt = @is_gmt ? 1 : 0 - major = 1 << 31 | # 1 bit - gmt << 30 | # 1 bit - tm[TM_FIELDS[:year]] << 14 | # 16 bits - tm[TM_FIELDS[:mon]] << 10 | # 4 bits - tm[TM_FIELDS[:mday]] << 5 | # 5 bits - tm[TM_FIELDS[:hour]] # 5 bits - minor = tm[TM_FIELDS[:min]] << 26 | # 6 bits - tm[TM_FIELDS[:sec]] << 20 | # 6 bits + major = 1 << 31 | # 1 bit + (@is_gmt ? 1 : 0) << 30 | # 1 bit + @tm[TM_FIELDS[:year]] << 14 | # 16 bits + @tm[TM_FIELDS[:mon]] << 10 | # 4 bits + @tm[TM_FIELDS[:mday]] << 5 | # 5 bits + @tm[TM_FIELDS[:hour]] # 5 bits + minor = @tm[TM_FIELDS[:min]] << 26 | # 6 bits + @tm[TM_FIELDS[:sec]] << 20 | # 6 bits @timeval[TIMEVAL_FIELDS[:usec]] # 20 bits [major, minor].pack 'VV' + ensure + @tm = tm + @is_gmt = is_gmt end def dup @@ -202,7 +198,7 @@ def self.at(secs_or_time, msecs = nil) end def strftime(format) - __strftime(@tm, format.to_str) + __strftime__(@tm, format.to_str) end def inspect @@ -376,14 +372,14 @@ def hash end def force_localtime - @tm = time_switch(@timeval.first, false) + time_switch false @is_gmt = false self end def force_gmtime - @tm = time_switch(@timeval.first, true) + time_switch true @is_gmt = true self diff --git a/rakelib/vm.rake b/rakelib/vm.rake index 40c79b0275..168372e91f 100644 --- a/rakelib/vm.rake +++ b/rakelib/vm.rake @@ -79,6 +79,7 @@ field_extract_headers = %w[ vm/builtin/thread.hpp vm/builtin/tuple.hpp vm/builtin/compactlookuptable.hpp + vm/builtin/time.hpp ] BC = "vm/instructions.bc" diff --git a/vm/builtin/object.cpp b/vm/builtin/object.cpp index 3b47b4741a..03769596ed 100644 --- a/vm/builtin/object.cpp +++ b/vm/builtin/object.cpp @@ -240,6 +240,9 @@ namespace rubinius { case CompactLookupTableType: type = "CompactLookupTable"; break; + case TimeType: + type = "Time"; + break; default: type = "unknown"; break; diff --git a/vm/builtin/time.cpp b/vm/builtin/time.cpp new file mode 100644 index 0000000000..820c2931d8 --- /dev/null +++ b/vm/builtin/time.cpp @@ -0,0 +1,76 @@ +#include "builtin/array.hpp" +#include "builtin/integer.hpp" +#include "builtin/time.hpp" +#include "objectmemory.hpp" + +#include +#include + +namespace rubinius { + Time* Time::create(STATE) { + Time* tm = (Time*)state->om->new_object(G(time_class), Time::fields); + + tm->gettimeofday(state); + tm->time_switch(state, Qfalse); + + return tm; + } + + Time* Time::gettimeofday(STATE) { + struct timeval tv; + + /* don't fill in the 2nd argument here. getting the timezone here + * this way is not portable and broken anyway. + */ + ::gettimeofday(&tv, NULL); + + /* update Time::TIMEVAL_FIELDS when changing order of fields */ + Array* ary = Array::create(state, 2); + ary->set(state, 0, Integer::from(state, tv.tv_sec)); + ary->set(state, 1, Integer::from(state, tv.tv_usec)); + + SET(this, timeval, ary); + + return this; + } + + Time* Time::time_switch(STATE, OBJECT gmt) { + time_t seconds = ((Integer*)this->timeval->get(state, 0))->to_native(); + struct tm *tm; + + if(gmt == Qtrue) { + tm = gmtime(&seconds); + } else { + tm = localtime(&seconds); + } + + /* update Time::TM_FIELDS when changing order of fields */ + Array* ary = Array::create(state, 11); + ary->set(state, 0, Integer::from(state, tm->tm_sec)); + ary->set(state, 1, Integer::from(state, tm->tm_min)); + ary->set(state, 2, Integer::from(state, tm->tm_hour)); + ary->set(state, 3, Integer::from(state, tm->tm_mday)); + ary->set(state, 4, Integer::from(state, tm->tm_mon)); + ary->set(state, 5, Integer::from(state, tm->tm_year)); + ary->set(state, 6, Integer::from(state, tm->tm_wday)); + ary->set(state, 7, Integer::from(state, tm->tm_yday)); + ary->set(state, 8, Integer::from(state, tm->tm_isdst)); + +#ifdef HAVE_STRUCT_TM_TM_GMTOFF + ary->set(state, 9, Integer::from(state, tm->tm_gmtoff)); +#else + ary->set(state, 9, Qnil); +#endif + +#ifdef HAVE_STRUCT_TM_TM_ZONE + ary->set(state, 10, String::create(state, tm->tm_zone)); +#else + ary->set(state, 10, Qnil); +#endif + + SET(this, tm, ary); + SET(this, is_gmt, gmt); + + return this; + } +} diff --git a/vm/builtin/time.hpp b/vm/builtin/time.hpp new file mode 100644 index 0000000000..e169b8d4c2 --- /dev/null +++ b/vm/builtin/time.hpp @@ -0,0 +1,35 @@ +#ifndef RBX_BUILTIN_TIME_HPP +#define RBX_BUILTIN_TIME_HPP + +#include "builtin/object.hpp" +#include "type_info.hpp" + +namespace rubinius { + class Array; + + class Time : public Object { + public: + const static size_t fields = 3; + const static object_type type = TimeType; + + Array* timeval; // slot + Array* tm; // slot + OBJECT is_gmt; // slot + + // Ruby.primitive :time_allocate + static Time* create(STATE); + + // Ruby.primitive :time_gettimeofday + Time* gettimeofday(STATE); + + // Ruby.primitive :time_witch + Time* time_switch(STATE, OBJECT gmt); + + class Info : public TypeInfo { + public: + BASIC_TYPEINFO(TypeInfo) + }; + }; +}; + +#endif diff --git a/vm/globals.hpp b/vm/globals.hpp index 302cdb97ce..b8b2af4f6d 100644 --- a/vm/globals.hpp +++ b/vm/globals.hpp @@ -65,6 +65,7 @@ namespace rubinius { TypedRoot compactlookuptable; TypedRoot access_variable; TypedRoot rubinius; + TypedRoot time_class; /* Add new globals above this line. */ @@ -161,7 +162,8 @@ namespace rubinius { dir(&roots), compactlookuptable(&roots), access_variable(&roots), - rubinius(&roots) + rubinius(&roots), + time_class(&roots) /* Add initialize of globals above this line. */ { } diff --git a/vm/object_types.hpp b/vm/object_types.hpp index 28945e19ea..640c6216bb 100644 --- a/vm/object_types.hpp +++ b/vm/object_types.hpp @@ -54,6 +54,7 @@ namespace rubinius { DirType , CompactLookupTableType, AccessVariableType, + TimeType , LastObjectType // must remain at end } object_type; diff --git a/vm/objects.cpp b/vm/objects.cpp index 629851094a..648c0f102b 100644 --- a/vm/objects.cpp +++ b/vm/objects.cpp @@ -4,6 +4,7 @@ #include "objectmemory.hpp" #include "vm.hpp" +#include "builtin/access_variable.hpp" #include "builtin/array.hpp" #include "builtin/block_environment.hpp" #include "builtin/class.hpp" @@ -26,8 +27,8 @@ #include "builtin/symbol.hpp" #include "builtin/task.hpp" #include "builtin/thread.hpp" +#include "builtin/time.hpp" #include "builtin/tuple.hpp" -#include "builtin/access_variable.hpp" #define SPECIAL_CLASS_MASK 0x1f #define SPECIAL_CLASS_SIZE 32 @@ -65,20 +66,24 @@ namespace rubinius { return cls; } + // TODO: document Class* VM::new_class(OBJECT sup, size_t fields) { Class *cls = new_basic_class(sup, fields); MetaClass::attach(this, cls); return cls; } + // TODO: document Class* VM::new_class(const char* name) { return new_class(name, G(object), G(object)->instance_fields->to_native()); } + // TODO: document Class* VM::new_class(const char* name, size_t fields) { return new_class(name, G(object), fields); } + // TODO: document Class* VM::new_class(const char* name, Module* under) { size_t fields = G(object)->instance_fields->to_native(); Class* cls = new_class(name, G(object), fields); @@ -86,10 +91,12 @@ namespace rubinius { return cls; } + // TODO: document Class* VM::new_class(const char* name, OBJECT sup, size_t fields) { return new_class(name, sup, fields, G(object)); } + // TODO: document Class* VM::new_class(const char* name, OBJECT sup, size_t fields, Module* under) { Class* cls = new_class(sup, fields); cls->setup(this, name, under); @@ -102,6 +109,7 @@ namespace rubinius { return mod; } + // TODO: document all the sections of bootstrap_ontology /* Creates the rubinius object universe from scratch. */ void VM::bootstrap_ontology() { /* Class is created first by hand, and twittle to setup the internal @@ -182,6 +190,9 @@ namespace rubinius { GO(compactlookuptable).set(new_class(G(tuple), CompactLookupTable::fields)); G(compactlookuptable)->instance_type = Fixnum::from(CompactLookupTableType); + GO(time_class).set(new_class(object, Time::fields)); + G(time_class)->instance_type = Fixnum::from(TimeType); + bootstrap_symbol(); G(object)->setup(this, "Object"); @@ -203,6 +214,7 @@ namespace rubinius { G(symbol)->setup(this, "Symbol"); G(dir)->setup(this, "Dir"); G(compactlookuptable)->setup(this, "CompactLookupTable"); + G(time_class)->setup(this, "Time"); GO(nil_class).set(new_class("NilClass", object, 0)); GO(true_class).set(new_class("TrueClass", object, 0)); diff --git a/vm/test/test_objects.hpp b/vm/test/test_objects.hpp index a4a0e962f3..18a599365e 100644 --- a/vm/test/test_objects.hpp +++ b/vm/test/test_objects.hpp @@ -174,4 +174,15 @@ class TestObjects : public CxxTest::TestSuite { TS_ASSERT_EQUALS(cls->instance_type, Fixnum::from(CompactLookupTableType)); check_const(compactlookuptable, "CompactLookupTable"); } + + void test_time_class() { + Class *cls; + + cls = G(time_class); + + TS_ASSERT_EQUALS(cls->class_object(state), G(klass)); + TS_ASSERT_EQUALS(cls->superclass, G(object)); + TS_ASSERT_EQUALS(cls->instance_type, Fixnum::from(TimeType)); + check_const(time_class, "Time"); + } }; diff --git a/vm/test/test_time.hpp b/vm/test/test_time.hpp new file mode 100644 index 0000000000..873e547f7e --- /dev/null +++ b/vm/test/test_time.hpp @@ -0,0 +1,61 @@ +#include "builtin/class.hpp" +#include "builtin/time.hpp" +#include "object_types.hpp" + +#include + +using namespace rubinius; + +class TestTime : public CxxTest::TestSuite { + public: + + VM *state; + + void setUp() { + state = new VM(); + } + + void tearDown() { + delete state; + } + + void test_create() { + Time* tm = Time::create(state); + + TS_ASSERT_EQUALS(tm->timeval->obj_type, ArrayType); + TS_ASSERT_EQUALS(tm->tm->obj_type, ArrayType); + TS_ASSERT_EQUALS(tm->is_gmt, Qfalse); + + TS_ASSERT_EQUALS(tm->timeval->size(), 2U); + TS_ASSERT_EQUALS(tm->tm->size(), 11U); + } + + void test_gettimeofday() { + Time* tm = Time::create(state); + + tm->timeval = Array::create(state, 0); + TS_ASSERT_EQUALS(tm->timeval->size(), 0U); + + tm->gettimeofday(state); + TS_ASSERT_EQUALS(tm->timeval->obj_type, ArrayType); + TS_ASSERT_EQUALS(tm->timeval->size(), 2U); + } + + void test_time_switch() { + Time* tm = Time::create(state); + + tm->tm = Array::create(state, 0); + TS_ASSERT_EQUALS(tm->tm->size(), 0U); + + tm->is_gmt = Qnil; + TS_ASSERT_EQUALS(tm->is_gmt, Qnil); + + tm->time_switch(state, Qtrue); + TS_ASSERT_EQUALS(tm->is_gmt, Qtrue); + TS_ASSERT_EQUALS(tm->tm->size(), 11U); + + tm->time_switch(state, Qfalse); + TS_ASSERT_EQUALS(tm->is_gmt, Qfalse); + TS_ASSERT_EQUALS(tm->tm->size(), 11U); + } +}; diff --git a/vm/test/test_vm.hpp b/vm/test/test_vm.hpp index 08add5e027..e3059fcf54 100644 --- a/vm/test/test_vm.hpp +++ b/vm/test/test_vm.hpp @@ -59,7 +59,7 @@ class TestVM : public CxxTest::TestSuite { } void test_globals() { - TS_ASSERT_EQUALS(state->globals.roots.size(), 122U); + TS_ASSERT_EQUALS(state->globals.roots.size(), 123U); } void test_collection() {