-
Notifications
You must be signed in to change notification settings - Fork 111
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
Initial implementation of Ion 1.1 raw binary writer #660
Merged
+1,312
−158
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
package com.amazon.ion.impl.bin | ||
|
||
import java.math.BigInteger | ||
|
||
/** | ||
* Functions for encoding FlexInts and FlexUInts. | ||
* | ||
* Expected usage is calling one of the `___length` functions, and then using the result as the input for | ||
* [writeFlexIntOrUIntInto]. The length and write functions are separate so that callers can make decisions or | ||
* compute other values based on the encoded size of the value. | ||
*/ | ||
object FlexInt { | ||
|
||
/** Determine the length of FlexUInt for the provided value. */ | ||
@JvmStatic | ||
fun flexUIntLength(value: Long): Int { | ||
val numLeadingZeros = java.lang.Long.numberOfLeadingZeros(value) | ||
val numMagnitudeBitsRequired = 64 - numLeadingZeros | ||
return (numMagnitudeBitsRequired - 1) / 7 + 1 | ||
} | ||
|
||
/** Determine the length of FlexInt for the provided value. */ | ||
@JvmStatic | ||
fun flexIntLength(value: Long): Int { | ||
val numMagnitudeBitsRequired: Int | ||
numMagnitudeBitsRequired = if (value < 0) { | ||
val numLeadingOnes = java.lang.Long.numberOfLeadingZeros(value.inv()) | ||
64 - numLeadingOnes | ||
} else { | ||
val numLeadingZeros = java.lang.Long.numberOfLeadingZeros(value) | ||
64 - numLeadingZeros | ||
} | ||
return numMagnitudeBitsRequired / 7 + 1 | ||
} | ||
|
||
/** | ||
* Writes a FlexInt or FlexUInt encoding of [value] into [data] starting at [offset]. | ||
* Use [flexIntLength] or [flexUIntLength] to get the value for the [numBytes] parameter. | ||
*/ | ||
@JvmStatic | ||
fun writeFlexIntOrUIntInto(data: ByteArray, offset: Int, value: Long, numBytes: Int) { | ||
var i = offset | ||
|
||
when (numBytes) { | ||
1 -> { | ||
data[i] = (0x01L or (value shl 1)).toByte() | ||
} | ||
2 -> { | ||
data[i] = (0x02L or (value shl 2)).toByte() | ||
data[++i] = (value shr 6).toByte() | ||
} | ||
3 -> { | ||
data[i] = (0x04L or (value shl 3)).toByte() | ||
data[++i] = (value shr 5).toByte() | ||
data[++i] = (value shr 13).toByte() | ||
} | ||
4 -> { | ||
data[i] = (0x08L or (value shl 4)).toByte() | ||
data[++i] = (value shr 4).toByte() | ||
data[++i] = (value shr 12).toByte() | ||
data[++i] = (value shr 20).toByte() | ||
} | ||
5 -> { | ||
data[i] = (0x10L or (value shl 5)).toByte() | ||
data[++i] = (value shr 3).toByte() | ||
data[++i] = (value shr 11).toByte() | ||
data[++i] = (value shr 19).toByte() | ||
data[++i] = (value shr 27).toByte() | ||
} | ||
6 -> { | ||
data[i] = (0x20L or (value shl 6)).toByte() | ||
data[++i] = (value shr 2).toByte() | ||
data[++i] = (value shr 10).toByte() | ||
data[++i] = (value shr 18).toByte() | ||
data[++i] = (value shr 26).toByte() | ||
data[++i] = (value shr 34).toByte() | ||
} | ||
7 -> { | ||
data[i] = (0x40L or (value shl 7)).toByte() | ||
data[++i] = (value shr 1).toByte() | ||
data[++i] = (value shr 9).toByte() | ||
data[++i] = (value shr 17).toByte() | ||
data[++i] = (value shr 25).toByte() | ||
data[++i] = (value shr 33).toByte() | ||
data[++i] = (value shr 41).toByte() | ||
} | ||
8 -> { | ||
data[i] = 0x80.toByte() | ||
data[++i] = (value shr 0).toByte() | ||
data[++i] = (value shr 8).toByte() | ||
data[++i] = (value shr 16).toByte() | ||
data[++i] = (value shr 24).toByte() | ||
data[++i] = (value shr 32).toByte() | ||
data[++i] = (value shr 40).toByte() | ||
data[++i] = (value shr 48).toByte() | ||
} | ||
9 -> { | ||
data[i] = 0 | ||
data[++i] = (0x01L or (value shl 1)).toByte() | ||
data[++i] = (value shr 7).toByte() | ||
data[++i] = (value shr 15).toByte() | ||
data[++i] = (value shr 23).toByte() | ||
data[++i] = (value shr 31).toByte() | ||
data[++i] = (value shr 39).toByte() | ||
data[++i] = (value shr 47).toByte() | ||
data[++i] = (value shr 55).toByte() | ||
} | ||
10 -> { | ||
data[i] = 0 | ||
data[++i] = (0x02L or (value shl 2)).toByte() | ||
data[++i] = (value shr 6).toByte() | ||
data[++i] = (value shr 14).toByte() | ||
data[++i] = (value shr 22).toByte() | ||
data[++i] = (value shr 30).toByte() | ||
data[++i] = (value shr 38).toByte() | ||
data[++i] = (value shr 46).toByte() | ||
data[++i] = (value shr 54).toByte() | ||
data[++i] = (value shr 62).toByte() | ||
} | ||
} | ||
} | ||
|
||
/** Determine the length of FlexUInt for the provided value. */ | ||
@JvmStatic | ||
fun flexUIntLength(value: BigInteger): Int { | ||
return (value.bitLength() - 1) / 7 + 1 | ||
} | ||
|
||
/** Determine the length of FlexInt for the provided value. */ | ||
@JvmStatic | ||
fun flexIntLength(value: BigInteger): Int { | ||
return value.bitLength() / 7 + 1 | ||
} | ||
|
||
/** | ||
* Writes a FlexInt or FlexUInt encoding of [value] into [data] starting at [offset]. | ||
* Use [flexIntLength] or [flexUIntLength] to get the value for the [numBytes] parameter. | ||
*/ | ||
@JvmStatic | ||
fun writeFlexIntOrUIntInto(data: ByteArray, offset: Int, value: BigInteger, numBytes: Int) { | ||
// TODO: Should we branch to the implementation for long if the number is small enough? | ||
// https://github.com/amazon-ion/ion-java/issues/614 | ||
val valueBytes = value.toByteArray() | ||
var i = 0 // `i` gets incremented for every byte written. | ||
|
||
// Start with leading zero bytes. | ||
// If there's 1-8 total bytes, we need no leading zero-bytes. | ||
// If there's 9-16 total bytes, we need one zero-byte | ||
// If there's 17-24 total bytes, we need two zero-bytes, etc. | ||
while (i < (numBytes - 1) / 8) { | ||
data[offset + i] = 0 | ||
i++ | ||
} | ||
|
||
// Write the last length bits, possibly also containing some value bits. | ||
val remainingLengthBits = (numBytes - 1) % 8 | ||
val lengthPart = (0x01 shl remainingLengthBits).toByte() | ||
val valueBitOffset = remainingLengthBits + 1 | ||
val valuePart = (valueBytes[valueBytes.size - 1].toInt() shl valueBitOffset).toByte() | ||
data[offset + i] = (valuePart.toInt() or lengthPart.toInt()).toByte() | ||
i++ | ||
for (valueByteOffset in valueBytes.size - 1 downTo 1) { | ||
// Technically it's only a nibble if the bitOffset is 4, so we call it nibble-ish | ||
val highNibbleIsh = (valueBytes[valueByteOffset - 1].toInt() shl valueBitOffset).toByte() | ||
val lowNibbleIsh = (valueBytes[valueByteOffset].toInt() and 0xFF shr 8 - valueBitOffset).toByte() | ||
data[offset + i] = (highNibbleIsh.toInt() or lowNibbleIsh.toInt()).toByte() | ||
i++ | ||
} | ||
if (i < numBytes) { | ||
data[offset + i] = (valueBytes[0].toInt() shr 8 - valueBitOffset).toByte() | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I discovered that using
when
to compare directly against some constants would allow the Kotlin compiler to generate a jump table, so I figured I'd just manually unroll the old loop based logic. Now the only branching in this method is the jump table for thewhen
, compared to the chainedif
/else
falling back to the loop in the previous implementation.