Skip to content

Commit

Permalink
Native: refactor to better utilize HMPP
Browse files Browse the repository at this point in the history
The boundaries between the common native code and the
platform-specific implementation is now less ad-hoc.
  • Loading branch information
dkhalanskyjb committed Nov 26, 2020
1 parent 5203f3c commit 8db72c1
Show file tree
Hide file tree
Showing 9 changed files with 181 additions and 151 deletions.
8 changes: 8 additions & 0 deletions core/build.gradle.kts
Expand Up @@ -113,15 +113,23 @@ kotlin {
extraOpts("-Xcompile-source", "$cinteropDir/cpp/apple.mm")
}
konanTarget.family == org.jetbrains.kotlin.konan.target.Family.LINUX -> {
// needed for the date library so that it does not try to download the timezone database
extraOpts("-Xsource-compiler-option", "-DUSE_OS_TZDB=1")
/* using a more modern C++ version causes the date library to use features that are not
* present in the currently outdated GCC root shipped with Kotlin/Native for Linux. */
extraOpts("-Xsource-compiler-option", "-std=c++11")
// the date library and its headers
extraOpts("-Xcompile-source", "$dateLibDir/src/tz.cpp")
extraOpts("-Xsource-compiler-option", "-I$dateLibDir/include")
// the main source for the platform bindings.
extraOpts("-Xcompile-source", "$cinteropDir/cpp/cdate.cpp")
}
konanTarget.family == org.jetbrains.kotlin.konan.target.Family.MINGW -> {
// needed to be able to use std::shared_mutex to implement caching.
extraOpts("-Xsource-compiler-option", "-std=c++17")
// the date library headers, needed for some pure calculations.
extraOpts("-Xsource-compiler-option", "-I$dateLibDir/include")
// the main source for the platform bindings.
extraOpts("-Xcompile-source", "$cinteropDir/cpp/windows.cpp")
}
else -> {
Expand Down
112 changes: 90 additions & 22 deletions core/native/cinterop_actuals/TimeZoneNative.kt
Expand Up @@ -4,35 +4,103 @@
*/
package kotlinx.datetime

import kotlinx.datetime.internal.*
import kotlinx.cinterop.*
import platform.posix.free

internal actual fun getCurrentSystemDefaultTimeZone(): TimeZone = memScoped {
val tzid = alloc<kotlinx.datetime.internal.TZIDVar>()
val string = kotlinx.datetime.internal.get_system_timezone(tzid.ptr)
?: throw RuntimeException("Failed to get the system timezone.")
val kotlinString = string.toKString()
free(string)
TimeZone(tzid.value, kotlinString)
}
internal actual class PlatformTimeZoneImpl(private val tzid: TZID, override val id: String): TimeZoneImpl {
actual companion object {
actual fun of(zoneId: String): PlatformTimeZoneImpl {
val tzid = timezone_by_name(zoneId)
if (tzid == TZID_INVALID) {
throw IllegalTimeZoneException("No timezone found with zone ID '$zoneId'")
}
return PlatformTimeZoneImpl(tzid, zoneId)
}

actual fun currentSystemDefault(): PlatformTimeZoneImpl = memScoped {
val tzid = alloc<TZIDVar>()
val string = get_system_timezone(tzid.ptr)
?: throw RuntimeException("Failed to get the system timezone.")
val kotlinString = string.toKString()
free(string)
PlatformTimeZoneImpl(tzid.value, kotlinString)
}

internal actual fun current_time(sec: kotlinx.cinterop.CValuesRef<platform.posix.int64_tVar /* = kotlinx.cinterop.LongVarOf<kotlin.Long> */>?, nano: kotlinx.cinterop.CValuesRef<platform.posix.int32_tVar>?): kotlin.Boolean =
kotlinx.datetime.internal.current_time(sec, nano)
actual val availableZoneIds: Set<String>
get() {
val set = mutableSetOf("UTC")
val zones = available_zone_ids()
?: throw RuntimeException("Failed to get the list of available timezones")
var ptr = zones
while (true) {
val cur = ptr.pointed.value ?: break
val zoneName = cur.toKString()
set.add(zoneName)
free(cur)
ptr = (ptr + 1)!!
}
free(zones)
return set
}
}

internal actual fun available_zone_ids(): kotlinx.cinterop.CPointer<kotlinx.cinterop.CPointerVar<kotlinx.cinterop.ByteVar>>? =
kotlinx.datetime.internal.available_zone_ids()
override fun atStartOfDay(date: LocalDate): Instant = memScoped {
val ldt = LocalDateTime(date, LocalTime.MIN)
val epochSeconds = ldt.toEpochSecond(ZoneOffsetImpl.UTC)
val midnightInstantSeconds = at_start_of_day(tzid, epochSeconds)
if (midnightInstantSeconds == Long.MAX_VALUE) {
throw RuntimeException("Unable to acquire the time of start of day at $date for zone $this")
}
Instant(midnightInstantSeconds, 0)
}

internal actual fun offset_at_datetime(zone: kotlinx.datetime.TZID /* = kotlin.ULong */, epoch_sec: platform.posix.int64_t /* = kotlin.Long */, offset: kotlinx.cinterop.CValuesRef<kotlinx.cinterop.IntVar /* = kotlinx.cinterop.IntVarOf<kotlin.Int> */>?): kotlin.Int =
kotlinx.datetime.internal.offset_at_datetime(zone, epoch_sec, offset)
override fun LocalDateTime.atZone(preferred: ZoneOffsetImpl?): ZonedDateTime = memScoped {
val epochSeconds = toEpochSecond(ZoneOffsetImpl.UTC)
val offset = alloc<IntVar>()
offset.value = preferred?.totalSeconds ?: Int.MAX_VALUE
val transitionDuration = offset_at_datetime(tzid, epochSeconds, offset.ptr)
if (offset.value == Int.MAX_VALUE) {
throw RuntimeException("Unable to acquire the offset at ${this@atZone} for zone ${this@PlatformTimeZoneImpl}")
}
val dateTime = try {
this@atZone.plusSeconds(transitionDuration)
} catch (e: IllegalArgumentException) {
throw DateTimeArithmeticException("Overflow whet correcting the date-time to not be in the transition gap", e)
} catch (e: ArithmeticException) {
throw RuntimeException("Anomalously long timezone transition gap reported", e)
}
ZonedDateTime(dateTime, TimeZone(this@PlatformTimeZoneImpl), ZoneOffset.ofSeconds(offset.value).offset)
}

internal actual fun at_start_of_day(zone: kotlinx.datetime.TZID /* = kotlin.ULong */, epoch_sec: platform.posix.int64_t /* = kotlin.Long */): kotlin.Long =
kotlinx.datetime.internal.at_start_of_day(zone, epoch_sec)
override fun offsetAt(instant: Instant): ZoneOffsetImpl {
val offset = offset_at_instant(tzid, instant.epochSeconds)
if (offset == Int.MAX_VALUE) {
throw RuntimeException("Unable to acquire the offset at instant $instant for zone $this")
}
return ZoneOffset.ofSeconds(offset).offset
}

internal actual fun offset_at_instant(zone: kotlinx.datetime.TZID /* = kotlin.ULong */, epoch_sec: platform.posix.int64_t /* = kotlin.Long */): kotlin.Int =
kotlinx.datetime.internal.offset_at_instant(zone, epoch_sec)
// org.threeten.bp.ZoneId#equals
override fun equals(other: Any?): Boolean =
this === other || other is PlatformTimeZoneImpl && this.id == other.id

internal actual fun timezone_by_name(zone_name: kotlin.String?): kotlinx.datetime.TZID /* = kotlin.ULong */ =
kotlinx.datetime.internal.timezone_by_name(zone_name)
// org.threeten.bp.ZoneId#hashCode
override fun hashCode(): Int = id.hashCode()

// org.threeten.bp.ZoneId#toString
override fun toString(): String = id
}

internal actual val TZID_INVALID: TZID
get() = kotlinx.datetime.internal.TZID_INVALID
internal actual fun currentTime(): Instant = memScoped {
val seconds = alloc<LongVar>()
val nanoseconds = alloc<IntVar>()
val result = current_time(seconds.ptr, nanoseconds.ptr)
try {
require(result)
require(nanoseconds.value >= 0 && nanoseconds.value < NANOS_PER_ONE)
Instant(seconds.value, nanoseconds.value)
} catch (e: IllegalArgumentException) {
throw IllegalStateException("The readings from the system clock are not representable as an Instant")
}
}
17 changes: 3 additions & 14 deletions core/native/src/Instant.kt
Expand Up @@ -8,8 +8,6 @@

package kotlinx.datetime

import kotlinx.cinterop.*
import platform.posix.*
import kotlin.math.*
import kotlin.time.*

Expand Down Expand Up @@ -83,6 +81,8 @@ private const val MAX_SECOND = 31494816403199L // +1000000-12-31T23:59:59

private fun isValidInstantSecond(second: Long) = second >= MIN_SECOND && second <= MAX_SECOND

internal expect fun currentTime(): Instant

@OptIn(ExperimentalTime::class)
public actual class Instant internal constructor(actual val epochSeconds: Long, actual val nanosecondsOfSecond: Int) : Comparable<Instant> {

Expand Down Expand Up @@ -208,18 +208,7 @@ public actual class Instant internal constructor(actual val epochSeconds: Long,
internal actual val MAX = Instant(MAX_SECOND, 999_999_999)

@Deprecated("Use Clock.System.now() instead", ReplaceWith("Clock.System.now()", "kotlinx.datetime.Clock"), level = DeprecationLevel.ERROR)
actual fun now(): Instant = memScoped {
val seconds = alloc<LongVar>()
val nanoseconds = alloc<IntVar>()
val result = current_time(seconds.ptr, nanoseconds.ptr)
try {
require(result)
require(nanoseconds.value >= 0 && nanoseconds.value < NANOS_PER_ONE)
Instant(seconds.value, nanoseconds.value)
} catch (e: IllegalArgumentException) {
throw IllegalStateException("The readings from the system clock are not representable as an Instant")
}
}
actual fun now(): Instant = currentTime()

// org.threeten.bp.Instant#ofEpochMilli
actual fun fromEpochMilliseconds(epochMilliseconds: Long): Instant =
Expand Down
2 changes: 1 addition & 1 deletion core/native/src/LocalDateTime.kt
Expand Up @@ -66,7 +66,7 @@ public actual class LocalDateTime internal constructor(
actual override fun toString(): String = date.toString() + 'T' + time.toString()

// org.threeten.bp.chrono.ChronoLocalDateTime#toEpochSecond
internal fun toEpochSecond(offset: ZoneOffset): Long {
internal fun toEpochSecond(offset: ZoneOffsetImpl): Long {
val epochDay = date.toEpochDay().toLong()
var secs: Long = epochDay * 86400 + time.toSecondOfDay()
secs -= offset.totalSeconds
Expand Down
132 changes: 25 additions & 107 deletions core/native/src/TimeZone.kt
Expand Up @@ -9,26 +9,13 @@
package kotlinx.datetime

import kotlin.math.abs
import kotlinx.cinterop.*
import platform.posix.*
import kotlin.native.concurrent.*

internal expect fun getCurrentSystemDefaultTimeZone(): TimeZone

internal typealias TZID = platform.posix.size_t
internal expect val TZID_INVALID: TZID
internal expect fun available_zone_ids(): kotlinx.cinterop.CPointer<kotlinx.cinterop.CPointerVar<kotlinx.cinterop.ByteVar>>?
internal expect fun offset_at_datetime(zone: kotlinx.datetime.TZID /* = kotlin.ULong */, epoch_sec: platform.posix.int64_t /* = kotlin.Long */, offset: kotlinx.cinterop.CValuesRef<kotlinx.cinterop.IntVar /* = kotlinx.cinterop.IntVarOf<kotlin.Int> */>?): kotlin.Int
internal expect fun at_start_of_day(zone: kotlinx.datetime.TZID /* = kotlin.ULong */, epoch_sec: platform.posix.int64_t /* = kotlin.Long */): kotlin.Long
internal expect fun offset_at_instant(zone: kotlinx.datetime.TZID /* = kotlin.ULong */, epoch_sec: platform.posix.int64_t /* = kotlin.Long */): kotlin.Int
internal expect fun timezone_by_name(zone_name: kotlin.String?): kotlinx.datetime.TZID /* = kotlin.ULong */
internal expect fun current_time(sec: kotlinx.cinterop.CValuesRef<platform.posix.int64_tVar /* = kotlinx.cinterop.LongVarOf<kotlin.Long> */>?, nano: kotlinx.cinterop.CValuesRef<platform.posix.int32_tVar>?): kotlin.Boolean

public actual open class TimeZone internal constructor(private val tzid: TZID, actual val id: String) {
public actual open class TimeZone internal constructor(internal val value: TimeZoneImpl) {

actual companion object {

actual fun currentSystemDefault(): TimeZone = getCurrentSystemDefaultTimeZone()
actual fun currentSystemDefault(): TimeZone = PlatformTimeZoneImpl.currentSystemDefault().let(::TimeZone)

actual val UTC: TimeZone = ZoneOffset.UTC

Expand All @@ -45,110 +32,59 @@ public actual open class TimeZone internal constructor(private val tzid: TZID, a
return ZoneOffset.of(zoneId)
}
if (zoneId == "UTC" || zoneId == "GMT" || zoneId == "UT") {
return ZoneOffset(0, zoneId)
return ZoneOffset(ZoneOffsetImpl(0, zoneId))
}
if (zoneId.startsWith("UTC+") || zoneId.startsWith("GMT+") ||
zoneId.startsWith("UTC-") || zoneId.startsWith("GMT-")) {
val offset = ZoneOffset.of(zoneId.substring(3))
return if (offset.totalSeconds == 0) {
ZoneOffset(0, zoneId.substring(0, 3))
} else ZoneOffset(offset.totalSeconds, zoneId.substring(0, 3) + offset.id)
return (if (offset.totalSeconds == 0) ZoneOffsetImpl(0, zoneId.substring(0, 3))
else ZoneOffsetImpl(offset.totalSeconds, zoneId.substring(0, 3) + offset.id)).let(::ZoneOffset)
}
if (zoneId.startsWith("UT+") || zoneId.startsWith("UT-")) {
val offset = ZoneOffset.of(zoneId.substring(2))
return if (offset.totalSeconds == 0) {
ZoneOffset(0, "UT")
} else ZoneOffset(offset.totalSeconds, "UT" + offset.id)
}
val tzid = timezone_by_name(zoneId)
if (tzid == TZID_INVALID) {
throw IllegalTimeZoneException("No timezone found with zone ID '$zoneId'")
return (if (offset.totalSeconds == 0) ZoneOffsetImpl(0, "UT")
else ZoneOffsetImpl(offset.totalSeconds, "UT" + offset.id)).let(::ZoneOffset)
}
return TimeZone(tzid, zoneId)
return TimeZone(PlatformTimeZoneImpl.of(zoneId))
}

actual val availableZoneIds: Set<String>
get() {
val set = mutableSetOf("UTC")
val zones = available_zone_ids()
?: throw RuntimeException("Failed to get the list of available timezones")
var ptr = zones
while (true) {
val cur = ptr.pointed.value ?: break
val zoneName = cur.toKString()
set.add(zoneName)
free(cur)
ptr = (ptr + 1)!!
}
free(zones)
return set
}
get() = PlatformTimeZoneImpl.availableZoneIds
}

actual val id
get() = value.id

actual fun Instant.toLocalDateTime(): LocalDateTime = try {
toZonedLocalDateTime(this@TimeZone).dateTime
} catch (e: IllegalArgumentException) {
throw DateTimeArithmeticException("Instant ${this@toLocalDateTime} is not representable as LocalDateTime", e)
}

internal open fun offsetAtImpl(instant: Instant): ZoneOffset {
val offset = offset_at_instant(tzid, instant.epochSeconds)
if (offset == Int.MAX_VALUE) {
throw RuntimeException("Unable to acquire the offset at instant $instant for zone $this")
}
return ZoneOffset.ofSeconds(offset)
}

actual fun LocalDateTime.toInstant(): Instant = atZone().toInstant()

internal open fun atStartOfDay(date: LocalDate): Instant = memScoped {
val ldt = LocalDateTime(date, LocalTime.MIN)
val epochSeconds = ldt.toEpochSecond(ZoneOffset.UTC)
val midnightInstantSeconds = at_start_of_day(tzid, epochSeconds)
if (midnightInstantSeconds == Long.MAX_VALUE) {
throw RuntimeException("Unable to acquire the time of start of day at $date for zone $this")
}
Instant(midnightInstantSeconds, 0)
}
internal open fun atStartOfDay(date: LocalDate): Instant = value.atStartOfDay(date)

internal open fun LocalDateTime.atZone(preferred: ZoneOffset? = null): ZonedDateTime = memScoped {
val epochSeconds = toEpochSecond(ZoneOffset.UTC)
val offset = alloc<IntVar>()
offset.value = preferred?.totalSeconds ?: Int.MAX_VALUE
val transitionDuration = offset_at_datetime(tzid, epochSeconds, offset.ptr)
if (offset.value == Int.MAX_VALUE) {
throw RuntimeException("Unable to acquire the offset at ${this@atZone} for zone ${this@TimeZone}")
}
val dateTime = try {
this@atZone.plusSeconds(transitionDuration)
} catch (e: IllegalArgumentException) {
throw DateTimeArithmeticException("Overflow whet correcting the date-time to not be in the transition gap", e)
} catch (e: ArithmeticException) {
throw RuntimeException("Anomalously long timezone transition gap reported", e)
}
ZonedDateTime(dateTime, this@TimeZone, ZoneOffset.ofSeconds(offset.value))
}
internal open fun LocalDateTime.atZone(preferred: ZoneOffsetImpl? = null): ZonedDateTime =
with(value) { atZone(preferred) }

// org.threeten.bp.ZoneId#equals
override fun equals(other: Any?): Boolean =
this === other || other is TimeZone && this.id == other.id

// org.threeten.bp.ZoneId#hashCode
override fun hashCode(): Int = id.hashCode()
this === other || other is TimeZone && this.value == other.value

// org.threeten.bp.ZoneId#toString
override fun toString(): String = id
override fun hashCode(): Int = value.hashCode()

override fun toString(): String = value.toString()
}

@ThreadLocal
private var zoneOffsetCache: MutableMap<Int, ZoneOffset> = mutableMapOf()

public actual class ZoneOffset internal constructor(actual val totalSeconds: Int, id: String) : TimeZone(TZID_INVALID, id) {
public actual class ZoneOffset internal constructor(internal val offset: ZoneOffsetImpl) : TimeZone(offset) {

actual val totalSeconds get() = offset.totalSeconds

companion object {
// org.threeten.bp.ZoneOffset#UTC
val UTC = ZoneOffset(0, "Z")
val UTC = ZoneOffset(ZoneOffsetImpl.UTC)

// org.threeten.bp.ZoneOffset#of
internal fun of(offsetId: String): ZoneOffset {
Expand Down Expand Up @@ -242,9 +178,9 @@ public actual class ZoneOffset internal constructor(actual val totalSeconds: Int
internal fun ofSeconds(seconds: Int): ZoneOffset =
if (seconds % (15 * SECONDS_PER_MINUTE) == 0) {
zoneOffsetCache[seconds] ?:
ZoneOffset(seconds, zoneIdByOffset(seconds)).also { zoneOffsetCache[seconds] = it }
ZoneOffset(ZoneOffsetImpl(seconds, zoneIdByOffset(seconds))).also { zoneOffsetCache[seconds] = it }
} else {
ZoneOffset(seconds, zoneIdByOffset(seconds))
ZoneOffset(ZoneOffsetImpl(seconds, zoneIdByOffset(seconds)))
}

// org.threeten.bp.ZoneOffset#parseNumber
Expand All @@ -260,28 +196,10 @@ public actual class ZoneOffset internal constructor(actual val totalSeconds: Int
return (ch1.toInt() - 48) * 10 + (ch2.toInt() - 48)
}
}

internal override fun atStartOfDay(date: LocalDate): Instant =
LocalDateTime(date, LocalTime.MIN).atZone(null).toInstant()

internal override fun LocalDateTime.atZone(preferred: ZoneOffset?): ZonedDateTime =
ZonedDateTime(this@atZone, this@ZoneOffset, this@ZoneOffset)

override fun offsetAtImpl(instant: Instant): ZoneOffset = this

// org.threeten.bp.ZoneOffset#toString
override fun toString(): String = id

// org.threeten.bp.ZoneOffset#hashCode
override fun hashCode(): Int = totalSeconds

// org.threeten.bp.ZoneOffset#equals
override fun equals(other: Any?): Boolean =
this === other || other is ZoneOffset && totalSeconds == other.totalSeconds
}

public actual fun TimeZone.offsetAt(instant: Instant): ZoneOffset =
offsetAtImpl(instant)
value.offsetAt(instant).let(::ZoneOffset)

public actual fun Instant.toLocalDateTime(timeZone: TimeZone): LocalDateTime =
with(timeZone) { toLocalDateTime() }
Expand Down

0 comments on commit 8db72c1

Please sign in to comment.