Give a bit order a type, make the SMT interface usable, and let the call policy reach a property - #642
Open
julianspeith wants to merge 4 commits into
Open
Give a bit order a type, make the SMT interface usable, and let the call policy reach a property#642julianspeith wants to merge 4 commits into
julianspeith wants to merge 4 commits into
Conversation
The plugin had no tests. Its interface is about to be rewritten around two
types instead of a map of pairs to maps, which touches every entry point
and the 1677 lines behind them, and there was nothing that would notice if
the behaviour changed on the way.
These describe what it does today rather than what it ought to do: an
order propagates from a pin group to the one it drives, an order that is
already known is reported back, reordering renames the pins to carry the
indices, and the export writes the problem out as json and says which word
each pin group became.
Three of them assert something other than what was expected, which is the
point of writing them first:
An order with a hole in it is accepted when continuous orders are not
enforced, but what comes out is continuous regardless: {0,1,2,4} in yields
{0,1,2,3} out. The flag decides what may be given, not what is produced.
With continuous orders enforced the destination is absent from the result
instead, and no error is reported.
A module pin group created with the C++ defaults is indexed 0, -1, -2, -3,
because the default is to descend from the start index, and move_pin then
refuses every positive index, so reordering such a group fails. The Python
binding defaults differ and produce 3, 2, 1, 0 for the same call.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four things, all in the SMT layer. SymbolicExecution::evaluate raised a TypeError on every call from Python. Both overloads were bound directly, so they handed back a Result that is not a registered type, where every other binding in that file unwraps it. They now return the Boolean function, or None, and true or false for the constraint overload, as Model::evaluate twenty lines above already did. SymbolicState::set used emplace, which leaves an existing binding alone, so setting a variable a second time did nothing at all: a loop stepping a state forward silently kept the value it started with. It now assigns. The two std::move calls on const references it also carried did nothing and are gone. SolverCall was never bound, so QueryConfig::with_call and Solver::has_local_solver_for could not be called from Python although both were bound, and SolverType was missing Bitwuzla. Solver::to_smt2 was not exposed at all, which is the way to hand a query to something outside HAL. Solver::has_local_solver_for tested SolverCall::Binary in both of its branches, so the one for SolverCall::Library was unreachable and library availability always reported false. The branches were written as a switch over the result of a find, with case true and default, which is what hid a duplicated condition in plain sight; they are plain conditions now. The last of these cannot be observed in a build that links no solver library, as this one does not: the corrected branch returns false there just as falling through did. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fifty-five properties were given the borrowed() call policy and none of them had it. def_property_readonly takes the getter, builds the cpp_function from it there and then, and only afterwards forwards the attributes that were passed alongside, so an attribute meant for the call never reaches the function that makes it. It compiles and reads as though it works. The effect is that reading gate.module, gate.fan_out_nets or netlist.gates handed over an object without keeping its netlist alive, while calling gate.get_module() did. Which is the defect the policy was introduced to fix, still present wherever a binding is a property rather than a method. Build the getter as a cpp_function that carries the policy, and pass that instead. Measured on a netlist of 3458 gates, reading gate.module now adds a reference to the netlist where it added none, and netlist.gates adds one per gate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The interface spoke in std::map<std::pair<Module*, PinGroup<ModulePin>*>, std::map<Net*, u32>>, which says four things at once and none of them by name, and appeared twenty-one times across the plugin. It is now a BitOrder, which is one pin group and the index of each of its nets, and a BitOrderResult, which is what a propagation reports: the orders it was given as well as the ones it worked out. The index is kept with each net rather than implied by position, so that an order with gaps in it can be expressed. Propagation produces a continuous order today, but that is a property of the algorithm and not of the interface, and it may well come to allow gaps. Two things follow from a result being an object rather than a container. Python can be handed one and keep the netlist it refers to alive, which a list cannot do. And a result holds its bit orders by module and pin group ID, so walking one no longer depends on where the modules and pin groups happen to have been allocated. The algorithm is untouched: the new types are translated to the map at the entry points and back at the exits, rather than the sixteen hundred lines behind them being rewritten. The tests written beforehand pass unchanged, which is what says the behaviour is the same. export_bitorder_propagation_information keeps returning one number per pin group, but that number is the index of the word it became in the exported file and not the index of a bit, so it is a WordIndex now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or 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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Four commits, in the order they were made, because each one made the next possible.
The plugin had no tests
bc395af447dadds five that describe what the bit-order propagation does today, before anything changed, so that an interface rewrite touching every entry point and the 1677 lines behind them could be shown not to alter the behaviour.Three of the five assert something other than what was expected, which is the reason for writing them first:
enforce_continuous_bitorders=falsedoes not preserve gaps. Given{0,1,2,4}the destination comes out{0,1,2,3}. The flag decides what may be given, not what is produced.enforce_continuous_bitorders=trueand the same input, the destination is simply absent from the result, and no error is reported.0, -1, -2, -3, because the default is to descend from the start index, andmove_pinthen refuses every positive index, so reordering such a group fails. The Python binding defaults differ and produce3, 2, 1, 0for the same call.The SMT interface was unusable from Python
52e2d32c592, four defects that belong together:SymbolicExecution::evaluateraised aTypeErroron every call: both overloads were bound directly, so they returned aResult, which is not a registered type. Every other binding in that file unwraps it.SymbolicState::setusedemplace, which leaves an existing binding alone, so setting a variable a second time did nothing and a loop stepping a state forward silently kept the value it started with.SolverCallwas never bound, soQueryConfig::with_callandSolver::has_local_solver_forcould not be called although both were bound;SolverTypewas missingBitwuzla;Solver::to_smt2was not exposed at all.Solver::has_local_solver_fortestedSolverCall::Binaryin both of its branches, so the one forLibrarywas unreachable and library availability always reportedfalse. Note this last one cannot be observed in a build that links no solver library, as CI does not — the corrected branch returnsfalsethere just as falling through did.The call policy was inert on every property
9c98546fc20. Fifty-five properties were given thehal::borrowed()policy introduced in #639 and none of them had it:def_property_readonlybuilds thecpp_functionfrom the getter there and then, and only afterwards forwards the attributes passed alongside, so an attribute meant for the call never reaches the function that makes it. It compiles and reads as though it works.The effect was that reading
gate.module,gate.fan_out_netsornetlist.gateshanded over an object without keeping its netlist alive, while callinggate.get_module()did — the very defect the policy was introduced to fix, still present wherever a binding is a property rather than a method. Measured on a netlist of 3458 gates:gate.modulegate.fan_out_netsnetlist.gatesgate.get_module()A bit order now has a type
28865d54979. The interface spoke instd::map<std::pair<Module*, PinGroup<ModulePin>*>, std::map<Net*, u32>>, which says four things at once and none of them by name, and appeared twenty-one times across the plugin.It is now a
BitOrder— one pin group and the index of each of its nets — and aBitOrderResult, which is what a propagation reports. The index is kept with each net rather than implied by position, so an order with gaps can be expressed: propagation produces a continuous order today, but that is a property of the algorithm rather than of the interface.Two things follow from a result being an object rather than a container. Python can be handed one and keep the netlist it refers to alive, which a list cannot do. And a result holds its bit orders by module and pin group ID, so walking one no longer depends on where the modules and pin groups were allocated.
The algorithm is untouched. The new types are translated to the map at the entry points and back at the exits rather than the sixteen hundred lines behind them being rewritten, and the tests written beforehand pass unchanged, which is what says the behaviour is the same.
export_bitorder_propagation_informationkeeps returning one number per pin group, but that number is the index of the word it became in the exported file and not the index of a bit, so it is aWordIndexnow.Verified
ctest38/38, the ten-hazard lifetime suite holds, and the 500-gate/500-net use-after-free test reports 0 wrong names and 0 wrong IDs. The wiki page for the plugin is updated in the same pass, including two descriptions that were already wrong:enforce_continuous_bitorderswas documented backwards, andexport_bitorder_propagation_informationwas described as a debugging dump rather than as an export for an external solver.