/// <summary> /// Initializes a new instance of the <see cref="TristateBitArray"/> class. /// </summary> /// <param name="copy">The bit array to copy. This is not modified.</param> public TristateBitArray(TristateBitArray copy) { numBits = copy.numBits; numUlongs = copy.numUlongs; mask = (ulong[])copy.mask.Clone(); value = (ulong[])copy.value.Clone(); }
/// <summary> /// Create a new array, with a consecutive series of bits (up to 64) with defined values. /// All other bits are left at their default undefined state. /// </summary> /// <param name="numBits">Number of bits in the resulting array.</param> /// <param name="start">Zero-based index of least significant bit in bitfield.</param> /// <param name="end">Index of most significant bit in bitfield (inclusive).</param> /// <param name="value"> /// Contains values of bits to be inserted. If the bitfield represents fewer than 64 bits, /// these bits are found in the least significant bits of 'value'. /// </param> /// <returns></returns> public static TristateBitArray LoadBitfieldValue(int numBits, int start, int end, ulong value) { Debug.Assert(numBits > 0 && start >= 0 && end < numBits && end >= start && (1 + end - start) <= 64); var tmp = new TristateBitArray(numBits); tmp.MergeBitfieldValue(start, end, value); return(tmp); }
/// <summary> /// Find the best set of bitfields. This method uses a recursive algorithm to build /// the set. /// </summary> private BitfieldSet?FindBestBitfieldSet(int min, int max, int ideal, int fields) { if (fields > 0) { BitfieldSet?best = null; // calculate maximum number of bits in THIS bitfield (which must leave at // least one bit for each of the remaining fields). int maxBits = max - (fields - 1); // iterate through number of possible bits in THIS bitfield for (int bits = 1; bits <= maxBits; bits++) { // recursively find the solution to the problem, excluding this bitfield BitfieldSet?bfSet = FindBestBitfieldSet(1, max - bits, ideal, fields - 1); if (bfSet == null) { return(null); } // generate an exclusion mask for the bits in bfSet (since the bitfields in // the set must not overlap) TristateBitArray exclusion = new TristateBitArray( ruleSet.Condition.DecodeBits).Union(bfSet.GetBitsForValue(0)); // find the best bitfield to add to the sub-solution Bitfield?bf = FindBestBitfield(bits, bits, bits, exclusion); if (bf != null) { // add the bitfield to the result bfSet.Add(bf); // track best solution if (best == null || bfSet.Quality > best.Quality) { best = bfSet; } } } // return the best solution return(best); } else { // return an empty set return(new BitfieldSet(Spec, ideal)); } }
/// <summary> /// Tests whether there are any bits that are defined in both arrays. Or, whether the /// intersection between both arrays' masks is non-zero. /// </summary> /// <param name="rhs">Compatible array to compare with. Must have the same number of bits.</param> /// <returns></returns> public bool MaskIntersectsWith(TristateBitArray rhs) { Debug.Assert(numBits == rhs.numBits); // iterate through ulongs, checking masks for intersections for (int i = 0; i < numUlongs; i++) { if ((mask[i] & rhs.mask[i]) != 0) { return(true); } } return(false); }
/// <summary> /// The sole publicly-visible entry point. Generates a tree, given a Specification. /// </summary> /// <param name="spec">The decoder specification.</param> /// <returns>The root of the decoder tree.</returns> public static Node BuildTree(Specification spec, TristateBitArray?flags) { if (flags == null) { flags = new TristateBitArray(spec.NumFlags); } // initialise a TreeBuilder with an empty condition and a RuleSet // including all rules. var initialCondition = new Condition(spec, new TristateBitArray(spec.NumBits), flags); RuleSet ruleSet = new RuleSet(spec).Derive(initialCondition); var builder = new TreeBuilder(spec, ruleSet); return(builder.Build()); }
/// <summary> /// Indicates whether two arrays are exactly equal in value. /// </summary> /// <param name="rhs"> /// The array to compare with. Both arrays must have the same number of bits. /// </param> /// <returns>True if arrays are equal.</returns> public bool IsEqual(TristateBitArray rhs) { // make sure we're comparing like with like Debug.Assert(numBits == rhs.numBits); // iterate through ulongs, checking mask & value for equality for (int i = 0; i < numUlongs; i++) { if (mask[i] != rhs.mask[i] || value[i] != rhs.value[i]) { return(false); } } return(true); }
/// <summary> /// Compute the intersection of two arrays. The intersection of two bits is undefined /// if either or both bits are undefined, or the bit's value if both are defined. /// </summary> /// <param name="rhs">Compatible array to compare with.</param> /// <returns>The resulting bit array.</returns> public TristateBitArray Intersection(TristateBitArray rhs) { Debug.Assert(numBits == rhs.numBits); var tmp = new TristateBitArray(this); for (int i = 0; i < numUlongs; i++) { ulong maskIntersection = tmp.mask[i] & rhs.mask[i]; tmp.mask[i] = maskIntersection; tmp.value[i] &= maskIntersection; Debug.Assert(tmp.value[i] == (rhs.value[i] & maskIntersection)); } return(tmp); }
/// <summary> /// Indicate whether two arrays are 'compatible' with each other. Two arrays are compatible /// if the bits with definite values in both arrays have the same values. Put another way, /// they are compatible if they are equal in the intersection of their masks. /// </summary> /// <param name="rhs">The array to compare with. Must have the same number of bits.</param> /// <returns>True if the arrays are compatible.</returns> public bool IsCompatible(TristateBitArray rhs) { Debug.Assert(numBits == rhs.numBits); // iterate through ulongs, checking values for equality within the // intersection of the masks for (int i = 0; i < numUlongs; i++) { ulong maskIntersection = mask[i] & rhs.mask[i]; if ((value[i] & maskIntersection) != (rhs.value[i] & maskIntersection)) { return(false); } } return(true); }
/// <summary> /// Parse a pattern rule. This consists of: /// a bit pattern (mandatory) /// a weight (optional) /// a flags specification (optional) /// one or more code fragments. /// </summary> private void ParseRule() { int lineNum = lexer.LineNum; Next(); if (spec.NumBits == 0) { SyntaxError("Missing 'bits' directive"); } // parse the pattern TristateBitArray bits = ParsePattern(); var flags = new TristateBitArray(spec.NumFlags); var fragment = new CodeFragment(spec); ulong weight = 1; // parse optional weight if (CrntCode == Token.Type.Dollar) { NextExpect(Token.Type.Float); weight = lexer.Crnt.Ulong; Next(); } // parse optional flags (between '[' and ']') if (CrntCode == Token.Type.BeginFlags) { Next(); flags = ParseFlags(); Expect(Token.Type.EndFlags); Next(); } // parse 1+ code fragments Expect(Token.Type.CodeFragment); ParseFragments(fragment); // generate rule and add it to the Specification var condition = new Condition(spec, bits, flags); var rule = new Rule(spec, condition, fragment, (int)weight, lineNum); spec.AddRule(rule); }
/// <summary> /// Compute the union of two arrays. The union of two bits is undefined if both bits are /// undefined, or the value of the bit if either is defined. Since the arrays must be /// compatible, a bit defined in both must have the same value. /// </summary> /// <param name="rhs">Compatible array to compare with.</param> /// <returns>The resulting bit array.</returns> public TristateBitArray Union(TristateBitArray rhs) { Debug.Assert(numBits == rhs.numBits); // iterate through ulongs, computing union var tmp = new TristateBitArray(this); for (int i = 0; i < numUlongs; i++) { ulong maskIntersection = tmp.mask[i] & rhs.mask[i]; Debug.Assert((tmp.value[i] & maskIntersection) == (rhs.value[i] & maskIntersection)); tmp.mask[i] |= rhs.mask[i]; tmp.value[i] |= rhs.value[i]; } return(tmp); }
/// <summary> /// Compute the difference between two arrays. The difference is undefined if the right-hand /// side is defined, or the bit's value otherwise. /// </summary> /// <param name="rhs">Compatible array to compare with.</param> /// <returns>The resulting bit array.</returns> public TristateBitArray Subtract(TristateBitArray rhs) { Debug.Assert(numBits == rhs.numBits); // iterate through ulongs, computing the difference var tmp = new TristateBitArray(this); for (int i = 0; i < numUlongs; i++) { Debug.Assert((tmp.value[i] & rhs.mask[i]) == rhs.value[i]); ulong maskRhsInverse = ~rhs.mask[i]; tmp.mask[i] &= maskRhsInverse; tmp.value[i] &= maskRhsInverse; } return(tmp); }
/// <summary> /// Parse the 'bits' part of a pattern specification. This consists of a sequence of '0', /// '1', or '.' bits, and is terminated by an EndPattern token (which doesn't directly map /// to an input character, but is generated by the lexer when it finds a non-bit character. /// </summary> /// <returns>Equivalent bit array.</returns> private TristateBitArray ParsePattern() { int i = spec.NumBits - 1; var value = new TristateBitArray(spec.NumBits); // loop over input tokens until we reach the terminator while (CrntCode != Token.Type.EndPattern) { // map the input token to a bit value TristateBitArray.BitValue bitValue = CrntCode switch { Token.Type.Zero => TristateBitArray.BitValue.Zero, Token.Type.One => TristateBitArray.BitValue.One, Token.Type.Dot => TristateBitArray.BitValue.Undefined, _ => throw new SyntaxErrorException(SyntaxErrorMessage(UnexpectedErrorMessage())) }; // abort if we've received too many bits if (i < 0) { break; } // set the bit value.SetBit(i, bitValue); i--; Next(); } // verify that we parsed the correct number of bits if (i != -1) { SyntaxError("Incorrect bit count in pattern"); } // skip 'endpattern' token Next(); return(value); }
/// <summary> /// Parse a flag specification: a comma-separated list of flag identifiers, each /// preceded by an optional '!' token. A flag specification is allowed to be /// empty. /// </summary> /// <returns>Equivalent bit pattern for flags.</returns> private TristateBitArray ParseFlags() { var bits = new TristateBitArray(spec.NumFlags); while (CrntCode == Token.Type.Not || CrntCode == Token.Type.Identifier) { // parse optional '!' bool invert = false; if (CrntCode == Token.Type.Not) { invert = true; Next(); } // now parse identifier Expect(Token.Type.Identifier); Flag?flag = spec.GetFlag(lexer.Crnt.String); if (flag == null) { SyntaxError($"Undeclared flag {lexer.Crnt.String}"); } else { bits.SetBit(flag.Index, invert ? TristateBitArray.BitValue.Zero : TristateBitArray.BitValue.One); } // is the following token a comma? if so, we can loop around to the // next flag Next(); if (CrntCode != Token.Type.Comma) { break; } // skip over comma Next(); } return(bits); }
/// <summary> /// Find the best single bitfield suitable for use in a switch statement. /// /// This algorithm is not perfect: it can sometimes return a sub-optimal bitfield. It uses a /// heuristic approach, under the assumption that the best bitfield is the same as the /// bitfield with the highest total bit quality (and nearest the desired bit length). This /// assumption tends to favour longer bitfields. In some cases, however, a shorter bitfield /// may have an equal ability to discriminate between rules. /// </summary> /// <param name="min">Fewest number of acceptable bits.</param> /// <param name="max">Largest number of acceptable bits.</param> /// <param name="ideal">Preferred number of acceptable bits.</param> /// <param name="exclusion">A mask indicating bits that should be excluded (optional).</param> /// <returns>The best bitfield meeting the criteria, or null in case of failure.</returns> private Bitfield?FindBestBitfield(int min, int max, int ideal, TristateBitArray?exclusion = null) { if (exclusion == null) { exclusion = ruleSet.Condition.DecodeBits; } Bitfield?best = null; // iterate through candidate start bits for (int start = MinSignificantBit; start <= MaxSignificantBit; start++) { // iterate through candidate end bits for (int end = start + min - 1; end <= start + max - 1; end++) { // discard any bitfield that provably contains zero-quality bits if (end <= MaxSignificantBit) { // viable bitfields cannot intersect with the exclusion mask var bits = TristateBitArray.LoadBitfieldValue(Spec.NumBits, start, end, 0); if (!exclusion.MaskIntersectsWith(bits)) { // calculate total bit quality over the range if (CalculateTotalQuality(start, end, out float totalQuality)) { var tmp = new Bitfield(Spec, start, end, ideal, totalQuality); // track highest overall quality if (best == null || tmp.Quality > best.Quality) { best = tmp; } } } } } } return(best); }
/// <summary> /// Try to improve efficiency by 'lifting' a test higher up the decoder tree. This is /// only done if the test is identical for every member of a rule set. /// </summary> private Node?BuildLift(string name, Func <Condition, TristateBitArray> extract, Func <TristateBitArray, Condition> init) { if (ruleSet.NumRules < 2) { return(null); } // extract component from 1st rule & verify that flags are specified TristateBitArray component = extract(ruleSet[0].EffectiveCondition); if (component.IsEmpty) { return(null); } // check that all other rules have the same component spec for (int i = 1; i < ruleSet.NumRules; i++) { if (!extract(ruleSet[i].EffectiveCondition).IsEqual(component)) { return(null); } } if (spec.Config.Verbose) { Console.WriteLine($"Performing {name} lifting optimisation."); } // build subtree without flags Condition cond = init(component); RuleSet rSet = ruleSet.Derive(cond); var builder = new TreeBuilder(spec, rSet, this); Node node = builder.Build(); // and attach it to an if-node return(new IfElseNode(spec, cond, node, new EmptyNode(spec))); }
/// <summary> /// Initializes a new instance of the <see cref="Condition"/> class. /// </summary> /// <param name="spec">Associated specification.</param> /// <param name="bitwise">Decode bit pattern.</param> /// <param name="flags">Flags pattern.</param> public Condition(Specification spec, TristateBitArray bitwise, TristateBitArray flags) : base(spec) { decodeBits = new TristateBitArray(bitwise); this.flags = new TristateBitArray(flags); }
/// <summary> /// Generate cache of quality ratings for each bit. Called by constructor. /// </summary> private void CalculateBitQuality() { // initialise temporary arrays int[] total = new int[Spec.NumBits]; int[] totalOne = new int[Spec.NumBits]; float[] score = new float[Spec.NumBits]; // iterate through each Rule in the RuleSet foreach (RuleSetEntry entry in ruleSet) { for (int i = 0; i < Spec.NumBits; i++) { // is this bit significant to the rule after taking the ruleset's // condition into account? if (entry.EffectiveCondition.DecodeBits.GetMaskBit(i)) { // accumulate totals total[i]++; // calculate total score, taking into account the Rule's weight TristateBitArray tmp = entry.EffectiveCondition.Flags; if (!tmp.IsEmpty) { score[i] += entry.Rule.Weight * Spec.Config.BitFlagCoef; } else { score[i] += entry.Rule.Weight; } // tally up bits which must equal one if (entry.EffectiveCondition.DecodeBits.GetValueBit(i)) { totalOne[i]++; } } } } // calculate total of all individual bit scores float totalScore = 0F; for (int i = 0; i < Spec.NumBits; i++) { totalScore += score[i]; } for (int i = 0; i < Spec.NumBits; i++) { // calculate smaller of total ones or zeroes int totalZero = total[i] - totalOne[i]; int totalSmaller = (totalZero < totalOne[i]) ? totalZero : totalOne[i]; // quantify, on 0-1 scale, how well balanced the values are. // that is, how evenly distributed are ones and zeroes? a bit that is set for // every rule is utterly useless for a switch, since all rules would fall into // a single case. float balance = 2F * (totalSmaller / (float)total[i]); // calculate overall bit quality float bitQ; if (score[i] != 0F) { bitQ = balance * score[i] / totalScore; } else { bitQ = 0F; // avoid NaNs in calculation } // store calculated quality in cache bitQuality[i] = bitQ; // accumulate totals totalBitQuality += bitQ; if (bitQ != 0F) { // track least significant bit if (i < minSignificantBit) { minSignificantBit = i; } // track most significant bit if (i > maxSignificantBit) { maxSignificantBit = i; } } } }