Skip to content

Commit 237baa8

Browse files
committed
Document BitstreamWriter
1 parent 32d87de commit 237baa8

File tree

2 files changed

+143
-13
lines changed

2 files changed

+143
-13
lines changed

Sources/TSCUtility/Bitstream.swift

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,13 @@ public protocol BitstreamVisitor {
7979
mutating func visit(record: BitcodeElement.Record) throws
8080
}
8181

82-
/// A top-level namespace for all bitstream
82+
/// A top-level namespace for all bitstream-related structures.
8383
public enum Bitstream {}
8484

8585
extension Bitstream {
86+
/// An `Abbreviation` represents the encoding definition for a user-defined
87+
/// record. An `Abbreviation` is the primary form of compression available in
88+
/// a bitstream file.
8689
public struct Abbreviation {
8790
public enum Operand {
8891
/// A literal value (emitted as a VBR8 field).
@@ -145,6 +148,22 @@ extension Bitstream {
145148
}
146149

147150
extension Bitstream {
151+
/// A `BlockID` is a fixed-width field that occurs at the start of all blocks.
152+
///
153+
/// Bistream reserves the first 7 block IDs for its own bookkeeping. User
154+
/// defined IDs are expected to start at
155+
/// `Bitstream.BlockID.firstApplicationID`.
156+
///
157+
/// When defining new block IDs, it may be helpful to refer to them by
158+
/// a user-defined literal. For example, the `dia` serialized diagnostics
159+
/// format used by Clang would define constants for block IDs as follows:
160+
///
161+
/// ```
162+
/// extension Bitstream.BlockID {
163+
/// static let metadata = Self.firstApplicationID
164+
/// static let diagnostics = Self.firstApplicationID + 1
165+
/// }
166+
/// ```
148167
public struct BlockID: RawRepresentable, Equatable, Hashable, Comparable, Identifiable {
149168
public var rawValue: UInt8
150169

@@ -170,16 +189,32 @@ extension Bitstream {
170189
}
171190

172191
extension Bitstream {
192+
/// An `AbbreviationID` is a fixed-width field that occurs at the start of
193+
/// abbreviated data records and inside block definitions.
194+
///
195+
/// Bitstream reserves 4 special abbreviation IDs for its own bookkeeping.
196+
/// User defined IDs are expected to start at
197+
/// `Bitstream.AbbreviationID.firstApplicationID`.
198+
///
199+
/// - Warning: Creating your own abbreviations by hand is not recommended as
200+
/// you could potentially corrupt or collide with another
201+
/// abbreviation defined by `BitstreamWriter`. Always use
202+
/// `BitstreamWriter.defineBlockInfoAbbreviation(_:_:)`
203+
/// to register abbreviations.
173204
public struct AbbreviationID: RawRepresentable, Equatable, Hashable, Comparable, Identifiable {
174205
public var rawValue: UInt64
175206

176207
public init(rawValue: UInt64) {
177208
self.rawValue = rawValue
178209
}
179210

211+
/// Marks the end of the current block.
180212
public static let endBlock = Self(rawValue: 0)
213+
/// Marks the beginning of a new block.
181214
public static let enterSubblock = Self(rawValue: 1)
215+
/// Marks the definition of a new abbreviation.
182216
public static let defineAbbreviation = Self(rawValue: 2)
217+
/// Marks the definition of a new unabbreviated record.
183218
public static let unabbreviatedRecord = Self(rawValue: 3)
184219
/// The first application-defined abbreviation ID.
185220
public static let firstApplicationID = Self(rawValue: 4)
@@ -191,9 +226,5 @@ extension Bitstream {
191226
public static func < (lhs: Self, rhs: Self) -> Bool {
192227
lhs.rawValue < rhs.rawValue
193228
}
194-
195-
public static func + (lhs: Self, rhs: UInt64) -> Self {
196-
return AbbreviationID(rawValue: lhs.rawValue + rhs)
197-
}
198229
}
199230
}

Sources/TSCUtility/BitstreamWriter.swift

Lines changed: 107 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,87 @@
1010

1111
import Foundation
1212

13+
/// A `BitstreamWriter` is an object that is capable of emitting data in the
14+
/// [LLVM Bitstream](https://llvm.org/docs/BitCodeFormat.html#bitstream-format)
15+
/// format.
16+
///
17+
/// Defining A Container Format
18+
/// ===========================
19+
///
20+
/// While `BitstreamWriter` provides APIs to write raw bytes into a bitstream
21+
/// file, it is recommended that the higher-level structured API be used
22+
/// instead. Begin by identifying the top-level blocks your container will need.
23+
/// Most container formats will need a metadata block followed by a series of
24+
/// user-defined blocks. These can be given in an extension of
25+
/// `Bitstream.BlockID` as they will be referred to often. For example:
26+
///
27+
/// ```
28+
/// extension Bitstream.BlockID {
29+
/// static let metadata = Self.firstApplicationID
30+
/// static let diagnostics = Self.firstApplicationID + 1
31+
/// }
32+
/// ```
33+
///
34+
/// Next, identify the kinds of records needed in the format and assign them
35+
/// unique, stable identifiers. For example:
36+
/// ```
37+
/// enum DiagnosticRecordID: UInt8 {
38+
/// case version = 1
39+
/// case diagnostic = 2
40+
/// case sourceRange = 3
41+
/// case diagnosticFlag = 4
42+
/// case category = 5
43+
/// case filename = 6
44+
/// case fixIt = 7
45+
/// }
46+
/// ```
47+
///
48+
/// Now, instantiate a `BitstreamWriter` and populate the leading "block info"
49+
/// block with records describing the data layout of sub-blocks and records. The following
50+
/// block info section describes the layout of the 'metadata' block which contains a single
51+
/// version record:
52+
///
53+
/// ```
54+
/// var versionAbbrev: Bitstream.AbbreviationID? = nil
55+
/// let recordWriter = BitstreamWriter()
56+
/// recordWriter.writeBlockInfoBlock {
57+
/// // Define the 'metadata' block and give it a name
58+
/// recordWriter.writeRecord(BitstreamWriter.BlockInfoCode.setBID) {
59+
/// $0.append(Bitstream.BlockID.metadata)
60+
/// }
61+
/// recordWriter.writeRecord(BitstreamWriter.BlockInfoCode.blockName) {
62+
/// $0.append("Meta")
63+
/// }
64+
///
65+
/// // Define the 'version' record and register its name
66+
/// recordWriter.writeRecord(BitstreamWriter.BlockInfoCode.setRecordName) {
67+
/// $0.append(DiagnosticRecordID.version)
68+
/// $0.append("Version")
69+
/// }
70+
///
71+
/// versionAbbrev = recordWriter.defineBlockInfoAbbreviation(.metadata, .init([
72+
/// .literalCode(RoundTripRecordID.version),
73+
/// .fixed(bitWidth: 32)
74+
/// ]))
75+
///
76+
/// // Emit a block ID for the 'diagnostics' block as above and define the
77+
/// // layout of its records similarly...
78+
/// }
79+
/// ```
80+
///
81+
/// Finally, write any blocks containing the actual data to be serialized.
82+
///
83+
/// ```
84+
/// recordWriter.writeBlock(.metadata, newAbbrevWidth: 3) {
85+
/// recordWriter.writeRecord(versionAbbrev!) {
86+
/// $0.append(RoundTripRecordID.version)
87+
/// $0.append(25 as UInt32)
88+
/// }
89+
/// }
90+
/// ```
91+
///
92+
/// The higher-level APIs will automatically ensure that `BitstreamWriter.data`
93+
/// is valid. Once serialization has completed, simply emit this data to a file.
1394
public final class BitstreamWriter {
1495
public enum BlockInfoCode: UInt8 {
1596
case setBID = 1
@@ -463,14 +544,12 @@ extension BitstreamWriter {
463544
// MARK: Block Management
464545

465546
extension BitstreamWriter {
466-
public func `switch`(to blockID: Bitstream.BlockID) {
467-
if currentBlockID == blockID { return }
468-
writeRecord(BlockInfoCode.setBID) {
469-
$0.append(blockID)
470-
}
471-
currentBlockID = blockID
472-
}
473-
547+
/// Defines a scope under which a new block's contents can be defined.
548+
///
549+
/// - Parameters:
550+
/// - blockID: The ID of the block to emit.
551+
/// - abbreviationBitWidth: The width of the largest abbreviation ID in this block.
552+
/// - defineSubBlock: A closure that is called to define the contents of the new block.
474553
public func withSubBlock(
475554
_ blockID: Bitstream.BlockID,
476555
abbreviationBitWidth: UInt8? = nil,
@@ -481,6 +560,17 @@ extension BitstreamWriter {
481560
self.endBlock()
482561
}
483562

563+
/// Marks the start of a new block record and switches to it.
564+
///
565+
/// - Note: You must call `BitstreamWriter.endBlock()` once you are finished
566+
/// encoding data into the newly-created block, else the resulting
567+
/// bitstream file will become corrupted. It is recommended that
568+
/// you use `BitstreamWriter.withSubBlock(_:abbreviationBitWidth:defineSubBlock:)`
569+
/// instead.
570+
///
571+
/// - Parameters:
572+
/// - blockID: The ID of the block to emit.
573+
/// - abbreviationBitWidth: The width of the largest abbreviation ID in this block.
484574
public func enterSubblock(
485575
_ blockID: Bitstream.BlockID,
486576
abbreviationBitWidth: UInt8? = nil
@@ -513,6 +603,7 @@ extension BitstreamWriter {
513603
}
514604
}
515605

606+
/// Marks the end of a new block record.
516607
public func endBlock() {
517608
guard let block = blockScope.popLast() else {
518609
fatalError("endBlock() called with no block registered")
@@ -563,4 +654,12 @@ extension BitstreamWriter {
563654
blockInfoRecords[id] = info
564655
return info
565656
}
657+
658+
private func `switch`(to blockID: Bitstream.BlockID) {
659+
if currentBlockID == blockID { return }
660+
writeRecord(BlockInfoCode.setBID) {
661+
$0.append(blockID)
662+
}
663+
currentBlockID = blockID
664+
}
566665
}

0 commit comments

Comments
 (0)