-
Notifications
You must be signed in to change notification settings - Fork 14
feat: Dataflow analysis framework #1476
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
218 commits
Select commit
Hold shift + click to select a range
c7a9d89
Just const_fold2 + inside that partial_value (taken from hugr_core)
acl-cqc ac45e53
merge/update+fmt (ValueName for ConstInt non-compiling as ConstInt no…
acl-cqc 8adaa6e
Missing imports / lints. Now running, but failing w/StackOverflow
acl-cqc 098c735
Fix tests...
acl-cqc 706c892
ValueKey using MaybeHash
acl-cqc 63bc944
tag() does not refer to self.is_compound
acl-cqc 5fa7edb
ValueHandle::{is_compound,num_fields,index} => {variant_values, as_sum}
acl-cqc 98bf94a
Rm ValueHandle::tag, use variant_values - inefficient, presume this i…
acl-cqc 295ec32
add variant_values, rewrite one use of outputs_for_variant
acl-cqc 5c8289e
...and the other two; remove outputs_for_variant
acl-cqc bf173ab
Rewrite tuple rule to avoid indexing
acl-cqc 0ae4d19
GC unused (tuple,variant)_field_value, iter_with_ports
acl-cqc 8608ba9
Common up via ValueRow.unpack_first
acl-cqc 2dca3e9
No DeRef for ValueHandle, just add get_type()
acl-cqc 51e68ea
ValueKey::{Select->Field,index->field}
acl-cqc 8635474
(join/meet)_mut_unsafe => try_(join/meet)_mut with Err for conflictin…
acl-cqc 80d5b86
Remove ValueHandle::variant_values - just have as_sum
acl-cqc 1c8be99
Optimize as_sum() by returning impl Iterator not Vec
acl-cqc b0afa54
Machine uses PV not PartialValue
acl-cqc d09a1fe
Parametrize PartialValue+PV+Machine by AbstractValue/Into<Value>, Con…
acl-cqc af8827b
Move partial_value.rs inside datalog/
acl-cqc 4b61436
Hide PartialSum/PartialValue
acl-cqc 8b31d8c
refactor: ValueRow::single_among_bottoms
acl-cqc 6a72961
Factor out propagate_leaf_op; add ValueRow::from_iter
acl-cqc 780af9b
Add handling for Tag
acl-cqc cabcf04
Remove PV (use typedef in datalog.rs)
acl-cqc 5e4a04f
Allow DFContext to interpret any leaf op (except MakeTuple/etc.); pub…
acl-cqc 1453886
Also fold extension ops
acl-cqc 221e96c
Comment as_sum
acl-cqc cd4e15c
Rename DataflowContext to HugrValueContext
acl-cqc c5ab2a9
Move {datalog=>value_handle}/context.rs - an impl, datalog uses only …
acl-cqc 6c80acf
Comment re. propagate_leaf_op
acl-cqc 636f14d
Hide PartialValue by abstracting DFContext::InterpretableVal: FromSum…
acl-cqc 0acdcc5
Use Value::sum, adding FromSum::Err; fmt
acl-cqc 1012e0a
Cargo.toml: use explicit git= tag for ascent
acl-cqc 6bd8dba
Fix rebase: TryHash, UnpackTuple/MakeTuple now in prelude
acl-cqc f7d288f
pub ValueRow+Partial(Value/Sum); add TotalContext
acl-cqc a62eb0f
Remove DFContext::hugr(), as_ref() does just as well
acl-cqc f0ec237
Move partial_value out of datalog, combine tests; move ValueRow out o…
acl-cqc 82a3f22
Move FromSum and try_into_value into total_context.rs
acl-cqc e6dc114
Separate mod dataflow from mod const_fold2
acl-cqc 03ff165
fmt
acl-cqc 25ed1fb
TailLoopTermination just examine whatever PartialValue's we have, rem…
acl-cqc 09911df
Drop non-(Bounded)Lattice impls of (join/meet)(_mut),top,bottom
acl-cqc 248fb07
doc fixes, remove missing-docs for partial_value...how does it still …
acl-cqc e2ad079
fix all warnings (inc Machine::new() -> impl Default)
acl-cqc 95e2dd9
distribute utils.rs -> machine.rs
acl-cqc 7f1e122
Move dataflow{/datalog=>}/test.rs
acl-cqc faff556
and more warnings
acl-cqc 401354d
fix extension tests
acl-cqc c468387
Fix doclink, fix DefaultHasher pre-1.76
acl-cqc 88db5b1
comment conditional test
acl-cqc 738b61b
Clarify (TODO untested) branches of join_mut
acl-cqc 8f9c1ed
Exploit invariant PartialValue::Value is not a sum (even single known…
acl-cqc a71ba97
Exploit invariant more, RIP join_mut_value_handle
acl-cqc 05280a8
By similar logic, RIP meet_mut_value_handle; assert_(in)variants now …
acl-cqc 96b0856
Rename TestSum(,Leaf)Type::assert_{invariants=>valid}
acl-cqc f21e278
Rename assert_(=>in)variants (i.e. to match); call in (join/meet)_mut…
acl-cqc ce53b1c
test unpacking constant tuple
acl-cqc 13f29a9
try_into_value returns new enum ValueOrSum; TryFrom<ValueOrSum> repla…
acl-cqc 9f1a5cd
Hide ValueRow (and move into datalog.rs)
acl-cqc 15e642e
Remove PartialSum::unit()
acl-cqc 514af13
PartialValue is private struct containing PVEnum (with ::Sum not ::Pa…
acl-cqc 5d86f46
variant => new_variant, unit => new_unit
acl-cqc d8c8140
Simplify PartialOrd for PartialSum, keys(1,2) support cmp
acl-cqc 1315685
PartialSum::variant_values does not take `len` (PartialValue:: still …
acl-cqc 2aaaeb9
clippy
acl-cqc 5619761
Machine::tail_loop_terminates + case_reachable return Option not panic
acl-cqc f3c175c
Machine::read_out_wire_value fails with ConstTypeError if there was one
acl-cqc aad2ef0
PartialValue::try_(join|meet)_mut are pub, don't mutate upon failure
acl-cqc 0a8cc12
Remove some commented-out code
acl-cqc bfcd0a6
Expose PartialSum
acl-cqc 248fb23
dataflow has docs! (enforced)
acl-cqc 5b8654e
clippy
acl-cqc c40e718
Move PartialValue::join into impl Lattice for
acl-cqc 8732a63
Machine::read_out_wire_value => PartialValue::try_into_wire_value
acl-cqc 346187d
read_out_wire_partial_value => read_out_wire
acl-cqc a139f9e
Remove ValueOrSum (and add Sum) via complex parametrization of try_in…
acl-cqc 7f2a91a
Datalog works on any AbstractValue; impl'd by PartialValue for a Base…
acl-cqc 1680829
PartialValue proptests: rm TestSumLeafType, replace ValueHandle with …
acl-cqc 2a57a15
tests: Rename type_check -> check_value
acl-cqc fcfcb6b
tidies
acl-cqc dcaa928
Add a couple more proptests, and a TEMPORARY FIX for a BUG pending be…
acl-cqc bcacbcc
Remove redundant test
acl-cqc 5192ed8
Refactor TailLoopTermination::from_control_value
acl-cqc e21bbd7
pub TailLoopTermination, rename members, doc
acl-cqc 22e0192
Test tidies (and some ALAN wtf? comments)
acl-cqc cae5e4f
Use Tag
acl-cqc 3014827
Add TestContext (no interpret_leaf_op), propolutate, avoid HugrValueC…
acl-cqc 64b9bb7
Avoid propolutate by interpreting LoadConstant (only)
acl-cqc 8bc5e12
Revert "Avoid propolutate by interpreting LoadConstant (only)"
acl-cqc a3a6213
tiny const_fold2 doc tweaks
acl-cqc a96ab20
(TEMP) remove const_fold2 module
acl-cqc 3051183
Merge remote-tracking branch 'origin/main' into acl/const_fold2
acl-cqc 777694c
(TEMP) Rm total_context
acl-cqc 5a16e6b
clippy
acl-cqc 4f31178
Better fix for PartialSum::try_meet_mut
acl-cqc 2b523c9
true_or_false uses pv_true+pv_false
acl-cqc 1d2cb9b
Update to ascent 0.7.0, drop fn join/meet as these are now trait-default
acl-cqc ee91bbe
Cargo.toml: oops, remove obsolete comment
acl-cqc e67051f
ValueRow cleanups (remove misleading 'pub's)
acl-cqc 94cee55
Refactor: rm tail_node, clone earlier in ValueRow::unpack_first, rm V…
acl-cqc 5cf5ff0
Add datalog for CFG
acl-cqc 7381087
refactor: follow unpack_first with enumerate
acl-cqc 60e33db
Remove comments from test_tail_loop_(iterates_twice->two_iters)
acl-cqc ef4f433
Add a test of tail loop around conditional
acl-cqc 5935489
improve that test - loop input is a sum and the variants have differe…
acl-cqc b198681
clippy/nth
acl-cqc ed30f80
revert accidental changes to hugr-core/src/types.rs (how?!)
acl-cqc 3f7808a
Cleanup conditional, cfg, unpack_first
acl-cqc 436b635
Complex CFG that does a not-XOR...but analysis generally says "true o…
acl-cqc 0374d13
Propagate case results to conditional output only if case reached; so…
acl-cqc 6a2dd9e
More test cases
acl-cqc a75fee9
refactor as fixture
acl-cqc 151e571
clippy
acl-cqc e15b04d
Revert "Datalog works on any AbstractValue; impl'd by PartialValue fo…
acl-cqc dc08f0d
(Re-)remove PVEnum
acl-cqc 436dcd2
Remove as_sum. AbstractValues are elements not sums
acl-cqc e817bbe
clippy
acl-cqc b06cfad
Refactor: remove 'fn input_count'
acl-cqc fd717be
Try to fix interpret_leaf_op: cannot use Bottom for output! But ascen…
acl-cqc 9b17439
interpret_leaf_op for ExtensionOps only; LoadConstant via value_from_…
acl-cqc 846d1ee
Correct comment BaseValue -> AbstractValue
acl-cqc 328e7f8
test Hugr now returns (XOR, AND) of two inputs, one case wrongly prod…
acl-cqc da3c05c
BB reachability, fixes!
acl-cqc 6930ad4
Test cases with true_or_false/top, standardize naming (->test_)condit…
acl-cqc 0a3e281
Try to common up by using case_reachable in conditional outputs - 6 t…
acl-cqc b1e0bfd
Make case_reachable a relation (dropping bool), not lattice - fixes t…
acl-cqc 355e814
Call (+test)
acl-cqc 22f3ce8
propolutate_out_wires => prepopulate and set in wires in run
acl-cqc 68b1d48
Rm/inline value_inputs/value_outputs, use UnpackTuple, comments
acl-cqc 77a5fa3
Merge remote-tracking branch 'origin/main' into acl/const_fold2
acl-cqc 2cc62f0
clippy
acl-cqc 8254771
docs
acl-cqc 7e81b15
Separate AnalysisResults from Machine, use context.exactly_one() not …
acl-cqc 34e82ed
Move try_into_wire_value => AnalysisResults.try_read_wire_value
acl-cqc 015707f
doc fixes and fix comment
acl-cqc ff39f7d
Try to make clippy happy
acl-cqc ada7ee1
Use ascent_run to drop context from all the relations. Lots cleanup t…
acl-cqc 7c02d41
DFContext does not Deref, pass Hugr separately
acl-cqc 3d4f016
Massively reduce scope of clippy-allow to inside run_datalog
acl-cqc 8bab4d5
Move ValueRow into own file
acl-cqc 3eccadf
Move Machine into datalog.rs, pub(super) fields in AnalysisResults, r…
acl-cqc 0f4fa52
Move machine.rs to results.rs
acl-cqc caa8aca
Remove enum IO, replace io_node -> input_child/output_child
acl-cqc e7f61fc
move docs
acl-cqc 811802c
Remove/inline/dedup dfb_block
acl-cqc 3713ea7
relation doc
acl-cqc d317809
datalog docs (each relation), move _cfg_succ_dest
acl-cqc 69c3270
comment, use exactly_one
acl-cqc dc56686
Allow to handle LoadFunction
acl-cqc 33a8592
doc
acl-cqc b153ada
Separate out ConstLoader
acl-cqc 5052ac0
value_from_(custom_const=>opaque), taking &OpaqueValue
acl-cqc ad0c6f2
Recombine DFContext with Hugr i.e. reinstate Deref constraint
acl-cqc 16a18f4
Replace Deref with HugrView, trivially obtainable by implementing AsRef
acl-cqc a0f2b2c
ValueRow Debug; ops default to PartialValue::Top less aggressively
acl-cqc 87eb700
And back to Deref, should allow using a region view not the whole Hugr
acl-cqc a49221b
fix doclink
acl-cqc 71ea55d
PartialSum::try_into_value also uses Option<...> as error-type
acl-cqc ea9db2e
Proper errors from try_into_value, Option<Extr...> from try_read_wire…
acl-cqc df31523
fmt
acl-cqc a490874
Add test running on region
acl-cqc 3af39aa
fix: provide PartialValue::Top for unspecified Hugr inputs
acl-cqc dc15999
try_into_value allows TryFrom by giving ExtractValueError *2* errorty…
acl-cqc 2d81264
improve docs
acl-cqc b61d252
Parametrize Machine::try_read_wire_value the same way
acl-cqc 8cac194
tweaks
acl-cqc fb3816e
Massively simplify xor_and_cfg, no need for conditionals
acl-cqc 19571f6
Use tru/fals constants
acl-cqc 69d0f5e
try_into_value: reorder type params, separate out where clause
acl-cqc cb60b5b
Merge remote-tracking branch 'origin/main' into HEAD
acl-cqc 2624ee8
We don't actually use portgraph, nor downcast-rs
acl-cqc ec526e8
Import RandomState from std::collections::hash_map for rust 1.75
acl-cqc 5650ee4
Use BREAK_TAG/CONTINUE_TAG
acl-cqc da2981c
No, use make_break/make_continue for easy cases
acl-cqc 0b68454
Refactor bb_reachable using then
acl-cqc 4916e9d
ConstLocation with Box - a lot of cloning
acl-cqc a4a64c0
No - Revert - just make ConstLocation store a reference
acl-cqc bc39b76
{value=>partial}_from_const, takes ConstLoc, inline traverse_value
acl-cqc 92669db
Make ConstLocation Copy
acl-cqc 33c8607
ConstLocation is From<Node>; move partial_from_const out to toplev, n…
acl-cqc 97496f9
Merge commit 'origin/main^' into HEAD
acl-cqc 8b76135
Generalize run to deal with Module(use main), and others; add run_lib
acl-cqc c18cbea
Shorten the got-all-required-inputs check (build got_inputs)
acl-cqc 1b64b4b
Shorten further...not as easy to follow
acl-cqc 39b8df1
Revert "Shorten further...not as easy to follow"
acl-cqc a5d987c
doc fixes, rename to run_library
acl-cqc 3e718fd
Add PartialValue::contains_bottom, also row_contains_bottom
acl-cqc 497686a
Don't call interpret_leaf_op if row_contains_bottom
acl-cqc e34c7be
Use row_contains_bottom for CFG+DFG, and augment unpack_first(=>_no_b…
acl-cqc 69a69f3
run_library => publish_function
acl-cqc 57ac432
Drop publish_function, pub prepopulate_wire
acl-cqc f9a9f24
ValueRow::single_known => singleton, set
acl-cqc 9cc368d
try_join / try_meet return extra bool
acl-cqc a61fbdb
shorten/common-up meet_mut + join_mut
acl-cqc 24cce0e
try_into_value: change bounds TryFrom -> TryInto; rename =>try_into_sum
acl-cqc a590766
Avoid a clone in try_into_sum
acl-cqc 2b2c461
Optimize+shorten join_mut / meet_mut via std::mem::swap
acl-cqc 124718d
refactor join_mut / meet_mut again, common-up assignment
acl-cqc 93b1f4d
clippy
acl-cqc 731a3b0
doclinks
acl-cqc 7040e83
prepopulate_df_inputs
acl-cqc 584327f
Revert "Use row_contains_bottom for CFG+DFG, and augment unpack_first…
acl-cqc 1a56145
Merge remote-tracking branch 'origin/main' into HEAD
acl-cqc 409f377
Fix FuncDefn::name, add test_module
acl-cqc 39b754a
clippy
acl-cqc c076a2c
clippy test
acl-cqc efdf516
Merge remote-tracking branch 'origin/main' into HEAD
acl-cqc 77b6739
try_into_{value=>concrete}
acl-cqc caa8882
try_read_wire_{value=>concrete}
acl-cqc c34bab7
Merge remote-tracking branch 'origin/main' into HEAD
acl-cqc d387cef
fix BOOL_T -> bool_t() and types needing extensions
acl-cqc df434e7
clippy
acl-cqc 650cdfd
And separate DFContext from HugrView once again
acl-cqc 071c7dd
Store HugrView (not DFContext) in Machine
acl-cqc c5bd7b0
Machine::run owns DFContext. Or &mut ?? (TODO update interpret_leaf_o…
acl-cqc eecdb22
interpret_leaf_op is &mut to allow impls that do caching etc.
acl-cqc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
#![warn(missing_docs)] | ||
//! Dataflow analysis of Hugrs. | ||
|
||
mod datalog; | ||
pub use datalog::Machine; | ||
mod value_row; | ||
|
||
mod results; | ||
pub use results::{AnalysisResults, TailLoopTermination}; | ||
|
||
mod partial_value; | ||
pub use partial_value::{AbstractValue, PartialSum, PartialValue, Sum}; | ||
|
||
use hugr_core::ops::constant::OpaqueValue; | ||
use hugr_core::ops::{ExtensionOp, Value}; | ||
use hugr_core::types::TypeArg; | ||
use hugr_core::{Hugr, Node}; | ||
|
||
/// Clients of the dataflow framework (particular analyses, such as constant folding) | ||
/// must implement this trait (including providing an appropriate domain type `V`). | ||
pub trait DFContext<V>: ConstLoader<V> { | ||
/// Given lattice values for each input, update lattice values for the (dataflow) outputs. | ||
/// For extension ops only, excluding [MakeTuple] and [UnpackTuple] which are handled automatically. | ||
/// `_outs` is an array with one element per dataflow output, each initialized to [PartialValue::Top] | ||
/// which is the correct value to leave if nothing can be deduced about that output. | ||
/// (The default does nothing, i.e. leaves `Top` for all outputs.) | ||
/// | ||
/// [MakeTuple]: hugr_core::extension::prelude::MakeTuple | ||
/// [UnpackTuple]: hugr_core::extension::prelude::UnpackTuple | ||
fn interpret_leaf_op( | ||
&mut self, | ||
_node: Node, | ||
_e: &ExtensionOp, | ||
_ins: &[PartialValue<V>], | ||
_outs: &mut [PartialValue<V>], | ||
) { | ||
} | ||
} | ||
|
||
/// A location where a [Value] could be find in a Hugr. That is, | ||
/// (perhaps deeply nested within [Value::Sum]s) within a [Node] | ||
/// that is a [Const](hugr_core::ops::Const). | ||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] | ||
pub enum ConstLocation<'a> { | ||
/// The specified-index'th field of the [Value::Sum] constant identified by the RHS | ||
Field(usize, &'a ConstLocation<'a>), | ||
/// The entire ([Const::value](hugr_core::ops::Const::value)) of the node. | ||
Node(Node), | ||
} | ||
|
||
impl From<Node> for ConstLocation<'_> { | ||
fn from(value: Node) -> Self { | ||
ConstLocation::Node(value) | ||
} | ||
} | ||
|
||
/// Trait for loading [PartialValue]s from constant [Value]s in a Hugr. | ||
/// Implementors will likely want to override some/all of [Self::value_from_opaque], | ||
/// [Self::value_from_const_hugr], and [Self::value_from_function]: the defaults | ||
/// are "correct" but maximally conservative (minimally informative). | ||
pub trait ConstLoader<V> { | ||
/// Produces an abstract value from an [OpaqueValue], if possible. | ||
/// The default just returns `None`, which will be interpreted as [PartialValue::Top]. | ||
croyzor marked this conversation as resolved.
Show resolved
Hide resolved
|
||
fn value_from_opaque(&self, _loc: ConstLocation, _val: &OpaqueValue) -> Option<V> { | ||
None | ||
} | ||
|
||
/// Produces an abstract value from a Hugr in a [Value::Function], if possible. | ||
/// The default just returns `None`, which will be interpreted as [PartialValue::Top]. | ||
fn value_from_const_hugr(&self, _loc: ConstLocation, _h: &Hugr) -> Option<V> { | ||
None | ||
} | ||
|
||
/// Produces an abstract value from a [FuncDefn] or [FuncDecl] node | ||
/// (that has been loaded via a [LoadFunction]), if possible. | ||
/// The default just returns `None`, which will be interpreted as [PartialValue::Top]. | ||
/// | ||
/// [FuncDefn]: hugr_core::ops::FuncDefn | ||
/// [FuncDecl]: hugr_core::ops::FuncDecl | ||
/// [LoadFunction]: hugr_core::ops::LoadFunction | ||
fn value_from_function(&self, _node: Node, _type_args: &[TypeArg]) -> Option<V> { | ||
None | ||
} | ||
} | ||
|
||
/// Produces a [PartialValue] from a constant. Traverses [Sum](Value::Sum) constants | ||
/// to their leaves ([Value::Extension] and [Value::Function]), | ||
/// converts these using [ConstLoader::value_from_opaque] and [ConstLoader::value_from_const_hugr], | ||
/// and builds nested [PartialValue::new_variant] to represent the structure. | ||
fn partial_from_const<'a, V>( | ||
cl: &impl ConstLoader<V>, | ||
loc: impl Into<ConstLocation<'a>>, | ||
cst: &Value, | ||
) -> PartialValue<V> { | ||
let loc = loc.into(); | ||
match cst { | ||
Value::Sum(hugr_core::ops::constant::Sum { tag, values, .. }) => { | ||
let elems = values | ||
.iter() | ||
.enumerate() | ||
.map(|(idx, elem)| partial_from_const(cl, ConstLocation::Field(idx, &loc), elem)); | ||
PartialValue::new_variant(*tag, elems) | ||
} | ||
Value::Extension { e } => cl | ||
.value_from_opaque(loc, e) | ||
.map(PartialValue::from) | ||
.unwrap_or(PartialValue::Top), | ||
Value::Function { hugr } => cl | ||
.value_from_const_hugr(loc, hugr) | ||
.map(PartialValue::from) | ||
.unwrap_or(PartialValue::Top), | ||
} | ||
} | ||
|
||
/// A row of inputs to a node contains bottom (can't happen, the node | ||
/// can't execute) if any element [contains_bottom](PartialValue::contains_bottom). | ||
pub fn row_contains_bottom<'a, V: AbstractValue + 'a>( | ||
elements: impl IntoIterator<Item = &'a PartialValue<V>>, | ||
) -> bool { | ||
elements.into_iter().any(PartialValue::contains_bottom) | ||
} | ||
|
||
#[cfg(test)] | ||
mod test; |
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.