Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Crystal::System::Time for win32 #5422

Merged
merged 1 commit into from Jan 3, 2018

Conversation

straight-shoota
Copy link
Member

  • compute_utc_offset: Always returns the current UTC offset for now.
    There seems to be no easy method to access UTC offset at an arbitrary
    point in time.
    The offset calculation will change anyway with Add time zones support #5324
  • compute_utc_seconds_and_nanoseconds: Precision is currently only
    100 nanoseconds because FILE_TIME has no higher resolution.
  • monotonic: Seems to work as expected. Precision can very depending on performance frequency.

@@ -0,0 +1,33 @@
lib LibC
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems that all these definitions are in winbase.h, so perhaps they should be in winbase.cr?

alias Word = UInt16
alias WChar = UInt16
alias DWord = UInt32
alias BOOL = Int32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are all in winapi.cr (which I actually think should be removed, would love to head the core team's opinion on this)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That depends on where they're expected to be defined in windows includes. AFAIK there is no winapi.h.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DWORDis declared in IntSafe.h, WCHAR in WinNT.h, WORD and BOOL in WinDef.h

https://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx

Copy link
Contributor

@RX14 RX14 Dec 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well I don't think they should be in sys/types (that's a header from the c99 standard), and if you are going to define these types in the PR, they should at least be removed from winapi.cr

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll put them in their respective locations.

daylight_bias : Long
end

fun get_time_zone_information = GetTimeZoneInformation(tz_info : TimeZoneInformation*) : DWord
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The standard for functions (which I argued against) was to prefix them with an _ so that their case could be kept the same. See winapi.cr.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I didn't know that. I'll change it, allthough it seems more convenient to "crystalize" internal names. But there are pros and cons for both styles.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need the _ anymore, just use GetTimeZoneInformation.

Opinion is to match syscalls 1:1. We don't have to go through crystal-like namings: they're not crystal methods, but Windows functions. Windows uses GetTimeZoneInformation not get_time_zone_information, let's respect that. It also makes automatically generating bindings (precomputed or on-the-fly) easier, and cleans up bindings by avoiding useless aliases.

{(since_epoch / 10_000_000).to_i64 + WINDOWS_EPOCH_IN_SECONDS, since_epoch.remainder(10_000_000).to_i32 * 100}
end

@@performance_frequency = uninitialized Int64
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@performance_frequency = begin
  ret = LibC._QueryPerformanceFrequency(out frequency)
  raise Errno.new("QueryPerformanceFrequency") if ret == 0
  frequency
end

should work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or just a plain:

ret = LibC.QueryPerformanceFrequency(out frequency)
raise Errno.new("QueryPerformanceFrequency") if ret == 0
@@performance_frequency = frequency

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or should it be lazy evaluated like mach_timebase_info for unix/macos?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer @RX14's version so you dont have a ret variable floating around

LibC.get_system_time_as_file_time(out filetime)
since_epoch = (filetime.high_date_time.to_u64 << 32) | filetime.low_date_time.to_u64

{(since_epoch / 10_000_000).to_i64 + WINDOWS_EPOCH_IN_SECONDS, since_epoch.remainder(10_000_000).to_i32 * 100}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nice to name these constants: FILETIME_TICKS_PER_SECOND and NANOSECONDS_PER_FILETIME_TICK

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FILETIME does not really represent abstract "ticks" but 100 nanosecond intervals. But I guess it's fine as a name and I don't have any better idea.

raise Errno.new("query_performance_counter")
end

{ticks / @@performance_frequency, (ticks.remainder(1_000_000_000) * 1_000_000_000 / @@performance_frequency).to_i32}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto on the naming the constants.

@RX14
Copy link
Contributor

RX14 commented Dec 21, 2017

You should be able to at least account for daylight time using StandardDate/StandardBias and DaylightDate/DaylistBias

end

fun get_time_zone_information = GetTimeZoneInformation(tz_info : TimeZoneInformation*) : DWord
fun get_system_time_as_file_time = GetSystemTimeAsFileTime(time : Filetime*)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like you want GetSystemTimePreciseAsFileTime actually. It's fine that the precision is 100ns, it's fairly impossible to synchronise the system clock to be more accurate than that anyway.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I don't know if it makes any difference. Both return a FILETIME structure with 100 ns resolution and the values returned by both functions seem to be equally precise (at least on my system).
GetSystemTimePreciseAsFileTime is only available on Windows 8+. I don't know which windows versions Crystal should support but it should easily be older ones.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've taken a look at Go's implementation and they use GetSystemTimePreciseAsFileTime as well. Might need some more investigation about the actual difference.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use the more precise one if it's there. The old one is remarkably inaccurate: see https://github.com/dotnet/coreclr/issues/5061.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have a fallback for API prior to Windows 8?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes we should do the same library open and function pointer trick that the coreclr does in the pr linked from that issue.

@RX14
Copy link
Contributor

RX14 commented Dec 21, 2017

Thanks so much for this PR! It's largely a great PR, although it seems that we need to document (and agree that we want) some more conventions related to windows naming.

@straight-shoota
Copy link
Member Author

Yeah, I was just trying to fit everything as I was going along. It seems, a few conventions exists that I wasn't aware of. Some guidance would be really helpfull for ongoing efforts to implement windows bindings.

@straight-shoota
Copy link
Member Author

About copmputing the UTC offset: It could be more sophisticated. We could even use GetTimeZoneInformationForYear and use the dates to calculate the real offset at seconds. But this will need some work and I don't think it's worth bothering right now as the entire time zone handling needs an overhaul anyway. We can continue to improve once #5324 gets merged (or a different path is chosen).
For now, the entire time zone handling is very limited anyway so it shouldn't be a big issue that it always uses the current offset.

@ysbaddaden
Copy link
Contributor

Some guidance would be really helpfull for ongoing efforts to implement windows bindings

Just match namings 1:1 for types, functions and struct fields as much as possible. If Windows uses UPPERCASE, then use UPPERCASE, if it uses CamelCase then use CamelCase, and so on. The only exception is lowercased type names, which must be transformed. Habit in bindings is to CamelCase them (e.g. size_t -> SizeT).

Last but not least: binding functions can start with an uppercase letter since some versions back, so LibC.GetTimeZoneInformation or LibC.LLVMDisposeModule work nicely.

@straight-shoota
Copy link
Member Author

@ysbaddaden Thanks, that's the kind of guidance I was looking for 👍

fun _GetTimeZoneInformation = GetTimeZoneInformation(tz_info : TIME_ZONE_INFORMATION*) : DWord
fun _GetSystemTimeAsFileTime = GetSystemTimeAsFileTime(time : FILETIME*)
fun get_time_zone_information = GetTimeZoneInformation(tz_info : TIME_ZONE_INFORMATION*) : DWord
fun get_system_time_as_file_time = GetSystemTimeAsFileTime(time : FILETIME*)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the difference with winbase.cr did I miss something ? Imo these modifications in this file are wrong

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, that file was commited on error.

def self.compute_utc_offset(seconds : Int64) : Int32
raise NotImplementedError.new("Crystal::System::Time.compute_utc_offset")
ret = LibC.GetTimeZoneInformation(out zone_information)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's at least have a TODO on ignoring seconds.

@straight-shoota
Copy link
Member Author

I've moved the type declarations to files corresponding to the header names and added some comments.

Specs are failing due to formatter error #5432

@@ -10,6 +10,8 @@ module Crystal::System::Time
NANOSECONDS_PER_SECOND = 1_000_000_000
FILETIME_TICKS_PER_SECOND = NANOSECONDS_PER_SECOND / NANOSECONDS_PER_FILETIME_TICK

# TODO: For now, this method returns the UTC offset currently in place.
# A proper implementation is required once Crystal's Time Zone API has been revised (see issue #5324)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer not to mention unmerged PRs as if it's a certainty they will be merged. Just say that this is not a complete implementation.

@straight-shoota straight-shoota force-pushed the jm-windows-time branch 2 times, most recently from 2f8d810 to d18942a Compare December 22, 2017 12:55
end

def self.compute_utc_seconds_and_nanoseconds : {Int64, Int32}
raise NotImplementedError.new("Crystal::System::Time.compute_utc_seconds_and_nanoseconds")
LibC.GetSystemTimePreciseAsFileTime(out filetime)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future we should use windows's dlopen equivalent to fallback to GetSystemTimeAsFileTime. Can we either do that in this PR (see this) or leave a TODO?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RX14 that approach will be required if we want to support anything older than Windows 8 (function was introduced then).

Considering a few other places might require such approach, perhaps adding a TODO will be recommended.

Cheers.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this check be at the callsite or in the bindings definition?
But I think I'd rather leave the implementation out of this PR.

Copy link
Contributor

@RX14 RX14 Dec 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dlopen and method exists check would want to be cached, yes. Leaving it for later should be fine.

We might want to create a helper API/macro for this eventually, not sure.

def self.compute_utc_offset(seconds : Int64) : Int32
raise NotImplementedError.new("Crystal::System::Time.compute_utc_offset")
ret = LibC.GetTimeZoneInformation(out zone_information)
raise Errno.new("GetTimeZoneInformation") if ret == -1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at it again, shouldn't this be WinError?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I didn't know this class even existed... it's not used anywhere until now.

Copy link
Contributor

@RX14 RX14 Dec 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes because windows has both errno and GetLastError for the C99 and win32 APIs respectively.

We shouldn't keep WinError or expose these platform-specific APIs at 1.0 either.

@@ -0,0 +1,3 @@
lib LibC
alias WChar = UInt16
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this WCHAR?

@@ -0,0 +1,4 @@
lib LibC
alias Word = UInt16
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this WORD?

@@ -0,0 +1,3 @@
lib LibC
alias DWord = UInt32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this DWORD?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. I was just copying that from winapi.cr. Changing that will require to change it in that file as well. Not sure if that should be in this PR... WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh, leave it as-is for now and i'll send a PR to rip out winapi.cr and redo that to the "new" specs.

@straight-shoota
Copy link
Member Author

This should probably wait for #5448. I'll rebase once it gets merged.

@RX14
Copy link
Contributor

RX14 commented Dec 25, 2017

We can merge in either order. I'd love a second review, or a lease to merge PRs which only touch code for windows with only one review.

* `compute_utc_offset`: Always returns the **current** UTC offset for now.
  There seems to be no easy method to access UTC offset at an arbitrary
  point in time.
* `compute_utc_seconds_and_nanoseconds`: Precision is currently only
  100 nanoseconds because `FILE_TIME` has no higher resolution.
* `monotonic`
@straight-shoota
Copy link
Member Author

Rebased on master including #5448 and squashed.

@RX14 RX14 merged commit 5c60ae2 into crystal-lang:master Jan 3, 2018
@RX14 RX14 added this to the Next milestone Jan 3, 2018
@straight-shoota straight-shoota deleted the jm-windows-time branch January 3, 2018 12:50
lukeasrodgers pushed a commit to lukeasrodgers/crystal that referenced this pull request Jan 7, 2018
* `compute_utc_offset`: Always returns the **current** UTC offset for now.
  There seems to be no easy method to access UTC offset at an arbitrary
  point in time.
* `compute_utc_seconds_and_nanoseconds`: Precision is currently only
  100 nanoseconds because `FILE_TIME` has no higher resolution.
* `monotonic`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants