1010
1111import 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.
1394public final class BitstreamWriter {
1495 public enum BlockInfoCode : UInt8 {
1596 case setBID = 1
@@ -463,14 +544,12 @@ extension BitstreamWriter {
463544// MARK: Block Management
464545
465546extension 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