Created
September 19, 2014 08:30
-
-
Save mikeash/a4968f890595302cb9e2 to your computer and use it in GitHub Desktop.
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
import Darwin | |
// Swift hates uninitialized values but we need to create stuff without | |
// an explicit initial value. This protocol is for anything that can be | |
// created with any sort of default value (e.g. all integer types init | |
// with zero. | |
protocol Initable { | |
init() | |
} | |
// Byte-level (unaligned) storage formed by concatenating two other | |
// storage types together | |
struct ByteStorageAdd<T: Initable, U: Initable>: Initable { | |
var t = T() | |
var u = U() | |
} | |
// The underlying type for byte-level (unaligned) storage is Int8. | |
// It needs to be Initable so we can use it sanely. The builtin | |
// types already define init(), we just need to declare conformance. | |
extension Int8: Initable {} | |
// Build up various byte-level storage sizes starting from a single | |
// byte. Other sizes can be constructed using ByteStorageAdd and | |
// can be added as typealiases here if that's useful | |
class ByteStorage { | |
typealias One = Int8 | |
typealias Two = ByteStorageAdd<One, One> | |
typealias Four = ByteStorageAdd<Two, Two> | |
typealias Eight = ByteStorageAdd<Four, Four> | |
} | |
// We need the equivalent of sizeof(T) at the level of the type system. | |
// The Storeable protocol handles this by mapping a Storeable type to | |
// a storage type which should be one of the ByteStorage typealiases or | |
// a ByteStorageAdd | |
protocol Storeable: Initable { | |
typealias Storage: Initable | |
} | |
// Fill out Storeable for all the explicitly-sized integer types | |
extension Int8: Storeable { | |
typealias Storage = ByteStorage.One | |
} | |
extension Int16: Storeable { | |
typealias Storage = ByteStorage.Two | |
} | |
extension Int32: Storeable { | |
typealias Storage = ByteStorage.Four | |
} | |
extension Int64: Storeable { | |
typealias Storage = ByteStorage.Eight | |
} | |
extension UInt8: Storeable { | |
typealias Storage = ByteStorage.One | |
} | |
extension UInt16: Storeable { | |
typealias Storage = ByteStorage.Two | |
} | |
extension UInt32: Storeable { | |
typealias Storage = ByteStorage.Four | |
} | |
extension UInt64: Storeable { | |
typealias Storage = ByteStorage.Eight | |
} | |
// Perform a byte copy from one type to another, swapping the bytes | |
// as it goes. This is basically an endian-swapping version of | |
// unsafeBitCast. | |
func SwappedRead<From, To: Initable>(var from: From) -> To { | |
precondition(sizeof(To) == sizeof(From)) | |
let size = sizeof(To) | |
var to = To() | |
withUnsafePointer(&from) { | |
(fromPtr: UnsafePointer<From>) -> Void in | |
withUnsafeMutablePointer(&to) { | |
(toPtr: UnsafeMutablePointer<To>) in | |
let fromBytePtr = UnsafePointer<Int8>(fromPtr) | |
let toBytePtr = UnsafeMutablePointer<Int8>(toPtr) | |
for i in 0..<size { | |
toBytePtr[i] = fromBytePtr[size - i - 1] | |
} | |
} | |
} | |
return to | |
} | |
// We need a concept of endianness in the type system. Fields can't contain | |
// any storage beyond their literal underlying storage type, so they can't | |
// encode their endianness as a property. By encoding endianness within the | |
// type system, we can define fields of different endiannesses at the type | |
// level without needing any additional storage in memory at runtime. | |
protocol Endianness { | |
class func read<From, To: Initable>(from: From) -> To | |
} | |
// Define big and little endian types, using either SwappedRead or unsafeBitCast. | |
// TODO: use conditional compilation to switch the implementations on big-endian | |
// systems. | |
class BigEndian: Endianness { | |
class func read<From, To: Initable>(from: From) -> To { | |
return SwappedRead(from) | |
} | |
} | |
class LittleEndian: Endianness { | |
class func read<From, To: Initable>(from: From) -> To { | |
return unsafeBitCast(from, To.self) | |
} | |
} | |
// An integer field that maps directly to underlying storage and reads/writes | |
// the values on demand. | |
struct IntField<T: Storeable, E: Endianness> { | |
var storage = T.Storage() | |
var value: T { | |
get { | |
return E.read(storage) | |
} | |
set { | |
storage = E.read(newValue) | |
} | |
} | |
} | |
// Take an arbitrary value and extract its contents as an array of Int8. | |
// The type should, obviously, be built to handle this propertly. | |
func ToByteArray<T>(var value: T) -> [Int8] { | |
return withUnsafePointer(&value) { | |
(ptr: UnsafePointer<T>) -> [Int8] in | |
let buffer = UnsafeBufferPointer<Int8>(start: UnsafePointer<Int8>(ptr), count: sizeof(T)) | |
return [Int8](buffer) | |
} | |
} | |
// Take an array of Int8 and splat those bytes into an arbitrary type. | |
// The array's length must equal the size of the type. Again, the type | |
// should be built to handle this. | |
func FromByteArray<T>(array: [Int8]) -> T { | |
precondition(array.count == sizeof(T)) | |
return array.withUnsafeBufferPointer { | |
return UnsafePointer<T>($0.baseAddress).memory | |
} | |
} | |
// An example struct with a bunch of integer fields. | |
struct TestStruct { | |
var tag = IntField<UInt8, BigEndian>() | |
var value = IntField<Int32, BigEndian>() | |
var value2 = IntField<Int32, LittleEndian>() | |
var value3 = IntField<UInt16, BigEndian>() | |
var value4 = IntField<UInt64, BigEndian>() | |
var value5 = IntField<UInt64, LittleEndian>() | |
} | |
var x = TestStruct() | |
x.tag.value = 42 | |
x.value.value = 1 | |
x.value2.value = 2 | |
x.value3.value = 3 | |
x.value4.value = 4 | |
x.value5.value = 5 | |
var data = ToByteArray(x) | |
println(data) | |
// [42, 0, 0, 0, 1, 2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 5, 0, 0, 0, 0, 0, 0, 0] | |
var y: TestStruct = FromByteArray(data) | |
println((y.tag.value, y.value.value, y.value2.value, y.value3.value, y.value4.value, y.value5.value)) | |
// (42, 1, 2, 3, 4, 5) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment