diff --git a/Cargo.lock b/Cargo.lock index e8ec35e56af..e637feb30fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1599,7 +1599,6 @@ dependencies = [ "majit-backend", "majit-gc", "majit-ir", - "majit-metainterp", "majit-translate", "smallvec", "target-lexicon", diff --git a/majit/README.md b/majit/README.md index 801d022b34f..93fa3bc6439 100644 --- a/majit/README.md +++ b/majit/README.md @@ -12,12 +12,12 @@ Write a bytecode interpreter. Annotate it with `#[jit_interp]`. majit does the r #[jit_interp(state = State, env = Program, ...)] fn mainloop(program: &Program, state: &mut State, driver: &mut JitDriver) { while pc < program.len() { - jit_merge_point!(driver, program, pc); + jit_merge_point!(driver, program, pc; state); match program[pc] { Op::Add => { /* ... */ } Op::Jump(target) => { - pc = target; can_enter_jit!(driver, target, state, ...); + pc = target; continue; } // ... @@ -29,6 +29,16 @@ fn mainloop(program: &Program, state: &mut State, driver: &mut JitDriver) Hot loops are detected, traced, optimized, and compiled to native code. Guard failures fall back to the interpreter transparently. +The `; state` tail is load-bearing. Without it the macro parses (the tail is +optional, `jit_interp/mod.rs` `MergePointArgs::parse`) and expands to the +observer/replay statement instead: the walk's outcome is discarded and the +native loop re-runs the same work, which duplicates execution between the +native loop and its interpreter fallback. That two-executor shape has been +retired. With it, the expansion writes the walk's state back and either takes +the loop's exit or resumes at the walked pc — so anything after the loop must +reconstruct its result from `state` alone, since the pc is not advanced on that +exit path. + ## Similarities with RPython majit and the RPython JIT share the same core ideas — and, per the project's parity rule, the same module names and data structures: @@ -54,7 +64,7 @@ majit works with **plain Rust**. Type recovery is not needed (the Rust compiler | `@jit.elidable` | `#[elidable]` | | `@jit.dont_look_inside` | `#[dont_look_inside]` | | `jit.JitDriver(greens=[...], reds=[...])` | `#[jit_driver(greens = [...], reds = [...])]` | -| `driver.jit_merge_point(...)` | `jit_merge_point!(driver, ...)` | +| `driver.jit_merge_point(...)` | `jit_merge_point!(driver, env, pc; state)` | | `driver.can_enter_jit(...)` | `can_enter_jit!(driver, ...)` | ### Translation: live image vs extracted artifacts diff --git a/majit/charon-corpus/corpus.ullbc b/majit/charon-corpus/corpus.ullbc index 098796b3c48..30eddb48f53 100644 --- a/majit/charon-corpus/corpus.ullbc +++ b/majit/charon-corpus/corpus.ullbc @@ -1 +1 @@ -{"charon_version":"0.1.201","translated":{"crate_name":"charon_corpus","options":{"ullbc":true,"precise_drops":false,"skip_borrowck":false,"mir":null,"rustc_args":[],"targets":[],"monomorphize":false,"monomorphize_mut":null,"start_from":[],"start_from_if_exists":[],"start_from_attribute":null,"start_from_pub":false,"include":[],"opaque":[],"exclude":[],"extract_opaque_bodies":false,"translate_all_methods":false,"lift_associated_types":[],"hide_marker_traits":false,"remove_adt_clauses":false,"hide_allocator":false,"remove_unused_self_clauses":false,"desugar_drops":false,"ops_to_function_calls":false,"index_to_function_calls":false,"treat_box_as_builtin":false,"raw_consts":false,"unsized_strings":false,"reconstruct_fallible_operations":false,"reconstruct_asserts":false,"unbind_item_vars":false,"print_original_ullbc":false,"print_ullbc":false,"print_built_llbc":false,"print_llbc":false,"dest_dir":null,"dest_file":"/Users/youknowone/Projects/pyre-6/build/llbc/corpus.ullbc","no_dedup_serialized_ast":false,"format":null,"no_serialize":false,"no_typecheck":false,"no_normalize":false,"abort_on_error":false,"error_on_warnings":false,"preset":null},"target_information":[{"key":"aarch64-apple-darwin","value":{"target_pointer_size":8,"is_little_endian":true}}],"files":[{"id":0,"name":{"Local":"src/lib.rs"},"crate_name":"charon_corpus","contents":"//! Charon fixture corpus: representative shapes from issue #97.\n//!\n//! 1. `straight_line_add` — straight-line interpreter-shaped function.\n//! 2. `branch_loop_sum` — branch + loop, like opcode dispatch fragments.\n//! 3. `strategy_dispatch` — enum-as-strategy (dict-strategy stand-in).\n//! 4. `desugar_mix` — `?`, `match`, and iterator desugaring together.\n\n#![allow(dead_code)]\n\npub type PyResult = Result;\n\n// --- 1. Straight-line ---------------------------------------------------\n\n#[inline(never)]\npub fn straight_line_add(a: i64, b: i64, c: i64) -> i64 {\n let s = a + b;\n let t = s * 2;\n t + c\n}\n\n// --- 2. Branch + loop ---------------------------------------------------\n\n#[inline(never)]\npub fn branch_loop_sum(slice: &[i64], threshold: i64) -> i64 {\n let mut acc: i64 = 0;\n for &v in slice {\n if v > threshold {\n acc += v;\n } else {\n acc -= v;\n }\n }\n acc\n}\n\n// --- 3. Strategy dispatch (dict-strategy stand-in) ----------------------\n\npub enum Strategy {\n Empty,\n IntKeyed { len: usize },\n StrKeyed { len: usize, capacity: usize },\n}\n\n#[inline(never)]\npub fn strategy_len(s: &Strategy) -> usize {\n match s {\n Strategy::Empty => 0,\n Strategy::IntKeyed { len } => *len,\n Strategy::StrKeyed { len, capacity: _ } => *len,\n }\n}\n\n// --- 4. Desugar mix: ?, match, iterator --------------------------------\n\npub enum Token {\n Add(i64),\n Sub(i64),\n Halt,\n}\n\nfn parse_one(raw: i64) -> PyResult {\n match raw {\n i64::MIN => Ok(Token::Halt),\n 0 => Err(\"halt-zero forbidden\"),\n v if v > 0 => Ok(Token::Add(v)),\n v => Ok(Token::Sub(-v)),\n }\n}\n\n#[inline(never)]\npub fn desugar_mix(input: &[i64]) -> PyResult {\n let mut acc: i64 = 0;\n for &raw in input.iter() {\n let tok = parse_one(raw)?;\n match tok {\n Token::Add(v) => acc += v,\n Token::Sub(v) => acc -= v,\n Token::Halt => break,\n }\n }\n Ok(acc)\n}\n\n// --- 5. Tuple round-trip: construct a tuple, read .0/.1 in same fn ------\n//\n// Exercises `Rvalue::Aggregate` for a *non-Adt* (tuple) value paired\n// with `Field` projection reads of that same local. The lowering must\n// emit a `__pos_` `FieldRead` symmetric to the construction-side\n// `FieldWrite` chain rather than collapsing every `.N` to the base.\n\n#[inline(never)]\npub fn tuple_roundtrip(a: i64, b: i64) -> i64 {\n let pair = (a + b, a - b);\n pair.0 * pair.1\n}\n\n// --- 6. Closures --------------------------------------------------------\n//\n// `bool_then_closure` is the exact `core::bool::::then` census shape:\n// an opaque combinator taking a `FnOnce` closure that captures a value from\n// the enclosing scope. Charon extracts the closure's `call_once` body as a\n// transparent inherent method of the closure type.\n\n#[inline(never)]\npub fn bool_then_closure(c: bool, x: i64) -> Option {\n c.then(|| x + 1)\n}\n\n// `then_some` is the eager sibling of `then`: it takes an already-evaluated\n// value rather than a closure, so the diamond's `then` arm wraps it in `Some`\n// directly (no `call_once`). Same Opaque-core-combinator residual shape.\n#[inline(never)]\npub fn bool_then_some(c: bool, x: i64) -> Option {\n c.then_some(x + 1)\n}\n\n// --- 7. Option question mark -------------------------------------------\n//\n// Exercises `Try::branch` on `Option`: `Some(v)` continues with `v`, while\n// `None` returns `None` normally from the enclosing Option-returning function.\n\n#[inline(never)]\nfn option_source(keep: bool, value: i64) -> Option {\n if keep { Some(value) } else { None }\n}\n\n#[inline(never)]\npub fn option_question_mark(keep: bool, value: i64, addend: i64) -> Option {\n let v = option_source(keep, value)?;\n Some(v + addend)\n}\n"},{"id":1,"name":{"Local":"/rustc/library/core/src/marker.rs"},"crate_name":"core","contents":null},{"id":2,"name":{"Local":"/rustc/library/core/src/lib.rs"},"crate_name":"core","contents":null},{"id":3,"name":{"Local":"/rustc/library/core/src/result.rs"},"crate_name":"core","contents":null},{"id":4,"name":{"Local":"/rustc/library/core/src/slice/iter.rs"},"crate_name":"core","contents":null},{"id":5,"name":{"Local":"/rustc/library/core/src/slice/mod.rs"},"crate_name":"core","contents":null},{"id":6,"name":{"Local":"/rustc/library/core/src/option.rs"},"crate_name":"core","contents":null},{"id":7,"name":{"Local":"/rustc/library/core/src/slice/iter/macros.rs"},"crate_name":"core","contents":null},{"id":8,"name":{"Local":"/rustc/library/core/src/ops/control_flow.rs"},"crate_name":"core","contents":null},{"id":9,"name":{"Local":"/rustc/library/core/src/ops/mod.rs"},"crate_name":"core","contents":null},{"id":10,"name":{"Local":"/rustc/library/core/src/convert/mod.rs"},"crate_name":"core","contents":null},{"id":11,"name":{"Local":"/rustc/library/core/src/iter/traits/collect.rs"},"crate_name":"core","contents":null},{"id":12,"name":{"Local":"/rustc/library/core/src/iter/traits/mod.rs"},"crate_name":"core","contents":null},{"id":13,"name":{"Local":"/rustc/library/core/src/iter/mod.rs"},"crate_name":"core","contents":null},{"id":14,"name":{"Local":"/rustc/library/core/src/iter/traits/iterator.rs"},"crate_name":"core","contents":null},{"id":15,"name":{"Local":"/rustc/library/core/src/bool.rs"},"crate_name":"core","contents":null},{"id":16,"name":{"Local":"/rustc/library/core/src/ops/function.rs"},"crate_name":"core","contents":null},{"id":17,"name":{"Local":"/rustc/library/core/src/array/iter.rs"},"crate_name":"core","contents":null},{"id":18,"name":{"Local":"/rustc/library/core/src/array/mod.rs"},"crate_name":"core","contents":null},{"id":19,"name":{"Local":"/rustc/library/core/src/num/nonzero.rs"},"crate_name":"core","contents":null},{"id":20,"name":{"Local":"/rustc/library/core/src/num/mod.rs"},"crate_name":"core","contents":null},{"id":21,"name":{"Local":"/rustc/library/core/src/iter/adapters/step_by.rs"},"crate_name":"core","contents":null},{"id":22,"name":{"Local":"/rustc/library/core/src/iter/adapters/mod.rs"},"crate_name":"core","contents":null},{"id":23,"name":{"Local":"/rustc/library/core/src/iter/adapters/chain.rs"},"crate_name":"core","contents":null},{"id":24,"name":{"Local":"/rustc/library/core/src/iter/adapters/zip.rs"},"crate_name":"core","contents":null},{"id":25,"name":{"Local":"/rustc/library/core/src/clone.rs"},"crate_name":"core","contents":null},{"id":26,"name":{"Local":"/rustc/library/core/src/iter/adapters/intersperse.rs"},"crate_name":"core","contents":null},{"id":27,"name":{"Local":"/rustc/library/core/src/iter/adapters/map.rs"},"crate_name":"core","contents":null},{"id":28,"name":{"Local":"/rustc/library/core/src/iter/adapters/filter.rs"},"crate_name":"core","contents":null},{"id":29,"name":{"Local":"/rustc/library/core/src/iter/adapters/filter_map.rs"},"crate_name":"core","contents":null},{"id":30,"name":{"Local":"/rustc/library/core/src/iter/adapters/enumerate.rs"},"crate_name":"core","contents":null},{"id":31,"name":{"Local":"/rustc/library/core/src/iter/adapters/peekable.rs"},"crate_name":"core","contents":null},{"id":32,"name":{"Local":"/rustc/library/core/src/iter/adapters/skip_while.rs"},"crate_name":"core","contents":null},{"id":33,"name":{"Local":"/rustc/library/core/src/iter/adapters/take_while.rs"},"crate_name":"core","contents":null},{"id":34,"name":{"Local":"/rustc/library/core/src/iter/adapters/map_while.rs"},"crate_name":"core","contents":null},{"id":35,"name":{"Local":"/rustc/library/core/src/iter/adapters/skip.rs"},"crate_name":"core","contents":null},{"id":36,"name":{"Local":"/rustc/library/core/src/iter/adapters/take.rs"},"crate_name":"core","contents":null},{"id":37,"name":{"Local":"/rustc/library/core/src/iter/adapters/scan.rs"},"crate_name":"core","contents":null},{"id":38,"name":{"Local":"/rustc/library/core/src/iter/adapters/flatten.rs"},"crate_name":"core","contents":null},{"id":39,"name":{"Local":"/rustc/library/core/src/iter/adapters/map_windows.rs"},"crate_name":"core","contents":null},{"id":40,"name":{"Local":"/rustc/library/core/src/iter/adapters/fuse.rs"},"crate_name":"core","contents":null},{"id":41,"name":{"Local":"/rustc/library/core/src/iter/adapters/inspect.rs"},"crate_name":"core","contents":null},{"id":42,"name":{"Local":"/rustc/library/core/src/ops/try_trait.rs"},"crate_name":"core","contents":null},{"id":43,"name":{"Local":"/rustc/library/core/src/default.rs"},"crate_name":"core","contents":null},{"id":44,"name":{"Local":"/rustc/library/core/src/iter/traits/double_ended.rs"},"crate_name":"core","contents":null},{"id":45,"name":{"Local":"/rustc/library/core/src/iter/traits/exact_size.rs"},"crate_name":"core","contents":null},{"id":46,"name":{"Local":"/rustc/library/core/src/cmp.rs"},"crate_name":"core","contents":null},{"id":47,"name":{"Local":"/rustc/library/core/src/iter/adapters/rev.rs"},"crate_name":"core","contents":null},{"id":48,"name":{"Local":"/rustc/library/core/src/iter/adapters/copied.rs"},"crate_name":"core","contents":null},{"id":49,"name":{"Local":"/rustc/library/core/src/iter/adapters/cloned.rs"},"crate_name":"core","contents":null},{"id":50,"name":{"Local":"/rustc/library/core/src/iter/adapters/cycle.rs"},"crate_name":"core","contents":null},{"id":51,"name":{"Local":"/rustc/library/core/src/iter/adapters/array_chunks.rs"},"crate_name":"core","contents":null},{"id":52,"name":{"Local":"/rustc/library/core/src/iter/traits/accum.rs"},"crate_name":"core","contents":null},{"id":53,"name":{"Local":"/rustc/library/core/src/num/niche_types.rs"},"crate_name":"core","contents":null}],"item_names":[{"key":{"Type":0},"value":[{"Ident":["charon_corpus",0]},{"Ident":["PyResult",0]}]},{"key":{"Fun":0},"value":[{"Ident":["charon_corpus",0]},{"Ident":["straight_line_add",0]}]},{"key":{"Fun":1},"value":[{"Ident":["charon_corpus",0]},{"Ident":["branch_loop_sum",0]}]},{"key":{"Type":1},"value":[{"Ident":["charon_corpus",0]},{"Ident":["Strategy",0]}]},{"key":{"Fun":2},"value":[{"Ident":["charon_corpus",0]},{"Ident":["strategy_len",0]}]},{"key":{"Type":2},"value":[{"Ident":["charon_corpus",0]},{"Ident":["Token",0]}]},{"key":{"Fun":3},"value":[{"Ident":["charon_corpus",0]},{"Ident":["parse_one",0]}]},{"key":{"Fun":4},"value":[{"Ident":["charon_corpus",0]},{"Ident":["desugar_mix",0]}]},{"key":{"Fun":5},"value":[{"Ident":["charon_corpus",0]},{"Ident":["tuple_roundtrip",0]}]},{"key":{"Fun":6},"value":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_closure",0]}]},{"key":{"Fun":7},"value":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_some",0]}]},{"key":{"Fun":8},"value":[{"Ident":["charon_corpus",0]},{"Ident":["option_source",0]}]},{"key":{"Fun":9},"value":[{"Ident":["charon_corpus",0]},{"Ident":["option_question_mark",0]}]},{"key":{"TraitDecl":0},"value":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Ident":["Sized",0]}]},{"key":{"Type":3},"value":[{"Ident":["core",0]},{"Ident":["result",0]},{"Ident":["Result",0]}]},{"key":{"TraitDecl":1},"value":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Ident":["MetaSized",0]}]},{"key":{"Type":4},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Ident":["Iter",0]}]},{"key":{"Type":5},"value":[{"Ident":["core",0]},{"Ident":["option",0]},{"Ident":["Option",0]}]},{"key":{"TraitImpl":0},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":0}}]},{"key":{"Fun":10},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":0}},{"Ident":["into_iter",0]}]},{"key":{"TraitImpl":1},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}}]},{"key":{"Fun":11},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["next",0]}]},{"key":{"Type":6},"value":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["control_flow",0]},{"Ident":["ControlFlow",0]}]},{"key":{"Type":7},"value":[{"Ident":["core",0]},{"Ident":["convert",0]},{"Ident":["Infallible",0]}]},{"key":{"Fun":12},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Impl":{"Ty":{"params":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":5,"beg":{"line":101,"col":5},"end":{"line":101,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[141,{"TypeVar":{"Bound":[1,0]}}]}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"HashConsedValue":[1040,{"Slice":{"HashConsedValue":[173,{"TypeVar":{"Bound":[0,0]}}]}}]},"kind":"InherentImplBlock"}}},{"Ident":["iter",0]}]},{"key":{"TraitImpl":2},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Impl":{"Trait":2}}]},{"key":{"Fun":13},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Impl":{"Trait":2}},{"Ident":["into_iter",0]}]},{"key":{"TraitDecl":2},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]}]},{"key":{"TraitImpl":3},"value":[{"Ident":["core",0]},{"Ident":["result",0]},{"Impl":{"Trait":3}}]},{"key":{"Fun":14},"value":[{"Ident":["core",0]},{"Ident":["result",0]},{"Impl":{"Trait":3}},{"Ident":["branch",0]}]},{"key":{"TraitImpl":4},"value":[{"Ident":["core",0]},{"Ident":["result",0]},{"Impl":{"Trait":4}}]},{"key":{"Fun":15},"value":[{"Ident":["core",0]},{"Ident":["result",0]},{"Impl":{"Trait":4}},{"Ident":["from_residual",0]}]},{"key":{"TraitDecl":3},"value":[{"Ident":["core",0]},{"Ident":["convert",0]},{"Ident":["From",0]}]},{"key":{"TraitImpl":5},"value":[{"Ident":["core",0]},{"Ident":["convert",0]},{"Impl":{"Trait":5}}]},{"key":{"Type":8},"value":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_closure",0]},{"Ident":["closure",0]}]},{"key":{"TraitImpl":6},"value":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_closure",0]},{"Impl":{"Trait":6}}]},{"key":{"Fun":16},"value":[{"Ident":["core",0]},{"Ident":["bool",0]},{"Impl":{"Ty":{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"HashConsedValue":[188,{"Literal":"Bool"}]},"kind":"InherentImplBlock"}}},{"Ident":["then",0]}]},{"key":{"TraitDecl":4},"value":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["function",0]},{"Ident":["FnOnce",0]}]},{"key":{"TraitDecl":5},"value":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Ident":["Destruct",0]}]},{"key":{"TraitImpl":7},"value":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_closure",0]},{"Ident":["closure",0]},{"Impl":{"Trait":7}}]},{"key":{"Fun":17},"value":[{"Ident":["core",0]},{"Ident":["bool",0]},{"Impl":{"Ty":{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"Deduplicated":188},"kind":"InherentImplBlock"}}},{"Ident":["then_some",0]}]},{"key":{"TraitImpl":8},"value":[{"Ident":["core",0]},{"Ident":["option",0]},{"Impl":{"Trait":8}}]},{"key":{"Fun":18},"value":[{"Ident":["core",0]},{"Ident":["option",0]},{"Impl":{"Trait":8}},{"Ident":["branch",0]}]},{"key":{"TraitImpl":9},"value":[{"Ident":["core",0]},{"Ident":["option",0]},{"Impl":{"Trait":9}}]},{"key":{"Fun":19},"value":[{"Ident":["core",0]},{"Ident":["option",0]},{"Impl":{"Trait":9}},{"Ident":["from_residual",0]}]},{"key":{"Type":9},"value":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Ident":["MetaSized",0]},{"Ident":["{vtable}",0]}]},{"key":{"TraitDecl":6},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["IntoIterator",0]}]},{"key":{"Global":0},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":0}},{"Ident":["{vtable}",0]}]},{"key":{"Global":1},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["{vtable}",0]}]},{"key":{"Fun":20},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["next_chunk",0]}]},{"key":{"Fun":21},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["size_hint",0]}]},{"key":{"Fun":22},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["count",0]}]},{"key":{"Fun":23},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["last",0]}]},{"key":{"Fun":24},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["advance_by",0]}]},{"key":{"Fun":25},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["nth",0]}]},{"key":{"Fun":26},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["step_by",0]}]},{"key":{"Type":10},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["{vtable}",0]}]},{"key":{"Fun":27},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["next",0]}]},{"key":{"Fun":28},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["next_chunk",0]}]},{"key":{"Type":11},"value":[{"Ident":["core",0]},{"Ident":["array",0]},{"Ident":["iter",0]},{"Ident":["IntoIter",0]}]},{"key":{"Fun":29},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["size_hint",0]}]},{"key":{"Fun":30},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["count",0]}]},{"key":{"Fun":31},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["last",0]}]},{"key":{"Fun":32},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["advance_by",0]}]},{"key":{"Type":12},"value":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["nonzero",0]},{"Ident":["NonZero",0]}]},{"key":{"TraitDecl":7},"value":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["nonzero",0]},{"Ident":["ZeroablePrimitive",0]}]},{"key":{"TraitImpl":10},"value":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["nonzero",0]},{"Impl":{"Trait":10}}]},{"key":{"Fun":33},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["nth",0]}]},{"key":{"Fun":34},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["step_by",0]}]},{"key":{"Type":13},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["step_by",0]},{"Ident":["StepBy",0]}]},{"key":{"Fun":35},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["chain",0]}]},{"key":{"Type":14},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["chain",0]},{"Ident":["Chain",0]}]},{"key":{"Fun":36},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["zip",0]}]},{"key":{"Type":15},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["zip",0]},{"Ident":["Zip",0]}]},{"key":{"Fun":37},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["intersperse",0]}]},{"key":{"TraitDecl":8},"value":[{"Ident":["core",0]},{"Ident":["clone",0]},{"Ident":["Clone",0]}]},{"key":{"Type":16},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["intersperse",0]},{"Ident":["Intersperse",0]}]},{"key":{"Fun":38},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["intersperse_with",0]}]},{"key":{"TraitDecl":9},"value":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["function",0]},{"Ident":["FnMut",0]}]},{"key":{"Type":17},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["intersperse",0]},{"Ident":["IntersperseWith",0]}]},{"key":{"Fun":39},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["map",0]}]},{"key":{"Type":18},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["map",0]},{"Ident":["Map",0]}]},{"key":{"Fun":40},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["for_each",0]}]},{"key":{"Fun":41},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["filter",0]}]},{"key":{"Type":19},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["filter",0]},{"Ident":["Filter",0]}]},{"key":{"Fun":42},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["filter_map",0]}]},{"key":{"Type":20},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["filter_map",0]},{"Ident":["FilterMap",0]}]},{"key":{"Fun":43},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["enumerate",0]}]},{"key":{"Type":21},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["enumerate",0]},{"Ident":["Enumerate",0]}]},{"key":{"Fun":44},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["peekable",0]}]},{"key":{"Type":22},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["peekable",0]},{"Ident":["Peekable",0]}]},{"key":{"Fun":45},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["skip_while",0]}]},{"key":{"Type":23},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["skip_while",0]},{"Ident":["SkipWhile",0]}]},{"key":{"Fun":46},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["take_while",0]}]},{"key":{"Type":24},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["take_while",0]},{"Ident":["TakeWhile",0]}]},{"key":{"Fun":47},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["map_while",0]}]},{"key":{"Type":25},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["map_while",0]},{"Ident":["MapWhile",0]}]},{"key":{"Fun":48},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["skip",0]}]},{"key":{"Type":26},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["skip",0]},{"Ident":["Skip",0]}]},{"key":{"Fun":49},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["take",0]}]},{"key":{"Type":27},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["take",0]},{"Ident":["Take",0]}]},{"key":{"Fun":50},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["scan",0]}]},{"key":{"Type":28},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["scan",0]},{"Ident":["Scan",0]}]},{"key":{"Fun":51},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["flat_map",0]}]},{"key":{"Type":29},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["flatten",0]},{"Ident":["FlatMap",0]}]},{"key":{"Fun":52},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["flatten",0]}]},{"key":{"Type":30},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["flatten",0]},{"Ident":["Flatten",0]}]},{"key":{"Fun":53},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["map_windows",0]}]},{"key":{"Type":31},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["map_windows",0]},{"Ident":["MapWindows",0]}]},{"key":{"Fun":54},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["fuse",0]}]},{"key":{"Type":32},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["fuse",0]},{"Ident":["Fuse",0]}]},{"key":{"Fun":55},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["inspect",0]}]},{"key":{"Type":33},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["inspect",0]},{"Ident":["Inspect",0]}]},{"key":{"Fun":56},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["by_ref",0]}]},{"key":{"Fun":57},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["collect",0]}]},{"key":{"TraitDecl":10},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["FromIterator",0]}]},{"key":{"Fun":58},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["try_collect",0]}]},{"key":{"TraitDecl":11},"value":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["try_trait",0]},{"Ident":["Try",0]}]},{"key":{"TraitDecl":12},"value":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["try_trait",0]},{"Ident":["Residual",0]}]},{"key":{"Fun":59},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["collect_into",0]}]},{"key":{"TraitDecl":13},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["Extend",0]}]},{"key":{"Fun":60},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["partition",0]}]},{"key":{"TraitDecl":14},"value":[{"Ident":["core",0]},{"Ident":["default",0]},{"Ident":["Default",0]}]},{"key":{"Fun":61},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["partition_in_place",0]}]},{"key":{"TraitDecl":15},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["double_ended",0]},{"Ident":["DoubleEndedIterator",0]}]},{"key":{"Fun":62},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["is_partitioned",0]}]},{"key":{"Fun":63},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["try_fold",0]}]},{"key":{"Fun":64},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["try_for_each",0]}]},{"key":{"Fun":65},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["fold",0]}]},{"key":{"Fun":66},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["reduce",0]}]},{"key":{"Fun":67},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["try_reduce",0]}]},{"key":{"Fun":68},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["all",0]}]},{"key":{"Fun":69},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["any",0]}]},{"key":{"Fun":70},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["find",0]}]},{"key":{"Fun":71},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["find_map",0]}]},{"key":{"Fun":72},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["try_find",0]}]},{"key":{"Fun":73},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["position",0]}]},{"key":{"Fun":74},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["rposition",0]}]},{"key":{"TraitDecl":16},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["exact_size",0]},{"Ident":["ExactSizeIterator",0]}]},{"key":{"Fun":75},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["max",0]}]},{"key":{"TraitDecl":17},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["Ord",0]}]},{"key":{"Fun":76},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["min",0]}]},{"key":{"Fun":77},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["max_by_key",0]}]},{"key":{"Fun":78},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["max_by",0]}]},{"key":{"Type":34},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["Ordering",0]}]},{"key":{"Fun":79},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["min_by_key",0]}]},{"key":{"Fun":80},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["min_by",0]}]},{"key":{"Fun":81},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["rev",0]}]},{"key":{"Type":35},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["rev",0]},{"Ident":["Rev",0]}]},{"key":{"Fun":82},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["unzip",0]}]},{"key":{"Fun":83},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["copied",0]}]},{"key":{"TraitDecl":18},"value":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Ident":["Copy",0]}]},{"key":{"Type":36},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["copied",0]},{"Ident":["Copied",0]}]},{"key":{"Fun":84},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["cloned",0]}]},{"key":{"Type":37},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["cloned",0]},{"Ident":["Cloned",0]}]},{"key":{"Fun":85},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["cycle",0]}]},{"key":{"Type":38},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["cycle",0]},{"Ident":["Cycle",0]}]},{"key":{"Fun":86},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["array_chunks",0]}]},{"key":{"Type":39},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["array_chunks",0]},{"Ident":["ArrayChunks",0]}]},{"key":{"Fun":87},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["sum",0]}]},{"key":{"TraitDecl":19},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["accum",0]},{"Ident":["Sum",0]}]},{"key":{"Fun":88},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["product",0]}]},{"key":{"TraitDecl":20},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["accum",0]},{"Ident":["Product",0]}]},{"key":{"Fun":89},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["cmp",0]}]},{"key":{"Fun":90},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["cmp_by",0]}]},{"key":{"Fun":91},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["partial_cmp",0]}]},{"key":{"TraitDecl":21},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialOrd",0]}]},{"key":{"Fun":92},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["partial_cmp_by",0]}]},{"key":{"Fun":93},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["eq",0]}]},{"key":{"TraitDecl":22},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialEq",0]}]},{"key":{"Fun":94},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["eq_by",0]}]},{"key":{"Fun":95},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["ne",0]}]},{"key":{"Fun":96},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["lt",0]}]},{"key":{"Fun":97},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["le",0]}]},{"key":{"Fun":98},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["gt",0]}]},{"key":{"Fun":99},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["ge",0]}]},{"key":{"Fun":100},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["is_sorted",0]}]},{"key":{"Fun":101},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["is_sorted_by",0]}]},{"key":{"Fun":102},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["is_sorted_by_key",0]}]},{"key":{"Fun":103},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["__iterator_get_unchecked",0]}]},{"key":{"TraitDecl":23},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["zip",0]},{"Ident":["TrustedRandomAccessNoCoerce",0]}]},{"key":{"Fun":104},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["chain",0]}]},{"key":{"Fun":105},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["zip",0]}]},{"key":{"Fun":106},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["intersperse",0]}]},{"key":{"Fun":107},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["intersperse_with",0]}]},{"key":{"Fun":108},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["map",0]}]},{"key":{"Fun":109},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["for_each",0]}]},{"key":{"Fun":110},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["filter",0]}]},{"key":{"Fun":111},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["filter_map",0]}]},{"key":{"Fun":112},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["enumerate",0]}]},{"key":{"Fun":113},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["peekable",0]}]},{"key":{"Fun":114},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["skip_while",0]}]},{"key":{"Fun":115},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["take_while",0]}]},{"key":{"Fun":116},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["map_while",0]}]},{"key":{"Fun":117},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["skip",0]}]},{"key":{"Fun":118},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["take",0]}]},{"key":{"Fun":119},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["scan",0]}]},{"key":{"Fun":120},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["flat_map",0]}]},{"key":{"Fun":121},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["flatten",0]}]},{"key":{"Fun":122},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["map_windows",0]}]},{"key":{"Fun":123},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["fuse",0]}]},{"key":{"Fun":124},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["inspect",0]}]},{"key":{"Fun":125},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["by_ref",0]}]},{"key":{"Fun":126},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["collect",0]}]},{"key":{"Fun":127},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["try_collect",0]}]},{"key":{"Fun":128},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["collect_into",0]}]},{"key":{"Fun":129},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["partition",0]}]},{"key":{"Fun":130},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["partition_in_place",0]}]},{"key":{"Fun":131},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["is_partitioned",0]}]},{"key":{"Fun":132},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["try_fold",0]}]},{"key":{"Fun":133},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["try_for_each",0]}]},{"key":{"Fun":134},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["fold",0]}]},{"key":{"Fun":135},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["reduce",0]}]},{"key":{"Fun":136},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["try_reduce",0]}]},{"key":{"Fun":137},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["all",0]}]},{"key":{"Fun":138},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["any",0]}]},{"key":{"Fun":139},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["find",0]}]},{"key":{"Fun":140},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["find_map",0]}]},{"key":{"Fun":141},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["try_find",0]}]},{"key":{"Fun":142},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["position",0]}]},{"key":{"Fun":143},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["rposition",0]}]},{"key":{"Fun":144},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["max",0]}]},{"key":{"Fun":145},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["min",0]}]},{"key":{"Fun":146},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["max_by_key",0]}]},{"key":{"Fun":147},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["max_by",0]}]},{"key":{"Fun":148},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["min_by_key",0]}]},{"key":{"Fun":149},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["min_by",0]}]},{"key":{"Fun":150},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["rev",0]}]},{"key":{"Fun":151},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["unzip",0]}]},{"key":{"Fun":152},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["copied",0]}]},{"key":{"Fun":153},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["cloned",0]}]},{"key":{"Fun":154},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["cycle",0]}]},{"key":{"Fun":155},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["array_chunks",0]}]},{"key":{"Fun":156},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["sum",0]}]},{"key":{"Fun":157},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["product",0]}]},{"key":{"Fun":158},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["cmp",0]}]},{"key":{"Fun":159},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["cmp_by",0]}]},{"key":{"Fun":160},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["partial_cmp",0]}]},{"key":{"Fun":161},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["partial_cmp_by",0]}]},{"key":{"Fun":162},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["eq",0]}]},{"key":{"Fun":163},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["eq_by",0]}]},{"key":{"Fun":164},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["ne",0]}]},{"key":{"Fun":165},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["lt",0]}]},{"key":{"Fun":166},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["le",0]}]},{"key":{"Fun":167},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["gt",0]}]},{"key":{"Fun":168},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["ge",0]}]},{"key":{"Fun":169},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["is_sorted",0]}]},{"key":{"Fun":170},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["is_sorted_by",0]}]},{"key":{"Fun":171},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["is_sorted_by_key",0]}]},{"key":{"Fun":172},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["__iterator_get_unchecked",0]}]},{"key":{"Global":2},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Impl":{"Trait":2}},{"Ident":["{vtable}",0]}]},{"key":{"TraitDecl":24},"value":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["try_trait",0]},{"Ident":["FromResidual",0]}]},{"key":{"Fun":173},"value":[{"Ident":["core",0]},{"Ident":["result",0]},{"Impl":{"Trait":3}},{"Ident":["from_output",0]}]},{"key":{"Fun":174},"value":[{"Ident":["core",0]},{"Ident":["convert",0]},{"Ident":["From",0]},{"Ident":["from",0]}]},{"key":{"Fun":175},"value":[{"Ident":["core",0]},{"Ident":["convert",0]},{"Impl":{"Trait":5}},{"Ident":["from",0]}]},{"key":{"TraitDecl":25},"value":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Ident":["Tuple",0]}]},{"key":{"Fun":176},"value":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_closure",0]},{"Impl":{"Trait":6}},{"Ident":["call_once",0]}]},{"key":{"Type":40},"value":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["function",0]},{"Ident":["FnOnce",0]},{"Ident":["{vtable}",0]}]},{"key":{"Fun":177},"value":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["function",0]},{"Ident":["FnOnce",0]},{"Ident":["call_once",0]}]},{"key":{"Fun":178},"value":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Ident":["Destruct",0]},{"Ident":["drop_in_place",0]}]},{"key":{"Fun":179},"value":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_closure",0]},{"Ident":["closure",0]},{"Impl":{"Trait":7}},{"Ident":["drop_in_place",0]}]},{"key":{"Fun":180},"value":[{"Ident":["core",0]},{"Ident":["option",0]},{"Impl":{"Trait":8}},{"Ident":["from_output",0]}]},{"key":{"Type":41},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["IntoIterator",0]},{"Ident":["{vtable}",0]}]},{"key":{"Fun":181},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["IntoIterator",0]},{"Ident":["into_iter",0]}]},{"key":{"TraitDecl":26},"value":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["nonzero",0]},{"Ident":["private",0]},{"Ident":["Sealed",0]}]},{"key":{"TraitImpl":11},"value":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Impl":{"Trait":11}}]},{"key":{"TraitImpl":12},"value":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["nonzero",0]},{"Impl":{"Trait":12}}]},{"key":{"Type":42},"value":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["niche_types",0]},{"Ident":["NonZeroUsizeInner",0]}]},{"key":{"TraitImpl":13},"value":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["niche_types",0]},{"Impl":{"Trait":13}}]},{"key":{"Fun":182},"value":[{"Ident":["core",0]},{"Ident":["clone",0]},{"Ident":["Clone",0]},{"Ident":["clone",0]}]},{"key":{"Fun":183},"value":[{"Ident":["core",0]},{"Ident":["clone",0]},{"Ident":["Clone",0]},{"Ident":["clone_from",0]}]},{"key":{"Type":43},"value":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["function",0]},{"Ident":["FnMut",0]},{"Ident":["{vtable}",0]}]},{"key":{"Fun":184},"value":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["function",0]},{"Ident":["FnMut",0]},{"Ident":["call_mut",0]}]},{"key":{"Fun":185},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["FromIterator",0]},{"Ident":["from_iter",0]}]},{"key":{"Fun":186},"value":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["try_trait",0]},{"Ident":["Try",0]},{"Ident":["from_output",0]}]},{"key":{"Fun":187},"value":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["try_trait",0]},{"Ident":["Try",0]},{"Ident":["branch",0]}]},{"key":{"Fun":188},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["Extend",0]},{"Ident":["extend",0]}]},{"key":{"Fun":189},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["Extend",0]},{"Ident":["extend_one",0]}]},{"key":{"Fun":190},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["Extend",0]},{"Ident":["extend_reserve",0]}]},{"key":{"Fun":191},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["Extend",0]},{"Ident":["extend_one_unchecked",0]}]},{"key":{"Fun":192},"value":[{"Ident":["core",0]},{"Ident":["default",0]},{"Ident":["Default",0]},{"Ident":["default",0]}]},{"key":{"Type":44},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["double_ended",0]},{"Ident":["DoubleEndedIterator",0]},{"Ident":["{vtable}",0]}]},{"key":{"Fun":193},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["double_ended",0]},{"Ident":["DoubleEndedIterator",0]},{"Ident":["next_back",0]}]},{"key":{"Fun":194},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["double_ended",0]},{"Ident":["DoubleEndedIterator",0]},{"Ident":["advance_back_by",0]}]},{"key":{"Fun":195},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["double_ended",0]},{"Ident":["DoubleEndedIterator",0]},{"Ident":["nth_back",0]}]},{"key":{"Fun":196},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["double_ended",0]},{"Ident":["DoubleEndedIterator",0]},{"Ident":["try_rfold",0]}]},{"key":{"Fun":197},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["double_ended",0]},{"Ident":["DoubleEndedIterator",0]},{"Ident":["rfold",0]}]},{"key":{"Fun":198},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["double_ended",0]},{"Ident":["DoubleEndedIterator",0]},{"Ident":["rfind",0]}]},{"key":{"Type":45},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["exact_size",0]},{"Ident":["ExactSizeIterator",0]},{"Ident":["{vtable}",0]}]},{"key":{"Fun":199},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["exact_size",0]},{"Ident":["ExactSizeIterator",0]},{"Ident":["len",0]}]},{"key":{"Fun":200},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["exact_size",0]},{"Ident":["ExactSizeIterator",0]},{"Ident":["is_empty",0]}]},{"key":{"TraitDecl":27},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["Eq",0]}]},{"key":{"Fun":201},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["Ord",0]},{"Ident":["cmp",0]}]},{"key":{"Fun":202},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["Ord",0]},{"Ident":["max",0]}]},{"key":{"Fun":203},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["Ord",0]},{"Ident":["min",0]}]},{"key":{"Fun":204},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["Ord",0]},{"Ident":["clamp",0]}]},{"key":{"Fun":205},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["accum",0]},{"Ident":["Sum",0]},{"Ident":["sum",0]}]},{"key":{"Fun":206},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["accum",0]},{"Ident":["Product",0]},{"Ident":["product",0]}]},{"key":{"Type":46},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialOrd",0]},{"Ident":["{vtable}",0]}]},{"key":{"Fun":207},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialOrd",0]},{"Ident":["partial_cmp",0]}]},{"key":{"Fun":208},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialOrd",0]},{"Ident":["lt",0]}]},{"key":{"Fun":209},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialOrd",0]},{"Ident":["le",0]}]},{"key":{"Fun":210},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialOrd",0]},{"Ident":["gt",0]}]},{"key":{"Fun":211},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialOrd",0]},{"Ident":["ge",0]}]},{"key":{"Fun":212},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialOrd",0]},{"Ident":["__chaining_lt",0]}]},{"key":{"Fun":213},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialOrd",0]},{"Ident":["__chaining_le",0]}]},{"key":{"Fun":214},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialOrd",0]},{"Ident":["__chaining_gt",0]}]},{"key":{"Fun":215},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialOrd",0]},{"Ident":["__chaining_ge",0]}]},{"key":{"Type":47},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialEq",0]},{"Ident":["{vtable}",0]}]},{"key":{"Fun":216},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialEq",0]},{"Ident":["eq",0]}]},{"key":{"Fun":217},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialEq",0]},{"Ident":["ne",0]}]},{"key":{"Fun":218},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["zip",0]},{"Ident":["TrustedRandomAccessNoCoerce",0]},{"Ident":["size",0]}]},{"key":{"Fun":219},"value":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["try_trait",0]},{"Ident":["FromResidual",0]},{"Ident":["from_residual",0]}]},{"key":{"Type":48},"value":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["nonzero",0]},{"Ident":["private",0]},{"Ident":["Sealed",0]},{"Ident":["{vtable}",0]}]},{"key":{"TraitImpl":14},"value":[{"Ident":["core",0]},{"Ident":["clone",0]},{"Ident":["impls",0]},{"Impl":{"Trait":14}}]},{"key":{"Global":3},"value":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["nonzero",0]},{"Impl":{"Trait":12}},{"Ident":["{vtable}",0]}]},{"key":{"TraitImpl":15},"value":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["niche_types",0]},{"Impl":{"Trait":15}}]},{"key":{"Fun":220},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["Eq",0]},{"Ident":["assert_receiver_is_total_eq",0]}]},{"key":{"Fun":221},"value":[{"Ident":["core",0]},{"Ident":["clone",0]},{"Ident":["impls",0]},{"Impl":{"Trait":14}},{"Ident":["clone",0]}]},{"key":{"Fun":222},"value":[{"Ident":["core",0]},{"Ident":["clone",0]},{"Ident":["impls",0]},{"Impl":{"Trait":14}},{"Ident":["clone_from",0]}]},{"key":{"Fun":223},"value":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["niche_types",0]},{"Impl":{"Trait":15}},{"Ident":["clone",0]}]},{"key":{"Fun":224},"value":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["niche_types",0]},{"Impl":{"Trait":15}},{"Ident":["clone_from",0]}]}],"assoc_item_names":[{"types":[],"methods":[],"consts":[]},{"types":[],"methods":[],"consts":[]},{"types":["Item"],"methods":["next","next_chunk","size_hint","count","last","advance_by","nth","step_by","chain","zip","intersperse","intersperse_with","map","for_each","filter","filter_map","enumerate","peekable","skip_while","take_while","map_while","skip","take","scan","flat_map","flatten","map_windows","fuse","inspect","by_ref","collect","try_collect","collect_into","partition","partition_in_place","is_partitioned","try_fold","try_for_each","fold","reduce","try_reduce","all","any","find","find_map","try_find","position","rposition","max","min","max_by_key","max_by","min_by_key","min_by","rev","unzip","copied","cloned","cycle","array_chunks","sum","product","cmp","cmp_by","partial_cmp","partial_cmp_by","eq","eq_by","ne","lt","le","gt","ge","is_sorted","is_sorted_by","is_sorted_by_key","__iterator_get_unchecked"],"consts":[]},{"types":[],"methods":["from"],"consts":[]},{"types":["Output"],"methods":["call_once"],"consts":[]},{"types":[],"methods":["drop_in_place"],"consts":[]},{"types":["Item","IntoIter"],"methods":["into_iter"],"consts":[]},{"types":["NonZeroInner"],"methods":[],"consts":[]},{"types":[],"methods":["clone","clone_from"],"consts":[]},{"types":[],"methods":["call_mut"],"consts":[]},{"types":[],"methods":["from_iter"],"consts":[]},{"types":["Output","Residual"],"methods":["from_output","branch"],"consts":[]},{"types":["TryType"],"methods":[],"consts":[]},{"types":[],"methods":["extend","extend_one","extend_reserve","extend_one_unchecked"],"consts":[]},{"types":[],"methods":["default"],"consts":[]},{"types":[],"methods":["next_back","advance_back_by","nth_back","try_rfold","rfold","rfind"],"consts":[]},{"types":[],"methods":["len","is_empty"],"consts":[]},{"types":[],"methods":["cmp","max","min","clamp"],"consts":[]},{"types":[],"methods":[],"consts":[]},{"types":[],"methods":["sum"],"consts":[]},{"types":[],"methods":["product"],"consts":[]},{"types":[],"methods":["partial_cmp","lt","le","gt","ge","__chaining_lt","__chaining_le","__chaining_gt","__chaining_ge"],"consts":[]},{"types":[],"methods":["eq","ne"],"consts":[]},{"types":[],"methods":["size"],"consts":["MAY_HAVE_SIDE_EFFECT"]},{"types":[],"methods":["from_residual"],"consts":[]},{"types":[],"methods":[],"consts":[]},{"types":[],"methods":[],"consts":[]},{"types":[],"methods":["assert_receiver_is_total_eq"],"consts":[]}],"short_names":[{"key":{"TraitImpl":0},"value":[{"Impl":{"Trait":0}}]},{"key":{"Fun":10},"value":[{"Impl":{"Trait":0}},{"Ident":["into_iter",0]}]},{"key":{"TraitImpl":1},"value":[{"Impl":{"Trait":1}}]},{"key":{"Fun":11},"value":[{"Impl":{"Trait":1}},{"Ident":["next",0]}]},{"key":{"TraitImpl":2},"value":[{"Impl":{"Trait":2}}]},{"key":{"Fun":13},"value":[{"Impl":{"Trait":2}},{"Ident":["into_iter",0]}]},{"key":{"TraitImpl":3},"value":[{"Impl":{"Trait":3}}]},{"key":{"Fun":14},"value":[{"Impl":{"Trait":3}},{"Ident":["branch",0]}]},{"key":{"TraitImpl":4},"value":[{"Impl":{"Trait":4}}]},{"key":{"Fun":15},"value":[{"Impl":{"Trait":4}},{"Ident":["from_residual",0]}]},{"key":{"TraitImpl":5},"value":[{"Impl":{"Trait":5}}]},{"key":{"TraitImpl":6},"value":[{"Impl":{"Trait":6}}]},{"key":{"TraitImpl":7},"value":[{"Impl":{"Trait":7}}]},{"key":{"TraitImpl":8},"value":[{"Impl":{"Trait":8}}]},{"key":{"Fun":18},"value":[{"Impl":{"Trait":8}},{"Ident":["branch",0]}]},{"key":{"TraitImpl":9},"value":[{"Impl":{"Trait":9}}]},{"key":{"Fun":19},"value":[{"Impl":{"Trait":9}},{"Ident":["from_residual",0]}]},{"key":{"Global":0},"value":[{"Impl":{"Trait":0}},{"Ident":["{vtable}",0]}]},{"key":{"Global":1},"value":[{"Impl":{"Trait":1}},{"Ident":["{vtable}",0]}]},{"key":{"Fun":20},"value":[{"Impl":{"Trait":1}},{"Ident":["next_chunk",0]}]},{"key":{"Fun":21},"value":[{"Impl":{"Trait":1}},{"Ident":["size_hint",0]}]},{"key":{"Fun":22},"value":[{"Impl":{"Trait":1}},{"Ident":["count",0]}]},{"key":{"Fun":23},"value":[{"Impl":{"Trait":1}},{"Ident":["last",0]}]},{"key":{"Fun":24},"value":[{"Impl":{"Trait":1}},{"Ident":["advance_by",0]}]},{"key":{"Fun":25},"value":[{"Impl":{"Trait":1}},{"Ident":["nth",0]}]},{"key":{"Fun":26},"value":[{"Impl":{"Trait":1}},{"Ident":["step_by",0]}]},{"key":{"TraitImpl":10},"value":[{"Impl":{"Trait":10}}]},{"key":{"Fun":104},"value":[{"Impl":{"Trait":1}},{"Ident":["chain",0]}]},{"key":{"Fun":105},"value":[{"Impl":{"Trait":1}},{"Ident":["zip",0]}]},{"key":{"Fun":106},"value":[{"Impl":{"Trait":1}},{"Ident":["intersperse",0]}]},{"key":{"Fun":107},"value":[{"Impl":{"Trait":1}},{"Ident":["intersperse_with",0]}]},{"key":{"Fun":108},"value":[{"Impl":{"Trait":1}},{"Ident":["map",0]}]},{"key":{"Fun":109},"value":[{"Impl":{"Trait":1}},{"Ident":["for_each",0]}]},{"key":{"Fun":110},"value":[{"Impl":{"Trait":1}},{"Ident":["filter",0]}]},{"key":{"Fun":111},"value":[{"Impl":{"Trait":1}},{"Ident":["filter_map",0]}]},{"key":{"Fun":112},"value":[{"Impl":{"Trait":1}},{"Ident":["enumerate",0]}]},{"key":{"Fun":113},"value":[{"Impl":{"Trait":1}},{"Ident":["peekable",0]}]},{"key":{"Fun":114},"value":[{"Impl":{"Trait":1}},{"Ident":["skip_while",0]}]},{"key":{"Fun":115},"value":[{"Impl":{"Trait":1}},{"Ident":["take_while",0]}]},{"key":{"Fun":116},"value":[{"Impl":{"Trait":1}},{"Ident":["map_while",0]}]},{"key":{"Fun":117},"value":[{"Impl":{"Trait":1}},{"Ident":["skip",0]}]},{"key":{"Fun":118},"value":[{"Impl":{"Trait":1}},{"Ident":["take",0]}]},{"key":{"Fun":119},"value":[{"Impl":{"Trait":1}},{"Ident":["scan",0]}]},{"key":{"Fun":120},"value":[{"Impl":{"Trait":1}},{"Ident":["flat_map",0]}]},{"key":{"Fun":121},"value":[{"Impl":{"Trait":1}},{"Ident":["flatten",0]}]},{"key":{"Fun":122},"value":[{"Impl":{"Trait":1}},{"Ident":["map_windows",0]}]},{"key":{"Fun":123},"value":[{"Impl":{"Trait":1}},{"Ident":["fuse",0]}]},{"key":{"Fun":124},"value":[{"Impl":{"Trait":1}},{"Ident":["inspect",0]}]},{"key":{"Fun":125},"value":[{"Impl":{"Trait":1}},{"Ident":["by_ref",0]}]},{"key":{"Fun":126},"value":[{"Impl":{"Trait":1}},{"Ident":["collect",0]}]},{"key":{"Fun":127},"value":[{"Impl":{"Trait":1}},{"Ident":["try_collect",0]}]},{"key":{"Fun":128},"value":[{"Impl":{"Trait":1}},{"Ident":["collect_into",0]}]},{"key":{"Fun":129},"value":[{"Impl":{"Trait":1}},{"Ident":["partition",0]}]},{"key":{"Fun":130},"value":[{"Impl":{"Trait":1}},{"Ident":["partition_in_place",0]}]},{"key":{"Fun":131},"value":[{"Impl":{"Trait":1}},{"Ident":["is_partitioned",0]}]},{"key":{"Fun":132},"value":[{"Impl":{"Trait":1}},{"Ident":["try_fold",0]}]},{"key":{"Fun":133},"value":[{"Impl":{"Trait":1}},{"Ident":["try_for_each",0]}]},{"key":{"Fun":134},"value":[{"Impl":{"Trait":1}},{"Ident":["fold",0]}]},{"key":{"Fun":135},"value":[{"Impl":{"Trait":1}},{"Ident":["reduce",0]}]},{"key":{"Fun":136},"value":[{"Impl":{"Trait":1}},{"Ident":["try_reduce",0]}]},{"key":{"Fun":137},"value":[{"Impl":{"Trait":1}},{"Ident":["all",0]}]},{"key":{"Fun":138},"value":[{"Impl":{"Trait":1}},{"Ident":["any",0]}]},{"key":{"Fun":139},"value":[{"Impl":{"Trait":1}},{"Ident":["find",0]}]},{"key":{"Fun":140},"value":[{"Impl":{"Trait":1}},{"Ident":["find_map",0]}]},{"key":{"Fun":141},"value":[{"Impl":{"Trait":1}},{"Ident":["try_find",0]}]},{"key":{"Fun":142},"value":[{"Impl":{"Trait":1}},{"Ident":["position",0]}]},{"key":{"Fun":143},"value":[{"Impl":{"Trait":1}},{"Ident":["rposition",0]}]},{"key":{"Fun":144},"value":[{"Impl":{"Trait":1}},{"Ident":["max",0]}]},{"key":{"Fun":145},"value":[{"Impl":{"Trait":1}},{"Ident":["min",0]}]},{"key":{"Fun":146},"value":[{"Impl":{"Trait":1}},{"Ident":["max_by_key",0]}]},{"key":{"Fun":147},"value":[{"Impl":{"Trait":1}},{"Ident":["max_by",0]}]},{"key":{"Fun":148},"value":[{"Impl":{"Trait":1}},{"Ident":["min_by_key",0]}]},{"key":{"Fun":149},"value":[{"Impl":{"Trait":1}},{"Ident":["min_by",0]}]},{"key":{"Fun":150},"value":[{"Impl":{"Trait":1}},{"Ident":["rev",0]}]},{"key":{"Fun":151},"value":[{"Impl":{"Trait":1}},{"Ident":["unzip",0]}]},{"key":{"Fun":152},"value":[{"Impl":{"Trait":1}},{"Ident":["copied",0]}]},{"key":{"Fun":153},"value":[{"Impl":{"Trait":1}},{"Ident":["cloned",0]}]},{"key":{"Fun":154},"value":[{"Impl":{"Trait":1}},{"Ident":["cycle",0]}]},{"key":{"Fun":155},"value":[{"Impl":{"Trait":1}},{"Ident":["array_chunks",0]}]},{"key":{"Fun":156},"value":[{"Impl":{"Trait":1}},{"Ident":["sum",0]}]},{"key":{"Fun":157},"value":[{"Impl":{"Trait":1}},{"Ident":["product",0]}]},{"key":{"Fun":158},"value":[{"Impl":{"Trait":1}},{"Ident":["cmp",0]}]},{"key":{"Fun":159},"value":[{"Impl":{"Trait":1}},{"Ident":["cmp_by",0]}]},{"key":{"Fun":160},"value":[{"Impl":{"Trait":1}},{"Ident":["partial_cmp",0]}]},{"key":{"Fun":161},"value":[{"Impl":{"Trait":1}},{"Ident":["partial_cmp_by",0]}]},{"key":{"Fun":162},"value":[{"Impl":{"Trait":1}},{"Ident":["eq",0]}]},{"key":{"Fun":163},"value":[{"Impl":{"Trait":1}},{"Ident":["eq_by",0]}]},{"key":{"Fun":164},"value":[{"Impl":{"Trait":1}},{"Ident":["ne",0]}]},{"key":{"Fun":165},"value":[{"Impl":{"Trait":1}},{"Ident":["lt",0]}]},{"key":{"Fun":166},"value":[{"Impl":{"Trait":1}},{"Ident":["le",0]}]},{"key":{"Fun":167},"value":[{"Impl":{"Trait":1}},{"Ident":["gt",0]}]},{"key":{"Fun":168},"value":[{"Impl":{"Trait":1}},{"Ident":["ge",0]}]},{"key":{"Fun":169},"value":[{"Impl":{"Trait":1}},{"Ident":["is_sorted",0]}]},{"key":{"Fun":170},"value":[{"Impl":{"Trait":1}},{"Ident":["is_sorted_by",0]}]},{"key":{"Fun":171},"value":[{"Impl":{"Trait":1}},{"Ident":["is_sorted_by_key",0]}]},{"key":{"Fun":172},"value":[{"Impl":{"Trait":1}},{"Ident":["__iterator_get_unchecked",0]}]},{"key":{"Global":2},"value":[{"Impl":{"Trait":2}},{"Ident":["{vtable}",0]}]},{"key":{"Fun":173},"value":[{"Impl":{"Trait":3}},{"Ident":["from_output",0]}]},{"key":{"Fun":175},"value":[{"Impl":{"Trait":5}},{"Ident":["from",0]}]},{"key":{"Fun":176},"value":[{"Impl":{"Trait":6}},{"Ident":["call_once",0]}]},{"key":{"Fun":179},"value":[{"Impl":{"Trait":7}},{"Ident":["drop_in_place",0]}]},{"key":{"Fun":180},"value":[{"Impl":{"Trait":8}},{"Ident":["from_output",0]}]},{"key":{"TraitImpl":11},"value":[{"Impl":{"Trait":11}}]},{"key":{"TraitImpl":12},"value":[{"Impl":{"Trait":12}}]},{"key":{"TraitImpl":13},"value":[{"Impl":{"Trait":13}}]},{"key":{"TraitImpl":14},"value":[{"Impl":{"Trait":14}}]},{"key":{"Global":3},"value":[{"Impl":{"Trait":12}},{"Ident":["{vtable}",0]}]},{"key":{"TraitImpl":15},"value":[{"Impl":{"Trait":15}}]},{"key":{"Fun":221},"value":[{"Impl":{"Trait":14}},{"Ident":["clone",0]}]},{"key":{"Fun":222},"value":[{"Impl":{"Trait":14}},{"Ident":["clone_from",0]}]},{"key":{"Fun":223},"value":[{"Impl":{"Trait":15}},{"Ident":["clone",0]}]},{"key":{"Fun":224},"value":[{"Impl":{"Trait":15}},{"Ident":["clone_from",0]}]},{"key":{"Fun":193},"value":[{"Ident":["next_back",0]}]},{"key":{"Fun":189},"value":[{"Ident":["extend_one",0]}]},{"key":{"Type":28},"value":[{"Ident":["Scan",0]}]},{"key":{"Fun":1},"value":[{"Ident":["branch_loop_sum",0]}]},{"key":{"Type":26},"value":[{"Ident":["Skip",0]}]},{"key":{"Fun":199},"value":[{"Ident":["len",0]}]},{"key":{"TraitDecl":22},"value":[{"Ident":["PartialEq",0]}]},{"key":{"Fun":4},"value":[{"Ident":["desugar_mix",0]}]},{"key":{"Type":18},"value":[{"Ident":["Map",0]}]},{"key":{"Type":17},"value":[{"Ident":["IntersperseWith",0]}]},{"key":{"TraitDecl":19},"value":[{"Ident":["Sum",0]}]},{"key":{"TraitDecl":11},"value":[{"Ident":["Try",0]}]},{"key":{"Type":19},"value":[{"Ident":["Filter",0]}]},{"key":{"Fun":204},"value":[{"Ident":["clamp",0]}]},{"key":{"Type":6},"value":[{"Ident":["ControlFlow",0]}]},{"key":{"Type":29},"value":[{"Ident":["FlatMap",0]}]},{"key":{"Type":25},"value":[{"Ident":["MapWhile",0]}]},{"key":{"TraitDecl":8},"value":[{"Ident":["Clone",0]}]},{"key":{"Type":27},"value":[{"Ident":["Take",0]}]},{"key":{"Fun":188},"value":[{"Ident":["extend",0]}]},{"key":{"Fun":191},"value":[{"Ident":["extend_one_unchecked",0]}]},{"key":{"Type":3},"value":[{"Ident":["Result",0]}]},{"key":{"TraitDecl":4},"value":[{"Ident":["FnOnce",0]}]},{"key":{"Fun":12},"value":[{"Ident":["iter",0]}]},{"key":{"Fun":184},"value":[{"Ident":["call_mut",0]}]},{"key":{"Fun":3},"value":[{"Ident":["parse_one",0]}]},{"key":{"TraitDecl":1},"value":[{"Ident":["MetaSized",0]}]},{"key":{"Fun":2},"value":[{"Ident":["strategy_len",0]}]},{"key":{"Type":39},"value":[{"Ident":["ArrayChunks",0]}]},{"key":{"Type":14},"value":[{"Ident":["Chain",0]}]},{"key":{"TraitDecl":24},"value":[{"Ident":["FromResidual",0]}]},{"key":{"Fun":195},"value":[{"Ident":["nth_back",0]}]},{"key":{"Type":1},"value":[{"Ident":["Strategy",0]}]},{"key":{"Fun":213},"value":[{"Ident":["__chaining_le",0]}]},{"key":{"Type":2},"value":[{"Ident":["Token",0]}]},{"key":{"Type":33},"value":[{"Ident":["Inspect",0]}]},{"key":{"Type":16},"value":[{"Ident":["Intersperse",0]}]},{"key":{"Type":36},"value":[{"Ident":["Copied",0]}]},{"key":{"TraitDecl":10},"value":[{"Ident":["FromIterator",0]}]},{"key":{"Type":42},"value":[{"Ident":["NonZeroUsizeInner",0]}]},{"key":{"Fun":6},"value":[{"Ident":["bool_then_closure",0]}]},{"key":{"Fun":16},"value":[{"Ident":["then",0]}]},{"key":{"Fun":9},"value":[{"Ident":["option_question_mark",0]}]},{"key":{"Type":30},"value":[{"Ident":["Flatten",0]}]},{"key":{"Fun":8},"value":[{"Ident":["option_source",0]}]},{"key":{"Fun":185},"value":[{"Ident":["from_iter",0]}]},{"key":{"Type":8},"value":[{"Ident":["closure",0]}]},{"key":{"Fun":192},"value":[{"Ident":["default",0]}]},{"key":{"Fun":214},"value":[{"Ident":["__chaining_gt",0]}]},{"key":{"Fun":194},"value":[{"Ident":["advance_back_by",0]}]},{"key":{"Fun":196},"value":[{"Ident":["try_rfold",0]}]},{"key":{"Fun":5},"value":[{"Ident":["tuple_roundtrip",0]}]},{"key":{"TraitDecl":6},"value":[{"Ident":["IntoIterator",0]}]},{"key":{"TraitDecl":13},"value":[{"Ident":["Extend",0]}]},{"key":{"Fun":200},"value":[{"Ident":["is_empty",0]}]},{"key":{"TraitDecl":12},"value":[{"Ident":["Residual",0]}]},{"key":{"TraitDecl":16},"value":[{"Ident":["ExactSizeIterator",0]}]},{"key":{"Fun":218},"value":[{"Ident":["size",0]}]},{"key":{"Fun":198},"value":[{"Ident":["rfind",0]}]},{"key":{"Type":4},"value":[{"Ident":["Iter",0]}]},{"key":{"Fun":17},"value":[{"Ident":["then_some",0]}]},{"key":{"Type":35},"value":[{"Ident":["Rev",0]}]},{"key":{"TraitDecl":20},"value":[{"Ident":["Product",0]}]},{"key":{"TraitDecl":5},"value":[{"Ident":["Destruct",0]}]},{"key":{"TraitDecl":21},"value":[{"Ident":["PartialOrd",0]}]},{"key":{"Type":11},"value":[{"Ident":["IntoIter",0]}]},{"key":{"Type":12},"value":[{"Ident":["NonZero",0]}]},{"key":{"Type":32},"value":[{"Ident":["Fuse",0]}]},{"key":{"TraitDecl":7},"value":[{"Ident":["ZeroablePrimitive",0]}]},{"key":{"Type":5},"value":[{"Ident":["Option",0]}]},{"key":{"TraitDecl":14},"value":[{"Ident":["Default",0]}]},{"key":{"Type":37},"value":[{"Ident":["Cloned",0]}]},{"key":{"Type":21},"value":[{"Ident":["Enumerate",0]}]},{"key":{"TraitDecl":17},"value":[{"Ident":["Ord",0]}]},{"key":{"TraitDecl":27},"value":[{"Ident":["Eq",0]}]},{"key":{"Fun":215},"value":[{"Ident":["__chaining_ge",0]}]},{"key":{"Fun":220},"value":[{"Ident":["assert_receiver_is_total_eq",0]}]},{"key":{"Type":13},"value":[{"Ident":["StepBy",0]}]},{"key":{"Type":7},"value":[{"Ident":["Infallible",0]}]},{"key":{"Fun":0},"value":[{"Ident":["straight_line_add",0]}]},{"key":{"TraitDecl":2},"value":[{"Ident":["Iterator",0]}]},{"key":{"TraitDecl":23},"value":[{"Ident":["TrustedRandomAccessNoCoerce",0]}]},{"key":{"Fun":190},"value":[{"Ident":["extend_reserve",0]}]},{"key":{"Type":15},"value":[{"Ident":["Zip",0]}]},{"key":{"TraitDecl":9},"value":[{"Ident":["FnMut",0]}]},{"key":{"Type":22},"value":[{"Ident":["Peekable",0]}]},{"key":{"Fun":7},"value":[{"Ident":["bool_then_some",0]}]},{"key":{"TraitDecl":26},"value":[{"Ident":["Sealed",0]}]},{"key":{"Fun":212},"value":[{"Ident":["__chaining_lt",0]}]},{"key":{"TraitDecl":0},"value":[{"Ident":["Sized",0]}]},{"key":{"Type":23},"value":[{"Ident":["SkipWhile",0]}]},{"key":{"TraitDecl":18},"value":[{"Ident":["Copy",0]}]},{"key":{"Type":31},"value":[{"Ident":["MapWindows",0]}]},{"key":{"TraitDecl":25},"value":[{"Ident":["Tuple",0]}]},{"key":{"Fun":197},"value":[{"Ident":["rfold",0]}]},{"key":{"Type":0},"value":[{"Ident":["PyResult",0]}]},{"key":{"TraitDecl":3},"value":[{"Ident":["From",0]}]},{"key":{"Type":20},"value":[{"Ident":["FilterMap",0]}]},{"key":{"TraitDecl":15},"value":[{"Ident":["DoubleEndedIterator",0]}]},{"key":{"Type":24},"value":[{"Ident":["TakeWhile",0]}]},{"key":{"Type":34},"value":[{"Ident":["Ordering",0]}]},{"key":{"Type":38},"value":[{"Ident":["Cycle",0]}]}],"type_decls":[{"def_id":0,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["PyResult",0]}],"span":{"data":{"file_id":0,"beg":{"line":10,"col":0},"end":{"line":10,"col":47}},"generated_from_span":null},"source_text":"pub type PyResult = Result;","attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":10,"col":18},"end":{"line":10,"col":19}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":{"Alias":{"HashConsedValue":[6131,{"Adt":{"id":{"Adt":3},"generics":{"regions":[],"types":[{"Deduplicated":173},{"HashConsedValue":[175,{"Ref":["Static",{"HashConsedValue":[174,{"Adt":{"id":{"Builtin":"Str"},"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}}]},"Shared"]}]}],"const_generics":[],"trait_refs":[{"HashConsedValue":[176,{"kind":{"Clause":{"Bound":[0,0]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[182,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[181,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[180,{"Ref":["Erased",{"Deduplicated":174},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":175}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},"layout":[],"ptr_metadata":"None"},{"def_id":1,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["Strategy",0]}],"span":{"data":{"file_id":0,"beg":{"line":38,"col":0},"end":{"line":42,"col":1}},"generated_from_span":null},"source_text":"pub enum Strategy {\n Empty,\n IntKeyed { len: usize },\n StrKeyed { len: usize, capacity: usize },\n}","attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":{"Enum":[{"id":0,"span":{"data":{"file_id":0,"beg":{"line":39,"col":4},"end":{"line":39,"col":9}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":"Empty","fields":[],"discriminant":{"Scalar":{"Signed":["Isize","0"]}}},{"id":1,"span":{"data":{"file_id":0,"beg":{"line":40,"col":4},"end":{"line":40,"col":12}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":"IntKeyed","fields":[{"span":{"data":{"file_id":0,"beg":{"line":40,"col":15},"end":{"line":40,"col":25}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":"len","ty":{"HashConsedValue":[591,{"Literal":{"UInt":"Usize"}}]}}],"discriminant":{"Scalar":{"Signed":["Isize","1"]}}},{"id":2,"span":{"data":{"file_id":0,"beg":{"line":41,"col":4},"end":{"line":41,"col":12}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":"StrKeyed","fields":[{"span":{"data":{"file_id":0,"beg":{"line":41,"col":15},"end":{"line":41,"col":25}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":"len","ty":{"Deduplicated":591}},{"span":{"data":{"file_id":0,"beg":{"line":41,"col":27},"end":{"line":41,"col":42}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":"capacity","ty":{"Deduplicated":591}}],"discriminant":{"Scalar":{"Signed":["Isize","2"]}}}]},"layout":[{"key":"aarch64-apple-darwin","value":{"size":24,"align":8,"discriminator":{"Branch":{"offset":0,"int_ty":{"Unsigned":"U64"},"children":[[{"start":{"Unsigned":["U64","0"]},"end":{"Unsigned":["U64","0"]}},{"Known":0}],[{"start":{"Unsigned":["U64","1"]},"end":{"Unsigned":["U64","1"]}},{"Known":1}],[{"start":{"Unsigned":["U64","2"]},"end":{"Unsigned":["U64","2"]}},{"Known":2}]],"fallback":"Invalid"}},"uninhabited":false,"variant_layouts":[{"field_offsets":[],"uninhabited":false,"tagger":[[0,{"Unsigned":["U64","0"]}]]},{"field_offsets":[8],"uninhabited":false,"tagger":[[0,{"Unsigned":["U64","1"]}]]},{"field_offsets":[8,16],"uninhabited":false,"tagger":[[0,{"Unsigned":["U64","2"]}]]}],"repr":{"repr_algo":"Rust","align_modif":null,"transparent":false,"explicit_discr_type":false}}}],"ptr_metadata":"None"},{"def_id":2,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["Token",0]}],"span":{"data":{"file_id":0,"beg":{"line":55,"col":0},"end":{"line":59,"col":1}},"generated_from_span":null},"source_text":"pub enum Token {\n Add(i64),\n Sub(i64),\n Halt,\n}","attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":{"Enum":[{"id":0,"span":{"data":{"file_id":0,"beg":{"line":56,"col":4},"end":{"line":56,"col":7}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":"Add","fields":[{"span":{"data":{"file_id":0,"beg":{"line":56,"col":8},"end":{"line":56,"col":11}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":null,"ty":{"HashConsedValue":[184,{"Literal":{"Int":"I64"}}]}}],"discriminant":{"Scalar":{"Signed":["Isize","0"]}}},{"id":1,"span":{"data":{"file_id":0,"beg":{"line":57,"col":4},"end":{"line":57,"col":7}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":"Sub","fields":[{"span":{"data":{"file_id":0,"beg":{"line":57,"col":8},"end":{"line":57,"col":11}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":null,"ty":{"Deduplicated":184}}],"discriminant":{"Scalar":{"Signed":["Isize","1"]}}},{"id":2,"span":{"data":{"file_id":0,"beg":{"line":58,"col":4},"end":{"line":58,"col":8}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":"Halt","fields":[],"discriminant":{"Scalar":{"Signed":["Isize","2"]}}}]},"layout":[{"key":"aarch64-apple-darwin","value":{"size":16,"align":8,"discriminator":{"Branch":{"offset":0,"int_ty":{"Unsigned":"U64"},"children":[[{"start":{"Unsigned":["U64","0"]},"end":{"Unsigned":["U64","0"]}},{"Known":0}],[{"start":{"Unsigned":["U64","1"]},"end":{"Unsigned":["U64","1"]}},{"Known":1}],[{"start":{"Unsigned":["U64","2"]},"end":{"Unsigned":["U64","2"]}},{"Known":2}]],"fallback":"Invalid"}},"uninhabited":false,"variant_layouts":[{"field_offsets":[8],"uninhabited":false,"tagger":[[0,{"Unsigned":["U64","0"]}]]},{"field_offsets":[8],"uninhabited":false,"tagger":[[0,{"Unsigned":["U64","1"]}]]},{"field_offsets":[],"uninhabited":false,"tagger":[[0,{"Unsigned":["U64","2"]}]]}],"repr":{"repr_algo":"Rust","align_modif":null,"transparent":false,"explicit_discr_type":false}}}],"ptr_metadata":"None"},{"def_id":3,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["result",0]},{"Ident":["Result",0]}],"span":{"data":{"file_id":3,"beg":{"line":557,"col":0},"end":{"line":557,"col":21}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" `Result` is a type that represents either success ([`Ok`]) or failure ([`Err`])."},{"DocComment":""},{"DocComment":" See the [module documentation](self) for details."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"Result"},"generics":{"regions":[],"types":[{"index":0,"name":"T"},{"index":1,"name":"E"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":3,"beg":{"line":557,"col":16},"end":{"line":557,"col":17}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":3,"beg":{"line":557,"col":19},"end":{"line":557,"col":20}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[1551,{"TypeVar":{"Bound":[1,1]}}]}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":{"Enum":[{"id":0,"span":{"data":{"file_id":3,"beg":{"line":561,"col":4},"end":{"line":561,"col":6}},"generated_from_span":null},"attr_info":{"attributes":[{"DocComment":" Contains the success value"}],"inline":null,"rename":null,"public":true},"name":"Ok","fields":[{"span":{"data":{"file_id":3,"beg":{"line":561,"col":53},"end":{"line":561,"col":54}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":null,"ty":{"Deduplicated":173}}],"discriminant":{"Scalar":{"Signed":["Isize","0"]}}},{"id":1,"span":{"data":{"file_id":3,"beg":{"line":566,"col":4},"end":{"line":566,"col":7}},"generated_from_span":null},"attr_info":{"attributes":[{"DocComment":" Contains the error value"}],"inline":null,"rename":null,"public":true},"name":"Err","fields":[{"span":{"data":{"file_id":3,"beg":{"line":566,"col":54},"end":{"line":566,"col":55}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":null,"ty":{"HashConsedValue":[1555,{"TypeVar":{"Bound":[0,1]}}]}}],"discriminant":{"Scalar":{"Signed":["Isize","1"]}}}]},"layout":[],"ptr_metadata":"None"},{"def_id":4,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Ident":["Iter",0]}],"span":{"data":{"file_id":4,"beg":{"line":69,"col":0},"end":{"line":69,"col":26}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Immutable slice iterator"},{"DocComment":""},{"DocComment":" This struct is created by the [`iter`] method on [slices]."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" Basic usage:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" // First, we need a slice to call the `iter` method on:"},{"DocComment":" let slice = &[1, 2, 3];"},{"DocComment":""},{"DocComment":" // Then we call `iter` on the slice to get the `Iter` iterator,"},{"DocComment":" // and iterate over it:"},{"DocComment":" for element in slice.iter() {"},{"DocComment":" println!(\"{element}\");"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" // This for loop actually already works without calling `iter`:"},{"DocComment":" for element in slice {"},{"DocComment":" println!(\"{element}\");"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" [`iter`]: slice::iter"},{"DocComment":" [slices]: slice"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"SliceIter"},"generics":{"regions":[{"index":0,"name":"'a","mutability":"Shared"}],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":4,"beg":{"line":69,"col":20},"end":{"line":69,"col":21}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[{"regions":[],"skip_binder":[{"Deduplicated":141},{"Var":{"Bound":[1,0]}}]},{"regions":[],"skip_binder":[{"Deduplicated":141},{"Var":{"Bound":[1,0]}}]}],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[{"key":"aarch64-apple-darwin","value":{"size":16,"align":8,"discriminator":{"Known":0},"uninhabited":false,"variant_layouts":[{"field_offsets":[0,8,16],"uninhabited":false,"tagger":[]}],"repr":{"repr_algo":"Rust","align_modif":null,"transparent":false,"explicit_discr_type":false}}}],"ptr_metadata":"None"},{"def_id":5,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["option",0]},{"Ident":["Option",0]}],"span":{"data":{"file_id":6,"beg":{"line":600,"col":0},"end":{"line":600,"col":18}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" The `Option` type. See [the module level documentation](self) for more."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"Option"},"generics":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":6,"beg":{"line":600,"col":16},"end":{"line":600,"col":17}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":{"Enum":[{"id":0,"span":{"data":{"file_id":6,"beg":{"line":604,"col":4},"end":{"line":604,"col":8}},"generated_from_span":null},"attr_info":{"attributes":[{"DocComment":" No value."}],"inline":null,"rename":null,"public":true},"name":"None","fields":[],"discriminant":{"Scalar":{"Signed":["Isize","0"]}}},{"id":1,"span":{"data":{"file_id":6,"beg":{"line":608,"col":4},"end":{"line":608,"col":8}},"generated_from_span":null},"attr_info":{"attributes":[{"DocComment":" Some value of type `T`."}],"inline":null,"rename":null,"public":true},"name":"Some","fields":[{"span":{"data":{"file_id":6,"beg":{"line":608,"col":55},"end":{"line":608,"col":56}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":null,"ty":{"Deduplicated":173}}],"discriminant":{"Scalar":{"Signed":["Isize","1"]}}}]},"layout":[],"ptr_metadata":"None"},{"def_id":6,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["control_flow",0]},{"Ident":["ControlFlow",0]}],"span":{"data":{"file_id":8,"beg":{"line":89,"col":0},"end":{"line":89,"col":31}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Used to tell an operation whether it should exit early or go on as usual."},{"DocComment":""},{"DocComment":" This is used when exposing things (like graph traversals or visitors) where"},{"DocComment":" you want the user to be able to choose whether to exit early."},{"DocComment":" Having the enum makes it clearer -- no more wondering \"wait, what did `false`"},{"DocComment":" mean again?\" -- and allows including a value."},{"DocComment":""},{"DocComment":" Similar to [`Option`] and [`Result`], this enum can be used with the `?` operator"},{"DocComment":" to return immediately if the [`Break`] variant is present or otherwise continue normally"},{"DocComment":" with the value inside the [`Continue`] variant."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" Early-exiting from [`Iterator::try_for_each`]:"},{"DocComment":" ```"},{"DocComment":" use std::ops::ControlFlow;"},{"DocComment":""},{"DocComment":" let r = (2..100).try_for_each(|x| {"},{"DocComment":" if 403 % x == 0 {"},{"DocComment":" return ControlFlow::Break(x)"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" ControlFlow::Continue(())"},{"DocComment":" });"},{"DocComment":" assert_eq!(r, ControlFlow::Break(13));"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" A basic tree traversal:"},{"DocComment":" ```"},{"DocComment":" use std::ops::ControlFlow;"},{"DocComment":""},{"DocComment":" pub struct TreeNode {"},{"DocComment":" value: T,"},{"DocComment":" left: Option>>,"},{"DocComment":" right: Option>>,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl TreeNode {"},{"DocComment":" pub fn traverse_inorder(&self, f: &mut impl FnMut(&T) -> ControlFlow) -> ControlFlow {"},{"DocComment":" if let Some(left) = &self.left {"},{"DocComment":" left.traverse_inorder(f)?;"},{"DocComment":" }"},{"DocComment":" f(&self.value)?;"},{"DocComment":" if let Some(right) = &self.right {"},{"DocComment":" right.traverse_inorder(f)?;"},{"DocComment":" }"},{"DocComment":" ControlFlow::Continue(())"},{"DocComment":" }"},{"DocComment":" fn leaf(value: T) -> Option>> {"},{"DocComment":" Some(Box::new(Self { value, left: None, right: None }))"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" let node = TreeNode {"},{"DocComment":" value: 0,"},{"DocComment":" left: TreeNode::leaf(1),"},{"DocComment":" right: Some(Box::new(TreeNode {"},{"DocComment":" value: -1,"},{"DocComment":" left: TreeNode::leaf(5),"},{"DocComment":" right: TreeNode::leaf(2),"},{"DocComment":" }))"},{"DocComment":" };"},{"DocComment":" let mut sum = 0;"},{"DocComment":""},{"DocComment":" let res = node.traverse_inorder(&mut |val| {"},{"DocComment":" if *val < 0 {"},{"DocComment":" ControlFlow::Break(*val)"},{"DocComment":" } else {"},{"DocComment":" sum += *val;"},{"DocComment":" ControlFlow::Continue(())"},{"DocComment":" }"},{"DocComment":" });"},{"DocComment":" assert_eq!(res, ControlFlow::Break(-1));"},{"DocComment":" assert_eq!(sum, 6);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" [`Break`]: ControlFlow::Break"},{"DocComment":" [`Continue`]: ControlFlow::Continue"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"ControlFlow"},"generics":{"regions":[],"types":[{"index":0,"name":"B"},{"index":1,"name":"C"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":8,"beg":{"line":89,"col":21},"end":{"line":89,"col":22}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":8,"beg":{"line":89,"col":24},"end":{"line":89,"col":30}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":{"Enum":[{"id":0,"span":{"data":{"file_id":8,"beg":{"line":93,"col":4},"end":{"line":93,"col":12}},"generated_from_span":null},"attr_info":{"attributes":[{"DocComment":" Move on to the next phase of the operation as normal."}],"inline":null,"rename":null,"public":true},"name":"Continue","fields":[{"span":{"data":{"file_id":8,"beg":{"line":93,"col":13},"end":{"line":93,"col":14}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":null,"ty":{"Deduplicated":1555}}],"discriminant":{"Scalar":{"Signed":["Isize","0"]}}},{"id":1,"span":{"data":{"file_id":8,"beg":{"line":97,"col":4},"end":{"line":97,"col":9}},"generated_from_span":null},"attr_info":{"attributes":[{"DocComment":" Exit the operation without running subsequent phases."}],"inline":null,"rename":null,"public":true},"name":"Break","fields":[{"span":{"data":{"file_id":8,"beg":{"line":97,"col":10},"end":{"line":97,"col":11}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":null,"ty":{"Deduplicated":173}}],"discriminant":{"Scalar":{"Signed":["Isize","1"]}}}]},"layout":[],"ptr_metadata":"None"},{"def_id":7,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["convert",0]},{"Ident":["Infallible",0]}],"span":{"data":{"file_id":10,"beg":{"line":930,"col":0},"end":{"line":930,"col":19}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" The error type for errors that can never happen."},{"DocComment":""},{"DocComment":" Since this enum has no variant, a value of this type can never actually exist."},{"DocComment":" This can be useful for generic APIs that use [`Result`] and parameterize the error type,"},{"DocComment":" to indicate that the result is always [`Ok`]."},{"DocComment":""},{"DocComment":" For example, the [`TryFrom`] trait (conversion that returns a [`Result`])"},{"DocComment":" has a blanket implementation for all types where a reverse [`Into`] implementation exists."},{"DocComment":""},{"DocComment":" ```ignore (illustrates std code, duplicating the impl in a doctest would be an error)"},{"DocComment":" impl TryFrom for T where U: Into {"},{"DocComment":" type Error = Infallible;"},{"DocComment":""},{"DocComment":" fn try_from(value: U) -> Result {"},{"DocComment":" Ok(U::into(value)) // Never returns `Err`"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" # Future compatibility"},{"DocComment":""},{"DocComment":" This enum has the same role as [the `!` “never” type][never],"},{"DocComment":" which is unstable in this version of Rust."},{"DocComment":" When `!` is stabilized, we plan to make `Infallible` a type alias to it:"},{"DocComment":""},{"DocComment":" ```ignore (illustrates future std change)"},{"DocComment":" pub type Infallible = !;"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" … and eventually deprecate `Infallible`."},{"DocComment":""},{"DocComment":" However there is one case where `!` syntax can be used"},{"DocComment":" before `!` is stabilized as a full-fledged type: in the position of a function’s return type."},{"DocComment":" Specifically, it is possible to have implementations for two different function pointer types:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" trait MyTrait {}"},{"DocComment":" impl MyTrait for fn() -> ! {}"},{"DocComment":" impl MyTrait for fn() -> std::convert::Infallible {}"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" With `Infallible` being an enum, this code is valid."},{"DocComment":" However when `Infallible` becomes an alias for the never type,"},{"DocComment":" the two `impl`s will start to overlap"},{"DocComment":" and therefore will be disallowed by the language’s trait coherence rules."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":{"Enum":[]},"layout":[{"key":"aarch64-apple-darwin","value":{"size":0,"align":1,"discriminator":null,"uninhabited":true,"variant_layouts":[],"repr":{"repr_algo":"Rust","align_modif":null,"transparent":false,"explicit_discr_type":false}}}],"ptr_metadata":"None"},{"def_id":8,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_closure",0]},{"Ident":["closure",0]}],"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"source_text":"|| x + 1","attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":{"Closure":{"info":{"kind":"FnOnce","fn_once_impl":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[{"Var":{"Bound":[1,0]}}],"types":[],"const_generics":[],"trait_refs":[]}}},"fn_mut_impl":null,"fn_impl":null,"signature":{"regions":[],"skip_binder":{"is_unsafe":false,"inputs":[],"output":{"Deduplicated":184}}}}}},"kind":{"Struct":[{"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"name":null,"ty":{"HashConsedValue":[6132,{"Ref":[{"Var":{"Bound":[0,0]}},{"Deduplicated":184},"Shared"]}]}}]},"layout":[{"key":"aarch64-apple-darwin","value":{"size":8,"align":8,"discriminator":{"Known":0},"uninhabited":false,"variant_layouts":[{"field_offsets":[0],"uninhabited":false,"tagger":[]}],"repr":{"repr_algo":"Rust","align_modif":null,"transparent":false,"explicit_discr_type":false}}}],"ptr_metadata":"None"},null,null,{"def_id":11,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["array",0]},{"Ident":["iter",0]},{"Ident":["IntoIter",0]}],"span":{"data":{"file_id":17,"beg":{"line":20,"col":0},"end":{"line":20,"col":38}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" A by-value [array] iterator."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"ArrayIntoIter"},"generics":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[{"index":0,"name":"N","ty":{"Deduplicated":591}}],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":17,"beg":{"line":20,"col":20},"end":{"line":20,"col":21}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":12,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["nonzero",0]},{"Ident":["NonZero",0]}],"span":{"data":{"file_id":19,"beg":{"line":127,"col":0},"end":{"line":127,"col":40}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" A value that is known not to equal zero."},{"DocComment":""},{"DocComment":" This enables some memory layout optimization."},{"DocComment":" For example, `Option>` is the same size as `u32`:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use core::{num::NonZero};"},{"DocComment":""},{"DocComment":" assert_eq!(size_of::>>(), size_of::());"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" # Layout"},{"DocComment":""},{"DocComment":" `NonZero` is guaranteed to have the same layout and bit validity as `T`"},{"DocComment":" with the exception that the all-zero bit pattern is invalid."},{"DocComment":" `Option>` is guaranteed to be compatible with `T`, including in"},{"DocComment":" FFI."},{"DocComment":""},{"DocComment":" Thanks to the [null pointer optimization], `NonZero` and"},{"DocComment":" `Option>` are guaranteed to have the same size and alignment:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::num::NonZero;"},{"DocComment":""},{"DocComment":" assert_eq!(size_of::>(), size_of::>>());"},{"DocComment":" assert_eq!(align_of::>(), align_of::>>());"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" [null pointer optimization]: crate::option#representation"},{"DocComment":""},{"DocComment":" # Note on generic usage"},{"DocComment":""},{"DocComment":" `NonZero` can only be used with some standard library primitive types"},{"DocComment":" (such as `u8`, `i32`, and etc.). The type parameter `T` must implement the"},{"DocComment":" internal trait [`ZeroablePrimitive`], which is currently permanently unstable"},{"DocComment":" and cannot be implemented by users. Therefore, you cannot use `NonZero`"},{"DocComment":" with your own types, nor can you implement traits for all `NonZero`,"},{"DocComment":" only for concrete types."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"NonZero"},"generics":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":19,"beg":{"line":127,"col":19},"end":{"line":127,"col":20}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":19,"beg":{"line":127,"col":22},"end":{"line":127,"col":39}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":7,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":13,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["step_by",0]},{"Ident":["StepBy",0]}],"span":{"data":{"file_id":21,"beg":{"line":16,"col":0},"end":{"line":16,"col":20}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator for stepping iterators by a custom amount."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`step_by`] method on [`Iterator`]. See"},{"DocComment":" its documentation for more."},{"DocComment":""},{"DocComment":" [`step_by`]: Iterator::step_by"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":21,"beg":{"line":16,"col":18},"end":{"line":16,"col":19}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":14,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["chain",0]},{"Ident":["Chain",0]}],"span":{"data":{"file_id":23,"beg":{"line":23,"col":0},"end":{"line":23,"col":22}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that links two iterators together, in a chain."},{"DocComment":""},{"DocComment":" This `struct` is created by [`chain`] or [`Iterator::chain`]. See their"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::iter::Chain;"},{"DocComment":" use std::slice::Iter;"},{"DocComment":""},{"DocComment":" let a1 = [1, 2, 3];"},{"DocComment":" let a2 = [4, 5, 6];"},{"DocComment":" let iter: Chain, Iter<'_, _>> = a1.iter().chain(a2.iter());"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"A"},{"index":1,"name":"B"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":23,"beg":{"line":23,"col":17},"end":{"line":23,"col":18}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":23,"beg":{"line":23,"col":20},"end":{"line":23,"col":21}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":15,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["zip",0]},{"Ident":["Zip",0]}],"span":{"data":{"file_id":24,"beg":{"line":15,"col":0},"end":{"line":15,"col":20}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that iterates two other iterators simultaneously."},{"DocComment":""},{"DocComment":" This `struct` is created by [`zip`] or [`Iterator::zip`]."},{"DocComment":" See their documentation for more."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"A"},{"index":1,"name":"B"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":24,"beg":{"line":15,"col":15},"end":{"line":15,"col":16}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":24,"beg":{"line":15,"col":18},"end":{"line":15,"col":19}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":16,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["intersperse",0]},{"Ident":["Intersperse",0]}],"span":{"data":{"file_id":26,"beg":{"line":10,"col":0},"end":{"line":10,"col":35}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator adapter that places a separator between all elements."},{"DocComment":""},{"DocComment":" This `struct` is created by [`Iterator::intersperse`]. See its documentation"},{"DocComment":" for more information."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":26,"beg":{"line":10,"col":23},"end":{"line":10,"col":24}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":26,"beg":{"line":10,"col":26},"end":{"line":10,"col":34}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":26,"beg":{"line":12,"col":13},"end":{"line":12,"col":18}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":8,"generics":{"regions":[],"types":[{"HashConsedValue":[4534,{"TraitType":[{"HashConsedValue":[4533,{"kind":{"Clause":{"Bound":[1,1]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"HashConsedValue":[1577,{"TypeVar":{"Bound":[2,0]}}]}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":17,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["intersperse",0]},{"Ident":["IntersperseWith",0]}],"span":{"data":{"file_id":26,"beg":{"line":91,"col":0},"end":{"line":91,"col":32}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator adapter that places a separator between all elements."},{"DocComment":""},{"DocComment":" This `struct` is created by [`Iterator::intersperse_with`]. See its"},{"DocComment":" documentation for more information."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"},{"index":1,"name":"G"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":26,"beg":{"line":91,"col":27},"end":{"line":91,"col":28}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":26,"beg":{"line":91,"col":30},"end":{"line":91,"col":31}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":26,"beg":{"line":93,"col":7},"end":{"line":93,"col":15}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":18,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["map",0]},{"Ident":["Map",0]}],"span":{"data":{"file_id":27,"beg":{"line":61,"col":0},"end":{"line":61,"col":20}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that maps the values of `iter` with `f`."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`map`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`map`]: Iterator::map"},{"DocComment":" [`Iterator`]: trait.Iterator.html"},{"DocComment":""},{"DocComment":" # Notes about side effects"},{"DocComment":""},{"DocComment":" The [`map`] iterator implements [`DoubleEndedIterator`], meaning that"},{"DocComment":" you can also [`map`] backwards:"},{"DocComment":""},{"DocComment":" ```rust"},{"DocComment":" let v: Vec = [1, 2, 3].into_iter().map(|x| x + 1).rev().collect();"},{"DocComment":""},{"DocComment":" assert_eq!(v, [4, 3, 2]);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" [`DoubleEndedIterator`]: trait.DoubleEndedIterator.html"},{"DocComment":""},{"DocComment":" But if your closure has state, iterating backwards may act in a way you do"},{"DocComment":" not expect. Let's go through an example. First, in the forward direction:"},{"DocComment":""},{"DocComment":" ```rust"},{"DocComment":" let mut c = 0;"},{"DocComment":""},{"DocComment":" for pair in ['a', 'b', 'c'].into_iter()"},{"DocComment":" .map(|letter| { c += 1; (letter, c) }) {"},{"DocComment":" println!(\"{pair:?}\");"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" This will print `('a', 1), ('b', 2), ('c', 3)`."},{"DocComment":""},{"DocComment":" Now consider this twist where we add a call to `rev`. This version will"},{"DocComment":" print `('c', 1), ('b', 2), ('a', 3)`. Note that the letters are reversed,"},{"DocComment":" but the values of the counter still go in order. This is because `map()` is"},{"DocComment":" still being called lazily on each item, but we are popping items off the"},{"DocComment":" back of the vector now, instead of shifting them from the front."},{"DocComment":""},{"DocComment":" ```rust"},{"DocComment":" let mut c = 0;"},{"DocComment":""},{"DocComment":" for pair in ['a', 'b', 'c'].into_iter()"},{"DocComment":" .map(|letter| { c += 1; (letter, c) })"},{"DocComment":" .rev() {"},{"DocComment":" println!(\"{pair:?}\");"},{"DocComment":" }"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"},{"index":1,"name":"F"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":27,"beg":{"line":61,"col":15},"end":{"line":61,"col":16}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":27,"beg":{"line":61,"col":18},"end":{"line":61,"col":19}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":19,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["filter",0]},{"Ident":["Filter",0]}],"span":{"data":{"file_id":28,"beg":{"line":21,"col":0},"end":{"line":21,"col":23}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that filters the elements of `iter` with `predicate`."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`filter`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`filter`]: Iterator::filter"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"},{"index":1,"name":"P"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":28,"beg":{"line":21,"col":18},"end":{"line":21,"col":19}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":28,"beg":{"line":21,"col":21},"end":{"line":21,"col":22}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":20,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["filter_map",0]},{"Ident":["FilterMap",0]}],"span":{"data":{"file_id":29,"beg":{"line":18,"col":0},"end":{"line":18,"col":26}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that uses `f` to both filter and map elements from `iter`."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`filter_map`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`filter_map`]: Iterator::filter_map"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"},{"index":1,"name":"F"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":29,"beg":{"line":18,"col":21},"end":{"line":18,"col":22}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":29,"beg":{"line":18,"col":24},"end":{"line":18,"col":25}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":21,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["enumerate",0]},{"Ident":["Enumerate",0]}],"span":{"data":{"file_id":30,"beg":{"line":18,"col":0},"end":{"line":18,"col":23}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that yields the current count and the element during iteration."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`enumerate`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`enumerate`]: Iterator::enumerate"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"Enumerate"},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":30,"beg":{"line":18,"col":21},"end":{"line":18,"col":22}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":22,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["peekable",0]},{"Ident":["Peekable",0]}],"span":{"data":{"file_id":31,"beg":{"line":17,"col":0},"end":{"line":17,"col":32}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator with a `peek()` that returns an optional reference to the next"},{"DocComment":" element."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`peekable`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`peekable`]: Iterator::peekable"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"IterPeekable"},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":31,"beg":{"line":17,"col":20},"end":{"line":17,"col":21}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":31,"beg":{"line":17,"col":23},"end":{"line":17,"col":31}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":23,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["skip_while",0]},{"Ident":["SkipWhile",0]}],"span":{"data":{"file_id":32,"beg":{"line":17,"col":0},"end":{"line":17,"col":26}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that rejects elements while `predicate` returns `true`."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`skip_while`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`skip_while`]: Iterator::skip_while"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"},{"index":1,"name":"P"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":32,"beg":{"line":17,"col":21},"end":{"line":17,"col":22}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":32,"beg":{"line":17,"col":24},"end":{"line":17,"col":25}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":24,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["take_while",0]},{"Ident":["TakeWhile",0]}],"span":{"data":{"file_id":33,"beg":{"line":17,"col":0},"end":{"line":17,"col":26}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that only accepts elements while `predicate` returns `true`."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`take_while`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`take_while`]: Iterator::take_while"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"},{"index":1,"name":"P"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":33,"beg":{"line":17,"col":21},"end":{"line":17,"col":22}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":33,"beg":{"line":17,"col":24},"end":{"line":17,"col":25}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":25,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["map_while",0]},{"Ident":["MapWhile",0]}],"span":{"data":{"file_id":34,"beg":{"line":17,"col":0},"end":{"line":17,"col":25}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that only accepts elements while `predicate` returns `Some(_)`."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`map_while`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`map_while`]: Iterator::map_while"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"},{"index":1,"name":"P"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":34,"beg":{"line":17,"col":20},"end":{"line":17,"col":21}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":34,"beg":{"line":17,"col":23},"end":{"line":17,"col":24}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":26,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["skip",0]},{"Ident":["Skip",0]}],"span":{"data":{"file_id":35,"beg":{"line":21,"col":0},"end":{"line":21,"col":18}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that skips over `n` elements of `iter`."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`skip`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`skip`]: Iterator::skip"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":35,"beg":{"line":21,"col":16},"end":{"line":21,"col":17}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":27,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["take",0]},{"Ident":["Take",0]}],"span":{"data":{"file_id":36,"beg":{"line":17,"col":0},"end":{"line":17,"col":18}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that only iterates over the first `n` iterations of `iter`."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`take`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`take`]: Iterator::take"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":36,"beg":{"line":17,"col":16},"end":{"line":17,"col":17}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":28,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["scan",0]},{"Ident":["Scan",0]}],"span":{"data":{"file_id":37,"beg":{"line":17,"col":0},"end":{"line":17,"col":25}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator to maintain state while iterating another iterator."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`scan`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`scan`]: Iterator::scan"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"},{"index":1,"name":"St"},{"index":2,"name":"F"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":37,"beg":{"line":17,"col":16},"end":{"line":17,"col":17}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":37,"beg":{"line":17,"col":19},"end":{"line":17,"col":21}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":37,"beg":{"line":17,"col":23},"end":{"line":17,"col":24}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[2592,{"TypeVar":{"Bound":[1,2]}}]}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":29,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["flatten",0]},{"Ident":["FlatMap",0]}],"span":{"data":{"file_id":38,"beg":{"line":17,"col":0},"end":{"line":17,"col":41}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that maps each element to an iterator, and yields the elements"},{"DocComment":" of the produced iterators."},{"DocComment":""},{"DocComment":" This `struct` is created by [`Iterator::flat_map`]. See its documentation"},{"DocComment":" for more."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"},{"index":1,"name":"U"},{"index":2,"name":"F"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":38,"beg":{"line":17,"col":19},"end":{"line":17,"col":20}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":38,"beg":{"line":17,"col":22},"end":{"line":17,"col":23}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":38,"beg":{"line":17,"col":39},"end":{"line":17,"col":40}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":2592}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":3,"span":{"data":{"file_id":38,"beg":{"line":17,"col":25},"end":{"line":17,"col":37}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":30,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["flatten",0]},{"Ident":["Flatten",0]}],"span":{"data":{"file_id":38,"beg":{"line":184,"col":0},"end":{"line":184,"col":51}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that flattens one level of nesting in an iterator of things"},{"DocComment":" that can be turned into iterators."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`flatten`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`flatten`]: Iterator::flatten()"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":38,"beg":{"line":184,"col":19},"end":{"line":184,"col":20}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":38,"beg":{"line":184,"col":22},"end":{"line":184,"col":50}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":38,"beg":{"line":184,"col":37},"end":{"line":184,"col":49}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":4534}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":31,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["map_windows",0]},{"Ident":["MapWindows",0]}],"span":{"data":{"file_id":39,"beg":{"line":11,"col":0},"end":{"line":11,"col":53}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator over the mapped windows of another iterator."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`Iterator::map_windows`]. See its"},{"DocComment":" documentation for more information."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"},{"index":1,"name":"F"}],"const_generics":[{"index":0,"name":"N","ty":{"Deduplicated":591}}],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":39,"beg":{"line":11,"col":22},"end":{"line":11,"col":23}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":39,"beg":{"line":11,"col":35},"end":{"line":11,"col":36}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":39,"beg":{"line":11,"col":25},"end":{"line":11,"col":33}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":32,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["fuse",0]},{"Ident":["Fuse",0]}],"span":{"data":{"file_id":40,"beg":{"line":17,"col":0},"end":{"line":17,"col":18}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that yields `None` forever after the underlying iterator"},{"DocComment":" yields `None` once."},{"DocComment":""},{"DocComment":" This `struct` is created by [`Iterator::fuse`]. See its documentation"},{"DocComment":" for more."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":40,"beg":{"line":17,"col":16},"end":{"line":17,"col":17}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":33,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["inspect",0]},{"Ident":["Inspect",0]}],"span":{"data":{"file_id":41,"beg":{"line":18,"col":0},"end":{"line":18,"col":24}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that calls a function with a reference to each element before"},{"DocComment":" yielding it."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`inspect`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`inspect`]: Iterator::inspect"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"},{"index":1,"name":"F"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":41,"beg":{"line":18,"col":19},"end":{"line":18,"col":20}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":41,"beg":{"line":18,"col":22},"end":{"line":18,"col":23}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":34,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["Ordering",0]}],"span":{"data":{"file_id":46,"beg":{"line":396,"col":0},"end":{"line":396,"col":17}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An `Ordering` is the result of a comparison between two values."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::cmp::Ordering;"},{"DocComment":""},{"DocComment":" assert_eq!(1.cmp(&2), Ordering::Less);"},{"DocComment":""},{"DocComment":" assert_eq!(1.cmp(&1), Ordering::Equal);"},{"DocComment":""},{"DocComment":" assert_eq!(2.cmp(&1), Ordering::Greater);"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"Ordering"},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":{"Enum":[{"id":0,"span":{"data":{"file_id":46,"beg":{"line":399,"col":4},"end":{"line":399,"col":8}},"generated_from_span":null},"attr_info":{"attributes":[{"DocComment":" An ordering where a compared value is less than another."}],"inline":null,"rename":null,"public":true},"name":"Less","fields":[],"discriminant":{"Scalar":{"Signed":["I8","-1"]}}},{"id":1,"span":{"data":{"file_id":46,"beg":{"line":402,"col":4},"end":{"line":402,"col":9}},"generated_from_span":null},"attr_info":{"attributes":[{"DocComment":" An ordering where a compared value is equal to another."}],"inline":null,"rename":null,"public":true},"name":"Equal","fields":[],"discriminant":{"Scalar":{"Signed":["I8","0"]}}},{"id":2,"span":{"data":{"file_id":46,"beg":{"line":405,"col":4},"end":{"line":405,"col":11}},"generated_from_span":null},"attr_info":{"attributes":[{"DocComment":" An ordering where a compared value is greater than another."}],"inline":null,"rename":null,"public":true},"name":"Greater","fields":[],"discriminant":{"Scalar":{"Signed":["I8","1"]}}}]},"layout":[{"key":"aarch64-apple-darwin","value":{"size":1,"align":1,"discriminator":{"Branch":{"offset":0,"int_ty":{"Signed":"I8"},"children":[[{"start":{"Signed":["I8","-1"]},"end":{"Signed":["I8","-1"]}},{"Known":0}],[{"start":{"Signed":["I8","0"]},"end":{"Signed":["I8","0"]}},{"Known":1}],[{"start":{"Signed":["I8","1"]},"end":{"Signed":["I8","1"]}},{"Known":2}]],"fallback":"Invalid"}},"uninhabited":false,"variant_layouts":[{"field_offsets":[],"uninhabited":false,"tagger":[[0,{"Signed":["I8","-1"]}]]},{"field_offsets":[],"uninhabited":false,"tagger":[[0,{"Signed":["I8","0"]}]]},{"field_offsets":[],"uninhabited":false,"tagger":[[0,{"Signed":["I8","1"]}]]}],"repr":{"repr_algo":"Rust","align_modif":null,"transparent":false,"explicit_discr_type":true}}}],"ptr_metadata":"None"},{"def_id":35,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["rev",0]},{"Ident":["Rev",0]}],"span":{"data":{"file_id":47,"beg":{"line":15,"col":0},"end":{"line":15,"col":17}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" A double-ended iterator with the direction inverted."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`rev`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`rev`]: Iterator::rev"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":47,"beg":{"line":15,"col":15},"end":{"line":15,"col":16}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":36,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["copied",0]},{"Ident":["Copied",0]}],"span":{"data":{"file_id":48,"beg":{"line":19,"col":0},"end":{"line":19,"col":20}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that copies the elements of an underlying iterator."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`copied`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`copied`]: Iterator::copied"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":48,"beg":{"line":19,"col":18},"end":{"line":19,"col":19}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":37,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["cloned",0]},{"Ident":["Cloned",0]}],"span":{"data":{"file_id":49,"beg":{"line":18,"col":0},"end":{"line":18,"col":20}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that clones the elements of an underlying iterator."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`cloned`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`cloned`]: Iterator::cloned"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":49,"beg":{"line":18,"col":18},"end":{"line":18,"col":19}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":38,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["cycle",0]},{"Ident":["Cycle",0]}],"span":{"data":{"file_id":50,"beg":{"line":15,"col":0},"end":{"line":15,"col":19}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that repeats endlessly."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`cycle`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`cycle`]: Iterator::cycle"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":50,"beg":{"line":15,"col":17},"end":{"line":15,"col":18}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":39,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["array_chunks",0]},{"Ident":["ArrayChunks",0]}],"span":{"data":{"file_id":51,"beg":{"line":19,"col":0},"end":{"line":19,"col":51}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator over `N` elements of the iterator at a time."},{"DocComment":""},{"DocComment":" The chunks do not overlap. If `N` does not divide the length of the"},{"DocComment":" iterator, then the last up to `N-1` elements will be omitted."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`array_chunks`][Iterator::array_chunks]"},{"DocComment":" method on [`Iterator`]. See its documentation for more."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[{"index":0,"name":"N","ty":{"Deduplicated":591}}],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":51,"beg":{"line":19,"col":23},"end":{"line":19,"col":24}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":51,"beg":{"line":19,"col":26},"end":{"line":19,"col":34}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},null,null,{"def_id":42,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["niche_types",0]},{"Ident":["NonZeroUsizeInner",0]}],"span":{"data":{"file_id":53,"beg":{"line":20,"col":8},"end":{"line":20,"col":55}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[{"key":"aarch64-apple-darwin","value":{"size":8,"align":8,"discriminator":{"Known":0},"uninhabited":false,"variant_layouts":[{"field_offsets":[0],"uninhabited":false,"tagger":[]}],"repr":{"repr_algo":"Rust","align_modif":null,"transparent":true,"explicit_discr_type":false}}}],"ptr_metadata":"None"},null,null,null,null,null,null],"fun_decls":[{"def_id":0,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["straight_line_add",0]}],"span":{"data":{"file_id":0,"beg":{"line":15,"col":0},"end":{"line":19,"col":1}},"generated_from_span":null},"source_text":"pub fn straight_line_add(a: i64, b: i64, c: i64) -> i64 {\n let s = a + b;\n let t = s * 2;\n t + c\n}","attr_info":{"attributes":[],"inline":"Never","rename":null,"public":true},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":184},{"Deduplicated":184},{"Deduplicated":184}],"output":{"Deduplicated":184}},"src":"TopLevel","is_global_initializer":null,"body":{"Unstructured":{"span":{"data":{"file_id":0,"beg":{"line":15,"col":0},"end":{"line":19,"col":1}},"generated_from_span":null},"bound_body_regions":0,"locals":{"arg_count":3,"locals":[{"index":0,"name":null,"span":{"data":{"file_id":0,"beg":{"line":15,"col":52},"end":{"line":15,"col":55}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":1,"name":"a","span":{"data":{"file_id":0,"beg":{"line":15,"col":25},"end":{"line":15,"col":26}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":2,"name":"b","span":{"data":{"file_id":0,"beg":{"line":15,"col":33},"end":{"line":15,"col":34}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":3,"name":"c","span":{"data":{"file_id":0,"beg":{"line":15,"col":41},"end":{"line":15,"col":42}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":4,"name":"s","span":{"data":{"file_id":0,"beg":{"line":16,"col":8},"end":{"line":16,"col":9}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":5,"name":null,"span":{"data":{"file_id":0,"beg":{"line":16,"col":12},"end":{"line":16,"col":13}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":6,"name":null,"span":{"data":{"file_id":0,"beg":{"line":16,"col":16},"end":{"line":16,"col":17}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":7,"name":null,"span":{"data":{"file_id":0,"beg":{"line":16,"col":12},"end":{"line":16,"col":17}},"generated_from_span":null},"ty":{"HashConsedValue":[189,{"Adt":{"id":"Tuple","generics":{"regions":[],"types":[{"Deduplicated":184},{"Deduplicated":188}],"const_generics":[],"trait_refs":[]}}}]}},{"index":8,"name":"t","span":{"data":{"file_id":0,"beg":{"line":17,"col":8},"end":{"line":17,"col":9}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":9,"name":null,"span":{"data":{"file_id":0,"beg":{"line":17,"col":12},"end":{"line":17,"col":13}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":10,"name":null,"span":{"data":{"file_id":0,"beg":{"line":17,"col":12},"end":{"line":17,"col":17}},"generated_from_span":null},"ty":{"Deduplicated":189}},{"index":11,"name":null,"span":{"data":{"file_id":0,"beg":{"line":18,"col":4},"end":{"line":18,"col":5}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":12,"name":null,"span":{"data":{"file_id":0,"beg":{"line":18,"col":8},"end":{"line":18,"col":9}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":13,"name":null,"span":{"data":{"file_id":0,"beg":{"line":18,"col":4},"end":{"line":18,"col":9}},"generated_from_span":null},"ty":{"Deduplicated":189}}]},"body":[{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":16,"col":8},"end":{"line":16,"col":9}},"generated_from_span":null},"kind":{"StorageLive":7},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":16,"col":8},"end":{"line":16,"col":9}},"generated_from_span":null},"kind":{"StorageLive":10},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":16,"col":8},"end":{"line":16,"col":9}},"generated_from_span":null},"kind":{"StorageLive":13},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":16,"col":8},"end":{"line":16,"col":9}},"generated_from_span":null},"kind":{"StorageLive":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":16,"col":12},"end":{"line":16,"col":13}},"generated_from_span":null},"kind":{"StorageLive":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":16,"col":12},"end":{"line":16,"col":13}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":5},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":1},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":16,"col":16},"end":{"line":16,"col":17}},"generated_from_span":null},"kind":{"StorageLive":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":16,"col":16},"end":{"line":16,"col":17}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":6},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":2},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":16,"col":12},"end":{"line":16,"col":17}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":7},"ty":{"Deduplicated":189}},{"BinaryOp":["AddChecked",{"Copy":{"kind":{"Local":5},"ty":{"Deduplicated":184}}},{"Copy":{"kind":{"Local":6},"ty":{"Deduplicated":184}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":16,"col":12},"end":{"line":16,"col":17}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Projection":[{"kind":{"Local":7},"ty":{"Deduplicated":189}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":188}}},"expected":false,"check_kind":{"Overflow":[{"Add":"Wrap"},{"Move":{"kind":{"Local":5},"ty":{"Deduplicated":184}}},{"Move":{"kind":{"Local":6},"ty":{"Deduplicated":184}}}]}},"target":2,"on_unwind":1}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":15,"col":0},"end":{"line":19,"col":1}},"generated_from_span":null},"kind":"UnwindResume","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":16,"col":12},"end":{"line":16,"col":17}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":4},"ty":{"Deduplicated":184}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":7},"ty":{"Deduplicated":189}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":16,"col":16},"end":{"line":16,"col":17}},"generated_from_span":null},"kind":{"StorageDead":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":16,"col":16},"end":{"line":16,"col":17}},"generated_from_span":null},"kind":{"StorageDead":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":17,"col":8},"end":{"line":17,"col":9}},"generated_from_span":null},"kind":{"StorageLive":8},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":17,"col":12},"end":{"line":17,"col":13}},"generated_from_span":null},"kind":{"StorageLive":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":17,"col":12},"end":{"line":17,"col":13}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":9},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":4},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":17,"col":12},"end":{"line":17,"col":17}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":10},"ty":{"Deduplicated":189}},{"BinaryOp":["MulChecked",{"Copy":{"kind":{"Local":9},"ty":{"Deduplicated":184}}},{"Const":{"kind":{"Literal":{"Scalar":{"Signed":["I64","2"]}}},"ty":{"Deduplicated":184}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":17,"col":12},"end":{"line":17,"col":17}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Projection":[{"kind":{"Local":10},"ty":{"Deduplicated":189}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":188}}},"expected":false,"check_kind":{"Overflow":[{"Mul":"Wrap"},{"Move":{"kind":{"Local":9},"ty":{"Deduplicated":184}}},{"Const":{"kind":{"Literal":{"Scalar":{"Signed":["I64","2"]}}},"ty":{"Deduplicated":184}}}]}},"target":3,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":17,"col":12},"end":{"line":17,"col":17}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":8},"ty":{"Deduplicated":184}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":10},"ty":{"Deduplicated":189}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":17,"col":16},"end":{"line":17,"col":17}},"generated_from_span":null},"kind":{"StorageDead":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":18,"col":4},"end":{"line":18,"col":5}},"generated_from_span":null},"kind":{"StorageLive":11},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":18,"col":4},"end":{"line":18,"col":5}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":11},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":8},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":18,"col":8},"end":{"line":18,"col":9}},"generated_from_span":null},"kind":{"StorageLive":12},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":18,"col":8},"end":{"line":18,"col":9}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":12},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":3},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":18,"col":4},"end":{"line":18,"col":9}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":13},"ty":{"Deduplicated":189}},{"BinaryOp":["AddChecked",{"Copy":{"kind":{"Local":11},"ty":{"Deduplicated":184}}},{"Copy":{"kind":{"Local":12},"ty":{"Deduplicated":184}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":18,"col":4},"end":{"line":18,"col":9}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Projection":[{"kind":{"Local":13},"ty":{"Deduplicated":189}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":188}}},"expected":false,"check_kind":{"Overflow":[{"Add":"Wrap"},{"Move":{"kind":{"Local":11},"ty":{"Deduplicated":184}}},{"Move":{"kind":{"Local":12},"ty":{"Deduplicated":184}}}]}},"target":4,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":18,"col":4},"end":{"line":18,"col":9}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":184}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":13},"ty":{"Deduplicated":189}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":18,"col":8},"end":{"line":18,"col":9}},"generated_from_span":null},"kind":{"StorageDead":12},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":18,"col":8},"end":{"line":18,"col":9}},"generated_from_span":null},"kind":{"StorageDead":11},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":19,"col":0},"end":{"line":19,"col":1}},"generated_from_span":null},"kind":{"StorageDead":8},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":19,"col":0},"end":{"line":19,"col":1}},"generated_from_span":null},"kind":{"StorageDead":4},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":19,"col":1},"end":{"line":19,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}}],"comments":[]}}},{"def_id":1,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["branch_loop_sum",0]}],"span":{"data":{"file_id":0,"beg":{"line":24,"col":0},"end":{"line":34,"col":1}},"generated_from_span":null},"source_text":"pub fn branch_loop_sum(slice: &[i64], threshold: i64) -> i64 {\n let mut acc: i64 = 0;\n for &v in slice {\n if v > threshold {\n acc += v;\n } else {\n acc -= v;\n }\n }\n acc\n}","attr_info":{"attributes":[],"inline":"Never","rename":null,"public":true},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"HashConsedValue":[191,{"Ref":[{"Var":{"Bound":[0,0]}},{"HashConsedValue":[190,{"Slice":{"Deduplicated":184}}]},"Shared"]}]},{"Deduplicated":184}],"output":{"Deduplicated":184}},"src":"TopLevel","is_global_initializer":null,"body":{"Unstructured":{"span":{"data":{"file_id":0,"beg":{"line":24,"col":0},"end":{"line":34,"col":1}},"generated_from_span":null},"bound_body_regions":30,"locals":{"arg_count":2,"locals":[{"index":0,"name":null,"span":{"data":{"file_id":0,"beg":{"line":24,"col":57},"end":{"line":24,"col":60}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":1,"name":"slice","span":{"data":{"file_id":0,"beg":{"line":24,"col":23},"end":{"line":24,"col":28}},"generated_from_span":null},"ty":{"HashConsedValue":[194,{"Ref":[{"Body":1},{"Deduplicated":190},"Shared"]}]}},{"index":2,"name":"threshold","span":{"data":{"file_id":0,"beg":{"line":24,"col":38},"end":{"line":24,"col":47}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":3,"name":"acc","span":{"data":{"file_id":0,"beg":{"line":25,"col":8},"end":{"line":25,"col":15}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":4,"name":null,"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"ty":{"HashConsedValue":[311,{"Adt":{"id":{"Adt":4},"generics":{"regions":[{"Body":3}],"types":[{"Deduplicated":184}],"const_generics":[],"trait_refs":[{"HashConsedValue":[309,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[308,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":184}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":184}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},{"index":5,"name":null,"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"ty":{"HashConsedValue":[312,{"Ref":[{"Body":4},{"Deduplicated":190},"Shared"]}]}},{"index":6,"name":"iter","span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"ty":{"HashConsedValue":[313,{"Adt":{"id":{"Adt":4},"generics":{"regions":[{"Body":5}],"types":[{"Deduplicated":184}],"const_generics":[],"trait_refs":[{"Deduplicated":309}]}}}]}},{"index":7,"name":null,"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"ty":{"HashConsedValue":[5358,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"HashConsedValue":[354,{"Ref":[{"Body":12},{"Deduplicated":184},"Shared"]}]}],"const_generics":[],"trait_refs":[{"HashConsedValue":[5357,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[5356,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":354}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":354}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},{"index":8,"name":null,"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"ty":{"HashConsedValue":[364,{"Ref":[{"Body":17},{"HashConsedValue":[363,{"Adt":{"id":{"Adt":4},"generics":{"regions":[{"Body":18}],"types":[{"Deduplicated":184}],"const_generics":[],"trait_refs":[{"Deduplicated":309}]}}}]},"Mut"]}]}},{"index":9,"name":null,"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"ty":{"HashConsedValue":[366,{"Ref":[{"Body":19},{"HashConsedValue":[365,{"Adt":{"id":{"Adt":4},"generics":{"regions":[{"Body":20}],"types":[{"Deduplicated":184}],"const_generics":[],"trait_refs":[{"Deduplicated":309}]}}}]},"Mut"]}]}},{"index":10,"name":null,"span":{"data":{"file_id":0,"beg":{"line":26,"col":4},"end":{"line":32,"col":5}},"generated_from_span":null},"ty":{"HashConsedValue":[367,{"Literal":{"Int":"Isize"}}]}},{"index":11,"name":"v","span":{"data":{"file_id":0,"beg":{"line":26,"col":9},"end":{"line":26,"col":10}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":12,"name":null,"span":{"data":{"file_id":0,"beg":{"line":27,"col":11},"end":{"line":27,"col":24}},"generated_from_span":null},"ty":{"Deduplicated":188}},{"index":13,"name":null,"span":{"data":{"file_id":0,"beg":{"line":27,"col":11},"end":{"line":27,"col":12}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":14,"name":null,"span":{"data":{"file_id":0,"beg":{"line":27,"col":15},"end":{"line":27,"col":24}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":15,"name":null,"span":{"data":{"file_id":0,"beg":{"line":28,"col":19},"end":{"line":28,"col":20}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":16,"name":null,"span":{"data":{"file_id":0,"beg":{"line":28,"col":12},"end":{"line":28,"col":20}},"generated_from_span":null},"ty":{"Deduplicated":189}},{"index":17,"name":null,"span":{"data":{"file_id":0,"beg":{"line":30,"col":19},"end":{"line":30,"col":20}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":18,"name":null,"span":{"data":{"file_id":0,"beg":{"line":30,"col":12},"end":{"line":30,"col":20}},"generated_from_span":null},"ty":{"Deduplicated":189}}]},"body":[{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":25,"col":8},"end":{"line":25,"col":15}},"generated_from_span":null},"kind":{"StorageLive":10},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":25,"col":8},"end":{"line":25,"col":15}},"generated_from_span":null},"kind":{"StorageLive":16},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":25,"col":8},"end":{"line":25,"col":15}},"generated_from_span":null},"kind":{"StorageLive":18},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":25,"col":8},"end":{"line":25,"col":15}},"generated_from_span":null},"kind":{"StorageLive":3},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":25,"col":23},"end":{"line":25,"col":24}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":3},"ty":{"Deduplicated":184}},{"Use":{"Const":{"kind":{"Literal":{"Scalar":{"Signed":["I64","0"]}}},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"StorageLive":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"StorageLive":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":5},"ty":{"Deduplicated":312}},{"Use":{"Copy":{"kind":{"Local":1},"ty":{"Deduplicated":194}}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Regular":{"kind":{"Fun":{"Regular":10}},"generics":{"regions":[{"Body":21}],"types":[{"Deduplicated":184}],"const_generics":[],"trait_refs":[{"Deduplicated":309}]}}},"args":[{"Move":{"kind":{"Local":5},"ty":{"Deduplicated":312}}}],"dest":{"kind":{"Local":4},"ty":{"Deduplicated":311}}},"target":2,"on_unwind":1}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":24,"col":0},"end":{"line":34,"col":1}},"generated_from_span":null},"kind":"UnwindResume","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":26,"col":18},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"StorageDead":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"StorageLive":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":6},"ty":{"Deduplicated":313}},{"Use":{"Move":{"kind":{"Local":4},"ty":{"Deduplicated":311}}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":26,"col":4},"end":{"line":32,"col":5}},"generated_from_span":null},"kind":{"Goto":{"target":3}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"StorageLive":7},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"StorageLive":8},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"StorageLive":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":9},"ty":{"Deduplicated":366}},{"Ref":{"place":{"kind":{"Local":6},"ty":{"Deduplicated":313}},"kind":"Mut","ptr_metadata":{"Const":{"kind":{"Adt":[null,[]]},"ty":{"HashConsedValue":[198,{"Adt":{"id":"Tuple","generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}}]}}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":8},"ty":{"Deduplicated":364}},{"Ref":{"place":{"kind":{"Projection":[{"kind":{"Local":9},"ty":{"Deduplicated":366}},"Deref"]},"ty":{"HashConsedValue":[6202,{"Adt":{"id":{"Adt":4},"generics":{"regions":[{"Body":22}],"types":[{"Deduplicated":184}],"const_generics":[],"trait_refs":[{"Deduplicated":309}]}}}]}},"kind":"TwoPhaseMut","ptr_metadata":{"Const":{"kind":{"Adt":[null,[]]},"ty":{"Deduplicated":198}}}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Regular":{"kind":{"Fun":{"Regular":11}},"generics":{"regions":[{"Body":23},{"Body":25}],"types":[{"Deduplicated":184}],"const_generics":[],"trait_refs":[{"Deduplicated":309}]}}},"args":[{"Move":{"kind":{"Local":8},"ty":{"Deduplicated":364}}}],"dest":{"kind":{"Local":7},"ty":{"Deduplicated":5358}}},"target":4,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":26,"col":18},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"StorageDead":8},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":10},"ty":{"Deduplicated":367}},{"Discriminant":{"kind":{"Local":7},"ty":{"Deduplicated":5358}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"Switch":{"discr":{"Move":{"kind":{"Local":10},"ty":{"Deduplicated":367}}},"targets":{"SwitchInt":[{"Int":"Isize"},[[{"Scalar":{"Signed":["Isize","0"]}},5],[{"Scalar":{"Signed":["Isize","1"]}},6]],7]}}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":32,"col":4},"end":{"line":32,"col":5}},"generated_from_span":null},"kind":{"StorageDead":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":32,"col":4},"end":{"line":32,"col":5}},"generated_from_span":null},"kind":{"StorageDead":7},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":32,"col":4},"end":{"line":32,"col":5}},"generated_from_span":null},"kind":{"StorageDead":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":32,"col":4},"end":{"line":32,"col":5}},"generated_from_span":null},"kind":{"StorageDead":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":33,"col":4},"end":{"line":33,"col":7}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":3},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":34,"col":0},"end":{"line":34,"col":1}},"generated_from_span":null},"kind":{"StorageDead":3},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":34,"col":1},"end":{"line":34,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":26,"col":9},"end":{"line":26,"col":10}},"generated_from_span":null},"kind":{"StorageLive":11},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":26,"col":9},"end":{"line":26,"col":10}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":11},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Projection":[{"kind":{"Local":7},"ty":{"Deduplicated":5358}},{"Field":[{"Adt":[5,1]},0]}]},"ty":{"HashConsedValue":[6203,{"Ref":[{"Body":29},{"Deduplicated":184},"Shared"]}]}},"Deref"]},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":27,"col":11},"end":{"line":27,"col":24}},"generated_from_span":null},"kind":{"StorageLive":12},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":27,"col":11},"end":{"line":27,"col":12}},"generated_from_span":null},"kind":{"StorageLive":13},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":27,"col":11},"end":{"line":27,"col":12}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":13},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":11},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":27,"col":15},"end":{"line":27,"col":24}},"generated_from_span":null},"kind":{"StorageLive":14},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":27,"col":15},"end":{"line":27,"col":24}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":14},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":2},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":27,"col":11},"end":{"line":27,"col":24}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":12},"ty":{"Deduplicated":188}},{"BinaryOp":["Gt",{"Move":{"kind":{"Local":13},"ty":{"Deduplicated":184}}},{"Move":{"kind":{"Local":14},"ty":{"Deduplicated":184}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":27,"col":11},"end":{"line":27,"col":24}},"generated_from_span":null},"kind":{"Switch":{"discr":{"Move":{"kind":{"Local":12},"ty":{"Deduplicated":188}}},"targets":{"If":[8,9]}}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"Abort":"UndefinedBehavior"},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":27,"col":23},"end":{"line":27,"col":24}},"generated_from_span":null},"kind":{"StorageDead":14},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":27,"col":23},"end":{"line":27,"col":24}},"generated_from_span":null},"kind":{"StorageDead":13},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":28,"col":19},"end":{"line":28,"col":20}},"generated_from_span":null},"kind":{"StorageLive":15},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":28,"col":19},"end":{"line":28,"col":20}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":15},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":11},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":28,"col":12},"end":{"line":28,"col":20}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":16},"ty":{"Deduplicated":189}},{"BinaryOp":["AddChecked",{"Copy":{"kind":{"Local":3},"ty":{"Deduplicated":184}}},{"Copy":{"kind":{"Local":15},"ty":{"Deduplicated":184}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":28,"col":12},"end":{"line":28,"col":20}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Projection":[{"kind":{"Local":16},"ty":{"Deduplicated":189}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":188}}},"expected":false,"check_kind":{"Overflow":[{"Add":"Wrap"},{"Copy":{"kind":{"Local":3},"ty":{"Deduplicated":184}}},{"Move":{"kind":{"Local":19},"ty":{"Deduplicated":184}}}]}},"target":10,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":27,"col":23},"end":{"line":27,"col":24}},"generated_from_span":null},"kind":{"StorageDead":14},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":27,"col":23},"end":{"line":27,"col":24}},"generated_from_span":null},"kind":{"StorageDead":13},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":30,"col":19},"end":{"line":30,"col":20}},"generated_from_span":null},"kind":{"StorageLive":17},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":30,"col":19},"end":{"line":30,"col":20}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":17},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":11},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":30,"col":12},"end":{"line":30,"col":20}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":18},"ty":{"Deduplicated":189}},{"BinaryOp":["SubChecked",{"Copy":{"kind":{"Local":3},"ty":{"Deduplicated":184}}},{"Copy":{"kind":{"Local":17},"ty":{"Deduplicated":184}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":30,"col":12},"end":{"line":30,"col":20}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Projection":[{"kind":{"Local":18},"ty":{"Deduplicated":189}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":188}}},"expected":false,"check_kind":{"Overflow":[{"Sub":"Wrap"},{"Copy":{"kind":{"Local":3},"ty":{"Deduplicated":184}}},{"Move":{"kind":{"Local":21},"ty":{"Deduplicated":184}}}]}},"target":11,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":28,"col":12},"end":{"line":28,"col":20}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":3},"ty":{"Deduplicated":184}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":16},"ty":{"Deduplicated":189}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":28,"col":19},"end":{"line":28,"col":20}},"generated_from_span":null},"kind":{"StorageDead":15},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":27,"col":8},"end":{"line":31,"col":9}},"generated_from_span":null},"kind":{"Goto":{"target":12}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":30,"col":12},"end":{"line":30,"col":20}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":3},"ty":{"Deduplicated":184}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":18},"ty":{"Deduplicated":189}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":30,"col":19},"end":{"line":30,"col":20}},"generated_from_span":null},"kind":{"StorageDead":17},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":27,"col":8},"end":{"line":31,"col":9}},"generated_from_span":null},"kind":{"Goto":{"target":12}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":31,"col":8},"end":{"line":31,"col":9}},"generated_from_span":null},"kind":{"StorageDead":12},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":32,"col":4},"end":{"line":32,"col":5}},"generated_from_span":null},"kind":{"StorageDead":11},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":32,"col":4},"end":{"line":32,"col":5}},"generated_from_span":null},"kind":{"StorageDead":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":32,"col":4},"end":{"line":32,"col":5}},"generated_from_span":null},"kind":{"StorageDead":7},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":26,"col":4},"end":{"line":32,"col":5}},"generated_from_span":null},"kind":{"Goto":{"target":3}},"comments_before":[]}}],"comments":[]}}},{"def_id":2,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["strategy_len",0]}],"span":{"data":{"file_id":0,"beg":{"line":45,"col":0},"end":{"line":51,"col":1}},"generated_from_span":null},"source_text":"pub fn strategy_len(s: &Strategy) -> usize {\n match s {\n Strategy::Empty => 0,\n Strategy::IntKeyed { len } => *len,\n Strategy::StrKeyed { len, capacity: _ } => *len,\n }\n}","attr_info":{"attributes":[],"inline":"Never","rename":null,"public":true},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"HashConsedValue":[6204,{"Ref":[{"Var":{"Bound":[0,0]}},{"HashConsedValue":[597,{"Adt":{"id":{"Adt":1},"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}}]},"Shared"]}]}],"output":{"Deduplicated":591}},"src":"TopLevel","is_global_initializer":null,"body":{"Unstructured":{"span":{"data":{"file_id":0,"beg":{"line":45,"col":0},"end":{"line":51,"col":1}},"generated_from_span":null},"bound_body_regions":5,"locals":{"arg_count":1,"locals":[{"index":0,"name":null,"span":{"data":{"file_id":0,"beg":{"line":45,"col":37},"end":{"line":45,"col":42}},"generated_from_span":null},"ty":{"Deduplicated":591}},{"index":1,"name":"s","span":{"data":{"file_id":0,"beg":{"line":45,"col":20},"end":{"line":45,"col":21}},"generated_from_span":null},"ty":{"HashConsedValue":[601,{"Ref":[{"Body":1},{"Deduplicated":597},"Shared"]}]}},{"index":2,"name":null,"span":{"data":{"file_id":0,"beg":{"line":47,"col":8},"end":{"line":47,"col":23}},"generated_from_span":null},"ty":{"Deduplicated":367}},{"index":3,"name":"len","span":{"data":{"file_id":0,"beg":{"line":48,"col":29},"end":{"line":48,"col":32}},"generated_from_span":null},"ty":{"HashConsedValue":[604,{"Ref":[{"Body":3},{"Deduplicated":591},"Shared"]}]}},{"index":4,"name":"len","span":{"data":{"file_id":0,"beg":{"line":49,"col":29},"end":{"line":49,"col":32}},"generated_from_span":null},"ty":{"HashConsedValue":[605,{"Ref":[{"Body":4},{"Deduplicated":591},"Shared"]}]}}]},"body":[{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":46,"col":10},"end":{"line":46,"col":11}},"generated_from_span":null},"kind":{"StorageLive":2},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":46,"col":10},"end":{"line":46,"col":11}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":2},"ty":{"Deduplicated":367}},{"Discriminant":{"kind":{"Projection":[{"kind":{"Local":1},"ty":{"Deduplicated":601}},"Deref"]},"ty":{"Deduplicated":597}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":46,"col":4},"end":{"line":46,"col":11}},"generated_from_span":null},"kind":{"Switch":{"discr":{"Move":{"kind":{"Local":2},"ty":{"Deduplicated":367}}},"targets":{"SwitchInt":[{"Int":"Isize"},[[{"Scalar":{"Signed":["Isize","0"]}},1],[{"Scalar":{"Signed":["Isize","1"]}},2],[{"Scalar":{"Signed":["Isize","2"]}},3]],4]}}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":47,"col":27},"end":{"line":47,"col":28}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":591}},{"Use":{"Const":{"kind":{"Literal":{"Scalar":{"Unsigned":["Usize","0"]}}},"ty":{"Deduplicated":591}}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":51,"col":1},"end":{"line":51,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":48,"col":29},"end":{"line":48,"col":32}},"generated_from_span":null},"kind":{"StorageLive":3},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":48,"col":29},"end":{"line":48,"col":32}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":3},"ty":{"Deduplicated":604}},{"Ref":{"place":{"kind":{"Projection":[{"kind":{"Projection":[{"kind":{"Local":1},"ty":{"Deduplicated":601}},"Deref"]},"ty":{"Deduplicated":597}},{"Field":[{"Adt":[1,1]},0]}]},"ty":{"Deduplicated":591}},"kind":"Shared","ptr_metadata":{"Const":{"kind":{"Adt":[null,[]]},"ty":{"Deduplicated":198}}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":48,"col":38},"end":{"line":48,"col":42}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":591}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Local":3},"ty":{"Deduplicated":604}},"Deref"]},"ty":{"Deduplicated":591}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":48,"col":41},"end":{"line":48,"col":42}},"generated_from_span":null},"kind":{"StorageDead":3},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":51,"col":1},"end":{"line":51,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":49,"col":29},"end":{"line":49,"col":32}},"generated_from_span":null},"kind":{"StorageLive":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":49,"col":29},"end":{"line":49,"col":32}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":4},"ty":{"Deduplicated":605}},{"Ref":{"place":{"kind":{"Projection":[{"kind":{"Projection":[{"kind":{"Local":1},"ty":{"Deduplicated":601}},"Deref"]},"ty":{"Deduplicated":597}},{"Field":[{"Adt":[1,2]},0]}]},"ty":{"Deduplicated":591}},"kind":"Shared","ptr_metadata":{"Const":{"kind":{"Adt":[null,[]]},"ty":{"Deduplicated":198}}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":49,"col":51},"end":{"line":49,"col":55}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":591}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Local":4},"ty":{"Deduplicated":605}},"Deref"]},"ty":{"Deduplicated":591}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":49,"col":54},"end":{"line":49,"col":55}},"generated_from_span":null},"kind":{"StorageDead":4},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":51,"col":1},"end":{"line":51,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":46,"col":10},"end":{"line":46,"col":11}},"generated_from_span":null},"kind":{"Abort":"UndefinedBehavior"},"comments_before":[]}}],"comments":[]}}},{"def_id":3,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["parse_one",0]}],"span":{"data":{"file_id":0,"beg":{"line":61,"col":0},"end":{"line":68,"col":1}},"generated_from_span":null},"source_text":"fn parse_one(raw: i64) -> PyResult {\n match raw {\n i64::MIN => Ok(Token::Halt),\n 0 => Err(\"halt-zero forbidden\"),\n v if v > 0 => Ok(Token::Add(v)),\n v => Ok(Token::Sub(-v)),\n }\n}","attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":184}],"output":{"HashConsedValue":[6205,{"Adt":{"id":{"Adt":3},"generics":{"regions":[],"types":[{"HashConsedValue":[611,{"Adt":{"id":{"Adt":2},"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}}]},{"Deduplicated":175}],"const_generics":[],"trait_refs":[{"HashConsedValue":[613,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[612,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":611}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":611}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":182}]}}}]}},"src":"TopLevel","is_global_initializer":null,"body":{"Unstructured":{"span":{"data":{"file_id":0,"beg":{"line":61,"col":0},"end":{"line":68,"col":1}},"generated_from_span":null},"bound_body_regions":35,"locals":{"arg_count":1,"locals":[{"index":0,"name":null,"span":{"data":{"file_id":0,"beg":{"line":61,"col":26},"end":{"line":61,"col":41}},"generated_from_span":null},"ty":{"HashConsedValue":[5365,{"Adt":{"id":{"Adt":3},"generics":{"regions":[],"types":[{"Deduplicated":611},{"HashConsedValue":[628,{"Ref":[{"Body":6},{"Deduplicated":174},"Shared"]}]}],"const_generics":[],"trait_refs":[{"Deduplicated":613},{"HashConsedValue":[5364,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[5363,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":628}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":628}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},{"index":1,"name":"raw","span":{"data":{"file_id":0,"beg":{"line":61,"col":13},"end":{"line":61,"col":16}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":2,"name":null,"span":{"data":{"file_id":0,"beg":{"line":62,"col":10},"end":{"line":62,"col":13}},"generated_from_span":null},"ty":{"HashConsedValue":[349,{"Ref":[{"Body":10},{"Deduplicated":184},"Shared"]}]}},{"index":3,"name":null,"span":{"data":{"file_id":0,"beg":{"line":63,"col":23},"end":{"line":63,"col":34}},"generated_from_span":null},"ty":{"Deduplicated":611}},{"index":4,"name":null,"span":{"data":{"file_id":0,"beg":{"line":64,"col":17},"end":{"line":64,"col":38}},"generated_from_span":null},"ty":{"HashConsedValue":[634,{"Ref":[{"Body":11},{"Deduplicated":174},"Shared"]}]}},{"index":5,"name":null,"span":{"data":{"file_id":0,"beg":{"line":64,"col":17},"end":{"line":64,"col":38}},"generated_from_span":null},"ty":{"HashConsedValue":[635,{"Ref":[{"Body":12},{"Deduplicated":174},"Shared"]}]}},{"index":6,"name":"v","span":{"data":{"file_id":0,"beg":{"line":65,"col":8},"end":{"line":65,"col":9}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":7,"name":"v","span":{"data":{"file_id":0,"beg":{"line":65,"col":8},"end":{"line":65,"col":9}},"generated_from_span":null},"ty":{"HashConsedValue":[355,{"Ref":[{"Body":13},{"Deduplicated":184},"Shared"]}]}},{"index":8,"name":null,"span":{"data":{"file_id":0,"beg":{"line":65,"col":13},"end":{"line":65,"col":18}},"generated_from_span":null},"ty":{"Deduplicated":188}},{"index":9,"name":null,"span":{"data":{"file_id":0,"beg":{"line":65,"col":13},"end":{"line":65,"col":14}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":10,"name":null,"span":{"data":{"file_id":0,"beg":{"line":65,"col":25},"end":{"line":65,"col":38}},"generated_from_span":null},"ty":{"Deduplicated":611}},{"index":11,"name":null,"span":{"data":{"file_id":0,"beg":{"line":65,"col":36},"end":{"line":65,"col":37}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":12,"name":"v","span":{"data":{"file_id":0,"beg":{"line":66,"col":8},"end":{"line":66,"col":9}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":13,"name":null,"span":{"data":{"file_id":0,"beg":{"line":66,"col":16},"end":{"line":66,"col":30}},"generated_from_span":null},"ty":{"Deduplicated":611}},{"index":14,"name":null,"span":{"data":{"file_id":0,"beg":{"line":66,"col":27},"end":{"line":66,"col":29}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":15,"name":null,"span":{"data":{"file_id":0,"beg":{"line":66,"col":28},"end":{"line":66,"col":29}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":16,"name":null,"span":{"data":{"file_id":0,"beg":{"line":66,"col":27},"end":{"line":66,"col":29}},"generated_from_span":null},"ty":{"Deduplicated":188}}]},"body":[{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":62,"col":4},"end":{"line":62,"col":13}},"generated_from_span":null},"kind":{"StorageLive":2},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":62,"col":4},"end":{"line":62,"col":13}},"generated_from_span":null},"kind":{"StorageLive":16},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":62,"col":4},"end":{"line":62,"col":13}},"generated_from_span":null},"kind":{"Switch":{"discr":{"Copy":{"kind":{"Local":1},"ty":{"Deduplicated":184}}},"targets":{"SwitchInt":[{"Int":"I64"},[[{"Scalar":{"Signed":["I64","-9223372036854775808"]}},1],[{"Scalar":{"Signed":["I64","0"]}},2]],3]}}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":63,"col":23},"end":{"line":63,"col":34}},"generated_from_span":null},"kind":{"StorageLive":3},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":63,"col":23},"end":{"line":63,"col":34}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":3},"ty":{"Deduplicated":611}},{"Aggregate":[{"Adt":[{"id":{"Adt":2},"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}},2,null]},[]]}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":63,"col":20},"end":{"line":63,"col":35}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":5365}},{"Aggregate":[{"Adt":[{"id":{"Adt":3},"generics":{"regions":[],"types":[{"Deduplicated":611},{"HashConsedValue":[6206,{"Ref":[{"Body":14},{"Deduplicated":174},"Shared"]}]}],"const_generics":[],"trait_refs":[{"Deduplicated":613},{"HashConsedValue":[6210,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[6208,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[6207,{"Ref":[{"Body":18},{"Deduplicated":174},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[6209,{"Ref":[{"Body":16},{"Deduplicated":174},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}]}},0,null]},[{"Move":{"kind":{"Local":3},"ty":{"Deduplicated":611}}}]]}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":63,"col":34},"end":{"line":63,"col":35}},"generated_from_span":null},"kind":{"StorageDead":3},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":68,"col":1},"end":{"line":68,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":64,"col":17},"end":{"line":64,"col":38}},"generated_from_span":null},"kind":{"StorageLive":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":64,"col":17},"end":{"line":64,"col":38}},"generated_from_span":null},"kind":{"StorageLive":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":64,"col":17},"end":{"line":64,"col":38}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":5},"ty":{"Deduplicated":635}},{"Use":{"Const":{"kind":{"Literal":{"Str":"halt-zero forbidden"}},"ty":{"HashConsedValue":[6211,{"Ref":[{"Body":19},{"Deduplicated":174},"Shared"]}]}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":64,"col":17},"end":{"line":64,"col":38}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":4},"ty":{"Deduplicated":634}},{"Ref":{"place":{"kind":{"Projection":[{"kind":{"Local":5},"ty":{"Deduplicated":635}},"Deref"]},"ty":{"Deduplicated":174}},"kind":"Shared","ptr_metadata":{"Copy":{"kind":{"Projection":[{"kind":{"Local":5},"ty":{"Deduplicated":635}},"PtrMetadata"]},"ty":{"Deduplicated":591}}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":64,"col":13},"end":{"line":64,"col":39}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":5365}},{"Aggregate":[{"Adt":[{"id":{"Adt":3},"generics":{"regions":[],"types":[{"Deduplicated":611},{"HashConsedValue":[6212,{"Ref":[{"Body":20},{"Deduplicated":174},"Shared"]}]}],"const_generics":[],"trait_refs":[{"Deduplicated":613},{"HashConsedValue":[6216,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[6214,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[6213,{"Ref":[{"Body":24},{"Deduplicated":174},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[6215,{"Ref":[{"Body":22},{"Deduplicated":174},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}]}},1,null]},[{"Move":{"kind":{"Local":4},"ty":{"Deduplicated":634}}}]]}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":64,"col":38},"end":{"line":64,"col":39}},"generated_from_span":null},"kind":{"StorageDead":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":64,"col":38},"end":{"line":64,"col":39}},"generated_from_span":null},"kind":{"StorageDead":5},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":68,"col":1},"end":{"line":68,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":65,"col":8},"end":{"line":65,"col":9}},"generated_from_span":null},"kind":{"StorageLive":7},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":8},"end":{"line":65,"col":9}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":7},"ty":{"Deduplicated":355}},{"Ref":{"place":{"kind":{"Local":1},"ty":{"Deduplicated":184}},"kind":"Shared","ptr_metadata":{"Const":{"kind":{"Adt":[null,[]]},"ty":{"Deduplicated":198}}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":62,"col":10},"end":{"line":62,"col":13}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":2},"ty":{"Deduplicated":349}},{"Ref":{"place":{"kind":{"Local":1},"ty":{"Deduplicated":184}},"kind":"Shallow","ptr_metadata":{"Const":{"kind":{"Adt":[null,[]]},"ty":{"Deduplicated":198}}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":13},"end":{"line":65,"col":18}},"generated_from_span":null},"kind":{"StorageLive":8},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":13},"end":{"line":65,"col":14}},"generated_from_span":null},"kind":{"StorageLive":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":13},"end":{"line":65,"col":14}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":9},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Local":7},"ty":{"Deduplicated":355}},"Deref"]},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":13},"end":{"line":65,"col":18}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":8},"ty":{"Deduplicated":188}},{"BinaryOp":["Gt",{"Move":{"kind":{"Local":9},"ty":{"Deduplicated":184}}},{"Const":{"kind":{"Literal":{"Scalar":{"Signed":["I64","0"]}}},"ty":{"Deduplicated":184}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":65,"col":13},"end":{"line":65,"col":18}},"generated_from_span":null},"kind":{"Switch":{"discr":{"Move":{"kind":{"Local":8},"ty":{"Deduplicated":188}}},"targets":{"If":[4,5]}}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":65,"col":17},"end":{"line":65,"col":18}},"generated_from_span":null},"kind":{"StorageDead":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":17},"end":{"line":65,"col":18}},"generated_from_span":null},"kind":{"StorageDead":8},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":8},"end":{"line":65,"col":9}},"generated_from_span":null},"kind":{"StorageLive":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":8},"end":{"line":65,"col":9}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":6},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":1},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":25},"end":{"line":65,"col":38}},"generated_from_span":null},"kind":{"StorageLive":10},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":36},"end":{"line":65,"col":37}},"generated_from_span":null},"kind":{"StorageLive":11},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":36},"end":{"line":65,"col":37}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":11},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":6},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":25},"end":{"line":65,"col":38}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":10},"ty":{"Deduplicated":611}},{"Aggregate":[{"Adt":[{"id":{"Adt":2},"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}},0,null]},[{"Move":{"kind":{"Local":11},"ty":{"Deduplicated":184}}}]]}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":37},"end":{"line":65,"col":38}},"generated_from_span":null},"kind":{"StorageDead":11},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":22},"end":{"line":65,"col":39}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":5365}},{"Aggregate":[{"Adt":[{"id":{"Adt":3},"generics":{"regions":[],"types":[{"Deduplicated":611},{"HashConsedValue":[6217,{"Ref":[{"Body":25},{"Deduplicated":174},"Shared"]}]}],"const_generics":[],"trait_refs":[{"Deduplicated":613},{"HashConsedValue":[6221,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[6219,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[6218,{"Ref":[{"Body":29},{"Deduplicated":174},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[6220,{"Ref":[{"Body":27},{"Deduplicated":174},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}]}},0,null]},[{"Move":{"kind":{"Local":10},"ty":{"Deduplicated":611}}}]]}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":38},"end":{"line":65,"col":39}},"generated_from_span":null},"kind":{"StorageDead":10},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":38},"end":{"line":65,"col":39}},"generated_from_span":null},"kind":{"StorageDead":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":38},"end":{"line":65,"col":39}},"generated_from_span":null},"kind":{"StorageDead":7},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":68,"col":1},"end":{"line":68,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":65,"col":17},"end":{"line":65,"col":18}},"generated_from_span":null},"kind":{"StorageDead":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":17},"end":{"line":65,"col":18}},"generated_from_span":null},"kind":{"StorageDead":8},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":38},"end":{"line":65,"col":39}},"generated_from_span":null},"kind":{"StorageDead":7},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":66,"col":8},"end":{"line":66,"col":9}},"generated_from_span":null},"kind":{"StorageLive":12},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":66,"col":8},"end":{"line":66,"col":9}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":12},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":1},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":66,"col":16},"end":{"line":66,"col":30}},"generated_from_span":null},"kind":{"StorageLive":13},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":66,"col":27},"end":{"line":66,"col":29}},"generated_from_span":null},"kind":{"StorageLive":14},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":66,"col":28},"end":{"line":66,"col":29}},"generated_from_span":null},"kind":{"StorageLive":15},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":66,"col":28},"end":{"line":66,"col":29}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":15},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":12},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":66,"col":27},"end":{"line":66,"col":29}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":16},"ty":{"Deduplicated":188}},{"BinaryOp":["Eq",{"Copy":{"kind":{"Local":15},"ty":{"Deduplicated":184}}},{"Const":{"kind":{"Literal":{"Scalar":{"Signed":["I64","-9223372036854775808"]}}},"ty":{"Deduplicated":184}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":66,"col":27},"end":{"line":66,"col":29}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Local":16},"ty":{"Deduplicated":188}}},"expected":false,"check_kind":{"OverflowNeg":{"Copy":{"kind":{"Local":15},"ty":{"Deduplicated":184}}}}},"target":7,"on_unwind":6}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":61,"col":0},"end":{"line":68,"col":1}},"generated_from_span":null},"kind":"UnwindResume","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":66,"col":27},"end":{"line":66,"col":29}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":14},"ty":{"Deduplicated":184}},{"UnaryOp":[{"Neg":"Wrap"},{"Move":{"kind":{"Local":15},"ty":{"Deduplicated":184}}}]}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":66,"col":28},"end":{"line":66,"col":29}},"generated_from_span":null},"kind":{"StorageDead":15},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":66,"col":16},"end":{"line":66,"col":30}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":13},"ty":{"Deduplicated":611}},{"Aggregate":[{"Adt":[{"id":{"Adt":2},"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}},1,null]},[{"Move":{"kind":{"Local":14},"ty":{"Deduplicated":184}}}]]}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":66,"col":29},"end":{"line":66,"col":30}},"generated_from_span":null},"kind":{"StorageDead":14},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":66,"col":13},"end":{"line":66,"col":31}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":5365}},{"Aggregate":[{"Adt":[{"id":{"Adt":3},"generics":{"regions":[],"types":[{"Deduplicated":611},{"HashConsedValue":[6222,{"Ref":[{"Body":30},{"Deduplicated":174},"Shared"]}]}],"const_generics":[],"trait_refs":[{"Deduplicated":613},{"HashConsedValue":[6226,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[6224,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[6223,{"Ref":[{"Body":34},{"Deduplicated":174},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[6225,{"Ref":[{"Body":32},{"Deduplicated":174},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}]}},0,null]},[{"Move":{"kind":{"Local":13},"ty":{"Deduplicated":611}}}]]}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":66,"col":30},"end":{"line":66,"col":31}},"generated_from_span":null},"kind":{"StorageDead":13},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":66,"col":30},"end":{"line":66,"col":31}},"generated_from_span":null},"kind":{"StorageDead":12},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":68,"col":1},"end":{"line":68,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}}],"comments":[]}}},{"def_id":4,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["desugar_mix",0]}],"span":{"data":{"file_id":0,"beg":{"line":71,"col":0},"end":{"line":82,"col":1}},"generated_from_span":null},"source_text":"pub fn desugar_mix(input: &[i64]) -> PyResult {\n let mut acc: i64 = 0;\n for &raw in input.iter() {\n let tok = parse_one(raw)?;\n match tok {\n Token::Add(v) => acc += v,\n Token::Sub(v) => acc -= v,\n Token::Halt => break,\n }\n }\n Ok(acc)\n}","attr_info":{"attributes":[],"inline":"Never","rename":null,"public":true},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":191}],"output":{"HashConsedValue":[6227,{"Adt":{"id":{"Adt":3},"generics":{"regions":[],"types":[{"Deduplicated":184},{"Deduplicated":175}],"const_generics":[],"trait_refs":[{"Deduplicated":309},{"Deduplicated":182}]}}}]}},"src":"TopLevel","is_global_initializer":null,"body":{"Unstructured":{"span":{"data":{"file_id":0,"beg":{"line":71,"col":0},"end":{"line":82,"col":1}},"generated_from_span":null},"bound_body_regions":147,"locals":{"arg_count":1,"locals":[{"index":0,"name":null,"span":{"data":{"file_id":0,"beg":{"line":71,"col":37},"end":{"line":71,"col":50}},"generated_from_span":null},"ty":{"HashConsedValue":[5419,{"Adt":{"id":{"Adt":3},"generics":{"regions":[],"types":[{"Deduplicated":184},{"Deduplicated":628}],"const_generics":[],"trait_refs":[{"Deduplicated":309},{"Deduplicated":5364}]}}}]}},{"index":1,"name":"input","span":{"data":{"file_id":0,"beg":{"line":71,"col":19},"end":{"line":71,"col":24}},"generated_from_span":null},"ty":{"HashConsedValue":[671,{"Ref":[{"Body":10},{"Deduplicated":190},"Shared"]}]}},{"index":2,"name":"acc","span":{"data":{"file_id":0,"beg":{"line":72,"col":8},"end":{"line":72,"col":15}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":3,"name":null,"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"ty":{"HashConsedValue":[673,{"Adt":{"id":{"Adt":4},"generics":{"regions":[{"Body":12}],"types":[{"Deduplicated":184}],"const_generics":[],"trait_refs":[{"Deduplicated":309}]}}}]}},{"index":4,"name":null,"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"ty":{"HashConsedValue":[674,{"Adt":{"id":{"Adt":4},"generics":{"regions":[{"Body":13}],"types":[{"Deduplicated":184}],"const_generics":[],"trait_refs":[{"Deduplicated":309}]}}}]}},{"index":5,"name":null,"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":21}},"generated_from_span":null},"ty":{"HashConsedValue":[675,{"Ref":[{"Body":14},{"Deduplicated":190},"Shared"]}]}},{"index":6,"name":"iter","span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"ty":{"HashConsedValue":[676,{"Adt":{"id":{"Adt":4},"generics":{"regions":[{"Body":15}],"types":[{"Deduplicated":184}],"const_generics":[],"trait_refs":[{"Deduplicated":309}]}}}]}},{"index":7,"name":null,"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"ty":{"HashConsedValue":[5422,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"HashConsedValue":[686,{"Ref":[{"Body":22},{"Deduplicated":184},"Shared"]}]}],"const_generics":[],"trait_refs":[{"HashConsedValue":[5421,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[5420,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":686}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":686}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},{"index":8,"name":null,"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"ty":{"HashConsedValue":[695,{"Ref":[{"Body":27},{"HashConsedValue":[694,{"Adt":{"id":{"Adt":4},"generics":{"regions":[{"Body":28}],"types":[{"Deduplicated":184}],"const_generics":[],"trait_refs":[{"Deduplicated":309}]}}}]},"Mut"]}]}},{"index":9,"name":null,"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"ty":{"HashConsedValue":[697,{"Ref":[{"Body":29},{"HashConsedValue":[696,{"Adt":{"id":{"Adt":4},"generics":{"regions":[{"Body":30}],"types":[{"Deduplicated":184}],"const_generics":[],"trait_refs":[{"Deduplicated":309}]}}}]},"Mut"]}]}},{"index":10,"name":null,"span":{"data":{"file_id":0,"beg":{"line":73,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"ty":{"Deduplicated":367}},{"index":11,"name":"raw","span":{"data":{"file_id":0,"beg":{"line":73,"col":9},"end":{"line":73,"col":12}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":12,"name":"tok","span":{"data":{"file_id":0,"beg":{"line":74,"col":12},"end":{"line":74,"col":15}},"generated_from_span":null},"ty":{"Deduplicated":611}},{"index":13,"name":null,"span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":33}},"generated_from_span":null},"ty":{"HashConsedValue":[5428,{"Adt":{"id":{"Adt":6},"generics":{"regions":[],"types":[{"HashConsedValue":[5425,{"Adt":{"id":{"Adt":3},"generics":{"regions":[],"types":[{"HashConsedValue":[812,{"Adt":{"id":{"Adt":7},"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}}]},{"HashConsedValue":[858,{"Ref":[{"Body":57},{"Deduplicated":174},"Shared"]}]}],"const_generics":[],"trait_refs":[{"HashConsedValue":[814,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[813,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":812}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":812}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[5424,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[5423,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":858}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":858}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]},{"Deduplicated":611}],"const_generics":[],"trait_refs":[{"HashConsedValue":[5427,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[5426,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":5425}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":5425}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":613}]}}}]}},{"index":14,"name":null,"span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":32}},"generated_from_span":null},"ty":{"HashConsedValue":[5431,{"Adt":{"id":{"Adt":3},"generics":{"regions":[],"types":[{"Deduplicated":611},{"HashConsedValue":[887,{"Ref":[{"Body":71},{"Deduplicated":174},"Shared"]}]}],"const_generics":[],"trait_refs":[{"Deduplicated":613},{"HashConsedValue":[5430,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[5429,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":887}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":887}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},{"index":15,"name":null,"span":{"data":{"file_id":0,"beg":{"line":74,"col":28},"end":{"line":74,"col":31}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":16,"name":null,"span":{"data":{"file_id":0,"beg":{"line":74,"col":32},"end":{"line":74,"col":33}},"generated_from_span":null},"ty":{"Deduplicated":367}},{"index":17,"name":"residual","span":{"data":{"file_id":0,"beg":{"line":74,"col":32},"end":{"line":74,"col":33}},"generated_from_span":null},"ty":{"HashConsedValue":[5434,{"Adt":{"id":{"Adt":3},"generics":{"regions":[],"types":[{"Deduplicated":812},{"HashConsedValue":[893,{"Ref":[{"Body":74},{"Deduplicated":174},"Shared"]}]}],"const_generics":[],"trait_refs":[{"Deduplicated":814},{"HashConsedValue":[5433,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[5432,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":893}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":893}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},{"index":18,"name":null,"span":{"data":{"file_id":0,"beg":{"line":74,"col":32},"end":{"line":74,"col":33}},"generated_from_span":null},"ty":{"HashConsedValue":[5437,{"Adt":{"id":{"Adt":3},"generics":{"regions":[],"types":[{"Deduplicated":812},{"HashConsedValue":[899,{"Ref":[{"Body":77},{"Deduplicated":174},"Shared"]}]}],"const_generics":[],"trait_refs":[{"Deduplicated":814},{"HashConsedValue":[5436,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[5435,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":899}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":899}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},{"index":19,"name":"val","span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":33}},"generated_from_span":null},"ty":{"Deduplicated":611}},{"index":20,"name":null,"span":{"data":{"file_id":0,"beg":{"line":76,"col":12},"end":{"line":76,"col":25}},"generated_from_span":null},"ty":{"Deduplicated":367}},{"index":21,"name":"v","span":{"data":{"file_id":0,"beg":{"line":76,"col":23},"end":{"line":76,"col":24}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":22,"name":null,"span":{"data":{"file_id":0,"beg":{"line":76,"col":36},"end":{"line":76,"col":37}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":23,"name":null,"span":{"data":{"file_id":0,"beg":{"line":76,"col":29},"end":{"line":76,"col":37}},"generated_from_span":null},"ty":{"Deduplicated":189}},{"index":24,"name":"v","span":{"data":{"file_id":0,"beg":{"line":77,"col":23},"end":{"line":77,"col":24}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":25,"name":null,"span":{"data":{"file_id":0,"beg":{"line":77,"col":36},"end":{"line":77,"col":37}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":26,"name":null,"span":{"data":{"file_id":0,"beg":{"line":77,"col":29},"end":{"line":77,"col":37}},"generated_from_span":null},"ty":{"Deduplicated":189}},{"index":27,"name":null,"span":{"data":{"file_id":0,"beg":{"line":81,"col":7},"end":{"line":81,"col":10}},"generated_from_span":null},"ty":{"Deduplicated":184}}]},"body":[{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":72,"col":8},"end":{"line":72,"col":15}},"generated_from_span":null},"kind":{"StorageLive":10},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":72,"col":8},"end":{"line":72,"col":15}},"generated_from_span":null},"kind":{"StorageLive":16},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":72,"col":8},"end":{"line":72,"col":15}},"generated_from_span":null},"kind":{"StorageLive":20},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":72,"col":8},"end":{"line":72,"col":15}},"generated_from_span":null},"kind":{"StorageLive":23},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":72,"col":8},"end":{"line":72,"col":15}},"generated_from_span":null},"kind":{"StorageLive":26},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":72,"col":8},"end":{"line":72,"col":15}},"generated_from_span":null},"kind":{"StorageLive":2},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":72,"col":23},"end":{"line":72,"col":24}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":2},"ty":{"Deduplicated":184}},{"Use":{"Const":{"kind":{"Literal":{"Scalar":{"Signed":["I64","0"]}}},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"StorageLive":3},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"StorageLive":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":21}},"generated_from_span":null},"kind":{"StorageLive":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":21}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":5},"ty":{"Deduplicated":675}},{"Ref":{"place":{"kind":{"Projection":[{"kind":{"Local":1},"ty":{"Deduplicated":671}},"Deref"]},"ty":{"Deduplicated":190}},"kind":"Shared","ptr_metadata":{"Copy":{"kind":{"Projection":[{"kind":{"Local":1},"ty":{"Deduplicated":671}},"PtrMetadata"]},"ty":{"Deduplicated":591}}}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Regular":{"kind":{"Fun":{"Regular":12}},"generics":{"regions":[{"Body":81}],"types":[{"Deduplicated":184}],"const_generics":[],"trait_refs":[{"Deduplicated":309}]}}},"args":[{"Move":{"kind":{"Local":5},"ty":{"Deduplicated":675}}}],"dest":{"kind":{"Local":4},"ty":{"Deduplicated":674}}},"target":2,"on_unwind":1}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":71,"col":0},"end":{"line":82,"col":1}},"generated_from_span":null},"kind":"UnwindResume","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":73,"col":27},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"StorageDead":5},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Regular":{"kind":{"Fun":{"Regular":13}},"generics":{"regions":[],"types":[{"HashConsedValue":[5438,{"Adt":{"id":{"Adt":4},"generics":{"regions":[{"Body":82}],"types":[{"Deduplicated":184}],"const_generics":[],"trait_refs":[{"Deduplicated":309}]}}}]}],"const_generics":[],"trait_refs":[{"HashConsedValue":[6235,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[6234,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":5438}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":5438}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6236,{"kind":{"TraitImpl":{"id":1,"generics":{"regions":[{"Body":82}],"types":[{"Deduplicated":184}],"const_generics":[],"trait_refs":[{"Deduplicated":309}]}}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":5438}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"args":[{"Move":{"kind":{"Local":4},"ty":{"Deduplicated":674}}}],"dest":{"kind":{"Local":3},"ty":{"Deduplicated":673}}},"target":3,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":73,"col":27},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"StorageDead":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"StorageLive":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":6},"ty":{"Deduplicated":676}},{"Use":{"Move":{"kind":{"Local":3},"ty":{"Deduplicated":673}}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":73,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"Goto":{"target":4}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"StorageLive":7},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"StorageLive":8},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"StorageLive":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":9},"ty":{"Deduplicated":697}},{"Ref":{"place":{"kind":{"Local":6},"ty":{"Deduplicated":676}},"kind":"Mut","ptr_metadata":{"Const":{"kind":{"Adt":[null,[]]},"ty":{"Deduplicated":198}}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":8},"ty":{"Deduplicated":695}},{"Ref":{"place":{"kind":{"Projection":[{"kind":{"Local":9},"ty":{"Deduplicated":697}},"Deref"]},"ty":{"HashConsedValue":[6237,{"Adt":{"id":{"Adt":4},"generics":{"regions":[{"Body":90}],"types":[{"Deduplicated":184}],"const_generics":[],"trait_refs":[{"Deduplicated":309}]}}}]}},"kind":"TwoPhaseMut","ptr_metadata":{"Const":{"kind":{"Adt":[null,[]]},"ty":{"Deduplicated":198}}}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Regular":{"kind":{"Fun":{"Regular":11}},"generics":{"regions":[{"Body":91},{"Body":93}],"types":[{"Deduplicated":184}],"const_generics":[],"trait_refs":[{"Deduplicated":309}]}}},"args":[{"Move":{"kind":{"Local":8},"ty":{"Deduplicated":695}}}],"dest":{"kind":{"Local":7},"ty":{"Deduplicated":5422}}},"target":5,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":73,"col":27},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"StorageDead":8},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":10},"ty":{"Deduplicated":367}},{"Discriminant":{"kind":{"Local":7},"ty":{"Deduplicated":5422}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"Switch":{"discr":{"Move":{"kind":{"Local":10},"ty":{"Deduplicated":367}}},"targets":{"SwitchInt":[{"Int":"Isize"},[[{"Scalar":{"Signed":["Isize","0"]}},9],[{"Scalar":{"Signed":["Isize","1"]}},6]],7]}}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":73,"col":9},"end":{"line":73,"col":12}},"generated_from_span":null},"kind":{"StorageLive":11},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":73,"col":9},"end":{"line":73,"col":12}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":11},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Projection":[{"kind":{"Local":7},"ty":{"Deduplicated":5422}},{"Field":[{"Adt":[5,1]},0]}]},"ty":{"HashConsedValue":[6238,{"Ref":[{"Body":97},{"Deduplicated":184},"Shared"]}]}},"Deref"]},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":12},"end":{"line":74,"col":15}},"generated_from_span":null},"kind":{"StorageLive":12},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"StorageLive":13},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":32}},"generated_from_span":null},"kind":{"StorageLive":14},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":28},"end":{"line":74,"col":31}},"generated_from_span":null},"kind":{"StorageLive":15},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":28},"end":{"line":74,"col":31}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":15},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":11},"ty":{"Deduplicated":184}}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":32}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Regular":{"kind":{"Fun":{"Regular":3}},"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}},"args":[{"Move":{"kind":{"Local":15},"ty":{"Deduplicated":184}}}],"dest":{"kind":{"Local":14},"ty":{"Deduplicated":5431}}},"target":8,"on_unwind":1}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"Abort":"UndefinedBehavior"},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":74,"col":31},"end":{"line":74,"col":32}},"generated_from_span":null},"kind":{"StorageDead":15},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Regular":{"kind":{"Fun":{"Regular":14}},"generics":{"regions":[],"types":[{"Deduplicated":611},{"HashConsedValue":[5444,{"Ref":[{"Body":98},{"Deduplicated":174},"Shared"]}]}],"const_generics":[],"trait_refs":[{"Deduplicated":613},{"HashConsedValue":[6240,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[6239,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":5444}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":5444}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"args":[{"Move":{"kind":{"Local":14},"ty":{"Deduplicated":5431}}}],"dest":{"kind":{"Local":13},"ty":{"Deduplicated":5428}}},"target":10,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":7},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":3},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":81,"col":7},"end":{"line":81,"col":10}},"generated_from_span":null},"kind":{"StorageLive":27},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":81,"col":7},"end":{"line":81,"col":10}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":27},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":2},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":81,"col":4},"end":{"line":81,"col":11}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":5419}},{"Aggregate":[{"Adt":[{"id":{"Adt":3},"generics":{"regions":[],"types":[{"Deduplicated":184},{"HashConsedValue":[6241,{"Ref":[{"Body":103},{"Deduplicated":174},"Shared"]}]}],"const_generics":[],"trait_refs":[{"Deduplicated":309},{"HashConsedValue":[6245,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[6243,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[6242,{"Ref":[{"Body":107},{"Deduplicated":174},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[6244,{"Ref":[{"Body":105},{"Deduplicated":174},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}]}},0,null]},[{"Move":{"kind":{"Local":27},"ty":{"Deduplicated":184}}}]]}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":81,"col":10},"end":{"line":81,"col":11}},"generated_from_span":null},"kind":{"StorageDead":27},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":82,"col":0},"end":{"line":82,"col":1}},"generated_from_span":null},"kind":{"StorageDead":2},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":82,"col":1},"end":{"line":82,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":74,"col":32},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"StorageDead":14},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":16},"ty":{"Deduplicated":367}},{"Discriminant":{"kind":{"Local":13},"ty":{"Deduplicated":5428}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"Switch":{"discr":{"Move":{"kind":{"Local":16},"ty":{"Deduplicated":367}}},"targets":{"SwitchInt":[{"Int":"Isize"},[[{"Scalar":{"Signed":["Isize","0"]}},11],[{"Scalar":{"Signed":["Isize","1"]}},12]],13]}}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"StorageLive":19},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":19},"ty":{"Deduplicated":611}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":13},"ty":{"Deduplicated":5428}},{"Field":[{"Adt":[6,0]},0]}]},"ty":{"Deduplicated":611}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":12},"ty":{"Deduplicated":611}},{"Use":{"Move":{"kind":{"Local":19},"ty":{"Deduplicated":611}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":32},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"StorageDead":19},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":33},"end":{"line":74,"col":34}},"generated_from_span":null},"kind":{"StorageDead":13},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":75,"col":14},"end":{"line":75,"col":17}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":20},"ty":{"Deduplicated":367}},{"Discriminant":{"kind":{"Local":12},"ty":{"Deduplicated":611}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":75,"col":8},"end":{"line":75,"col":17}},"generated_from_span":null},"kind":{"Switch":{"discr":{"Move":{"kind":{"Local":20},"ty":{"Deduplicated":367}}},"targets":{"SwitchInt":[{"Int":"Isize"},[[{"Scalar":{"Signed":["Isize","0"]}},14],[{"Scalar":{"Signed":["Isize","1"]}},15],[{"Scalar":{"Signed":["Isize","2"]}},16]],17]}}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":74,"col":32},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"StorageLive":17},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":32},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":17},"ty":{"Deduplicated":5434}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Local":13},"ty":{"Deduplicated":5428}},{"Field":[{"Adt":[6,1]},0]}]},"ty":{"HashConsedValue":[6251,{"Adt":{"id":{"Adt":3},"generics":{"regions":[],"types":[{"Deduplicated":812},{"HashConsedValue":[6246,{"Ref":[{"Body":126},{"Deduplicated":174},"Shared"]}]}],"const_generics":[],"trait_refs":[{"Deduplicated":814},{"HashConsedValue":[6250,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[6248,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[6247,{"Ref":[{"Body":127},{"Deduplicated":174},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[6249,{"Ref":[{"Body":128},{"Deduplicated":174},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":32},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"StorageLive":18},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":32},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":18},"ty":{"Deduplicated":5437}},{"Use":{"Copy":{"kind":{"Local":17},"ty":{"Deduplicated":5434}}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Regular":{"kind":{"Fun":{"Regular":15}},"generics":{"regions":[],"types":[{"Deduplicated":184},{"HashConsedValue":[5458,{"Ref":[{"Body":129},{"Deduplicated":174},"Shared"]}]},{"Deduplicated":5458}],"const_generics":[],"trait_refs":[{"Deduplicated":309},{"HashConsedValue":[5460,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[5459,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":5458}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":5458}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":5460},{"HashConsedValue":[6252,{"kind":{"TraitImpl":{"id":5,"generics":{"regions":[],"types":[{"Deduplicated":5458}],"const_generics":[],"trait_refs":[{"Deduplicated":5460}]}}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":3,"generics":{"regions":[],"types":[{"Deduplicated":5458},{"Deduplicated":5458}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"args":[{"Move":{"kind":{"Local":18},"ty":{"Deduplicated":5437}}}],"dest":{"kind":{"Local":0},"ty":{"Deduplicated":5419}}},"target":18,"on_unwind":1}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"Abort":"UndefinedBehavior"},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":76,"col":23},"end":{"line":76,"col":24}},"generated_from_span":null},"kind":{"StorageLive":21},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":76,"col":23},"end":{"line":76,"col":24}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":21},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Local":12},"ty":{"Deduplicated":611}},{"Field":[{"Adt":[2,0]},0]}]},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":76,"col":36},"end":{"line":76,"col":37}},"generated_from_span":null},"kind":{"StorageLive":22},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":76,"col":36},"end":{"line":76,"col":37}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":22},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":21},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":76,"col":29},"end":{"line":76,"col":37}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":23},"ty":{"Deduplicated":189}},{"BinaryOp":["AddChecked",{"Copy":{"kind":{"Local":2},"ty":{"Deduplicated":184}}},{"Copy":{"kind":{"Local":22},"ty":{"Deduplicated":184}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":76,"col":29},"end":{"line":76,"col":37}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Projection":[{"kind":{"Local":23},"ty":{"Deduplicated":189}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":188}}},"expected":false,"check_kind":{"Overflow":[{"Add":"Wrap"},{"Copy":{"kind":{"Local":2},"ty":{"Deduplicated":184}}},{"Move":{"kind":{"Local":27},"ty":{"Deduplicated":184}}}]}},"target":19,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":77,"col":23},"end":{"line":77,"col":24}},"generated_from_span":null},"kind":{"StorageLive":24},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":77,"col":23},"end":{"line":77,"col":24}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":24},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Local":12},"ty":{"Deduplicated":611}},{"Field":[{"Adt":[2,1]},0]}]},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":77,"col":36},"end":{"line":77,"col":37}},"generated_from_span":null},"kind":{"StorageLive":25},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":77,"col":36},"end":{"line":77,"col":37}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":25},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":24},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":77,"col":29},"end":{"line":77,"col":37}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":26},"ty":{"Deduplicated":189}},{"BinaryOp":["SubChecked",{"Copy":{"kind":{"Local":2},"ty":{"Deduplicated":184}}},{"Copy":{"kind":{"Local":25},"ty":{"Deduplicated":184}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":77,"col":29},"end":{"line":77,"col":37}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Projection":[{"kind":{"Local":26},"ty":{"Deduplicated":189}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":188}}},"expected":false,"check_kind":{"Overflow":[{"Sub":"Wrap"},{"Copy":{"kind":{"Local":2},"ty":{"Deduplicated":184}}},{"Move":{"kind":{"Local":30},"ty":{"Deduplicated":184}}}]}},"target":20,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":12},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":11},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"kind":{"Goto":{"target":9}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":75,"col":14},"end":{"line":75,"col":17}},"generated_from_span":null},"kind":{"Abort":"UndefinedBehavior"},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":74,"col":32},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"StorageDead":18},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":32},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"StorageDead":17},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":33},"end":{"line":74,"col":34}},"generated_from_span":null},"kind":{"StorageDead":13},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":12},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":11},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":7},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":3},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":82,"col":0},"end":{"line":82,"col":1}},"generated_from_span":null},"kind":{"StorageDead":2},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":82,"col":1},"end":{"line":82,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":76,"col":29},"end":{"line":76,"col":37}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":2},"ty":{"Deduplicated":184}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":23},"ty":{"Deduplicated":189}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":76,"col":36},"end":{"line":76,"col":37}},"generated_from_span":null},"kind":{"StorageDead":22},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":76,"col":36},"end":{"line":76,"col":37}},"generated_from_span":null},"kind":{"StorageDead":21},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":76,"col":36},"end":{"line":76,"col":37}},"generated_from_span":null},"kind":{"Goto":{"target":21}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":77,"col":29},"end":{"line":77,"col":37}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":2},"ty":{"Deduplicated":184}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":26},"ty":{"Deduplicated":189}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":77,"col":36},"end":{"line":77,"col":37}},"generated_from_span":null},"kind":{"StorageDead":25},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":77,"col":36},"end":{"line":77,"col":37}},"generated_from_span":null},"kind":{"StorageDead":24},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":77,"col":36},"end":{"line":77,"col":37}},"generated_from_span":null},"kind":{"Goto":{"target":21}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":12},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":11},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":7},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":73,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"Goto":{"target":4}},"comments_before":[]}}],"comments":[]}}},{"def_id":5,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["tuple_roundtrip",0]}],"span":{"data":{"file_id":0,"beg":{"line":92,"col":0},"end":{"line":95,"col":1}},"generated_from_span":null},"source_text":"pub fn tuple_roundtrip(a: i64, b: i64) -> i64 {\n let pair = (a + b, a - b);\n pair.0 * pair.1\n}","attr_info":{"attributes":[],"inline":"Never","rename":null,"public":true},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":184},{"Deduplicated":184}],"output":{"Deduplicated":184}},"src":"TopLevel","is_global_initializer":null,"body":{"Unstructured":{"span":{"data":{"file_id":0,"beg":{"line":92,"col":0},"end":{"line":95,"col":1}},"generated_from_span":null},"bound_body_regions":0,"locals":{"arg_count":2,"locals":[{"index":0,"name":null,"span":{"data":{"file_id":0,"beg":{"line":92,"col":42},"end":{"line":92,"col":45}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":1,"name":"a","span":{"data":{"file_id":0,"beg":{"line":92,"col":23},"end":{"line":92,"col":24}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":2,"name":"b","span":{"data":{"file_id":0,"beg":{"line":92,"col":31},"end":{"line":92,"col":32}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":3,"name":"pair","span":{"data":{"file_id":0,"beg":{"line":93,"col":8},"end":{"line":93,"col":12}},"generated_from_span":null},"ty":{"HashConsedValue":[1396,{"Adt":{"id":"Tuple","generics":{"regions":[],"types":[{"Deduplicated":184},{"Deduplicated":184}],"const_generics":[],"trait_refs":[]}}}]}},{"index":4,"name":null,"span":{"data":{"file_id":0,"beg":{"line":93,"col":16},"end":{"line":93,"col":21}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":5,"name":null,"span":{"data":{"file_id":0,"beg":{"line":93,"col":16},"end":{"line":93,"col":17}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":6,"name":null,"span":{"data":{"file_id":0,"beg":{"line":93,"col":20},"end":{"line":93,"col":21}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":7,"name":null,"span":{"data":{"file_id":0,"beg":{"line":93,"col":16},"end":{"line":93,"col":21}},"generated_from_span":null},"ty":{"Deduplicated":189}},{"index":8,"name":null,"span":{"data":{"file_id":0,"beg":{"line":93,"col":23},"end":{"line":93,"col":28}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":9,"name":null,"span":{"data":{"file_id":0,"beg":{"line":93,"col":23},"end":{"line":93,"col":24}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":10,"name":null,"span":{"data":{"file_id":0,"beg":{"line":93,"col":27},"end":{"line":93,"col":28}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":11,"name":null,"span":{"data":{"file_id":0,"beg":{"line":93,"col":23},"end":{"line":93,"col":28}},"generated_from_span":null},"ty":{"Deduplicated":189}},{"index":12,"name":null,"span":{"data":{"file_id":0,"beg":{"line":94,"col":4},"end":{"line":94,"col":10}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":13,"name":null,"span":{"data":{"file_id":0,"beg":{"line":94,"col":13},"end":{"line":94,"col":19}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":14,"name":null,"span":{"data":{"file_id":0,"beg":{"line":94,"col":4},"end":{"line":94,"col":19}},"generated_from_span":null},"ty":{"Deduplicated":189}}]},"body":[{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":93,"col":8},"end":{"line":93,"col":12}},"generated_from_span":null},"kind":{"StorageLive":7},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":8},"end":{"line":93,"col":12}},"generated_from_span":null},"kind":{"StorageLive":11},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":8},"end":{"line":93,"col":12}},"generated_from_span":null},"kind":{"StorageLive":14},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":8},"end":{"line":93,"col":12}},"generated_from_span":null},"kind":{"StorageLive":3},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":16},"end":{"line":93,"col":21}},"generated_from_span":null},"kind":{"StorageLive":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":16},"end":{"line":93,"col":17}},"generated_from_span":null},"kind":{"StorageLive":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":16},"end":{"line":93,"col":17}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":5},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":1},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":20},"end":{"line":93,"col":21}},"generated_from_span":null},"kind":{"StorageLive":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":20},"end":{"line":93,"col":21}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":6},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":2},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":16},"end":{"line":93,"col":21}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":7},"ty":{"Deduplicated":189}},{"BinaryOp":["AddChecked",{"Copy":{"kind":{"Local":5},"ty":{"Deduplicated":184}}},{"Copy":{"kind":{"Local":6},"ty":{"Deduplicated":184}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":93,"col":16},"end":{"line":93,"col":21}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Projection":[{"kind":{"Local":7},"ty":{"Deduplicated":189}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":188}}},"expected":false,"check_kind":{"Overflow":[{"Add":"Wrap"},{"Move":{"kind":{"Local":5},"ty":{"Deduplicated":184}}},{"Move":{"kind":{"Local":6},"ty":{"Deduplicated":184}}}]}},"target":2,"on_unwind":1}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":92,"col":0},"end":{"line":95,"col":1}},"generated_from_span":null},"kind":"UnwindResume","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":93,"col":16},"end":{"line":93,"col":21}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":4},"ty":{"Deduplicated":184}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":7},"ty":{"Deduplicated":189}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":20},"end":{"line":93,"col":21}},"generated_from_span":null},"kind":{"StorageDead":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":20},"end":{"line":93,"col":21}},"generated_from_span":null},"kind":{"StorageDead":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":23},"end":{"line":93,"col":28}},"generated_from_span":null},"kind":{"StorageLive":8},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":23},"end":{"line":93,"col":24}},"generated_from_span":null},"kind":{"StorageLive":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":23},"end":{"line":93,"col":24}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":9},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":1},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":27},"end":{"line":93,"col":28}},"generated_from_span":null},"kind":{"StorageLive":10},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":27},"end":{"line":93,"col":28}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":10},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":2},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":23},"end":{"line":93,"col":28}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":11},"ty":{"Deduplicated":189}},{"BinaryOp":["SubChecked",{"Copy":{"kind":{"Local":9},"ty":{"Deduplicated":184}}},{"Copy":{"kind":{"Local":10},"ty":{"Deduplicated":184}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":93,"col":23},"end":{"line":93,"col":28}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Projection":[{"kind":{"Local":11},"ty":{"Deduplicated":189}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":188}}},"expected":false,"check_kind":{"Overflow":[{"Sub":"Wrap"},{"Move":{"kind":{"Local":9},"ty":{"Deduplicated":184}}},{"Move":{"kind":{"Local":10},"ty":{"Deduplicated":184}}}]}},"target":3,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":93,"col":23},"end":{"line":93,"col":28}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":8},"ty":{"Deduplicated":184}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":11},"ty":{"Deduplicated":189}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":27},"end":{"line":93,"col":28}},"generated_from_span":null},"kind":{"StorageDead":10},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":27},"end":{"line":93,"col":28}},"generated_from_span":null},"kind":{"StorageDead":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":15},"end":{"line":93,"col":29}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":3},"ty":{"Deduplicated":1396}},{"Aggregate":[{"Adt":[{"id":"Tuple","generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}},null,null]},[{"Move":{"kind":{"Local":4},"ty":{"Deduplicated":184}}},{"Move":{"kind":{"Local":8},"ty":{"Deduplicated":184}}}]]}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":28},"end":{"line":93,"col":29}},"generated_from_span":null},"kind":{"StorageDead":8},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":28},"end":{"line":93,"col":29}},"generated_from_span":null},"kind":{"StorageDead":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":94,"col":4},"end":{"line":94,"col":10}},"generated_from_span":null},"kind":{"StorageLive":12},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":94,"col":4},"end":{"line":94,"col":10}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":12},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Local":3},"ty":{"Deduplicated":1396}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":94,"col":13},"end":{"line":94,"col":19}},"generated_from_span":null},"kind":{"StorageLive":13},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":94,"col":13},"end":{"line":94,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":13},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Local":3},"ty":{"Deduplicated":1396}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":94,"col":4},"end":{"line":94,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":14},"ty":{"Deduplicated":189}},{"BinaryOp":["MulChecked",{"Copy":{"kind":{"Local":12},"ty":{"Deduplicated":184}}},{"Copy":{"kind":{"Local":13},"ty":{"Deduplicated":184}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":94,"col":4},"end":{"line":94,"col":19}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Projection":[{"kind":{"Local":14},"ty":{"Deduplicated":189}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":188}}},"expected":false,"check_kind":{"Overflow":[{"Mul":"Wrap"},{"Move":{"kind":{"Local":12},"ty":{"Deduplicated":184}}},{"Move":{"kind":{"Local":13},"ty":{"Deduplicated":184}}}]}},"target":4,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":94,"col":4},"end":{"line":94,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":184}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":14},"ty":{"Deduplicated":189}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":94,"col":18},"end":{"line":94,"col":19}},"generated_from_span":null},"kind":{"StorageDead":13},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":94,"col":18},"end":{"line":94,"col":19}},"generated_from_span":null},"kind":{"StorageDead":12},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":95,"col":0},"end":{"line":95,"col":1}},"generated_from_span":null},"kind":{"StorageDead":3},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":95,"col":1},"end":{"line":95,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}}],"comments":[]}}},{"def_id":6,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_closure",0]}],"span":{"data":{"file_id":0,"beg":{"line":105,"col":0},"end":{"line":107,"col":1}},"generated_from_span":null},"source_text":"pub fn bool_then_closure(c: bool, x: i64) -> Option {\n c.then(|| x + 1)\n}","attr_info":{"attributes":[],"inline":"Never","rename":null,"public":true},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":188},{"Deduplicated":184}],"output":{"HashConsedValue":[1397,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":184}],"const_generics":[],"trait_refs":[{"Deduplicated":309}]}}}]}},"src":"TopLevel","is_global_initializer":null,"body":{"Unstructured":{"span":{"data":{"file_id":0,"beg":{"line":105,"col":0},"end":{"line":107,"col":1}},"generated_from_span":null},"bound_body_regions":16,"locals":{"arg_count":2,"locals":[{"index":0,"name":null,"span":{"data":{"file_id":0,"beg":{"line":105,"col":45},"end":{"line":105,"col":56}},"generated_from_span":null},"ty":{"Deduplicated":1397}},{"index":1,"name":"c","span":{"data":{"file_id":0,"beg":{"line":105,"col":25},"end":{"line":105,"col":26}},"generated_from_span":null},"ty":{"Deduplicated":188}},{"index":2,"name":"x","span":{"data":{"file_id":0,"beg":{"line":105,"col":34},"end":{"line":105,"col":35}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":3,"name":null,"span":{"data":{"file_id":0,"beg":{"line":106,"col":4},"end":{"line":106,"col":5}},"generated_from_span":null},"ty":{"Deduplicated":188}},{"index":4,"name":null,"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"ty":{"HashConsedValue":[1415,{"Adt":{"id":{"Adt":8},"generics":{"regions":[{"Body":1}],"types":[],"const_generics":[],"trait_refs":[]}}}]}},{"index":5,"name":null,"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"ty":{"HashConsedValue":[1417,{"Ref":[{"Body":3},{"Deduplicated":184},"Shared"]}]}}]},"body":[{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":106,"col":4},"end":{"line":106,"col":5}},"generated_from_span":null},"kind":{"StorageLive":3},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":106,"col":4},"end":{"line":106,"col":5}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":3},"ty":{"Deduplicated":188}},{"Use":{"Copy":{"kind":{"Local":1},"ty":{"Deduplicated":188}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"kind":{"StorageLive":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"kind":{"StorageLive":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":5},"ty":{"Deduplicated":1417}},{"Ref":{"place":{"kind":{"Local":2},"ty":{"Deduplicated":184}},"kind":"Shared","ptr_metadata":{"Const":{"kind":{"Adt":[null,[]]},"ty":{"Deduplicated":198}}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":4},"ty":{"Deduplicated":1415}},{"Aggregate":[{"Adt":[{"id":{"Adt":8},"generics":{"regions":[{"Body":4}],"types":[],"const_generics":[],"trait_refs":[]}},null,null]},[{"Move":{"kind":{"Local":5},"ty":{"Deduplicated":1417}}}]]}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":106,"col":12},"end":{"line":106,"col":13}},"generated_from_span":null},"kind":{"StorageDead":5},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":106,"col":4},"end":{"line":106,"col":20}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Regular":{"kind":{"Fun":{"Regular":16}},"generics":{"regions":[],"types":[{"Deduplicated":184},{"HashConsedValue":[5473,{"Adt":{"id":{"Adt":8},"generics":{"regions":[{"Body":5}],"types":[],"const_generics":[],"trait_refs":[]}}}]}],"const_generics":[],"trait_refs":[{"Deduplicated":309},{"HashConsedValue":[6258,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[6257,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":5473}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":5473}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6259,{"kind":{"TraitImpl":{"id":6,"generics":{"regions":[{"Body":5}],"types":[],"const_generics":[],"trait_refs":[]}}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":5473},{"Deduplicated":198}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6260,{"kind":{"TraitImpl":{"id":7,"generics":{"regions":[{"Body":5}],"types":[],"const_generics":[],"trait_refs":[]}}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":5,"generics":{"regions":[],"types":[{"Deduplicated":5473}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"args":[{"Move":{"kind":{"Local":3},"ty":{"Deduplicated":188}}},{"Move":{"kind":{"Local":4},"ty":{"Deduplicated":1415}}}],"dest":{"kind":{"Local":0},"ty":{"Deduplicated":1397}}},"target":2,"on_unwind":1}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":105,"col":0},"end":{"line":107,"col":1}},"generated_from_span":null},"kind":"UnwindResume","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":106,"col":19},"end":{"line":106,"col":20}},"generated_from_span":null},"kind":{"StorageDead":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":106,"col":19},"end":{"line":106,"col":20}},"generated_from_span":null},"kind":{"StorageDead":3},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":107,"col":1},"end":{"line":107,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}}],"comments":[]}}},{"def_id":7,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_some",0]}],"span":{"data":{"file_id":0,"beg":{"line":113,"col":0},"end":{"line":115,"col":1}},"generated_from_span":null},"source_text":"pub fn bool_then_some(c: bool, x: i64) -> Option {\n c.then_some(x + 1)\n}","attr_info":{"attributes":[],"inline":"Never","rename":null,"public":true},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":188},{"Deduplicated":184}],"output":{"Deduplicated":1397}},"src":"TopLevel","is_global_initializer":null,"body":{"Unstructured":{"span":{"data":{"file_id":0,"beg":{"line":113,"col":0},"end":{"line":115,"col":1}},"generated_from_span":null},"bound_body_regions":0,"locals":{"arg_count":2,"locals":[{"index":0,"name":null,"span":{"data":{"file_id":0,"beg":{"line":113,"col":42},"end":{"line":113,"col":53}},"generated_from_span":null},"ty":{"Deduplicated":1397}},{"index":1,"name":"c","span":{"data":{"file_id":0,"beg":{"line":113,"col":22},"end":{"line":113,"col":23}},"generated_from_span":null},"ty":{"Deduplicated":188}},{"index":2,"name":"x","span":{"data":{"file_id":0,"beg":{"line":113,"col":31},"end":{"line":113,"col":32}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":3,"name":null,"span":{"data":{"file_id":0,"beg":{"line":114,"col":4},"end":{"line":114,"col":5}},"generated_from_span":null},"ty":{"Deduplicated":188}},{"index":4,"name":null,"span":{"data":{"file_id":0,"beg":{"line":114,"col":16},"end":{"line":114,"col":21}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":5,"name":null,"span":{"data":{"file_id":0,"beg":{"line":114,"col":16},"end":{"line":114,"col":17}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":6,"name":null,"span":{"data":{"file_id":0,"beg":{"line":114,"col":16},"end":{"line":114,"col":21}},"generated_from_span":null},"ty":{"Deduplicated":189}}]},"body":[{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":114,"col":4},"end":{"line":114,"col":5}},"generated_from_span":null},"kind":{"StorageLive":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":114,"col":4},"end":{"line":114,"col":5}},"generated_from_span":null},"kind":{"StorageLive":3},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":114,"col":4},"end":{"line":114,"col":5}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":3},"ty":{"Deduplicated":188}},{"Use":{"Copy":{"kind":{"Local":1},"ty":{"Deduplicated":188}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":114,"col":16},"end":{"line":114,"col":21}},"generated_from_span":null},"kind":{"StorageLive":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":114,"col":16},"end":{"line":114,"col":17}},"generated_from_span":null},"kind":{"StorageLive":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":114,"col":16},"end":{"line":114,"col":17}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":5},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":2},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":114,"col":16},"end":{"line":114,"col":21}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":6},"ty":{"Deduplicated":189}},{"BinaryOp":["AddChecked",{"Copy":{"kind":{"Local":5},"ty":{"Deduplicated":184}}},{"Const":{"kind":{"Literal":{"Scalar":{"Signed":["I64","1"]}}},"ty":{"Deduplicated":184}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":114,"col":16},"end":{"line":114,"col":21}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Projection":[{"kind":{"Local":6},"ty":{"Deduplicated":189}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":188}}},"expected":false,"check_kind":{"Overflow":[{"Add":"Wrap"},{"Move":{"kind":{"Local":5},"ty":{"Deduplicated":184}}},{"Const":{"kind":{"Literal":{"Scalar":{"Signed":["I64","1"]}}},"ty":{"Deduplicated":184}}}]}},"target":2,"on_unwind":1}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":113,"col":0},"end":{"line":115,"col":1}},"generated_from_span":null},"kind":"UnwindResume","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":114,"col":16},"end":{"line":114,"col":21}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":4},"ty":{"Deduplicated":184}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":6},"ty":{"Deduplicated":189}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":114,"col":20},"end":{"line":114,"col":21}},"generated_from_span":null},"kind":{"StorageDead":5},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":114,"col":4},"end":{"line":114,"col":22}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Regular":{"kind":{"Fun":{"Regular":17}},"generics":{"regions":[],"types":[{"Deduplicated":184}],"const_generics":[],"trait_refs":[{"Deduplicated":309},{"HashConsedValue":[6262,{"kind":{"BuiltinOrAuto":{"builtin_data":"NoopDestruct","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":5,"generics":{"regions":[],"types":[{"Deduplicated":184}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"args":[{"Move":{"kind":{"Local":3},"ty":{"Deduplicated":188}}},{"Move":{"kind":{"Local":4},"ty":{"Deduplicated":184}}}],"dest":{"kind":{"Local":0},"ty":{"Deduplicated":1397}}},"target":3,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":114,"col":21},"end":{"line":114,"col":22}},"generated_from_span":null},"kind":{"StorageDead":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":114,"col":21},"end":{"line":114,"col":22}},"generated_from_span":null},"kind":{"StorageDead":3},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":115,"col":1},"end":{"line":115,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}}],"comments":[]}}},{"def_id":8,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["option_source",0]}],"span":{"data":{"file_id":0,"beg":{"line":123,"col":0},"end":{"line":125,"col":1}},"generated_from_span":null},"source_text":"fn option_source(keep: bool, value: i64) -> Option {\n if keep { Some(value) } else { None }\n}","attr_info":{"attributes":[],"inline":"Never","rename":null,"public":false},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":188},{"Deduplicated":184}],"output":{"Deduplicated":1397}},"src":"TopLevel","is_global_initializer":null,"body":{"Unstructured":{"span":{"data":{"file_id":0,"beg":{"line":123,"col":0},"end":{"line":125,"col":1}},"generated_from_span":null},"bound_body_regions":0,"locals":{"arg_count":2,"locals":[{"index":0,"name":null,"span":{"data":{"file_id":0,"beg":{"line":123,"col":44},"end":{"line":123,"col":55}},"generated_from_span":null},"ty":{"Deduplicated":1397}},{"index":1,"name":"keep","span":{"data":{"file_id":0,"beg":{"line":123,"col":17},"end":{"line":123,"col":21}},"generated_from_span":null},"ty":{"Deduplicated":188}},{"index":2,"name":"value","span":{"data":{"file_id":0,"beg":{"line":123,"col":29},"end":{"line":123,"col":34}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":3,"name":null,"span":{"data":{"file_id":0,"beg":{"line":124,"col":7},"end":{"line":124,"col":11}},"generated_from_span":null},"ty":{"Deduplicated":188}},{"index":4,"name":null,"span":{"data":{"file_id":0,"beg":{"line":124,"col":19},"end":{"line":124,"col":24}},"generated_from_span":null},"ty":{"Deduplicated":184}}]},"body":[{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":124,"col":7},"end":{"line":124,"col":11}},"generated_from_span":null},"kind":{"StorageLive":3},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":124,"col":7},"end":{"line":124,"col":11}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":3},"ty":{"Deduplicated":188}},{"Use":{"Copy":{"kind":{"Local":1},"ty":{"Deduplicated":188}}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":124,"col":7},"end":{"line":124,"col":11}},"generated_from_span":null},"kind":{"Switch":{"discr":{"Move":{"kind":{"Local":3},"ty":{"Deduplicated":188}}},"targets":{"If":[1,2]}}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":124,"col":19},"end":{"line":124,"col":24}},"generated_from_span":null},"kind":{"StorageLive":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":124,"col":19},"end":{"line":124,"col":24}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":4},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":2},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":124,"col":14},"end":{"line":124,"col":25}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":1397}},{"Aggregate":[{"Adt":[{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":184}],"const_generics":[],"trait_refs":[{"Deduplicated":309}]}},1,null]},[{"Move":{"kind":{"Local":4},"ty":{"Deduplicated":184}}}]]}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":124,"col":24},"end":{"line":124,"col":25}},"generated_from_span":null},"kind":{"StorageDead":4},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":124,"col":4},"end":{"line":124,"col":41}},"generated_from_span":null},"kind":{"Goto":{"target":3}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":124,"col":35},"end":{"line":124,"col":39}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":1397}},{"Aggregate":[{"Adt":[{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":184}],"const_generics":[],"trait_refs":[{"Deduplicated":309}]}},0,null]},[]]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":124,"col":4},"end":{"line":124,"col":41}},"generated_from_span":null},"kind":{"Goto":{"target":3}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":124,"col":40},"end":{"line":124,"col":41}},"generated_from_span":null},"kind":{"StorageDead":3},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":125,"col":1},"end":{"line":125,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}}],"comments":[]}}},{"def_id":9,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["option_question_mark",0]}],"span":{"data":{"file_id":0,"beg":{"line":128,"col":0},"end":{"line":131,"col":1}},"generated_from_span":null},"source_text":"pub fn option_question_mark(keep: bool, value: i64, addend: i64) -> Option {\n let v = option_source(keep, value)?;\n Some(v + addend)\n}","attr_info":{"attributes":[],"inline":"Never","rename":null,"public":true},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":188},{"Deduplicated":184},{"Deduplicated":184}],"output":{"Deduplicated":1397}},"src":"TopLevel","is_global_initializer":null,"body":{"Unstructured":{"span":{"data":{"file_id":0,"beg":{"line":128,"col":0},"end":{"line":131,"col":1}},"generated_from_span":null},"bound_body_regions":0,"locals":{"arg_count":3,"locals":[{"index":0,"name":null,"span":{"data":{"file_id":0,"beg":{"line":128,"col":68},"end":{"line":128,"col":79}},"generated_from_span":null},"ty":{"Deduplicated":1397}},{"index":1,"name":"keep","span":{"data":{"file_id":0,"beg":{"line":128,"col":28},"end":{"line":128,"col":32}},"generated_from_span":null},"ty":{"Deduplicated":188}},{"index":2,"name":"value","span":{"data":{"file_id":0,"beg":{"line":128,"col":40},"end":{"line":128,"col":45}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":3,"name":"addend","span":{"data":{"file_id":0,"beg":{"line":128,"col":52},"end":{"line":128,"col":58}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":4,"name":"v","span":{"data":{"file_id":0,"beg":{"line":129,"col":8},"end":{"line":129,"col":9}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":5,"name":null,"span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":39}},"generated_from_span":null},"ty":{"HashConsedValue":[1513,{"Adt":{"id":{"Adt":6},"generics":{"regions":[],"types":[{"HashConsedValue":[1510,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":812}],"const_generics":[],"trait_refs":[{"Deduplicated":814}]}}}]},{"Deduplicated":184}],"const_generics":[],"trait_refs":[{"HashConsedValue":[1512,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[1511,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":1510}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1510}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":309}]}}}]}},{"index":6,"name":null,"span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":38}},"generated_from_span":null},"ty":{"Deduplicated":1397}},{"index":7,"name":null,"span":{"data":{"file_id":0,"beg":{"line":129,"col":26},"end":{"line":129,"col":30}},"generated_from_span":null},"ty":{"Deduplicated":188}},{"index":8,"name":null,"span":{"data":{"file_id":0,"beg":{"line":129,"col":32},"end":{"line":129,"col":37}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":9,"name":null,"span":{"data":{"file_id":0,"beg":{"line":129,"col":38},"end":{"line":129,"col":39}},"generated_from_span":null},"ty":{"Deduplicated":367}},{"index":10,"name":"residual","span":{"data":{"file_id":0,"beg":{"line":129,"col":38},"end":{"line":129,"col":39}},"generated_from_span":null},"ty":{"Deduplicated":1510}},{"index":11,"name":null,"span":{"data":{"file_id":0,"beg":{"line":129,"col":38},"end":{"line":129,"col":39}},"generated_from_span":null},"ty":{"Deduplicated":1510}},{"index":12,"name":"val","span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":39}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":13,"name":null,"span":{"data":{"file_id":0,"beg":{"line":130,"col":9},"end":{"line":130,"col":19}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":14,"name":null,"span":{"data":{"file_id":0,"beg":{"line":130,"col":9},"end":{"line":130,"col":10}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":15,"name":null,"span":{"data":{"file_id":0,"beg":{"line":130,"col":13},"end":{"line":130,"col":19}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":16,"name":null,"span":{"data":{"file_id":0,"beg":{"line":130,"col":9},"end":{"line":130,"col":19}},"generated_from_span":null},"ty":{"Deduplicated":189}}]},"body":[{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":129,"col":8},"end":{"line":129,"col":9}},"generated_from_span":null},"kind":{"StorageLive":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":8},"end":{"line":129,"col":9}},"generated_from_span":null},"kind":{"StorageLive":16},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":8},"end":{"line":129,"col":9}},"generated_from_span":null},"kind":{"StorageLive":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"StorageLive":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":38}},"generated_from_span":null},"kind":{"StorageLive":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":26},"end":{"line":129,"col":30}},"generated_from_span":null},"kind":{"StorageLive":7},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":26},"end":{"line":129,"col":30}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":7},"ty":{"Deduplicated":188}},{"Use":{"Copy":{"kind":{"Local":1},"ty":{"Deduplicated":188}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":32},"end":{"line":129,"col":37}},"generated_from_span":null},"kind":{"StorageLive":8},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":32},"end":{"line":129,"col":37}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":8},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":2},"ty":{"Deduplicated":184}}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":38}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Regular":{"kind":{"Fun":{"Regular":8}},"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}},"args":[{"Move":{"kind":{"Local":7},"ty":{"Deduplicated":188}}},{"Move":{"kind":{"Local":8},"ty":{"Deduplicated":184}}}],"dest":{"kind":{"Local":6},"ty":{"Deduplicated":1397}}},"target":2,"on_unwind":1}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":128,"col":0},"end":{"line":131,"col":1}},"generated_from_span":null},"kind":"UnwindResume","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":129,"col":37},"end":{"line":129,"col":38}},"generated_from_span":null},"kind":{"StorageDead":8},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":37},"end":{"line":129,"col":38}},"generated_from_span":null},"kind":{"StorageDead":7},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Regular":{"kind":{"Fun":{"Regular":18}},"generics":{"regions":[],"types":[{"Deduplicated":184}],"const_generics":[],"trait_refs":[{"Deduplicated":309}]}}},"args":[{"Move":{"kind":{"Local":6},"ty":{"Deduplicated":1397}}}],"dest":{"kind":{"Local":5},"ty":{"Deduplicated":1513}}},"target":3,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":129,"col":38},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"StorageDead":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":9},"ty":{"Deduplicated":367}},{"Discriminant":{"kind":{"Local":5},"ty":{"Deduplicated":1513}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"Switch":{"discr":{"Move":{"kind":{"Local":9},"ty":{"Deduplicated":367}}},"targets":{"SwitchInt":[{"Int":"Isize"},[[{"Scalar":{"Signed":["Isize","0"]}},4],[{"Scalar":{"Signed":["Isize","1"]}},5]],6]}}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"StorageLive":12},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":12},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Local":5},"ty":{"Deduplicated":1513}},{"Field":[{"Adt":[6,0]},0]}]},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":4},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":12},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":38},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"StorageDead":12},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":39},"end":{"line":129,"col":40}},"generated_from_span":null},"kind":{"StorageDead":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":130,"col":9},"end":{"line":130,"col":19}},"generated_from_span":null},"kind":{"StorageLive":13},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":130,"col":9},"end":{"line":130,"col":10}},"generated_from_span":null},"kind":{"StorageLive":14},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":130,"col":9},"end":{"line":130,"col":10}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":14},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":4},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":130,"col":13},"end":{"line":130,"col":19}},"generated_from_span":null},"kind":{"StorageLive":15},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":130,"col":13},"end":{"line":130,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":15},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Local":3},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":130,"col":9},"end":{"line":130,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":16},"ty":{"Deduplicated":189}},{"BinaryOp":["AddChecked",{"Copy":{"kind":{"Local":14},"ty":{"Deduplicated":184}}},{"Copy":{"kind":{"Local":15},"ty":{"Deduplicated":184}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":130,"col":9},"end":{"line":130,"col":19}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Projection":[{"kind":{"Local":16},"ty":{"Deduplicated":189}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":188}}},"expected":false,"check_kind":{"Overflow":[{"Add":"Wrap"},{"Move":{"kind":{"Local":15},"ty":{"Deduplicated":184}}},{"Move":{"kind":{"Local":16},"ty":{"Deduplicated":184}}}]}},"target":7,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":129,"col":38},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"StorageLive":10},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":38},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":10},"ty":{"Deduplicated":1510}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Local":5},"ty":{"Deduplicated":1513}},{"Field":[{"Adt":[6,1]},0]}]},"ty":{"Deduplicated":1510}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":38},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"StorageLive":11},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":38},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":11},"ty":{"Deduplicated":1510}},{"Use":{"Copy":{"kind":{"Local":10},"ty":{"Deduplicated":1510}}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Regular":{"kind":{"Fun":{"Regular":19}},"generics":{"regions":[],"types":[{"Deduplicated":184}],"const_generics":[],"trait_refs":[{"Deduplicated":309}]}}},"args":[{"Move":{"kind":{"Local":11},"ty":{"Deduplicated":1510}}}],"dest":{"kind":{"Local":0},"ty":{"Deduplicated":1397}}},"target":8,"on_unwind":1}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"Abort":"UndefinedBehavior"},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":130,"col":9},"end":{"line":130,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":13},"ty":{"Deduplicated":184}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":16},"ty":{"Deduplicated":189}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":130,"col":18},"end":{"line":130,"col":19}},"generated_from_span":null},"kind":{"StorageDead":15},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":130,"col":18},"end":{"line":130,"col":19}},"generated_from_span":null},"kind":{"StorageDead":14},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":130,"col":4},"end":{"line":130,"col":20}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":1397}},{"Aggregate":[{"Adt":[{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":184}],"const_generics":[],"trait_refs":[{"Deduplicated":309}]}},1,null]},[{"Move":{"kind":{"Local":13},"ty":{"Deduplicated":184}}}]]}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":130,"col":19},"end":{"line":130,"col":20}},"generated_from_span":null},"kind":{"StorageDead":13},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":131,"col":0},"end":{"line":131,"col":1}},"generated_from_span":null},"kind":{"StorageDead":4},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":131,"col":1},"end":{"line":131,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":129,"col":38},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"StorageDead":11},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":38},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"StorageDead":10},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":39},"end":{"line":129,"col":40}},"generated_from_span":null},"kind":{"StorageDead":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":131,"col":0},"end":{"line":131,"col":1}},"generated_from_span":null},"kind":{"StorageDead":4},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":131,"col":1},"end":{"line":131,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}}],"comments":[]}}},{"def_id":10,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":0}},{"Ident":["into_iter",0]}],"span":{"data":{"file_id":4,"beg":{"line":25,"col":4},"end":{"line":25,"col":37}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[{"index":0,"name":"'a","mutability":"Unknown"}],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":4,"beg":{"line":21,"col":9},"end":{"line":21,"col":10}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"HashConsedValue":[1568,{"Ref":[{"Var":{"Bound":[0,0]}},{"Deduplicated":1040},"Shared"]}]}],"output":{"HashConsedValue":[1591,{"Adt":{"id":{"Adt":4},"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[{"Deduplicated":173}],"const_generics":[],"trait_refs":[{"Deduplicated":176}]}}}]}},"src":{"TraitImpl":{"impl_ref":{"id":0,"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[{"Deduplicated":173}],"const_generics":[],"trait_refs":[{"Deduplicated":176}]}},"trait_ref":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":1568}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"reuses_default":true}},"is_global_initializer":null,"body":"Opaque"},{"def_id":11,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["next",0]}],"span":{"data":{"file_id":7,"beg":{"line":157,"col":12},"end":{"line":157,"col":47}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":"Hint","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[{"index":0,"name":"'a","mutability":"Unknown"},{"index":1,"name":null,"mutability":"Unknown"}],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":7,"beg":{"line":153,"col":17},"end":{"line":153,"col":18}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"HashConsedValue":[6263,{"Ref":[{"Var":{"Bound":[0,1]}},{"Deduplicated":1591},"Mut"]}]}],"output":{"HashConsedValue":[6265,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"HashConsedValue":[4517,{"Ref":[{"Var":{"Bound":[0,0]}},{"Deduplicated":173},"Shared"]}]}],"const_generics":[],"trait_refs":[{"HashConsedValue":[6264,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[5483,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[5482,{"Ref":["Erased",{"Deduplicated":141},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[1576,{"Ref":[{"Var":{"Bound":[1,0]}},{"Deduplicated":141},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},"src":{"TraitImpl":{"impl_ref":{"id":1,"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[{"Deduplicated":173}],"const_generics":[],"trait_refs":[{"Deduplicated":176}]}},"trait_ref":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":1591}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"reuses_default":true}},"is_global_initializer":null,"body":"Opaque"},{"def_id":12,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Impl":{"Ty":{"params":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":5,"beg":{"line":101,"col":5},"end":{"line":101,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"Deduplicated":1040},"kind":"InherentImplBlock"}}},{"Ident":["iter",0]}],"span":{"data":{"file_id":5,"beg":{"line":1040,"col":4},"end":{"line":1040,"col":43}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Returns an iterator over the slice."},{"DocComment":""},{"DocComment":" The iterator yields all items from start to end."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let x = &[1, 2, 4];"},{"DocComment":" let mut iterator = x.iter();"},{"DocComment":""},{"DocComment":" assert_eq!(iterator.next(), Some(&1));"},{"DocComment":" assert_eq!(iterator.next(), Some(&2));"},{"DocComment":" assert_eq!(iterator.next(), Some(&4));"},{"DocComment":" assert_eq!(iterator.next(), None);"},{"DocComment":" ```"}],"inline":"Hint","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"slice_iter"},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":5,"beg":{"line":101,"col":5},"end":{"line":101,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":1568}],"output":{"Deduplicated":1591}},"src":"TopLevel","is_global_initializer":null,"body":"Opaque"},{"def_id":13,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Impl":{"Trait":2}},{"Ident":["into_iter",0]}],"span":{"data":{"file_id":11,"beg":{"line":322,"col":4},"end":{"line":322,"col":27}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":"Hint","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":11,"beg":{"line":317,"col":5},"end":{"line":317,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":11,"beg":{"line":317,"col":8},"end":{"line":317,"col":24}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":173}],"output":{"Deduplicated":173}},"src":{"TraitImpl":{"impl_ref":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":173}],"const_generics":[],"trait_refs":[{"Deduplicated":176},{"HashConsedValue":[4528,{"kind":{"Clause":{"Bound":[0,1]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}]}]}},"trait_ref":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":173}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"reuses_default":true}},"is_global_initializer":null,"body":"Opaque"},{"def_id":14,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["result",0]},{"Impl":{"Trait":3}},{"Ident":["branch",0]}],"span":{"data":{"file_id":3,"beg":{"line":2172,"col":4},"end":{"line":2172,"col":64}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":"Hint","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"T"},{"index":1,"name":"E"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":3,"beg":{"line":2162,"col":5},"end":{"line":2162,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":3,"beg":{"line":2162,"col":8},"end":{"line":2162,"col":9}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"HashConsedValue":[4540,{"Adt":{"id":{"Adt":3},"generics":{"regions":[],"types":[{"Deduplicated":173},{"Deduplicated":1555}],"const_generics":[],"trait_refs":[{"Deduplicated":176},{"HashConsedValue":[2241,{"kind":{"Clause":{"Bound":[0,1]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}],"output":{"HashConsedValue":[6266,{"Adt":{"id":{"Adt":6},"generics":{"regions":[],"types":[{"HashConsedValue":[4560,{"Adt":{"id":{"Adt":3},"generics":{"regions":[],"types":[{"Deduplicated":812},{"Deduplicated":1555}],"const_generics":[],"trait_refs":[{"Deduplicated":814},{"Deduplicated":2241}]}}}]},{"Deduplicated":173}],"const_generics":[],"trait_refs":[{"HashConsedValue":[4562,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[4561,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[4546,{"Adt":{"id":{"Adt":3},"generics":{"regions":[],"types":[{"Deduplicated":812},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[{"Deduplicated":814},{"HashConsedValue":[2630,{"kind":{"Clause":{"Bound":[1,1]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[2212,{"TypeVar":{"Bound":[2,1]}}]}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":4546}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":176}]}}}]}},"src":{"TraitImpl":{"impl_ref":{"id":3,"generics":{"regions":[],"types":[{"Deduplicated":173},{"Deduplicated":1555}],"const_generics":[],"trait_refs":[{"Deduplicated":176},{"Deduplicated":2241}]}},"trait_ref":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":4540}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":1},"reuses_default":true}},"is_global_initializer":null,"body":"Opaque"},{"def_id":15,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["result",0]},{"Impl":{"Trait":4}},{"Ident":["from_residual",0]}],"span":{"data":{"file_id":3,"beg":{"line":2187,"col":4},"end":{"line":2187,"col":70}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":"Hint","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"T"},{"index":1,"name":"E"},{"index":2,"name":"F"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":3,"beg":{"line":2182,"col":5},"end":{"line":2182,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":3,"beg":{"line":2182,"col":8},"end":{"line":2182,"col":9}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":3,"beg":{"line":2182,"col":11},"end":{"line":2182,"col":12}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":2592}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":3,"span":{"data":{"file_id":3,"beg":{"line":2182,"col":14},"end":{"line":2182,"col":29}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":3,"generics":{"regions":[],"types":[{"Deduplicated":2592},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":4560}],"output":{"HashConsedValue":[4564,{"Adt":{"id":{"Adt":3},"generics":{"regions":[],"types":[{"Deduplicated":173},{"HashConsedValue":[2605,{"TypeVar":{"Bound":[0,2]}}]}],"const_generics":[],"trait_refs":[{"Deduplicated":176},{"HashConsedValue":[2633,{"kind":{"Clause":{"Bound":[0,2]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":2592}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},"src":{"TraitImpl":{"impl_ref":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":173},{"Deduplicated":1555},{"Deduplicated":2605}],"const_generics":[],"trait_refs":[{"Deduplicated":176},{"Deduplicated":2241},{"Deduplicated":2633},{"HashConsedValue":[4565,{"kind":{"Clause":{"Bound":[0,3]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":3,"generics":{"regions":[],"types":[{"Deduplicated":2592},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}]}]}},"trait_ref":{"id":24,"generics":{"regions":[],"types":[{"Deduplicated":4564},{"Deduplicated":4560}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"reuses_default":true}},"is_global_initializer":null,"body":"Opaque"},{"def_id":16,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["bool",0]},{"Impl":{"Ty":{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"Deduplicated":188},"kind":"InherentImplBlock"}}},{"Ident":["then",0]}],"span":{"data":{"file_id":15,"beg":{"line":65,"col":4},"end":{"line":65,"col":94}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Returns `Some(f())` if the `bool` is [`true`](../std/keyword.true.html),"},{"DocComment":" or `None` otherwise."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" assert_eq!(false.then(|| 0), None);"},{"DocComment":" assert_eq!(true.then(|| 0), Some(0));"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let mut a = 0;"},{"DocComment":""},{"DocComment":" true.then(|| { a += 1; });"},{"DocComment":" false.then(|| { a += 1; });"},{"DocComment":""},{"DocComment":" // `a` is incremented once because the closure is evaluated lazily by"},{"DocComment":" // `then`."},{"DocComment":" assert_eq!(a, 1);"},{"DocComment":" ```"}],"inline":"Hint","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"bool_then"},"generics":{"regions":[],"types":[{"index":0,"name":"T"},{"index":1,"name":"F"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":15,"beg":{"line":65,"col":22},"end":{"line":65,"col":23}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":15,"beg":{"line":65,"col":25},"end":{"line":65,"col":26}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":15,"beg":{"line":65,"col":28},"end":{"line":65,"col":49}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":1551},{"Deduplicated":198}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":3,"span":{"data":{"file_id":15,"beg":{"line":65,"col":52},"end":{"line":65,"col":68}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":5,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[{"regions":[],"skip_binder":{"trait_ref":{"HashConsedValue":[6103,{"kind":{"Clause":{"Bound":[1,2]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":2212},{"Deduplicated":198}],"const_generics":[],"trait_refs":[]}}}}]},"type_id":0,"ty":{"Deduplicated":141}}}]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":188},{"Deduplicated":1555}],"output":{"HashConsedValue":[3327,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":173}],"const_generics":[],"trait_refs":[{"Deduplicated":176}]}}}]}},"src":"TopLevel","is_global_initializer":null,"body":"Opaque"},{"def_id":17,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["bool",0]},{"Impl":{"Ty":{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"Deduplicated":188},"kind":"InherentImplBlock"}}},{"Ident":["then_some",0]}],"span":{"data":{"file_id":15,"beg":{"line":36,"col":4},"end":{"line":36,"col":72}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Returns `Some(t)` if the `bool` is [`true`](../std/keyword.true.html),"},{"DocComment":" or `None` otherwise."},{"DocComment":""},{"DocComment":" Arguments passed to `then_some` are eagerly evaluated; if you are"},{"DocComment":" passing the result of a function call, it is recommended to use"},{"DocComment":" [`then`], which is lazily evaluated."},{"DocComment":""},{"DocComment":" [`then`]: bool::then"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" assert_eq!(false.then_some(0), None);"},{"DocComment":" assert_eq!(true.then_some(0), Some(0));"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let mut a = 0;"},{"DocComment":" let mut function_with_side_effects = || { a += 1; };"},{"DocComment":""},{"DocComment":" true.then_some(function_with_side_effects());"},{"DocComment":" false.then_some(function_with_side_effects());"},{"DocComment":""},{"DocComment":" // `a` is incremented twice because the value passed to `then_some` is"},{"DocComment":" // evaluated eagerly."},{"DocComment":" assert_eq!(a, 2);"},{"DocComment":" ```"}],"inline":"Hint","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":15,"beg":{"line":36,"col":27},"end":{"line":36,"col":28}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":15,"beg":{"line":36,"col":30},"end":{"line":36,"col":46}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":5,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":188},{"Deduplicated":173}],"output":{"Deduplicated":3327}},"src":"TopLevel","is_global_initializer":null,"body":"Opaque"},{"def_id":18,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["option",0]},{"Impl":{"Trait":8}},{"Ident":["branch",0]}],"span":{"data":{"file_id":6,"beg":{"line":2765,"col":4},"end":{"line":2765,"col":64}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":"Hint","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":6,"beg":{"line":2755,"col":5},"end":{"line":2755,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":3327}],"output":{"HashConsedValue":[6267,{"Adt":{"id":{"Adt":6},"generics":{"regions":[],"types":[{"Deduplicated":1510},{"Deduplicated":173}],"const_generics":[],"trait_refs":[{"Deduplicated":1512},{"Deduplicated":176}]}}}]}},"src":{"TraitImpl":{"impl_ref":{"id":8,"generics":{"regions":[],"types":[{"Deduplicated":173}],"const_generics":[],"trait_refs":[{"Deduplicated":176}]}},"trait_ref":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":3327}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":1},"reuses_default":true}},"is_global_initializer":null,"body":"Opaque"},{"def_id":19,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["option",0]},{"Impl":{"Trait":9}},{"Ident":["from_residual",0]}],"span":{"data":{"file_id":6,"beg":{"line":2779,"col":4},"end":{"line":2779,"col":67}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":"Hint","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":6,"beg":{"line":2777,"col":5},"end":{"line":2777,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":1510}],"output":{"Deduplicated":3327}},"src":{"TraitImpl":{"impl_ref":{"id":9,"generics":{"regions":[],"types":[{"Deduplicated":173}],"const_generics":[],"trait_refs":[{"Deduplicated":176}]}},"trait_ref":{"id":24,"generics":{"regions":[],"types":[{"Deduplicated":3327},{"Deduplicated":1510}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"reuses_default":true}},"is_global_initializer":null,"body":"Opaque"},null,null,null,null,null,null,null,{"def_id":27,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["next",0]}],"span":{"data":{"file_id":14,"beg":{"line":77,"col":4},"end":{"line":77,"col":45}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Advances the iterator and returns the next value."},{"DocComment":""},{"DocComment":" Returns [`None`] when iteration is finished. Individual iterator"},{"DocComment":" implementations may choose to resume iteration, and so calling `next()`"},{"DocComment":" again may or may not eventually start returning [`Some(Item)`] again at some"},{"DocComment":" point."},{"DocComment":""},{"DocComment":" [`Some(Item)`]: Some"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let a = [1, 2, 3];"},{"DocComment":""},{"DocComment":" let mut iter = a.into_iter();"},{"DocComment":""},{"DocComment":" // A call to next() returns the next value..."},{"DocComment":" assert_eq!(Some(1), iter.next());"},{"DocComment":" assert_eq!(Some(2), iter.next());"},{"DocComment":" assert_eq!(Some(3), iter.next());"},{"DocComment":""},{"DocComment":" // ... and then None once it's over."},{"DocComment":" assert_eq!(None, iter.next());"},{"DocComment":""},{"DocComment":" // More calls may or may not return `None`. Here, they always will."},{"DocComment":" assert_eq!(None, iter.next());"},{"DocComment":" assert_eq!(None, iter.next());"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"next"},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"HashConsedValue":[2945,{"Ref":[{"Var":{"Bound":[0,0]}},{"Deduplicated":173},"Mut"]}]}],"output":{"HashConsedValue":[6271,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"HashConsedValue":[6268,{"TraitType":[{"HashConsedValue":[4631,{"kind":{"Clause":{"Bound":[0,0]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[{"HashConsedValue":[6270,{"kind":{"ParentClause":[{"Deduplicated":4631},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[6269,{"TraitType":[{"HashConsedValue":[5591,{"kind":{"Clause":{"Bound":[1,0]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":1577}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},"src":{"TraitDecl":{"trait_ref":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":173}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,{"def_id":173,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["result",0]},{"Impl":{"Trait":3}},{"Ident":["from_output",0]}],"span":{"data":{"file_id":3,"beg":{"line":2167,"col":4},"end":{"line":2167,"col":48}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":"Hint","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"T"},{"index":1,"name":"E"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":3,"beg":{"line":2162,"col":5},"end":{"line":2162,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":3,"beg":{"line":2162,"col":8},"end":{"line":2162,"col":9}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":173}],"output":{"Deduplicated":4540}},"src":{"TraitImpl":{"impl_ref":{"id":3,"generics":{"regions":[],"types":[{"Deduplicated":173},{"Deduplicated":1555}],"const_generics":[],"trait_refs":[{"Deduplicated":176},{"Deduplicated":2241}]}},"trait_ref":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":4540}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"reuses_default":true}},"is_global_initializer":null,"body":"Opaque"},{"def_id":174,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["convert",0]},{"Ident":["From",0]},{"Ident":["from",0]}],"span":{"data":{"file_id":10,"beg":{"line":592,"col":4},"end":{"line":592,"col":30}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Converts to this type from the input type."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"from_fn"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":3,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":1555}],"output":{"Deduplicated":173}},"src":{"TraitDecl":{"trait_ref":{"id":3,"generics":{"regions":[],"types":[{"Deduplicated":173},{"Deduplicated":1555}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},{"def_id":175,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["convert",0]},{"Impl":{"Trait":5}},{"Ident":["from",0]}],"span":{"data":{"file_id":10,"beg":{"line":788,"col":4},"end":{"line":788,"col":22}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Returns the argument unchanged."}],"inline":"Always","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":10,"beg":{"line":785,"col":5},"end":{"line":785,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":173}],"output":{"Deduplicated":173}},"src":{"TraitImpl":{"impl_ref":{"id":5,"generics":{"regions":[],"types":[{"Deduplicated":173}],"const_generics":[],"trait_refs":[{"Deduplicated":176}]}},"trait_ref":{"id":3,"generics":{"regions":[],"types":[{"Deduplicated":173},{"Deduplicated":173}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"reuses_default":true}},"is_global_initializer":null,"body":"Opaque"},{"def_id":176,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_closure",0]},{"Impl":{"Trait":6}},{"Ident":["call_once",0]}],"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"source_text":"|| x + 1","attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"HashConsedValue":[4580,{"Adt":{"id":{"Adt":8},"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[],"const_generics":[],"trait_refs":[]}}}]},{"Deduplicated":198}],"output":{"Deduplicated":184}},"src":{"TraitImpl":{"impl_ref":{"id":6,"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[],"const_generics":[],"trait_refs":[]}},"trait_ref":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":4580},{"Deduplicated":198}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"reuses_default":false}},"is_global_initializer":null,"body":{"Unstructured":{"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"bound_body_regions":1,"locals":{"arg_count":2,"locals":[{"index":0,"name":null,"span":{"data":{"file_id":0,"beg":{"line":106,"col":13},"end":{"line":106,"col":13}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":1,"name":null,"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"ty":{"Deduplicated":4580}},{"index":2,"name":"tupled_args","span":{"data":{"file_id":0,"beg":{"line":0,"col":0},"end":{"line":0,"col":0}},"generated_from_span":null},"ty":{"Deduplicated":198}},{"index":3,"name":null,"span":{"data":{"file_id":0,"beg":{"line":106,"col":14},"end":{"line":106,"col":15}},"generated_from_span":null},"ty":{"Deduplicated":184}},{"index":4,"name":null,"span":{"data":{"file_id":0,"beg":{"line":106,"col":14},"end":{"line":106,"col":19}},"generated_from_span":null},"ty":{"Deduplicated":189}}]},"body":[{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":106,"col":14},"end":{"line":106,"col":15}},"generated_from_span":null},"kind":{"StorageLive":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":106,"col":14},"end":{"line":106,"col":15}},"generated_from_span":null},"kind":{"StorageLive":3},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":106,"col":14},"end":{"line":106,"col":15}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":3},"ty":{"Deduplicated":184}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Projection":[{"kind":{"Local":1},"ty":{"Deduplicated":4580}},{"Field":[{"Adt":[8,null]},0]}]},"ty":{"HashConsedValue":[6272,{"Ref":[{"Body":0},{"Deduplicated":184},"Shared"]}]}},"Deref"]},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":106,"col":14},"end":{"line":106,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":4},"ty":{"Deduplicated":189}},{"BinaryOp":["AddChecked",{"Copy":{"kind":{"Local":3},"ty":{"Deduplicated":184}}},{"Const":{"kind":{"Literal":{"Scalar":{"Signed":["I64","1"]}}},"ty":{"Deduplicated":184}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":106,"col":14},"end":{"line":106,"col":19}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Projection":[{"kind":{"Local":4},"ty":{"Deduplicated":189}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":188}}},"expected":false,"check_kind":{"Overflow":[{"Add":"Wrap"},{"Move":{"kind":{"Local":3},"ty":{"Deduplicated":184}}},{"Const":{"kind":{"Literal":{"Scalar":{"Signed":["I64","1"]}}},"ty":{"Deduplicated":184}}}]}},"target":2,"on_unwind":1}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"kind":"UnwindResume","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":106,"col":14},"end":{"line":106,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":184}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":4},"ty":{"Deduplicated":189}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":184}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":106,"col":18},"end":{"line":106,"col":19}},"generated_from_span":null},"kind":{"StorageDead":3},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":106,"col":19},"end":{"line":106,"col":19}},"generated_from_span":null},"kind":"Return","comments_before":[]}}],"comments":[]}}},{"def_id":177,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["function",0]},{"Ident":["FnOnce",0]},{"Ident":["call_once",0]}],"span":{"data":{"file_id":16,"beg":{"line":250,"col":4},"end":{"line":250,"col":70}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Performs the call operation."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"Args"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":173},{"Deduplicated":1555}],"output":{"HashConsedValue":[6274,{"TraitType":[{"HashConsedValue":[6273,{"kind":{"Clause":{"Bound":[0,0]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}},"src":{"TraitDecl":{"trait_ref":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":173},{"Deduplicated":1555}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},{"def_id":178,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Ident":["Destruct",0]},{"Ident":["drop_in_place",0]}],"span":{"data":{"file_id":1,"beg":{"line":1063,"col":0},"end":{"line":1063,"col":38}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":true,"inputs":[{"HashConsedValue":[6275,{"RawPtr":[{"Deduplicated":173},"Mut"]}]}],"output":{"Deduplicated":198}},"src":{"TraitDecl":{"trait_ref":{"id":5,"generics":{"regions":[],"types":[{"Deduplicated":173}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},{"def_id":179,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_closure",0]},{"Ident":["closure",0]},{"Impl":{"Trait":7}},{"Ident":["drop_in_place",0]}],"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"source_text":"|| x + 1","attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":true,"inputs":[{"HashConsedValue":[6276,{"RawPtr":[{"Deduplicated":4580},"Mut"]}]}],"output":{"Deduplicated":198}},"src":{"TraitImpl":{"impl_ref":{"id":7,"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[],"const_generics":[],"trait_refs":[]}},"trait_ref":{"id":5,"generics":{"regions":[],"types":[{"Deduplicated":4580}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"reuses_default":false}},"is_global_initializer":null,"body":"Missing"},{"def_id":180,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["option",0]},{"Impl":{"Trait":8}},{"Ident":["from_output",0]}],"span":{"data":{"file_id":6,"beg":{"line":2760,"col":4},"end":{"line":2760,"col":48}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":"Hint","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":6,"beg":{"line":2755,"col":5},"end":{"line":2755,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":173}],"output":{"Deduplicated":3327}},"src":{"TraitImpl":{"impl_ref":{"id":8,"generics":{"regions":[],"types":[{"Deduplicated":173}],"const_generics":[],"trait_refs":[{"Deduplicated":176}]}},"trait_ref":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":3327}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"reuses_default":true}},"is_global_initializer":null,"body":"Opaque"},{"def_id":181,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["IntoIterator",0]},{"Ident":["into_iter",0]}],"span":{"data":{"file_id":11,"beg":{"line":312,"col":4},"end":{"line":312,"col":41}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Creates an iterator from a value."},{"DocComment":""},{"DocComment":" See the [module-level documentation] for more."},{"DocComment":""},{"DocComment":" [module-level documentation]: crate::iter"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let v = [1, 2, 3];"},{"DocComment":" let mut iter = v.into_iter();"},{"DocComment":""},{"DocComment":" assert_eq!(Some(1), iter.next());"},{"DocComment":" assert_eq!(Some(2), iter.next());"},{"DocComment":" assert_eq!(Some(3), iter.next());"},{"DocComment":" assert_eq!(None, iter.next());"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"into_iter"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":173}],"output":{"HashConsedValue":[6277,{"TraitType":[{"HashConsedValue":[5600,{"kind":{"Clause":{"Bound":[0,0]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}]},1]}]}},"src":{"TraitDecl":{"trait_ref":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":173}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},{"def_id":182,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["clone",0]},{"Ident":["Clone",0]},{"Ident":["clone",0]}],"span":{"data":{"file_id":25,"beg":{"line":236,"col":4},"end":{"line":236,"col":28}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Returns a duplicate of the value."},{"DocComment":""},{"DocComment":" Note that what \"duplicate\" means varies by type:"},{"DocComment":" - For most types, this creates a deep, independent copy"},{"DocComment":" - For reference types like `&T`, this creates another reference to the same value"},{"DocComment":" - For smart pointers like [`Arc`] or [`Rc`], this increments the reference count"},{"DocComment":" but still points to the same underlying data"},{"DocComment":""},{"DocComment":" [`Arc`]: ../../std/sync/struct.Arc.html"},{"DocComment":" [`Rc`]: ../../std/rc/struct.Rc.html"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #![allow(noop_method_call)]"},{"DocComment":" let hello = \"Hello\"; // &str implements Clone"},{"DocComment":""},{"DocComment":" assert_eq!(\"Hello\", hello.clone());"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" Example with a reference-counted type:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::sync::{Arc, Mutex};"},{"DocComment":""},{"DocComment":" let data = Arc::new(Mutex::new(vec![1, 2, 3]));"},{"DocComment":" let data_clone = data.clone(); // Creates another Arc pointing to the same Mutex"},{"DocComment":""},{"DocComment":" {"},{"DocComment":" let mut lock = data.lock().unwrap();"},{"DocComment":" lock.push(4);"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" // Changes are visible through the clone because they share the same underlying data"},{"DocComment":" assert_eq!(*data_clone.lock().unwrap(), vec![1, 2, 3, 4]);"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"clone_fn"},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":8,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":4517}],"output":{"Deduplicated":173}},"src":{"TraitDecl":{"trait_ref":{"id":8,"generics":{"regions":[],"types":[{"Deduplicated":173}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},null,{"def_id":184,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["function",0]},{"Ident":["FnMut",0]},{"Ident":["call_mut",0]}],"span":{"data":{"file_id":16,"beg":{"line":166,"col":4},"end":{"line":166,"col":74}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Performs the call operation."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[{"index":0,"name":"Self"},{"index":1,"name":"Args"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":9,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":2945},{"Deduplicated":1555}],"output":{"HashConsedValue":[6280,{"TraitType":[{"HashConsedValue":[6279,{"kind":{"ParentClause":[{"HashConsedValue":[6278,{"kind":{"Clause":{"Bound":[0,0]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":9,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}]},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}},"src":{"TraitDecl":{"trait_ref":{"id":9,"generics":{"regions":[],"types":[{"Deduplicated":173},{"Deduplicated":1555}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},{"def_id":185,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["FromIterator",0]},{"Ident":["from_iter",0]}],"span":{"data":{"file_id":11,"beg":{"line":152,"col":4},"end":{"line":152,"col":61}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Creates a value from an iterator."},{"DocComment":""},{"DocComment":" See the [module-level documentation] for more."},{"DocComment":""},{"DocComment":" [module-level documentation]: crate::iter"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let five_fives = std::iter::repeat(5).take(5);"},{"DocComment":""},{"DocComment":" let v = Vec::from_iter(five_fives);"},{"DocComment":""},{"DocComment":" assert_eq!(v, vec![5, 5, 5, 5, 5]);"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"from_iter_fn"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"A"},{"index":2,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":10,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":11,"beg":{"line":152,"col":17},"end":{"line":152,"col":18}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":2592}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":11,"beg":{"line":152,"col":20},"end":{"line":152,"col":42}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":2592}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[{"regions":[],"skip_binder":{"trait_ref":{"HashConsedValue":[5230,{"kind":{"Clause":{"Bound":[1,2]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"HashConsedValue":[2596,{"TypeVar":{"Bound":[2,2]}}]}],"const_generics":[],"trait_refs":[]}}}}]},"type_id":0,"ty":{"Deduplicated":1551}}}]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":2605}],"output":{"Deduplicated":173}},"src":{"TraitDecl":{"trait_ref":{"id":10,"generics":{"regions":[],"types":[{"Deduplicated":173},{"Deduplicated":1555}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},{"def_id":186,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["try_trait",0]},{"Ident":["Try",0]},{"Ident":["from_output",0]}],"span":{"data":{"file_id":42,"beg":{"line":192,"col":4},"end":{"line":192,"col":49}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Constructs the type from its `Output` type."},{"DocComment":""},{"DocComment":" This should be implemented consistently with the `branch` method"},{"DocComment":" such that applying the `?` operator will get back the original value:"},{"DocComment":" `Try::from_output(x).branch() --> ControlFlow::Continue(x)`."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #![feature(try_trait_v2)]"},{"DocComment":" use std::ops::Try;"},{"DocComment":""},{"DocComment":" assert_eq!( as Try>::from_output(3), Ok(3));"},{"DocComment":" assert_eq!( as Try>::from_output(4), Some(4));"},{"DocComment":" assert_eq!("},{"DocComment":" as Try>::from_output(5),"},{"DocComment":" std::ops::ControlFlow::Continue(5),"},{"DocComment":" );"},{"DocComment":""},{"DocComment":" # fn make_question_mark_work() -> Option<()> {"},{"DocComment":" assert_eq!(Option::from_output(4)?, 4);"},{"DocComment":" # None }"},{"DocComment":" # make_question_mark_work();"},{"DocComment":""},{"DocComment":" // This is used, for example, on the accumulator in `try_fold`:"},{"DocComment":" let r = std::iter::empty().try_fold(4, |_, ()| -> Option<_> { unreachable!() });"},{"DocComment":" assert_eq!(r, Some(4));"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"from_output"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"HashConsedValue":[5232,{"TraitType":[{"HashConsedValue":[5231,{"kind":{"Clause":{"Bound":[0,0]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"output":{"Deduplicated":173}},"src":{"TraitDecl":{"trait_ref":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":173}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},{"def_id":187,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["try_trait",0]},{"Ident":["Try",0]},{"Ident":["branch",0]}],"span":{"data":{"file_id":42,"beg":{"line":219,"col":4},"end":{"line":219,"col":65}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Used in `?` to decide whether the operator should produce a value"},{"DocComment":" (because this returned [`ControlFlow::Continue`])"},{"DocComment":" or propagate a value back to the caller"},{"DocComment":" (because this returned [`ControlFlow::Break`])."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #![feature(try_trait_v2)]"},{"DocComment":" use std::ops::{ControlFlow, Try};"},{"DocComment":""},{"DocComment":" assert_eq!(Ok::<_, String>(3).branch(), ControlFlow::Continue(3));"},{"DocComment":" assert_eq!(Err::(3).branch(), ControlFlow::Break(Err(3)));"},{"DocComment":""},{"DocComment":" assert_eq!(Some(3).branch(), ControlFlow::Continue(3));"},{"DocComment":" assert_eq!(None::.branch(), ControlFlow::Break(None));"},{"DocComment":""},{"DocComment":" assert_eq!(ControlFlow::::Continue(3).branch(), ControlFlow::Continue(3));"},{"DocComment":" assert_eq!("},{"DocComment":" ControlFlow::<_, String>::Break(3).branch(),"},{"DocComment":" ControlFlow::Break(ControlFlow::Break(3)),"},{"DocComment":" );"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"branch"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":173}],"output":{"HashConsedValue":[6286,{"Adt":{"id":{"Adt":6},"generics":{"regions":[],"types":[{"HashConsedValue":[6281,{"TraitType":[{"Deduplicated":5231},1]}]},{"Deduplicated":5232}],"const_generics":[],"trait_refs":[{"HashConsedValue":[6283,{"kind":{"ParentClause":[{"HashConsedValue":[6282,{"kind":{"ParentClause":[{"Deduplicated":5231},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":24,"generics":{"regions":[],"types":[{"Deduplicated":141},{"HashConsedValue":[5235,{"TraitType":[{"HashConsedValue":[5234,{"kind":{"Clause":{"Bound":[1,0]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":1577}],"const_generics":[],"trait_refs":[]}}}}]},1]}]}],"const_generics":[],"trait_refs":[]}}}}]},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":5235}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6285,{"kind":{"ParentClause":[{"Deduplicated":5231},2]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[6284,{"TraitType":[{"Deduplicated":5234},0]}]}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},"src":{"TraitDecl":{"trait_ref":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":173}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":1},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},{"def_id":188,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["Extend",0]},{"Ident":["extend",0]}],"span":{"data":{"file_id":11,"beg":{"line":416,"col":4},"end":{"line":416,"col":61}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Extends a collection with the contents of an iterator."},{"DocComment":""},{"DocComment":" As this is the only required method for this trait, the [trait-level] docs"},{"DocComment":" contain more details."},{"DocComment":""},{"DocComment":" [trait-level]: Extend"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" // You can extend a String with some chars:"},{"DocComment":" let mut message = String::from(\"abc\");"},{"DocComment":""},{"DocComment":" message.extend(['d', 'e', 'f'].iter());"},{"DocComment":""},{"DocComment":" assert_eq!(\"abcdef\", &message);"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[{"index":0,"name":"Self"},{"index":1,"name":"A"},{"index":2,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":13,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":11,"beg":{"line":416,"col":14},"end":{"line":416,"col":15}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":2592}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":11,"beg":{"line":416,"col":17},"end":{"line":416,"col":39}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":2592}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[{"regions":[],"skip_binder":{"trait_ref":{"Deduplicated":5230},"type_id":0,"ty":{"Deduplicated":1551}}}]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":2945},{"Deduplicated":2605}],"output":{"Deduplicated":198}},"src":{"TraitDecl":{"trait_ref":{"id":13,"generics":{"regions":[],"types":[{"Deduplicated":173},{"Deduplicated":1555}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},null,null,null,{"def_id":192,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["default",0]},{"Ident":["Default",0]},{"Ident":["default",0]}],"span":{"data":{"file_id":43,"beg":{"line":139,"col":4},"end":{"line":139,"col":25}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Returns the \"default value\" for a type."},{"DocComment":""},{"DocComment":" Default values are often some kind of initial value, identity value, or anything else that"},{"DocComment":" may make sense as a default."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" Using built-in default values:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let i: i8 = Default::default();"},{"DocComment":" let (x, y): (Option, f64) = Default::default();"},{"DocComment":" let (a, b, (c, d)): (i32, u32, (bool, bool)) = Default::default();"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" Making your own:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #[allow(dead_code)]"},{"DocComment":" enum Kind {"},{"DocComment":" A,"},{"DocComment":" B,"},{"DocComment":" C,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl Default for Kind {"},{"DocComment":" fn default() -> Self { Kind::A }"},{"DocComment":" }"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"default_fn"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":14,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[],"output":{"Deduplicated":173}},"src":{"TraitDecl":{"trait_ref":{"id":14,"generics":{"regions":[],"types":[{"Deduplicated":173}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},{"def_id":193,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["double_ended",0]},{"Ident":["DoubleEndedIterator",0]},{"Ident":["next_back",0]}],"span":{"data":{"file_id":44,"beg":{"line":94,"col":4},"end":{"line":94,"col":50}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Removes and returns an element from the end of the iterator."},{"DocComment":""},{"DocComment":" Returns `None` when there are no more elements."},{"DocComment":""},{"DocComment":" The [trait-level] docs contain more details."},{"DocComment":""},{"DocComment":" [trait-level]: DoubleEndedIterator"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" Basic usage:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let numbers = vec![1, 2, 3, 4, 5, 6];"},{"DocComment":""},{"DocComment":" let mut iter = numbers.iter();"},{"DocComment":""},{"DocComment":" assert_eq!(Some(&1), iter.next());"},{"DocComment":" assert_eq!(Some(&6), iter.next_back());"},{"DocComment":" assert_eq!(Some(&5), iter.next_back());"},{"DocComment":" assert_eq!(Some(&2), iter.next());"},{"DocComment":" assert_eq!(Some(&3), iter.next());"},{"DocComment":" assert_eq!(Some(&4), iter.next());"},{"DocComment":" assert_eq!(None, iter.next());"},{"DocComment":" assert_eq!(None, iter.next_back());"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" # Remarks"},{"DocComment":""},{"DocComment":" The elements yielded by `DoubleEndedIterator`'s methods may differ from"},{"DocComment":" the ones yielded by [`Iterator`]'s methods:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let vec = vec![(1, 'a'), (1, 'b'), (1, 'c'), (2, 'a'), (2, 'b')];"},{"DocComment":" let uniq_by_fst_comp = || {"},{"DocComment":" let mut seen = std::collections::HashSet::new();"},{"DocComment":" vec.iter().copied().filter(move |x| seen.insert(x.0))"},{"DocComment":" };"},{"DocComment":""},{"DocComment":" assert_eq!(uniq_by_fst_comp().last(), Some((2, 'a')));"},{"DocComment":" assert_eq!(uniq_by_fst_comp().next_back(), Some((2, 'b')));"},{"DocComment":""},{"DocComment":" assert_eq!("},{"DocComment":" uniq_by_fst_comp().fold(vec![], |mut v, x| {v.push(x); v}),"},{"DocComment":" vec![(1, 'a'), (2, 'a')]"},{"DocComment":" );"},{"DocComment":" assert_eq!("},{"DocComment":" uniq_by_fst_comp().rfold(vec![], |mut v, x| {v.push(x); v}),"},{"DocComment":" vec![(2, 'b'), (1, 'c')]"},{"DocComment":" );"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":15,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":2945}],"output":{"HashConsedValue":[6290,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"HashConsedValue":[6287,{"TraitType":[{"HashConsedValue":[5242,{"kind":{"ParentClause":[{"HashConsedValue":[5241,{"kind":{"Clause":{"Bound":[0,0]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":15,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}]},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[{"HashConsedValue":[6289,{"kind":{"ParentClause":[{"Deduplicated":5242},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[6288,{"TraitType":[{"HashConsedValue":[5613,{"kind":{"ParentClause":[{"HashConsedValue":[5612,{"kind":{"Clause":{"Bound":[1,0]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":15,"generics":{"regions":[],"types":[{"Deduplicated":1577}],"const_generics":[],"trait_refs":[]}}}}]},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":1577}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},"src":{"TraitDecl":{"trait_ref":{"id":15,"generics":{"regions":[],"types":[{"Deduplicated":173}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},null,null,null,null,null,null,null,{"def_id":201,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["Ord",0]},{"Ident":["cmp",0]}],"span":{"data":{"file_id":46,"beg":{"line":991,"col":4},"end":{"line":991,"col":44}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" This method returns an [`Ordering`] between `self` and `other`."},{"DocComment":""},{"DocComment":" By convention, `self.cmp(&other)` returns the ordering matching the expression"},{"DocComment":" `self other` if true."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::cmp::Ordering;"},{"DocComment":""},{"DocComment":" assert_eq!(5.cmp(&10), Ordering::Less);"},{"DocComment":" assert_eq!(10.cmp(&5), Ordering::Greater);"},{"DocComment":" assert_eq!(5.cmp(&5), Ordering::Equal);"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"ord_cmp_method"},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"},{"index":1,"name":null,"mutability":"Unknown"}],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":17,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":4517},{"HashConsedValue":[6291,{"Ref":[{"Var":{"Bound":[0,1]}},{"Deduplicated":173},"Shared"]}]}],"output":{"HashConsedValue":[3569,{"Adt":{"id":{"Adt":34},"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}}]}},"src":{"TraitDecl":{"trait_ref":{"id":17,"generics":{"regions":[],"types":[{"Deduplicated":173}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},null,null,null,{"def_id":205,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["accum",0]},{"Ident":["Sum",0]},{"Ident":["sum",0]}],"span":{"data":{"file_id":52,"beg":{"line":21,"col":4},"end":{"line":21,"col":51}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Takes an iterator and generates `Self` from the elements by \"summing up\""},{"DocComment":" the items."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"A"},{"index":2,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":19,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":52,"beg":{"line":21,"col":11},"end":{"line":21,"col":12}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":2592}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":52,"beg":{"line":21,"col":14},"end":{"line":21,"col":32}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":2592}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[{"regions":[],"skip_binder":{"trait_ref":{"HashConsedValue":[5257,{"kind":{"Clause":{"Bound":[1,2]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":2596}],"const_generics":[],"trait_refs":[]}}}}]},"type_id":0,"ty":{"Deduplicated":1551}}}]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":2605}],"output":{"Deduplicated":173}},"src":{"TraitDecl":{"trait_ref":{"id":19,"generics":{"regions":[],"types":[{"Deduplicated":173},{"Deduplicated":1555}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},{"def_id":206,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["accum",0]},{"Ident":["Product",0]},{"Ident":["product",0]}],"span":{"data":{"file_id":52,"beg":{"line":42,"col":4},"end":{"line":42,"col":55}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Takes an iterator and generates `Self` from the elements by multiplying"},{"DocComment":" the items."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"A"},{"index":2,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":20,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":52,"beg":{"line":42,"col":15},"end":{"line":42,"col":16}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":2592}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":52,"beg":{"line":42,"col":18},"end":{"line":42,"col":36}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":2592}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[{"regions":[],"skip_binder":{"trait_ref":{"Deduplicated":5257},"type_id":0,"ty":{"Deduplicated":1551}}}]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":2605}],"output":{"Deduplicated":173}},"src":{"TraitDecl":{"trait_ref":{"id":20,"generics":{"regions":[],"types":[{"Deduplicated":173},{"Deduplicated":1555}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},{"def_id":207,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialOrd",0]},{"Ident":["partial_cmp",0]}],"span":{"data":{"file_id":46,"beg":{"line":1387,"col":4},"end":{"line":1387,"col":59}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" This method returns an ordering between `self` and `other` values if one exists."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::cmp::Ordering;"},{"DocComment":""},{"DocComment":" let result = 1.0.partial_cmp(&2.0);"},{"DocComment":" assert_eq!(result, Some(Ordering::Less));"},{"DocComment":""},{"DocComment":" let result = 1.0.partial_cmp(&1.0);"},{"DocComment":" assert_eq!(result, Some(Ordering::Equal));"},{"DocComment":""},{"DocComment":" let result = 2.0.partial_cmp(&1.0);"},{"DocComment":" assert_eq!(result, Some(Ordering::Greater));"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" When comparison is impossible:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let result = f64::NAN.partial_cmp(&1.0);"},{"DocComment":" assert_eq!(result, None);"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"cmp_partialord_cmp"},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"},{"index":1,"name":null,"mutability":"Unknown"}],"types":[{"index":0,"name":"Self"},{"index":1,"name":"Rhs"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":21,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":4517},{"HashConsedValue":[5258,{"Ref":[{"Var":{"Bound":[0,1]}},{"Deduplicated":1555},"Shared"]}]}],"output":{"HashConsedValue":[3938,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":3569}],"const_generics":[],"trait_refs":[{"HashConsedValue":[3937,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[3936,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":3569}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":3569}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},"src":{"TraitDecl":{"trait_ref":{"id":21,"generics":{"regions":[],"types":[{"Deduplicated":173},{"Deduplicated":1555}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},null,null,null,null,null,null,null,null,{"def_id":216,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialEq",0]},{"Ident":["eq",0]}],"span":{"data":{"file_id":46,"beg":{"line":256,"col":4},"end":{"line":256,"col":38}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Tests for `self` and `other` values to be equal, and is used by `==`."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"cmp_partialeq_eq"},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"},{"index":1,"name":null,"mutability":"Unknown"}],"types":[{"index":0,"name":"Self"},{"index":1,"name":"Rhs"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":22,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":4517},{"Deduplicated":5258}],"output":{"Deduplicated":188}},"src":{"TraitDecl":{"trait_ref":{"id":22,"generics":{"regions":[],"types":[{"Deduplicated":173},{"Deduplicated":1555}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},null,null,{"def_id":219,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["try_trait",0]},{"Ident":["FromResidual",0]},{"Ident":["from_residual",0]}],"span":{"data":{"file_id":42,"beg":{"line":333,"col":4},"end":{"line":333,"col":42}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Constructs the type from a compatible `Residual` type."},{"DocComment":""},{"DocComment":" This should be implemented consistently with the `branch` method such"},{"DocComment":" that applying the `?` operator will get back an equivalent residual:"},{"DocComment":" `FromResidual::from_residual(r).branch() --> ControlFlow::Break(r)`."},{"DocComment":" (The residual is not mandated to be *identical* when interconversion is involved.)"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #![feature(try_trait_v2)]"},{"DocComment":" use std::ops::{ControlFlow, FromResidual};"},{"DocComment":""},{"DocComment":" assert_eq!(Result::::from_residual(Err(3_u8)), Err(3));"},{"DocComment":" assert_eq!(Option::::from_residual(None), None);"},{"DocComment":" assert_eq!("},{"DocComment":" ControlFlow::<_, String>::from_residual(ControlFlow::Break(5)),"},{"DocComment":" ControlFlow::Break(5),"},{"DocComment":" );"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"from_residual"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"R"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":24,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":1555}],"output":{"Deduplicated":173}},"src":{"TraitDecl":{"trait_ref":{"id":24,"generics":{"regions":[],"types":[{"Deduplicated":173},{"Deduplicated":1555}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},null,{"def_id":221,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["clone",0]},{"Ident":["impls",0]},{"Impl":{"Trait":14}},{"Ident":["clone",0]}],"span":{"data":{"file_id":25,"beg":{"line":614,"col":20},"end":{"line":614,"col":43}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":"Always","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"HashConsedValue":[6292,{"Ref":[{"Var":{"Bound":[0,0]}},{"Deduplicated":591},"Shared"]}]}],"output":{"Deduplicated":591}},"src":{"TraitImpl":{"impl_ref":{"id":14,"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}},"trait_ref":{"id":8,"generics":{"regions":[],"types":[{"Deduplicated":591}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"reuses_default":true}},"is_global_initializer":null,"body":"Opaque"},null,{"def_id":223,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["niche_types",0]},{"Impl":{"Trait":15}},{"Ident":["clone",0]}],"span":{"data":{"file_id":53,"beg":{"line":17,"col":17},"end":{"line":17,"col":22}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":"Hint","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"HashConsedValue":[6293,{"Ref":[{"Var":{"Bound":[0,0]}},{"HashConsedValue":[4706,{"Adt":{"id":{"Adt":42},"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}}]},"Shared"]}]}],"output":{"Deduplicated":4706}},"src":{"TraitImpl":{"impl_ref":{"id":15,"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}},"trait_ref":{"id":8,"generics":{"regions":[],"types":[{"Deduplicated":4706}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"reuses_default":true}},"is_global_initializer":null,"body":"Opaque"},null],"global_decls":[null,null,null,null],"trait_decls":[{"def_id":0,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Ident":["Sized",0]}],"span":{"data":{"file_id":1,"beg":{"line":161,"col":0},"end":{"line":161,"col":26}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Types with a constant size known at compile time."},{"DocComment":""},{"DocComment":" All type parameters have an implicit bound of `Sized`. The special syntax"},{"DocComment":" `?Sized` can be used to remove this bound if it's not appropriate."},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #![allow(dead_code)]"},{"DocComment":" struct Foo(T);"},{"DocComment":" struct Bar(T);"},{"DocComment":""},{"DocComment":" // struct FooUse(Foo<[i32]>); // error: Sized is not implemented for [i32]"},{"DocComment":" struct BarUse(Bar<[i32]>); // OK"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" The one exception is the implicit `Self` type of a trait. A trait does not"},{"DocComment":" have an implicit `Sized` bound as this is incompatible with [trait object]s"},{"DocComment":" where, by definition, the trait needs to work with all possible implementors,"},{"DocComment":" and thus could be any size."},{"DocComment":""},{"DocComment":" Although Rust will let you bind `Sized` to a trait, you won't"},{"DocComment":" be able to use it to form a trait object later:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #![allow(unused_variables)]"},{"DocComment":" trait Foo { }"},{"DocComment":" trait Bar: Sized { }"},{"DocComment":""},{"DocComment":" struct Impl;"},{"DocComment":" impl Foo for Impl { }"},{"DocComment":" impl Bar for Impl { }"},{"DocComment":""},{"DocComment":" let x: &dyn Foo = &Impl; // OK"},{"DocComment":" // let y: &dyn Bar = &Impl; // error: the trait `Bar` cannot be made into an object"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" [trait object]: ../../book/ch17-02-trait-objects.html"},{"Unknown":{"path":"diagnostic::on_unimplemented","args":"message =\n\"the size for values of type `{Self}` cannot be known at compilation time\",\nlabel = \"doesn't have a size known at compile-time\""}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"sized"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":1,"beg":{"line":161,"col":17},"end":{"line":161,"col":26}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[],"vtable":null},{"def_id":1,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Ident":["MetaSized",0]}],"span":{"data":{"file_id":1,"beg":{"line":178,"col":0},"end":{"line":178,"col":33}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Types with a size that can be determined from pointer metadata."},{"Unknown":{"path":"diagnostic::on_unimplemented","args":"message = \"the size for values of type `{Self}` cannot be known\", label =\n\"doesn't have a known size\""}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"meta_sized"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[],"consts":[],"types":[],"methods":[],"vtable":{"id":{"Adt":9},"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}},{"def_id":2,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]}],"span":{"data":{"file_id":14,"beg":{"line":41,"col":0},"end":{"line":41,"col":24}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" A trait for dealing with iterators."},{"DocComment":""},{"DocComment":" This is the main iterator trait. For more about the concept of iterators"},{"DocComment":" generally, please see the [module-level documentation]. In particular, you"},{"DocComment":" may want to know how to [implement `Iterator`][impl]."},{"DocComment":""},{"DocComment":" [module-level documentation]: crate::iter"},{"DocComment":" [impl]: crate::iter#implementing-iterator"},{"Unknown":{"path":"rustc_on_unimplemented","args":"on(Self = \"core::ops::range::RangeTo\", note =\n\"you might have meant to use a bounded `Range`\"),\non(Self = \"core::ops::range::RangeToInclusive\", note =\n\"you might have meant to use a bounded `RangeInclusive`\"), label =\n\"`{Self}` is not an iterator\", message = \"`{Self}` is not an iterator\""}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"iterator"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":14,"beg":{"line":41,"col":0},"end":{"line":4130,"col":1}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":14,"beg":{"line":45,"col":4},"end":{"line":45,"col":14}},"generated_from_span":null},"origin":{"TraitItem":0},"trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[1697,{"TraitType":[{"HashConsedValue":[1696,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":1577}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"Item","attr_info":{"attributes":[{"DocComment":" The type of the elements being iterated over."}],"inline":null,"rename":null,"public":false},"default":null,"implied_clauses":[]},"kind":{"TraitType":[2,0]}}],"methods":[{"params":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"next","attr_info":{"attributes":[{"DocComment":" Advances the iterator and returns the next value."},{"DocComment":""},{"DocComment":" Returns [`None`] when iteration is finished. Individual iterator"},{"DocComment":" implementations may choose to resume iteration, and so calling `next()`"},{"DocComment":" again may or may not eventually start returning [`Some(Item)`] again at some"},{"DocComment":" point."},{"DocComment":""},{"DocComment":" [`Some(Item)`]: Some"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let a = [1, 2, 3];"},{"DocComment":""},{"DocComment":" let mut iter = a.into_iter();"},{"DocComment":""},{"DocComment":" // A call to next() returns the next value..."},{"DocComment":" assert_eq!(Some(1), iter.next());"},{"DocComment":" assert_eq!(Some(2), iter.next());"},{"DocComment":" assert_eq!(Some(3), iter.next());"},{"DocComment":""},{"DocComment":" // ... and then None once it's over."},{"DocComment":" assert_eq!(None, iter.next());"},{"DocComment":""},{"DocComment":" // More calls may or may not return `None`. Here, they always will."},{"DocComment":" assert_eq!(None, iter.next());"},{"DocComment":" assert_eq!(None, iter.next());"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"HashConsedValue":[1695,{"Ref":[{"Var":{"Bound":[0,0]}},{"Deduplicated":141},"Mut"]}]}],"output":{"HashConsedValue":[6134,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":1697}],"const_generics":[],"trait_refs":[{"HashConsedValue":[6133,{"kind":{"ParentClause":[{"Deduplicated":1696},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[1675,{"TraitType":[{"HashConsedValue":[1674,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"HashConsedValue":[1585,{"TypeVar":{"Bound":[3,0]}}]}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},"item":{"id":27,"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[{"Deduplicated":1696}]}}},"kind":{"TraitMethod":[2,0]}},null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"vtable":{"id":{"Adt":10},"generics":{"regions":[],"types":[{"HashConsedValue":[6136,{"TraitType":[{"HashConsedValue":[6135,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}},{"def_id":3,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["convert",0]},{"Ident":["From",0]}],"span":{"data":{"file_id":10,"beg":{"line":587,"col":0},"end":{"line":587,"col":30}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Used to do value-to-value conversions while consuming the input value. It is the reciprocal of"},{"DocComment":" [`Into`]."},{"DocComment":""},{"DocComment":" One should always prefer implementing `From` over [`Into`]"},{"DocComment":" because implementing `From` automatically provides one with an implementation of [`Into`]"},{"DocComment":" thanks to the blanket implementation in the standard library."},{"DocComment":""},{"DocComment":" Only implement [`Into`] when targeting a version prior to Rust 1.41 and converting to a type"},{"DocComment":" outside the current crate."},{"DocComment":" `From` was not able to do these types of conversions in earlier versions because of Rust's"},{"DocComment":" orphaning rules."},{"DocComment":" See [`Into`] for more details."},{"DocComment":""},{"DocComment":" Prefer using [`Into`] over [`From`] when specifying trait bounds on a generic function"},{"DocComment":" to ensure that types that only implement [`Into`] can be used as well."},{"DocComment":""},{"DocComment":" The `From` trait is also very useful when performing error handling. When constructing a function"},{"DocComment":" that is capable of failing, the return type will generally be of the form `Result`."},{"DocComment":" `From` simplifies error handling by allowing a function to return a single error type"},{"DocComment":" that encapsulates multiple error types. See the \"Examples\" section and [the book][book] for more"},{"DocComment":" details."},{"DocComment":""},{"DocComment":" **Note: This trait must not fail**. The `From` trait is intended for perfect conversions."},{"DocComment":" If the conversion can fail or is not perfect, use [`TryFrom`]."},{"DocComment":""},{"DocComment":" # Generic Implementations"},{"DocComment":""},{"DocComment":" - `From for U` implies [`Into`]` for T`"},{"DocComment":" - `From` is reflexive, which means that `From for T` is implemented"},{"DocComment":""},{"DocComment":" # When to implement `From`"},{"DocComment":""},{"DocComment":" While there's no technical restrictions on which conversions can be done using"},{"DocComment":" a `From` implementation, the general expectation is that the conversions"},{"DocComment":" should typically be restricted as follows:"},{"DocComment":""},{"DocComment":" * The conversion is *infallible*: if the conversion can fail, use [`TryFrom`]"},{"DocComment":" instead; don't provide a `From` impl that panics."},{"DocComment":""},{"DocComment":" * The conversion is *lossless*: semantically, it should not lose or discard"},{"DocComment":" information. For example, `i32: From` exists, where the original"},{"DocComment":" value can be recovered using `u16: TryFrom`. And `String: From<&str>`"},{"DocComment":" exists, where you can get something equivalent to the original value via"},{"DocComment":" `Deref`. But `From` cannot be used to convert from `u32` to `u16`, since"},{"DocComment":" that cannot succeed in a lossless way. (There's some wiggle room here for"},{"DocComment":" information not considered semantically relevant. For example,"},{"DocComment":" `Box<[T]>: From>` exists even though it might not preserve capacity,"},{"DocComment":" like how two vectors can be equal despite differing capacities.)"},{"DocComment":""},{"DocComment":" * The conversion is *value-preserving*: the conceptual kind and meaning of"},{"DocComment":" the resulting value is the same, even though the Rust type and technical"},{"DocComment":" representation might be different. For example `-1_i8 as u8` is *lossless*,"},{"DocComment":" since `as` casting back can recover the original value, but that conversion"},{"DocComment":" is *not* available via `From` because `-1` and `255` are different conceptual"},{"DocComment":" values (despite being identical bit patterns technically). But"},{"DocComment":" `f32: From` *is* available because `1_i16` and `1.0_f32` are conceptually"},{"DocComment":" the same real number (despite having very different bit patterns technically)."},{"DocComment":" `String: From` is available because they're both *text*, but"},{"DocComment":" `String: From` is *not* available, since `1` (a number) and `\"1\"`"},{"DocComment":" (text) are too different. (Converting values to text is instead covered"},{"DocComment":" by the [`Display`](crate::fmt::Display) trait.)"},{"DocComment":""},{"DocComment":" * The conversion is *obvious*: it's the only reasonable conversion between"},{"DocComment":" the two types. Otherwise it's better to have it be a named method or"},{"DocComment":" constructor, like how [`str::as_bytes`] is a method and how integers have"},{"DocComment":" methods like [`u32::from_ne_bytes`], [`u32::from_le_bytes`], and"},{"DocComment":" [`u32::from_be_bytes`], none of which are `From` implementations. Whereas"},{"DocComment":" there's only one reasonable way to wrap an [`Ipv6Addr`](crate::net::Ipv6Addr)"},{"DocComment":" into an [`IpAddr`](crate::net::IpAddr), thus `IpAddr: From` exists."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" [`String`] implements `From<&str>`:"},{"DocComment":""},{"DocComment":" An explicit conversion from a `&str` to a String is done as follows:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let string = \"hello\".to_string();"},{"DocComment":" let other_string = String::from(\"hello\");"},{"DocComment":""},{"DocComment":" assert_eq!(string, other_string);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" While performing error handling it is often useful to implement `From` for your own error type."},{"DocComment":" By converting underlying error types to our own custom error type that encapsulates the"},{"DocComment":" underlying error type, we can return a single error type without losing information on the"},{"DocComment":" underlying cause. The '?' operator automatically converts the underlying error type to our"},{"DocComment":" custom error type with `From::from`."},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::fs;"},{"DocComment":" use std::io;"},{"DocComment":" use std::num;"},{"DocComment":""},{"DocComment":" enum CliError {"},{"DocComment":" IoError(io::Error),"},{"DocComment":" ParseError(num::ParseIntError),"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl From for CliError {"},{"DocComment":" fn from(error: io::Error) -> Self {"},{"DocComment":" CliError::IoError(error)"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl From for CliError {"},{"DocComment":" fn from(error: num::ParseIntError) -> Self {"},{"DocComment":" CliError::ParseError(error)"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" fn open_and_parse_file(file_name: &str) -> Result {"},{"DocComment":" let mut contents = fs::read_to_string(&file_name)?;"},{"DocComment":" let num: i32 = contents.trim().parse()?;"},{"DocComment":" Ok(num)"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" [`String`]: ../../std/string/struct.String.html"},{"DocComment":" [`from`]: From::from"},{"DocComment":" [book]: ../../book/ch09-00-error-handling.html"},{"Unknown":{"path":"rustc_on_unimplemented","args":"on(all(Self = \"&str\", T = \"alloc::string::String\"), note =\n\"to coerce a `{T}` into a `{Self}`, use `&*` as a prefix\",)"}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"From"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"T"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":10,"beg":{"line":587,"col":25},"end":{"line":587,"col":30}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":10,"beg":{"line":587,"col":21},"end":{"line":587,"col":22}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"from","attr_info":{"attributes":[{"DocComment":" Converts to this type from the input type."}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":1551}],"output":{"Deduplicated":141}},"item":{"id":174,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[{"HashConsedValue":[5624,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":3,"generics":{"regions":[],"types":[{"Deduplicated":1577},{"Deduplicated":2212}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"kind":{"TraitMethod":[3,0]}}],"vtable":null},{"def_id":4,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["function",0]},{"Ident":["FnOnce",0]}],"span":{"data":{"file_id":16,"beg":{"line":242,"col":0},"end":{"line":242,"col":35}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" The version of the call operator that takes a by-value receiver."},{"DocComment":""},{"DocComment":" Instances of `FnOnce` can be called, but might not be callable multiple"},{"DocComment":" times. Because of this, if the only thing known about a type is that it"},{"DocComment":" implements `FnOnce`, it can only be called once."},{"DocComment":""},{"DocComment":" `FnOnce` is implemented automatically by closures that might consume captured"},{"DocComment":" variables, as well as all types that implement [`FnMut`], e.g., (safe)"},{"DocComment":" [function pointers] (since `FnOnce` is a supertrait of [`FnMut`])."},{"DocComment":""},{"DocComment":" Since both [`Fn`] and [`FnMut`] are subtraits of `FnOnce`, any instance of"},{"DocComment":" [`Fn`] or [`FnMut`] can be used where a `FnOnce` is expected."},{"DocComment":""},{"DocComment":" Use `FnOnce` as a bound when you want to accept a parameter of function-like"},{"DocComment":" type and only need to call it once. If you need to call the parameter"},{"DocComment":" repeatedly, use [`FnMut`] as a bound; if you also need it to not mutate"},{"DocComment":" state, use [`Fn`]."},{"DocComment":""},{"DocComment":" See the [chapter on closures in *The Rust Programming Language*][book] for"},{"DocComment":" some more information on this topic."},{"DocComment":""},{"DocComment":" Also of note is the special syntax for `Fn` traits (e.g."},{"DocComment":" `Fn(usize, bool) -> usize`). Those interested in the technical details of"},{"DocComment":" this can refer to [the relevant section in the *Rustonomicon*][nomicon]."},{"DocComment":""},{"DocComment":" [book]: ../../book/ch13-01-closures.html"},{"DocComment":" [function pointers]: fn"},{"DocComment":" [nomicon]: ../../nomicon/hrtb.html"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ## Using a `FnOnce` parameter"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" fn consume_with_relish(func: F)"},{"DocComment":" where F: FnOnce() -> String"},{"DocComment":" {"},{"DocComment":" // `func` consumes its captured variables, so it cannot be run more"},{"DocComment":" // than once."},{"DocComment":" println!(\"Consumed: {}\", func());"},{"DocComment":""},{"DocComment":" println!(\"Delicious!\");"},{"DocComment":""},{"DocComment":" // Attempting to invoke `func()` again will throw a `use of moved"},{"DocComment":" // value` error for `func`."},{"DocComment":" }"},{"DocComment":""},{"DocComment":" let x = String::from(\"x\");"},{"DocComment":" let consume_and_return_x = move || x;"},{"DocComment":" consume_with_relish(consume_and_return_x);"},{"DocComment":""},{"DocComment":" // `consume_and_return_x` can no longer be invoked at this point"},{"DocComment":" ```"},{"Unknown":{"path":"rustc_on_unimplemented","args":"on(Args = \"()\", note =\n\"wrap the `{Self}` in a closure with no arguments: `|| {{ /* code */ }}`\"),\non(Self = \"unsafe fn\", note =\n\"unsafe function cannot be called generically without an unsafe block\", label\n= \"call the function in a closure: `|| unsafe {{ /* code */ }}`\"), message =\n\"expected a `{Trait}` closure, found `{Self}`\", label =\n\"expected an `{Trait}` closure, found `{Self}`\""}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"fn_once"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"Args"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":16,"beg":{"line":242,"col":0},"end":{"line":251,"col":1}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":16,"beg":{"line":242,"col":23},"end":{"line":242,"col":27}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":16,"beg":{"line":242,"col":29},"end":{"line":242,"col":34}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":25,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":3,"span":{"data":{"file_id":16,"beg":{"line":246,"col":4},"end":{"line":246,"col":16}},"generated_from_span":null},"origin":{"TraitItem":0},"trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[5283,{"TraitType":[{"HashConsedValue":[4595,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":1577},{"Deduplicated":2212}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"Output","attr_info":{"attributes":[{"DocComment":" The returned type after the call operator is used."}],"inline":null,"rename":null,"public":false},"default":null,"implied_clauses":[]},"kind":{"TraitType":[4,0]}}],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"call_once","attr_info":{"attributes":[{"DocComment":" Performs the call operation."}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":141},{"Deduplicated":1551}],"output":{"Deduplicated":5283}},"item":{"id":177,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[{"Deduplicated":4595}]}}},"kind":{"TraitMethod":[4,0]}}],"vtable":{"id":{"Adt":40},"generics":{"regions":[],"types":[{"Deduplicated":1555},{"HashConsedValue":[6138,{"TraitType":[{"HashConsedValue":[6137,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}},{"def_id":5,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Ident":["Destruct",0]}],"span":{"data":{"file_id":1,"beg":{"line":1063,"col":0},"end":{"line":1063,"col":38}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" A marker for types that can be dropped."},{"DocComment":""},{"DocComment":" This should be used for `[const]` bounds,"},{"DocComment":" as non-const bounds will always hold for every type."},{"Unknown":{"path":"rustc_on_unimplemented","args":"message = \"can't drop `{Self}`\", append_const_msg"}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"destruct"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[],"consts":[],"types":[],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"drop_in_place","attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":true,"inputs":[{"HashConsedValue":[6139,{"RawPtr":[{"Deduplicated":141},"Mut"]}]}],"output":{"Deduplicated":198}},"item":{"id":178,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}},"kind":{"TraitMethod":[5,0]}}],"vtable":null},{"def_id":6,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["IntoIterator",0]}],"span":{"data":{"file_id":11,"beg":{"line":283,"col":0},"end":{"line":283,"col":28}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Conversion into an [`Iterator`]."},{"DocComment":""},{"DocComment":" By implementing `IntoIterator` for a type, you define how it will be"},{"DocComment":" converted to an iterator. This is common for types which describe a"},{"DocComment":" collection of some kind."},{"DocComment":""},{"DocComment":" One benefit of implementing `IntoIterator` is that your type will [work"},{"DocComment":" with Rust's `for` loop syntax](crate::iter#for-loops-and-intoiterator)."},{"DocComment":""},{"DocComment":" See also: [`FromIterator`]."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" Basic usage:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let v = [1, 2, 3];"},{"DocComment":" let mut iter = v.into_iter();"},{"DocComment":""},{"DocComment":" assert_eq!(Some(1), iter.next());"},{"DocComment":" assert_eq!(Some(2), iter.next());"},{"DocComment":" assert_eq!(Some(3), iter.next());"},{"DocComment":" assert_eq!(None, iter.next());"},{"DocComment":" ```"},{"DocComment":" Implementing `IntoIterator` for your type:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" // A sample collection, that's just a wrapper over Vec"},{"DocComment":" #[derive(Debug)]"},{"DocComment":" struct MyCollection(Vec);"},{"DocComment":""},{"DocComment":" // Let's give it some methods so we can create one and add things"},{"DocComment":" // to it."},{"DocComment":" impl MyCollection {"},{"DocComment":" fn new() -> MyCollection {"},{"DocComment":" MyCollection(Vec::new())"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" fn add(&mut self, elem: i32) {"},{"DocComment":" self.0.push(elem);"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" // and we'll implement IntoIterator"},{"DocComment":" impl IntoIterator for MyCollection {"},{"DocComment":" type Item = i32;"},{"DocComment":" type IntoIter = std::vec::IntoIter;"},{"DocComment":""},{"DocComment":" fn into_iter(self) -> Self::IntoIter {"},{"DocComment":" self.0.into_iter()"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" // Now we can make a new collection..."},{"DocComment":" let mut c = MyCollection::new();"},{"DocComment":""},{"DocComment":" // ... add some stuff to it ..."},{"DocComment":" c.add(0);"},{"DocComment":" c.add(1);"},{"DocComment":" c.add(2);"},{"DocComment":""},{"DocComment":" // ... and then turn it into an Iterator:"},{"DocComment":" for (i, n) in c.into_iter().enumerate() {"},{"DocComment":" assert_eq!(i as i32, n);"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" It is common to use `IntoIterator` as a trait bound. This allows"},{"DocComment":" the input collection type to change, so long as it is still an"},{"DocComment":" iterator. Additional bounds can be specified by restricting on"},{"DocComment":" `Item`:"},{"DocComment":""},{"DocComment":" ```rust"},{"DocComment":" fn collect_as_strings(collection: T) -> Vec"},{"DocComment":" where"},{"DocComment":" T: IntoIterator,"},{"DocComment":" T::Item: std::fmt::Debug,"},{"DocComment":" {"},{"DocComment":" collection"},{"DocComment":" .into_iter()"},{"DocComment":" .map(|item| format!(\"{item:?}\"))"},{"DocComment":" .collect()"},{"DocComment":" }"},{"DocComment":" ```"},{"Unknown":{"path":"rustc_on_unimplemented","args":"on(Self = \"core::ops::range::RangeTo\", label =\n\"if you meant to iterate until a value, add a starting value\", note =\n\"`..end` is a `RangeTo`, which cannot be iterated on; you might have meant to have a \\\n bounded `Range`: `0..end`\"),\non(Self = \"core::ops::range::RangeToInclusive\", label =\n\"if you meant to iterate until a value (including it), add a starting value\",\nnote =\n\"`..=end` is a `RangeToInclusive`, which cannot be iterated on; you might have meant \\\n to have a bounded `RangeInclusive`: `0..=end`\"),\non(Self = \"[]\", label =\n\"`{Self}` is not an iterator; try calling `.into_iter()` or `.iter()`\"),\non(Self = \"&[]\", label =\n\"`{Self}` is not an iterator; try calling `.iter()`\"),\non(Self = \"alloc::vec::Vec\", label =\n\"`{Self}` is not an iterator; try calling `.into_iter()` or `.iter()`\"),\non(Self = \"&str\", label =\n\"`{Self}` is not an iterator; try calling `.chars()` or `.bytes()`\"),\non(Self = \"alloc::string::String\", label =\n\"`{Self}` is not an iterator; try calling `.chars()` or `.bytes()`\"),\non(Self = \"{integral}\", note =\n\"if you want to iterate between `start` until a value `end`, use the exclusive range \\\n syntax `start..end` or the inclusive range syntax `start..=end`\"),\non(Self = \"{float}\", note =\n\"if you want to iterate between `start` until a value `end`, use the exclusive range \\\n syntax `start..end` or the inclusive range syntax `start..=end`\"),\nlabel = \"`{Self}` is not an iterator\", message = \"`{Self}` is not an iterator\""}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"IntoIterator"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[{"regions":[],"skip_binder":{"trait_ref":{"HashConsedValue":[6005,{"kind":{"ParentClause":[{"HashConsedValue":[4629,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":1577}],"const_generics":[],"trait_refs":[]}}}}]},3]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"HashConsedValue":[5629,{"TraitType":[{"HashConsedValue":[5628,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}]},1]}]}],"const_generics":[],"trait_refs":[]}}}}]},"type_id":0,"ty":{"HashConsedValue":[5518,{"TraitType":[{"Deduplicated":4629},0]}]}}}]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":11,"beg":{"line":283,"col":0},"end":{"line":313,"col":1}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":11,"beg":{"line":287,"col":4},"end":{"line":287,"col":14}},"generated_from_span":null},"origin":{"TraitItem":0},"trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":5518}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":11,"beg":{"line":291,"col":4},"end":{"line":291,"col":47}},"generated_from_span":null},"origin":{"TraitItem":1},"trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[5287,{"TraitType":[{"Deduplicated":4629},1]}]}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":3,"span":{"data":{"file_id":11,"beg":{"line":291,"col":19},"end":{"line":291,"col":46}},"generated_from_span":null},"origin":{"TraitItem":1},"trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":5287}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"Item","attr_info":{"attributes":[{"DocComment":" The type of the elements being iterated over."}],"inline":null,"rename":null,"public":false},"default":null,"implied_clauses":[]},"kind":{"TraitType":[6,0]}},{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"IntoIter","attr_info":{"attributes":[{"DocComment":" Which kind of iterator are we turning this into?"}],"inline":null,"rename":null,"public":false},"default":null,"implied_clauses":[]},"kind":{"TraitType":[6,1]}}],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"into_iter","attr_info":{"attributes":[{"DocComment":" Creates an iterator from a value."},{"DocComment":""},{"DocComment":" See the [module-level documentation] for more."},{"DocComment":""},{"DocComment":" [module-level documentation]: crate::iter"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let v = [1, 2, 3];"},{"DocComment":" let mut iter = v.into_iter();"},{"DocComment":""},{"DocComment":" assert_eq!(Some(1), iter.next());"},{"DocComment":" assert_eq!(Some(2), iter.next());"},{"DocComment":" assert_eq!(Some(3), iter.next());"},{"DocComment":" assert_eq!(None, iter.next());"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":141}],"output":{"Deduplicated":5287}},"item":{"id":181,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[{"Deduplicated":4629}]}}},"kind":{"TraitMethod":[6,0]}}],"vtable":{"id":{"Adt":41},"generics":{"regions":[],"types":[{"HashConsedValue":[6140,{"TraitType":[{"HashConsedValue":[4609,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}]},0]}]},{"HashConsedValue":[6141,{"TraitType":[{"Deduplicated":4609},1]}]}],"const_generics":[],"trait_refs":[]}}},{"def_id":7,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["nonzero",0]},{"Ident":["ZeroablePrimitive",0]}],"span":{"data":{"file_id":19,"beg":{"line":33,"col":0},"end":{"line":33,"col":66}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" A marker trait for primitive types which can be zero."},{"DocComment":""},{"DocComment":" This is an implementation detail for [NonZero]\\ which may disappear or be replaced at any time."},{"DocComment":""},{"DocComment":" # Safety"},{"DocComment":""},{"DocComment":" Types implementing this trait must be primitives that are valid when zeroed."},{"DocComment":""},{"DocComment":" The associated `Self::NonZeroInner` type must have the same size+align as `Self`,"},{"DocComment":" but with a niche and bit validity making it so the following `transmutes` are sound:"},{"DocComment":""},{"DocComment":" - `Self::NonZeroInner` to `Option`"},{"DocComment":" - `Option` to `Self`"},{"DocComment":""},{"DocComment":" (And, consequently, `Self::NonZeroInner` to `Self`.)"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":19,"beg":{"line":33,"col":36},"end":{"line":33,"col":41}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":19,"beg":{"line":33,"col":44},"end":{"line":33,"col":48}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":18,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":19,"beg":{"line":33,"col":51},"end":{"line":33,"col":66}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":26,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":3,"span":{"data":{"file_id":19,"beg":{"line":35,"col":23},"end":{"line":35,"col":28}},"generated_from_span":null},"origin":{"TraitItem":0},"trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[5522,{"TraitType":[{"HashConsedValue":[5521,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":7,"generics":{"regions":[],"types":[{"Deduplicated":1577}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":4,"span":{"data":{"file_id":19,"beg":{"line":35,"col":31},"end":{"line":35,"col":35}},"generated_from_span":null},"origin":{"TraitItem":0},"trait_":{"regions":[],"skip_binder":{"id":18,"generics":{"regions":[],"types":[{"Deduplicated":5522}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"NonZeroInner","attr_info":{"attributes":[{"DocComment":" A type like `Self` but with a niche that includes zero."}],"inline":null,"rename":null,"public":false},"default":null,"implied_clauses":[]},"kind":{"TraitType":[7,0]}}],"methods":[],"vtable":null},{"def_id":8,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["clone",0]},{"Ident":["Clone",0]}],"span":{"data":{"file_id":25,"beg":{"line":194,"col":0},"end":{"line":194,"col":28}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" A common trait that allows explicit creation of a duplicate value."},{"DocComment":""},{"DocComment":" Calling [`clone`] always produces a new value."},{"DocComment":" However, for types that are references to other data (such as smart pointers or references),"},{"DocComment":" the new value may still point to the same underlying data, rather than duplicating it."},{"DocComment":" See [`Clone::clone`] for more details."},{"DocComment":""},{"DocComment":" This distinction is especially important when using `#[derive(Clone)]` on structs containing"},{"DocComment":" smart pointers like `Arc>` - the cloned struct will share mutable state with the"},{"DocComment":" original."},{"DocComment":""},{"DocComment":" Differs from [`Copy`] in that [`Copy`] is implicit and an inexpensive bit-wise copy, while"},{"DocComment":" `Clone` is always explicit and may or may not be expensive. [`Copy`] has no methods, so you"},{"DocComment":" cannot change its behavior, but when implementing `Clone`, the `clone` method you provide"},{"DocComment":" may run arbitrary code."},{"DocComment":""},{"DocComment":" Since `Clone` is a supertrait of [`Copy`], any type that implements `Copy` must also implement"},{"DocComment":" `Clone`."},{"DocComment":""},{"DocComment":" ## Derivable"},{"DocComment":""},{"DocComment":" This trait can be used with `#[derive]` if all fields are `Clone`. The `derive`d"},{"DocComment":" implementation of [`Clone`] calls [`clone`] on each field."},{"DocComment":""},{"DocComment":" [`clone`]: Clone::clone"},{"DocComment":""},{"DocComment":" For a generic struct, `#[derive]` implements `Clone` conditionally by adding bound `Clone` on"},{"DocComment":" generic parameters."},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" // `derive` implements Clone for Reading when T is Clone."},{"DocComment":" #[derive(Clone)]"},{"DocComment":" struct Reading {"},{"DocComment":" frequency: T,"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" ## How can I implement `Clone`?"},{"DocComment":""},{"DocComment":" Types that are [`Copy`] should have a trivial implementation of `Clone`. More formally:"},{"DocComment":" if `T: Copy`, `x: T`, and `y: &T`, then `let x = y.clone();` is equivalent to `let x = *y;`."},{"DocComment":" Manual implementations should be careful to uphold this invariant; however, unsafe code"},{"DocComment":" must not rely on it to ensure memory safety."},{"DocComment":""},{"DocComment":" An example is a generic struct holding a function pointer. In this case, the"},{"DocComment":" implementation of `Clone` cannot be `derive`d, but can be implemented as:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" struct Generate(fn() -> T);"},{"DocComment":""},{"DocComment":" impl Copy for Generate {}"},{"DocComment":""},{"DocComment":" impl Clone for Generate {"},{"DocComment":" fn clone(&self) -> Self {"},{"DocComment":" *self"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" If we `derive`:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #[derive(Copy, Clone)]"},{"DocComment":" struct Generate(fn() -> T);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" the auto-derived implementations will have unnecessary `T: Copy` and `T: Clone` bounds:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # struct Generate(fn() -> T);"},{"DocComment":""},{"DocComment":" // Automatically derived"},{"DocComment":" impl Copy for Generate { }"},{"DocComment":""},{"DocComment":" // Automatically derived"},{"DocComment":" impl Clone for Generate {"},{"DocComment":" fn clone(&self) -> Generate {"},{"DocComment":" Generate(Clone::clone(&self.0))"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" The bounds are unnecessary because clearly the function itself should be"},{"DocComment":" copy- and cloneable even if its return type is not:"},{"DocComment":""},{"DocComment":" ```compile_fail,E0599"},{"DocComment":" #[derive(Copy, Clone)]"},{"DocComment":" struct Generate(fn() -> T);"},{"DocComment":""},{"DocComment":" struct NotCloneable;"},{"DocComment":""},{"DocComment":" fn generate_not_cloneable() -> NotCloneable {"},{"DocComment":" NotCloneable"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" Generate(generate_not_cloneable).clone(); // error: trait bounds were not satisfied"},{"DocComment":" // Note: With the manual implementations the above line will compile."},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" ## `Clone` and `PartialEq`/`Eq`"},{"DocComment":" `Clone` is intended for the duplication of objects. Consequently, when implementing"},{"DocComment":" both `Clone` and [`PartialEq`], the following property is expected to hold:"},{"DocComment":" ```text"},{"DocComment":" x == x -> x.clone() == x"},{"DocComment":" ```"},{"DocComment":" In other words, if an object compares equal to itself,"},{"DocComment":" its clone must also compare equal to the original."},{"DocComment":""},{"DocComment":" For types that also implement [`Eq`] – for which `x == x` always holds –"},{"DocComment":" this implies that `x.clone() == x` must always be true."},{"DocComment":" Standard library collections such as"},{"DocComment":" [`HashMap`], [`HashSet`], [`BTreeMap`], [`BTreeSet`] and [`BinaryHeap`]"},{"DocComment":" rely on their keys respecting this property for correct behavior."},{"DocComment":" Furthermore, these collections require that cloning a key preserves the outcome of the"},{"DocComment":" [`Hash`] and [`Ord`] methods. Thankfully, this follows automatically from `x.clone() == x`"},{"DocComment":" if `Hash` and `Ord` are correctly implemented according to their own requirements."},{"DocComment":""},{"DocComment":" When deriving both `Clone` and [`PartialEq`] using `#[derive(Clone, PartialEq)]`"},{"DocComment":" or when additionally deriving [`Eq`] using `#[derive(Clone, PartialEq, Eq)]`,"},{"DocComment":" then this property is automatically upheld – provided that it is satisfied by"},{"DocComment":" the underlying types."},{"DocComment":""},{"DocComment":" Violating this property is a logic error. The behavior resulting from a logic error is not"},{"DocComment":" specified, but users of the trait must ensure that such logic errors do *not* result in"},{"DocComment":" undefined behavior. This means that `unsafe` code **must not** rely on this property"},{"DocComment":" being satisfied."},{"DocComment":""},{"DocComment":" ## Additional implementors"},{"DocComment":""},{"DocComment":" In addition to the [implementors listed below][impls],"},{"DocComment":" the following types also implement `Clone`:"},{"DocComment":""},{"DocComment":" * Function item types (i.e., the distinct types defined for each function)"},{"DocComment":" * Function pointer types (e.g., `fn() -> i32`)"},{"DocComment":" * Closure types, if they capture no value from the environment"},{"DocComment":" or if all such captured values implement `Clone` themselves."},{"DocComment":" Note that variables captured by shared reference always implement `Clone`"},{"DocComment":" (even if the referent doesn't),"},{"DocComment":" while variables captured by mutable reference never implement `Clone`."},{"DocComment":""},{"DocComment":" [`HashMap`]: ../../std/collections/struct.HashMap.html"},{"DocComment":" [`HashSet`]: ../../std/collections/struct.HashSet.html"},{"DocComment":" [`BTreeMap`]: ../../std/collections/struct.BTreeMap.html"},{"DocComment":" [`BTreeSet`]: ../../std/collections/struct.BTreeSet.html"},{"DocComment":" [`BinaryHeap`]: ../../std/collections/struct.BinaryHeap.html"},{"DocComment":" [impls]: #implementors"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"clone"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":25,"beg":{"line":194,"col":23},"end":{"line":194,"col":28}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[{"params":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"clone","attr_info":{"attributes":[{"DocComment":" Returns a duplicate of the value."},{"DocComment":""},{"DocComment":" Note that what \"duplicate\" means varies by type:"},{"DocComment":" - For most types, this creates a deep, independent copy"},{"DocComment":" - For reference types like `&T`, this creates another reference to the same value"},{"DocComment":" - For smart pointers like [`Arc`] or [`Rc`], this increments the reference count"},{"DocComment":" but still points to the same underlying data"},{"DocComment":""},{"DocComment":" [`Arc`]: ../../std/sync/struct.Arc.html"},{"DocComment":" [`Rc`]: ../../std/rc/struct.Rc.html"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #![allow(noop_method_call)]"},{"DocComment":" let hello = \"Hello\"; // &str implements Clone"},{"DocComment":""},{"DocComment":" assert_eq!(\"Hello\", hello.clone());"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" Example with a reference-counted type:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::sync::{Arc, Mutex};"},{"DocComment":""},{"DocComment":" let data = Arc::new(Mutex::new(vec![1, 2, 3]));"},{"DocComment":" let data_clone = data.clone(); // Creates another Arc pointing to the same Mutex"},{"DocComment":""},{"DocComment":" {"},{"DocComment":" let mut lock = data.lock().unwrap();"},{"DocComment":" lock.push(4);"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" // Changes are visible through the clone because they share the same underlying data"},{"DocComment":" assert_eq!(*data_clone.lock().unwrap(), vec![1, 2, 3, 4]);"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"HashConsedValue":[1820,{"Ref":[{"Var":{"Bound":[0,0]}},{"Deduplicated":141},"Shared"]}]}],"output":{"Deduplicated":141}},"item":{"id":182,"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[{"HashConsedValue":[5633,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":8,"generics":{"regions":[],"types":[{"Deduplicated":1577}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"kind":{"TraitMethod":[8,0]}},null],"vtable":null},{"def_id":9,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["function",0]},{"Ident":["FnMut",0]}],"span":{"data":{"file_id":16,"beg":{"line":163,"col":0},"end":{"line":163,"col":48}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" The version of the call operator that takes a mutable receiver."},{"DocComment":""},{"DocComment":" Instances of `FnMut` can be called repeatedly and may mutate state."},{"DocComment":""},{"DocComment":" `FnMut` is implemented automatically by closures which take mutable"},{"DocComment":" references to captured variables, as well as all types that implement"},{"DocComment":" [`Fn`], e.g., (safe) [function pointers] (since `FnMut` is a supertrait of"},{"DocComment":" [`Fn`]). Additionally, for any type `F` that implements `FnMut`, `&mut F`"},{"DocComment":" implements `FnMut`, too."},{"DocComment":""},{"DocComment":" Since [`FnOnce`] is a supertrait of `FnMut`, any instance of `FnMut` can be"},{"DocComment":" used where a [`FnOnce`] is expected, and since [`Fn`] is a subtrait of"},{"DocComment":" `FnMut`, any instance of [`Fn`] can be used where `FnMut` is expected."},{"DocComment":""},{"DocComment":" Use `FnMut` as a bound when you want to accept a parameter of function-like"},{"DocComment":" type and need to call it repeatedly, while allowing it to mutate state."},{"DocComment":" If you don't want the parameter to mutate state, use [`Fn`] as a"},{"DocComment":" bound; if you don't need to call it repeatedly, use [`FnOnce`]."},{"DocComment":""},{"DocComment":" See the [chapter on closures in *The Rust Programming Language*][book] for"},{"DocComment":" some more information on this topic."},{"DocComment":""},{"DocComment":" Also of note is the special syntax for `Fn` traits (e.g."},{"DocComment":" `Fn(usize, bool) -> usize`). Those interested in the technical details of"},{"DocComment":" this can refer to [the relevant section in the *Rustonomicon*][nomicon]."},{"DocComment":""},{"DocComment":" [book]: ../../book/ch13-01-closures.html"},{"DocComment":" [function pointers]: fn"},{"DocComment":" [nomicon]: ../../nomicon/hrtb.html"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ## Calling a mutably capturing closure"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let mut x = 5;"},{"DocComment":" {"},{"DocComment":" let mut square_x = || x *= x;"},{"DocComment":" square_x();"},{"DocComment":" }"},{"DocComment":" assert_eq!(x, 25);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" ## Using a `FnMut` parameter"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" fn do_twice(mut func: F)"},{"DocComment":" where F: FnMut()"},{"DocComment":" {"},{"DocComment":" func();"},{"DocComment":" func();"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" let mut x: usize = 1;"},{"DocComment":" {"},{"DocComment":" let add_two_to_x = || x += 2;"},{"DocComment":" do_twice(add_two_to_x);"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" assert_eq!(x, 5);"},{"DocComment":" ```"},{"Unknown":{"path":"rustc_on_unimplemented","args":"on(Args = \"()\", note =\n\"wrap the `{Self}` in a closure with no arguments: `|| {{ /* code */ }}`\"),\non(Self = \"unsafe fn\", note =\n\"unsafe function cannot be called generically without an unsafe block\", label\n= \"call the function in a closure: `|| unsafe {{ /* code */ }}`\"), message =\n\"expected a `{Trait}` closure, found `{Self}`\", label =\n\"expected an `{Trait}` closure, found `{Self}`\""}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"fn_mut"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"Args"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":16,"beg":{"line":163,"col":0},"end":{"line":167,"col":1}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":16,"beg":{"line":163,"col":36},"end":{"line":163,"col":48}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":16,"beg":{"line":163,"col":22},"end":{"line":163,"col":26}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":3,"span":{"data":{"file_id":16,"beg":{"line":163,"col":28},"end":{"line":163,"col":33}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":25,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[{"params":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"call_mut","attr_info":{"attributes":[{"DocComment":" Performs the call operation."}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":1695},{"Deduplicated":1551}],"output":{"HashConsedValue":[6143,{"TraitType":[{"HashConsedValue":[6142,{"kind":{"ParentClause":[{"HashConsedValue":[4747,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":9,"generics":{"regions":[],"types":[{"Deduplicated":1577},{"Deduplicated":2212}],"const_generics":[],"trait_refs":[]}}}}]},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":1577},{"Deduplicated":2212}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}},"item":{"id":184,"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[{"Deduplicated":141},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[{"Deduplicated":4747}]}}},"kind":{"TraitMethod":[9,0]}}],"vtable":{"id":{"Adt":43},"generics":{"regions":[],"types":[{"Deduplicated":1555},{"HashConsedValue":[6146,{"TraitType":[{"HashConsedValue":[6145,{"kind":{"ParentClause":[{"HashConsedValue":[6144,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":9,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}]},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}},{"def_id":10,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["FromIterator",0]}],"span":{"data":{"file_id":11,"beg":{"line":134,"col":0},"end":{"line":134,"col":32}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Conversion from an [`Iterator`]."},{"DocComment":""},{"DocComment":" By implementing `FromIterator` for a type, you define how it will be"},{"DocComment":" created from an iterator. This is common for types which describe a"},{"DocComment":" collection of some kind."},{"DocComment":""},{"DocComment":" If you want to create a collection from the contents of an iterator, the"},{"DocComment":" [`Iterator::collect()`] method is preferred. However, when you need to"},{"DocComment":" specify the container type, [`FromIterator::from_iter()`] can be more"},{"DocComment":" readable than using a turbofish (e.g. `::>()`). See the"},{"DocComment":" [`Iterator::collect()`] documentation for more examples of its use."},{"DocComment":""},{"DocComment":" See also: [`IntoIterator`]."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" Basic usage:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let five_fives = std::iter::repeat(5).take(5);"},{"DocComment":""},{"DocComment":" let v = Vec::from_iter(five_fives);"},{"DocComment":""},{"DocComment":" assert_eq!(v, vec![5, 5, 5, 5, 5]);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" Using [`Iterator::collect()`] to implicitly use `FromIterator`:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let five_fives = std::iter::repeat(5).take(5);"},{"DocComment":""},{"DocComment":" let v: Vec = five_fives.collect();"},{"DocComment":""},{"DocComment":" assert_eq!(v, vec![5, 5, 5, 5, 5]);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" Using [`FromIterator::from_iter()`] as a more readable alternative to"},{"DocComment":" [`Iterator::collect()`]:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::collections::VecDeque;"},{"DocComment":" let first = (0..10).collect::>();"},{"DocComment":" let second = VecDeque::from_iter(0..10);"},{"DocComment":""},{"DocComment":" assert_eq!(first, second);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" Implementing `FromIterator` for your type:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" // A sample collection, that's just a wrapper over Vec"},{"DocComment":" #[derive(Debug)]"},{"DocComment":" struct MyCollection(Vec);"},{"DocComment":""},{"DocComment":" // Let's give it some methods so we can create one and add things"},{"DocComment":" // to it."},{"DocComment":" impl MyCollection {"},{"DocComment":" fn new() -> MyCollection {"},{"DocComment":" MyCollection(Vec::new())"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" fn add(&mut self, elem: i32) {"},{"DocComment":" self.0.push(elem);"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" // and we'll implement FromIterator"},{"DocComment":" impl FromIterator for MyCollection {"},{"DocComment":" fn from_iter>(iter: I) -> Self {"},{"DocComment":" let mut c = MyCollection::new();"},{"DocComment":""},{"DocComment":" for i in iter {"},{"DocComment":" c.add(i);"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" c"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" // Now we can make a new iterator..."},{"DocComment":" let iter = (0..5).into_iter();"},{"DocComment":""},{"DocComment":" // ... and make a MyCollection out of it"},{"DocComment":" let c = MyCollection::from_iter(iter);"},{"DocComment":""},{"DocComment":" assert_eq!(c.0, vec![0, 1, 2, 3, 4]);"},{"DocComment":""},{"DocComment":" // collect works too!"},{"DocComment":""},{"DocComment":" let iter = (0..5).into_iter();"},{"DocComment":" let c: MyCollection = iter.collect();"},{"DocComment":""},{"DocComment":" assert_eq!(c.0, vec![0, 1, 2, 3, 4]);"},{"DocComment":" ```"},{"Unknown":{"path":"rustc_on_unimplemented","args":"on(Self = \"&[{A}]\", message =\n\"a slice of type `{Self}` cannot be built since we need to store the elements somewhere\",\nlabel = \"try explicitly collecting into a `Vec<{A}>`\",),\non(all(A = \"{integer}\", any(Self = \"&[{integral}]\",)), message =\n\"a slice of type `{Self}` cannot be built since we need to store the elements somewhere\",\nlabel = \"try explicitly collecting into a `Vec<{A}>`\",),\non(Self = \"[{A}]\", message =\n\"a slice of type `{Self}` cannot be built since `{Self}` has no definite size\",\nlabel = \"try explicitly collecting into a `Vec<{A}>`\",),\non(all(A = \"{integer}\", any(Self = \"[{integral}]\",)), message =\n\"a slice of type `{Self}` cannot be built since `{Self}` has no definite size\",\nlabel = \"try explicitly collecting into a `Vec<{A}>`\",),\non(Self = \"[{A}; _]\", message =\n\"an array of type `{Self}` cannot be built directly from an iterator\", label =\n\"try collecting into a `Vec<{A}>`, then using `.try_into()`\",),\non(all(A = \"{integer}\", any(Self = \"[{integral}; _]\",)), message =\n\"an array of type `{Self}` cannot be built directly from an iterator\", label =\n\"try collecting into a `Vec<{A}>`, then using `.try_into()`\",), message =\n\"a value of type `{Self}` cannot be built from an iterator \\\n over elements of type `{A}`\",\nlabel =\n\"value of type `{Self}` cannot be built from `std::iter::Iterator`\""}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"FromIterator"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"A"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":11,"beg":{"line":134,"col":27},"end":{"line":134,"col":32}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":11,"beg":{"line":134,"col":23},"end":{"line":134,"col":24}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[{"params":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":11,"beg":{"line":152,"col":17},"end":{"line":152,"col":18}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":11,"beg":{"line":152,"col":20},"end":{"line":152,"col":42}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[{"regions":[],"skip_binder":{"trait_ref":{"HashConsedValue":[3855,{"kind":{"Clause":{"Bound":[1,1]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":1577}],"const_generics":[],"trait_refs":[]}}}}]},"type_id":0,"ty":{"Deduplicated":2212}}}]},"skip_binder":{"name":"from_iter","attr_info":{"attributes":[{"DocComment":" Creates a value from an iterator."},{"DocComment":""},{"DocComment":" See the [module-level documentation] for more."},{"DocComment":""},{"DocComment":" [module-level documentation]: crate::iter"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let five_fives = std::iter::repeat(5).take(5);"},{"DocComment":""},{"DocComment":" let v = Vec::from_iter(five_fives);"},{"DocComment":""},{"DocComment":" assert_eq!(v, vec![5, 5, 5, 5, 5]);"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":173}],"output":{"Deduplicated":141}},"item":{"id":185,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551},{"Deduplicated":173}],"const_generics":[],"trait_refs":[{"HashConsedValue":[5639,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":10,"generics":{"regions":[],"types":[{"Deduplicated":1577},{"Deduplicated":2212}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":176},{"HashConsedValue":[3856,{"kind":{"Clause":{"Bound":[0,1]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"kind":{"TraitMethod":[10,0]}}],"vtable":null},{"def_id":11,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["try_trait",0]},{"Ident":["Try",0]}],"span":{"data":{"file_id":42,"beg":{"line":133,"col":0},"end":{"line":133,"col":41}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" The `?` operator and `try {}` blocks."},{"DocComment":""},{"DocComment":" `try_*` methods typically involve a type implementing this trait. For"},{"DocComment":" example, the closures passed to [`Iterator::try_fold`] and"},{"DocComment":" [`Iterator::try_for_each`] must return such a type."},{"DocComment":""},{"DocComment":" `Try` types are typically those containing two or more categories of values,"},{"DocComment":" some subset of which are so commonly handled via early returns that it's"},{"DocComment":" worth providing a terse (but still visible) syntax to make that easy."},{"DocComment":""},{"DocComment":" This is most often seen for error handling with [`Result`] and [`Option`]."},{"DocComment":" The quintessential implementation of this trait is on [`ControlFlow`]."},{"DocComment":""},{"DocComment":" # Using `Try` in Generic Code"},{"DocComment":""},{"DocComment":" `Iterator::try_fold` was stabilized to call back in Rust 1.27, but"},{"DocComment":" this trait is much newer. To illustrate the various associated types and"},{"DocComment":" methods, let's implement our own version."},{"DocComment":""},{"DocComment":" As a reminder, an infallible version of a fold looks something like this:"},{"DocComment":" ```"},{"DocComment":" fn simple_fold("},{"DocComment":" iter: impl Iterator,"},{"DocComment":" mut accum: A,"},{"DocComment":" mut f: impl FnMut(A, T) -> A,"},{"DocComment":" ) -> A {"},{"DocComment":" for x in iter {"},{"DocComment":" accum = f(accum, x);"},{"DocComment":" }"},{"DocComment":" accum"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" So instead of `f` returning just an `A`, we'll need it to return some other"},{"DocComment":" type that produces an `A` in the \"don't short circuit\" path. Conveniently,"},{"DocComment":" that's also the type we need to return from the function."},{"DocComment":""},{"DocComment":" Let's add a new generic parameter `R` for that type, and bound it to the"},{"DocComment":" output type that we want:"},{"DocComment":" ```"},{"DocComment":" # #![feature(try_trait_v2)]"},{"DocComment":" # use std::ops::Try;"},{"DocComment":" fn simple_try_fold_1>("},{"DocComment":" iter: impl Iterator,"},{"DocComment":" mut accum: A,"},{"DocComment":" mut f: impl FnMut(A, T) -> R,"},{"DocComment":" ) -> R {"},{"DocComment":" todo!()"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" If we get through the entire iterator, we need to wrap up the accumulator"},{"DocComment":" into the return type using [`Try::from_output`]:"},{"DocComment":" ```"},{"DocComment":" # #![feature(try_trait_v2)]"},{"DocComment":" # use std::ops::{ControlFlow, Try};"},{"DocComment":" fn simple_try_fold_2>("},{"DocComment":" iter: impl Iterator,"},{"DocComment":" mut accum: A,"},{"DocComment":" mut f: impl FnMut(A, T) -> R,"},{"DocComment":" ) -> R {"},{"DocComment":" for x in iter {"},{"DocComment":" let cf = f(accum, x).branch();"},{"DocComment":" match cf {"},{"DocComment":" ControlFlow::Continue(a) => accum = a,"},{"DocComment":" ControlFlow::Break(_) => todo!(),"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":" R::from_output(accum)"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" We'll also need [`FromResidual::from_residual`] to turn the residual back"},{"DocComment":" into the original type. But because it's a supertrait of `Try`, we don't"},{"DocComment":" need to mention it in the bounds. All types which implement `Try` can be"},{"DocComment":" recreated from their corresponding residual, so we'll just call it:"},{"DocComment":" ```"},{"DocComment":" # #![feature(try_trait_v2)]"},{"DocComment":" # use std::ops::{ControlFlow, Try};"},{"DocComment":" pub fn simple_try_fold_3>("},{"DocComment":" iter: impl Iterator,"},{"DocComment":" mut accum: A,"},{"DocComment":" mut f: impl FnMut(A, T) -> R,"},{"DocComment":" ) -> R {"},{"DocComment":" for x in iter {"},{"DocComment":" let cf = f(accum, x).branch();"},{"DocComment":" match cf {"},{"DocComment":" ControlFlow::Continue(a) => accum = a,"},{"DocComment":" ControlFlow::Break(r) => return R::from_residual(r),"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":" R::from_output(accum)"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" But this \"call `branch`, then `match` on it, and `return` if it was a"},{"DocComment":" `Break`\" is exactly what happens inside the `?` operator. So rather than"},{"DocComment":" do all this manually, we can just use `?` instead:"},{"DocComment":" ```"},{"DocComment":" # #![feature(try_trait_v2)]"},{"DocComment":" # use std::ops::Try;"},{"DocComment":" fn simple_try_fold>("},{"DocComment":" iter: impl Iterator,"},{"DocComment":" mut accum: A,"},{"DocComment":" mut f: impl FnMut(A, T) -> R,"},{"DocComment":" ) -> R {"},{"DocComment":" for x in iter {"},{"DocComment":" accum = f(accum, x)?;"},{"DocComment":" }"},{"DocComment":" R::from_output(accum)"},{"DocComment":" }"},{"DocComment":" ```"},{"Unknown":{"path":"rustc_on_unimplemented","args":"on(all(from_desugaring = \"TryBlock\"), message =\n\"a `try` block must return `Result` or `Option` \\\n (or another type that implements `{This}`)\",\nlabel =\n\"could not wrap the final value of the block as `{Self}` doesn't implement `Try`\",),\non(all(from_desugaring = \"QuestionMark\"), message =\n\"the `?` operator can only be applied to values that implement `{This}`\",\nlabel = \"the `?` operator cannot be applied to type `{Self}`\")"}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"Try"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":42,"beg":{"line":133,"col":0},"end":{"line":220,"col":1}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":42,"beg":{"line":133,"col":21},"end":{"line":133,"col":41}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":24,"generics":{"regions":[],"types":[{"Deduplicated":141},{"HashConsedValue":[4779,{"TraitType":[{"HashConsedValue":[4778,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":1577}],"const_generics":[],"trait_refs":[]}}}}]},1]}]}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":42,"beg":{"line":136,"col":4},"end":{"line":136,"col":16}},"generated_from_span":null},"origin":{"TraitItem":0},"trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[4792,{"TraitType":[{"Deduplicated":4778},0]}]}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":3,"span":{"data":{"file_id":42,"beg":{"line":160,"col":4},"end":{"line":160,"col":18}},"generated_from_span":null},"origin":{"TraitItem":1},"trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":4779}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"Output","attr_info":{"attributes":[{"DocComment":" The type of the value produced by `?` when *not* short-circuiting."}],"inline":null,"rename":null,"public":false},"default":null,"implied_clauses":[]},"kind":{"TraitType":[11,0]}},{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"Residual","attr_info":{"attributes":[{"DocComment":" The type of the value passed to [`FromResidual::from_residual`]"},{"DocComment":" as part of `?` when short-circuiting."},{"DocComment":""},{"DocComment":" This represents the possible values of the `Self` type which are *not*"},{"DocComment":" represented by the `Output` type."},{"DocComment":""},{"DocComment":" # Note to Implementors"},{"DocComment":""},{"DocComment":" The choice of this type is critical to interconversion."},{"DocComment":" Unlike the `Output` type, which will often be a raw generic type,"},{"DocComment":" this type is typically a newtype of some sort to \"color\" the type"},{"DocComment":" so that it's distinguishable from the residuals of other types."},{"DocComment":""},{"DocComment":" This is why `Result::Residual` is not `E`, but `Result`."},{"DocComment":" That way it's distinct from `ControlFlow::Residual`, for example,"},{"DocComment":" and thus `?` on `ControlFlow` cannot be used in a method returning `Result`."},{"DocComment":""},{"DocComment":" If you're making a generic type `Foo` that implements `Try`,"},{"DocComment":" then typically you can use `Foo` as its `Residual`"},{"DocComment":" type: that type will have a \"hole\" in the correct place, and will maintain the"},{"DocComment":" \"foo-ness\" of the residual so other types need to opt-in to interconversion."}],"inline":null,"rename":null,"public":false},"default":null,"implied_clauses":[]},"kind":{"TraitType":[11,1]}}],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"from_output","attr_info":{"attributes":[{"DocComment":" Constructs the type from its `Output` type."},{"DocComment":""},{"DocComment":" This should be implemented consistently with the `branch` method"},{"DocComment":" such that applying the `?` operator will get back the original value:"},{"DocComment":" `Try::from_output(x).branch() --> ControlFlow::Continue(x)`."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #![feature(try_trait_v2)]"},{"DocComment":" use std::ops::Try;"},{"DocComment":""},{"DocComment":" assert_eq!( as Try>::from_output(3), Ok(3));"},{"DocComment":" assert_eq!( as Try>::from_output(4), Some(4));"},{"DocComment":" assert_eq!("},{"DocComment":" as Try>::from_output(5),"},{"DocComment":" std::ops::ControlFlow::Continue(5),"},{"DocComment":" );"},{"DocComment":""},{"DocComment":" # fn make_question_mark_work() -> Option<()> {"},{"DocComment":" assert_eq!(Option::from_output(4)?, 4);"},{"DocComment":" # None }"},{"DocComment":" # make_question_mark_work();"},{"DocComment":""},{"DocComment":" // This is used, for example, on the accumulator in `try_fold`:"},{"DocComment":" let r = std::iter::empty().try_fold(4, |_, ()| -> Option<_> { unreachable!() });"},{"DocComment":" assert_eq!(r, Some(4));"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":4792}],"output":{"Deduplicated":141}},"item":{"id":186,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[{"Deduplicated":4778}]}}},"kind":{"TraitMethod":[11,0]}},{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"branch","attr_info":{"attributes":[{"DocComment":" Used in `?` to decide whether the operator should produce a value"},{"DocComment":" (because this returned [`ControlFlow::Continue`])"},{"DocComment":" or propagate a value back to the caller"},{"DocComment":" (because this returned [`ControlFlow::Break`])."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #![feature(try_trait_v2)]"},{"DocComment":" use std::ops::{ControlFlow, Try};"},{"DocComment":""},{"DocComment":" assert_eq!(Ok::<_, String>(3).branch(), ControlFlow::Continue(3));"},{"DocComment":" assert_eq!(Err::(3).branch(), ControlFlow::Break(Err(3)));"},{"DocComment":""},{"DocComment":" assert_eq!(Some(3).branch(), ControlFlow::Continue(3));"},{"DocComment":" assert_eq!(None::.branch(), ControlFlow::Break(None));"},{"DocComment":""},{"DocComment":" assert_eq!(ControlFlow::::Continue(3).branch(), ControlFlow::Continue(3));"},{"DocComment":" assert_eq!("},{"DocComment":" ControlFlow::<_, String>::Break(3).branch(),"},{"DocComment":" ControlFlow::Break(ControlFlow::Break(3)),"},{"DocComment":" );"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":141}],"output":{"HashConsedValue":[6151,{"Adt":{"id":{"Adt":6},"generics":{"regions":[],"types":[{"Deduplicated":4779},{"Deduplicated":4792}],"const_generics":[],"trait_refs":[{"HashConsedValue":[6148,{"kind":{"ParentClause":[{"HashConsedValue":[6147,{"kind":{"ParentClause":[{"Deduplicated":4778},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":24,"generics":{"regions":[],"types":[{"Deduplicated":1577},{"HashConsedValue":[4786,{"TraitType":[{"HashConsedValue":[4784,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}]},1]}]}],"const_generics":[],"trait_refs":[]}}}}]},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":4786}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6150,{"kind":{"ParentClause":[{"Deduplicated":4778},2]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[6149,{"TraitType":[{"Deduplicated":4784},0]}]}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},"item":{"id":187,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[{"Deduplicated":4778}]}}},"kind":{"TraitMethod":[11,1]}}],"vtable":null},{"def_id":12,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["try_trait",0]},{"Ident":["Residual",0]}],"span":{"data":{"file_id":42,"beg":{"line":364,"col":0},"end":{"line":364,"col":34}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Allows retrieving the canonical type implementing [`Try`] that has this type"},{"DocComment":" as its residual and allows it to hold an `O` as its output."},{"DocComment":""},{"DocComment":" If you think of the `Try` trait as splitting a type into its [`Try::Output`]"},{"DocComment":" and [`Try::Residual`] components, this allows putting them back together."},{"DocComment":""},{"DocComment":" For example,"},{"DocComment":" `Result: Try>`,"},{"DocComment":" and in the other direction,"},{"DocComment":" ` as Residual>::TryType = Result`."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"O"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[{"regions":[],"skip_binder":{"trait_ref":{"HashConsedValue":[5645,{"kind":{"ParentClause":[{"HashConsedValue":[5523,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":12,"generics":{"regions":[],"types":[{"Deduplicated":1577},{"Deduplicated":2212}],"const_generics":[],"trait_refs":[]}}}}]},3]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":11,"generics":{"regions":[],"types":[{"HashConsedValue":[5525,{"TraitType":[{"HashConsedValue":[4818,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":12,"generics":{"regions":[],"types":[{"Deduplicated":1585},{"HashConsedValue":[4553,{"TypeVar":{"Bound":[3,1]}}]}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}}}]},"type_id":0,"ty":{"Deduplicated":1551}}},{"regions":[],"skip_binder":{"trait_ref":{"Deduplicated":5645},"type_id":1,"ty":{"Deduplicated":141}}}]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":42,"beg":{"line":364,"col":29},"end":{"line":364,"col":34}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":42,"beg":{"line":364,"col":25},"end":{"line":364,"col":26}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":42,"beg":{"line":368,"col":4},"end":{"line":368,"col":59}},"generated_from_span":null},"origin":{"TraitItem":0},"trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[5524,{"TraitType":[{"Deduplicated":5523},0]}]}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":3,"span":{"data":{"file_id":42,"beg":{"line":368,"col":18},"end":{"line":368,"col":58}},"generated_from_span":null},"origin":{"TraitItem":0},"trait_":{"regions":[],"skip_binder":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":5524}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"TryType","attr_info":{"attributes":[{"DocComment":" The \"return\" type of this meta-function."}],"inline":null,"rename":null,"public":false},"default":null,"implied_clauses":[]},"kind":{"TraitType":[12,0]}}],"methods":[],"vtable":null},{"def_id":13,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["Extend",0]}],"span":{"data":{"file_id":11,"beg":{"line":397,"col":0},"end":{"line":397,"col":19}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Extend a collection with the contents of an iterator."},{"DocComment":""},{"DocComment":" Iterators produce a series of values, and collections can also be thought"},{"DocComment":" of as a series of values. The `Extend` trait bridges this gap, allowing you"},{"DocComment":" to extend a collection by including the contents of that iterator. When"},{"DocComment":" extending a collection with an already existing key, that entry is updated"},{"DocComment":" or, in the case of collections that permit multiple entries with equal"},{"DocComment":" keys, that entry is inserted."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" Basic usage:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" // You can extend a String with some chars:"},{"DocComment":" let mut message = String::from(\"The first three letters are: \");"},{"DocComment":""},{"DocComment":" message.extend(&['a', 'b', 'c']);"},{"DocComment":""},{"DocComment":" assert_eq!(\"abc\", &message[29..32]);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" Implementing `Extend`:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" // A sample collection, that's just a wrapper over Vec"},{"DocComment":" #[derive(Debug)]"},{"DocComment":" struct MyCollection(Vec);"},{"DocComment":""},{"DocComment":" // Let's give it some methods so we can create one and add things"},{"DocComment":" // to it."},{"DocComment":" impl MyCollection {"},{"DocComment":" fn new() -> MyCollection {"},{"DocComment":" MyCollection(Vec::new())"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" fn add(&mut self, elem: i32) {"},{"DocComment":" self.0.push(elem);"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" // since MyCollection has a list of i32s, we implement Extend for i32"},{"DocComment":" impl Extend for MyCollection {"},{"DocComment":""},{"DocComment":" // This is a bit simpler with the concrete type signature: we can call"},{"DocComment":" // extend on anything which can be turned into an Iterator which gives"},{"DocComment":" // us i32s. Because we need i32s to put into MyCollection."},{"DocComment":" fn extend>(&mut self, iter: T) {"},{"DocComment":""},{"DocComment":" // The implementation is very straightforward: loop through the"},{"DocComment":" // iterator, and add() each element to ourselves."},{"DocComment":" for elem in iter {"},{"DocComment":" self.add(elem);"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" let mut c = MyCollection::new();"},{"DocComment":""},{"DocComment":" c.add(5);"},{"DocComment":" c.add(6);"},{"DocComment":" c.add(7);"},{"DocComment":""},{"DocComment":" // let's extend our collection with three more numbers"},{"DocComment":" c.extend(vec![1, 2, 3]);"},{"DocComment":""},{"DocComment":" // we've added these elements onto the end"},{"DocComment":" assert_eq!(\"MyCollection([5, 6, 7, 1, 2, 3])\", format!(\"{c:?}\"));"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"A"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":11,"beg":{"line":397,"col":0},"end":{"line":451,"col":1}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":11,"beg":{"line":397,"col":17},"end":{"line":397,"col":18}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[{"params":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":11,"beg":{"line":416,"col":14},"end":{"line":416,"col":15}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":11,"beg":{"line":416,"col":17},"end":{"line":416,"col":39}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[{"regions":[],"skip_binder":{"trait_ref":{"Deduplicated":3855},"type_id":0,"ty":{"Deduplicated":2212}}}]},"skip_binder":{"name":"extend","attr_info":{"attributes":[{"DocComment":" Extends a collection with the contents of an iterator."},{"DocComment":""},{"DocComment":" As this is the only required method for this trait, the [trait-level] docs"},{"DocComment":" contain more details."},{"DocComment":""},{"DocComment":" [trait-level]: Extend"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" // You can extend a String with some chars:"},{"DocComment":" let mut message = String::from(\"abc\");"},{"DocComment":""},{"DocComment":" message.extend(['d', 'e', 'f'].iter());"},{"DocComment":""},{"DocComment":" assert_eq!(\"abcdef\", &message);"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":1695},{"Deduplicated":173}],"output":{"Deduplicated":198}},"item":{"id":188,"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[{"Deduplicated":141},{"Deduplicated":1551},{"Deduplicated":173}],"const_generics":[],"trait_refs":[{"HashConsedValue":[5646,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":13,"generics":{"regions":[],"types":[{"Deduplicated":1577},{"Deduplicated":2212}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":176},{"Deduplicated":3856}]}}},"kind":{"TraitMethod":[13,0]}},null,null,null],"vtable":null},{"def_id":14,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["default",0]},{"Ident":["Default",0]}],"span":{"data":{"file_id":43,"beg":{"line":107,"col":0},"end":{"line":107,"col":30}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" A trait for giving a type a useful default value."},{"DocComment":""},{"DocComment":" Sometimes, you want to fall back to some kind of default value, and"},{"DocComment":" don't particularly care what it is. This comes up often with `struct`s"},{"DocComment":" that define a set of options:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #[allow(dead_code)]"},{"DocComment":" struct SomeOptions {"},{"DocComment":" foo: i32,"},{"DocComment":" bar: f32,"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" How can we define some default values? You can use `Default`:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #[allow(dead_code)]"},{"DocComment":" #[derive(Default)]"},{"DocComment":" struct SomeOptions {"},{"DocComment":" foo: i32,"},{"DocComment":" bar: f32,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" fn main() {"},{"DocComment":" let options: SomeOptions = Default::default();"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" Now, you get all of the default values. Rust implements `Default` for various primitive types."},{"DocComment":""},{"DocComment":" If you want to override a particular option, but still retain the other defaults:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #[allow(dead_code)]"},{"DocComment":" # #[derive(Default)]"},{"DocComment":" # struct SomeOptions {"},{"DocComment":" # foo: i32,"},{"DocComment":" # bar: f32,"},{"DocComment":" # }"},{"DocComment":" fn main() {"},{"DocComment":" let options = SomeOptions { foo: 42, ..Default::default() };"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" ## Derivable"},{"DocComment":""},{"DocComment":" This trait can be used with `#[derive]` if all of the type's fields implement"},{"DocComment":" `Default`. When `derive`d, it will use the default value for each field's type."},{"DocComment":""},{"DocComment":" ### `enum`s"},{"DocComment":""},{"DocComment":" When using `#[derive(Default)]` on an `enum`, you need to choose which unit variant will be"},{"DocComment":" default. You do this by placing the `#[default]` attribute on the variant."},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #[derive(Default)]"},{"DocComment":" enum Kind {"},{"DocComment":" #[default]"},{"DocComment":" A,"},{"DocComment":" B,"},{"DocComment":" C,"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" You cannot use the `#[default]` attribute on non-unit or non-exhaustive variants."},{"DocComment":""},{"DocComment":" The `#[default]` attribute was stabilized in Rust 1.62.0."},{"DocComment":""},{"DocComment":" ## How can I implement `Default`?"},{"DocComment":""},{"DocComment":" Provide an implementation for the `default()` method that returns the value of"},{"DocComment":" your type that should be the default:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #![allow(dead_code)]"},{"DocComment":" enum Kind {"},{"DocComment":" A,"},{"DocComment":" B,"},{"DocComment":" C,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl Default for Kind {"},{"DocComment":" fn default() -> Self { Kind::A }"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #[allow(dead_code)]"},{"DocComment":" #[derive(Default)]"},{"DocComment":" struct SomeOptions {"},{"DocComment":" foo: i32,"},{"DocComment":" bar: f32,"},{"DocComment":" }"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"Default"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":43,"beg":{"line":107,"col":25},"end":{"line":107,"col":30}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"default","attr_info":{"attributes":[{"DocComment":" Returns the \"default value\" for a type."},{"DocComment":""},{"DocComment":" Default values are often some kind of initial value, identity value, or anything else that"},{"DocComment":" may make sense as a default."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" Using built-in default values:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let i: i8 = Default::default();"},{"DocComment":" let (x, y): (Option, f64) = Default::default();"},{"DocComment":" let (a, b, (c, d)): (i32, u32, (bool, bool)) = Default::default();"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" Making your own:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #[allow(dead_code)]"},{"DocComment":" enum Kind {"},{"DocComment":" A,"},{"DocComment":" B,"},{"DocComment":" C,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl Default for Kind {"},{"DocComment":" fn default() -> Self { Kind::A }"},{"DocComment":" }"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[],"output":{"Deduplicated":141}},"item":{"id":192,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[{"HashConsedValue":[5647,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":14,"generics":{"regions":[],"types":[{"Deduplicated":1577}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"kind":{"TraitMethod":[14,0]}}],"vtable":null},{"def_id":15,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["double_ended",0]},{"Ident":["DoubleEndedIterator",0]}],"span":{"data":{"file_id":44,"beg":{"line":41,"col":0},"end":{"line":41,"col":39}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator able to yield elements from both ends."},{"DocComment":""},{"DocComment":" Something that implements `DoubleEndedIterator` has one extra capability"},{"DocComment":" over something that implements [`Iterator`]: the ability to also take"},{"DocComment":" `Item`s from the back, as well as the front."},{"DocComment":""},{"DocComment":" It is important to note that both back and forth work on the same range,"},{"DocComment":" and do not cross: iteration is over when they meet in the middle."},{"DocComment":""},{"DocComment":" In a similar fashion to the [`Iterator`] protocol, once a"},{"DocComment":" `DoubleEndedIterator` returns [`None`] from a [`next_back()`], calling it"},{"DocComment":" again may or may not ever return [`Some`] again. [`next()`] and"},{"DocComment":" [`next_back()`] are interchangeable for this purpose."},{"DocComment":""},{"DocComment":" [`next_back()`]: DoubleEndedIterator::next_back"},{"DocComment":" [`next()`]: Iterator::next"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" Basic usage:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let numbers = vec![1, 2, 3, 4, 5, 6];"},{"DocComment":""},{"DocComment":" let mut iter = numbers.iter();"},{"DocComment":""},{"DocComment":" assert_eq!(Some(&1), iter.next());"},{"DocComment":" assert_eq!(Some(&6), iter.next_back());"},{"DocComment":" assert_eq!(Some(&5), iter.next_back());"},{"DocComment":" assert_eq!(Some(&2), iter.next());"},{"DocComment":" assert_eq!(Some(&3), iter.next());"},{"DocComment":" assert_eq!(Some(&4), iter.next());"},{"DocComment":" assert_eq!(None, iter.next());"},{"DocComment":" assert_eq!(None, iter.next_back());"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"DoubleEndedIterator"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":44,"beg":{"line":41,"col":0},"end":{"line":380,"col":1}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":44,"beg":{"line":41,"col":31},"end":{"line":41,"col":39}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[{"params":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"next_back","attr_info":{"attributes":[{"DocComment":" Removes and returns an element from the end of the iterator."},{"DocComment":""},{"DocComment":" Returns `None` when there are no more elements."},{"DocComment":""},{"DocComment":" The [trait-level] docs contain more details."},{"DocComment":""},{"DocComment":" [trait-level]: DoubleEndedIterator"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" Basic usage:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let numbers = vec![1, 2, 3, 4, 5, 6];"},{"DocComment":""},{"DocComment":" let mut iter = numbers.iter();"},{"DocComment":""},{"DocComment":" assert_eq!(Some(&1), iter.next());"},{"DocComment":" assert_eq!(Some(&6), iter.next_back());"},{"DocComment":" assert_eq!(Some(&5), iter.next_back());"},{"DocComment":" assert_eq!(Some(&2), iter.next());"},{"DocComment":" assert_eq!(Some(&3), iter.next());"},{"DocComment":" assert_eq!(Some(&4), iter.next());"},{"DocComment":" assert_eq!(None, iter.next());"},{"DocComment":" assert_eq!(None, iter.next_back());"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" # Remarks"},{"DocComment":""},{"DocComment":" The elements yielded by `DoubleEndedIterator`'s methods may differ from"},{"DocComment":" the ones yielded by [`Iterator`]'s methods:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let vec = vec![(1, 'a'), (1, 'b'), (1, 'c'), (2, 'a'), (2, 'b')];"},{"DocComment":" let uniq_by_fst_comp = || {"},{"DocComment":" let mut seen = std::collections::HashSet::new();"},{"DocComment":" vec.iter().copied().filter(move |x| seen.insert(x.0))"},{"DocComment":" };"},{"DocComment":""},{"DocComment":" assert_eq!(uniq_by_fst_comp().last(), Some((2, 'a')));"},{"DocComment":" assert_eq!(uniq_by_fst_comp().next_back(), Some((2, 'b')));"},{"DocComment":""},{"DocComment":" assert_eq!("},{"DocComment":" uniq_by_fst_comp().fold(vec![], |mut v, x| {v.push(x); v}),"},{"DocComment":" vec![(1, 'a'), (2, 'a')]"},{"DocComment":" );"},{"DocComment":" assert_eq!("},{"DocComment":" uniq_by_fst_comp().rfold(vec![], |mut v, x| {v.push(x); v}),"},{"DocComment":" vec![(2, 'b'), (1, 'c')]"},{"DocComment":" );"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":1695}],"output":{"HashConsedValue":[6154,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"HashConsedValue":[6152,{"TraitType":[{"HashConsedValue":[4882,{"kind":{"ParentClause":[{"HashConsedValue":[4881,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":15,"generics":{"regions":[],"types":[{"Deduplicated":1577}],"const_generics":[],"trait_refs":[]}}}}]},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":1577}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[{"HashConsedValue":[6153,{"kind":{"ParentClause":[{"Deduplicated":4882},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[4886,{"TraitType":[{"HashConsedValue":[4885,{"kind":{"ParentClause":[{"HashConsedValue":[4884,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":15,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}]},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},"item":{"id":193,"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[{"Deduplicated":4881}]}}},"kind":{"TraitMethod":[15,0]}},null,null,null,null,null],"vtable":{"id":{"Adt":44},"generics":{"regions":[],"types":[{"HashConsedValue":[6157,{"TraitType":[{"HashConsedValue":[6156,{"kind":{"ParentClause":[{"HashConsedValue":[6155,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":15,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}]},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}},{"def_id":16,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["exact_size",0]},{"Ident":["ExactSizeIterator",0]}],"span":{"data":{"file_id":45,"beg":{"line":86,"col":0},"end":{"line":86,"col":37}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that knows its exact length."},{"DocComment":""},{"DocComment":" Many [`Iterator`]s don't know how many times they will iterate, but some do."},{"DocComment":" If an iterator knows how many times it can iterate, providing access to"},{"DocComment":" that information can be useful. For example, if you want to iterate"},{"DocComment":" backwards, a good start is to know where the end is."},{"DocComment":""},{"DocComment":" When implementing an `ExactSizeIterator`, you must also implement"},{"DocComment":" [`Iterator`]. When doing so, the implementation of [`Iterator::size_hint`]"},{"DocComment":" *must* return the exact size of the iterator."},{"DocComment":""},{"DocComment":" The [`len`] method has a default implementation, so you usually shouldn't"},{"DocComment":" implement it. However, you may be able to provide a more performant"},{"DocComment":" implementation than the default, so overriding it in this case makes sense."},{"DocComment":""},{"DocComment":" Note that this trait is a safe trait and as such does *not* and *cannot*"},{"DocComment":" guarantee that the returned length is correct. This means that `unsafe`"},{"DocComment":" code **must not** rely on the correctness of [`Iterator::size_hint`]. The"},{"DocComment":" unstable and unsafe [`TrustedLen`](super::marker::TrustedLen) trait gives"},{"DocComment":" this additional guarantee."},{"DocComment":""},{"DocComment":" [`len`]: ExactSizeIterator::len"},{"DocComment":""},{"DocComment":" # When *shouldn't* an adapter be `ExactSizeIterator`?"},{"DocComment":""},{"DocComment":" If an adapter makes an iterator *longer*, then it's usually incorrect for"},{"DocComment":" that adapter to implement `ExactSizeIterator`. The inner exact-sized"},{"DocComment":" iterator might already be `usize::MAX`-long, and thus the length of the"},{"DocComment":" longer adapted iterator would no longer be exactly representable in `usize`."},{"DocComment":""},{"DocComment":" This is why [`Chain`](crate::iter::Chain) isn't `ExactSizeIterator`,"},{"DocComment":" even when `A` and `B` are both `ExactSizeIterator`."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" Basic usage:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" // a finite range knows exactly how many times it will iterate"},{"DocComment":" let five = 0..5;"},{"DocComment":""},{"DocComment":" assert_eq!(5, five.len());"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" In the [module-level docs], we implemented an [`Iterator`], `Counter`."},{"DocComment":" Let's implement `ExactSizeIterator` for it as well:"},{"DocComment":""},{"DocComment":" [module-level docs]: crate::iter"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # struct Counter {"},{"DocComment":" # count: usize,"},{"DocComment":" # }"},{"DocComment":" # impl Counter {"},{"DocComment":" # fn new() -> Counter {"},{"DocComment":" # Counter { count: 0 }"},{"DocComment":" # }"},{"DocComment":" # }"},{"DocComment":" # impl Iterator for Counter {"},{"DocComment":" # type Item = usize;"},{"DocComment":" # fn next(&mut self) -> Option {"},{"DocComment":" # self.count += 1;"},{"DocComment":" # if self.count < 6 {"},{"DocComment":" # Some(self.count)"},{"DocComment":" # } else {"},{"DocComment":" # None"},{"DocComment":" # }"},{"DocComment":" # }"},{"DocComment":" # }"},{"DocComment":" impl ExactSizeIterator for Counter {"},{"DocComment":" // We can easily calculate the remaining number of iterations."},{"DocComment":" fn len(&self) -> usize {"},{"DocComment":" 5 - self.count"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" // And now we can use it!"},{"DocComment":""},{"DocComment":" let mut counter = Counter::new();"},{"DocComment":""},{"DocComment":" assert_eq!(5, counter.len());"},{"DocComment":" let _ = counter.next();"},{"DocComment":" assert_eq!(4, counter.len());"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":45,"beg":{"line":86,"col":0},"end":{"line":151,"col":1}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":45,"beg":{"line":86,"col":29},"end":{"line":86,"col":37}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[null,null],"vtable":{"id":{"Adt":45},"generics":{"regions":[],"types":[{"HashConsedValue":[6160,{"TraitType":[{"HashConsedValue":[6159,{"kind":{"ParentClause":[{"HashConsedValue":[6158,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":16,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}]},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}},{"def_id":17,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["Ord",0]}],"span":{"data":{"file_id":46,"beg":{"line":973,"col":0},"end":{"line":973,"col":73}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Trait for types that form a [total order](https://en.wikipedia.org/wiki/Total_order)."},{"DocComment":""},{"DocComment":" Implementations must be consistent with the [`PartialOrd`] implementation, and ensure `max`,"},{"DocComment":" `min`, and `clamp` are consistent with `cmp`:"},{"DocComment":""},{"DocComment":" - `partial_cmp(a, b) == Some(cmp(a, b))`."},{"DocComment":" - `max(a, b) == max_by(a, b, cmp)` (ensured by the default implementation)."},{"DocComment":" - `min(a, b) == min_by(a, b, cmp)` (ensured by the default implementation)."},{"DocComment":" - For `a.clamp(min, max)`, see the [method docs](#method.clamp) (ensured by the default"},{"DocComment":" implementation)."},{"DocComment":""},{"DocComment":" Violating these requirements is a logic error. The behavior resulting from a logic error is not"},{"DocComment":" specified, but users of the trait must ensure that such logic errors do *not* result in"},{"DocComment":" undefined behavior. This means that `unsafe` code **must not** rely on the correctness of these"},{"DocComment":" methods."},{"DocComment":""},{"DocComment":" ## Corollaries"},{"DocComment":""},{"DocComment":" From the above and the requirements of `PartialOrd`, it follows that for all `a`, `b` and `c`:"},{"DocComment":""},{"DocComment":" - exactly one of `a < b`, `a == b` or `a > b` is true; and"},{"DocComment":" - `<` is transitive: `a < b` and `b < c` implies `a < c`. The same must hold for both `==` and"},{"DocComment":" `>`."},{"DocComment":""},{"DocComment":" Mathematically speaking, the `<` operator defines a strict [weak order]. In cases where `==`"},{"DocComment":" conforms to mathematical equality, it also defines a strict [total order]."},{"DocComment":""},{"DocComment":" [weak order]: https://en.wikipedia.org/wiki/Weak_ordering"},{"DocComment":" [total order]: https://en.wikipedia.org/wiki/Total_order"},{"DocComment":""},{"DocComment":" ## Derivable"},{"DocComment":""},{"DocComment":" This trait can be used with `#[derive]`."},{"DocComment":""},{"DocComment":" When `derive`d on structs, it will produce a"},{"DocComment":" [lexicographic](https://en.wikipedia.org/wiki/Lexicographic_order) ordering based on the"},{"DocComment":" top-to-bottom declaration order of the struct's members."},{"DocComment":""},{"DocComment":" When `derive`d on enums, variants are ordered primarily by their discriminants. Secondarily,"},{"DocComment":" they are ordered by their fields. By default, the discriminant is smallest for variants at the"},{"DocComment":" top, and largest for variants at the bottom. Here's an example:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #[derive(PartialEq, Eq, PartialOrd, Ord)]"},{"DocComment":" enum E {"},{"DocComment":" Top,"},{"DocComment":" Bottom,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" assert!(E::Top < E::Bottom);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" However, manually setting the discriminants can override this default behavior:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #[derive(PartialEq, Eq, PartialOrd, Ord)]"},{"DocComment":" enum E {"},{"DocComment":" Top = 2,"},{"DocComment":" Bottom = 1,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" assert!(E::Bottom < E::Top);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" ## Lexicographical comparison"},{"DocComment":""},{"DocComment":" Lexicographical comparison is an operation with the following properties:"},{"DocComment":" - Two sequences are compared element by element."},{"DocComment":" - The first mismatching element defines which sequence is lexicographically less or greater"},{"DocComment":" than the other."},{"DocComment":" - If one sequence is a prefix of another, the shorter sequence is lexicographically less than"},{"DocComment":" the other."},{"DocComment":" - If two sequences have equivalent elements and are of the same length, then the sequences are"},{"DocComment":" lexicographically equal."},{"DocComment":" - An empty sequence is lexicographically less than any non-empty sequence."},{"DocComment":" - Two empty sequences are lexicographically equal."},{"DocComment":""},{"DocComment":" ## How can I implement `Ord`?"},{"DocComment":""},{"DocComment":" `Ord` requires that the type also be [`PartialOrd`], [`PartialEq`], and [`Eq`]."},{"DocComment":""},{"DocComment":" Because `Ord` implies a stronger ordering relationship than [`PartialOrd`], and both `Ord` and"},{"DocComment":" [`PartialOrd`] must agree, you must choose how to implement `Ord` **first**. You can choose to"},{"DocComment":" derive it, or implement it manually. If you derive it, you should derive all four traits. If you"},{"DocComment":" implement it manually, you should manually implement all four traits, based on the"},{"DocComment":" implementation of `Ord`."},{"DocComment":""},{"DocComment":" Here's an example where you want to define the `Character` comparison by `health` and"},{"DocComment":" `experience` only, disregarding the field `mana`:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::cmp::Ordering;"},{"DocComment":""},{"DocComment":" struct Character {"},{"DocComment":" health: u32,"},{"DocComment":" experience: u32,"},{"DocComment":" mana: f32,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl Ord for Character {"},{"DocComment":" fn cmp(&self, other: &Self) -> Ordering {"},{"DocComment":" self.experience"},{"DocComment":" .cmp(&other.experience)"},{"DocComment":" .then(self.health.cmp(&other.health))"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialOrd for Character {"},{"DocComment":" fn partial_cmp(&self, other: &Self) -> Option {"},{"DocComment":" Some(self.cmp(other))"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialEq for Character {"},{"DocComment":" fn eq(&self, other: &Self) -> bool {"},{"DocComment":" self.health == other.health && self.experience == other.experience"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl Eq for Character {}"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" If all you need is to `slice::sort` a type by a field value, it can be simpler to use"},{"DocComment":" `slice::sort_by_key`."},{"DocComment":""},{"DocComment":" ## Examples of incorrect `Ord` implementations"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::cmp::Ordering;"},{"DocComment":""},{"DocComment":" #[derive(Debug)]"},{"DocComment":" struct Character {"},{"DocComment":" health: f32,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl Ord for Character {"},{"DocComment":" fn cmp(&self, other: &Self) -> std::cmp::Ordering {"},{"DocComment":" if self.health < other.health {"},{"DocComment":" Ordering::Less"},{"DocComment":" } else if self.health > other.health {"},{"DocComment":" Ordering::Greater"},{"DocComment":" } else {"},{"DocComment":" Ordering::Equal"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialOrd for Character {"},{"DocComment":" fn partial_cmp(&self, other: &Self) -> Option {"},{"DocComment":" Some(self.cmp(other))"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialEq for Character {"},{"DocComment":" fn eq(&self, other: &Self) -> bool {"},{"DocComment":" self.health == other.health"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl Eq for Character {}"},{"DocComment":""},{"DocComment":" let a = Character { health: 4.5 };"},{"DocComment":" let b = Character { health: f32::NAN };"},{"DocComment":""},{"DocComment":" // Mistake: floating-point values do not form a total order and using the built-in comparison"},{"DocComment":" // operands to implement `Ord` irregardless of that reality does not change it. Use"},{"DocComment":" // `f32::total_cmp` if you need a total order for floating-point values."},{"DocComment":""},{"DocComment":" // Reflexivity requirement of `Ord` is not given."},{"DocComment":" assert!(a == a);"},{"DocComment":" assert!(b != b);"},{"DocComment":""},{"DocComment":" // Antisymmetry requirement of `Ord` is not given. Only one of a < c and c < a is allowed to be"},{"DocComment":" // true, not both or neither."},{"DocComment":" assert_eq!((a < b) as u8 + (b < a) as u8, 0);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::cmp::Ordering;"},{"DocComment":""},{"DocComment":" #[derive(Debug)]"},{"DocComment":" struct Character {"},{"DocComment":" health: u32,"},{"DocComment":" experience: u32,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialOrd for Character {"},{"DocComment":" fn partial_cmp(&self, other: &Self) -> Option {"},{"DocComment":" Some(self.cmp(other))"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl Ord for Character {"},{"DocComment":" fn cmp(&self, other: &Self) -> std::cmp::Ordering {"},{"DocComment":" if self.health < 50 {"},{"DocComment":" self.health.cmp(&other.health)"},{"DocComment":" } else {"},{"DocComment":" self.experience.cmp(&other.experience)"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" // For performance reasons implementing `PartialEq` this way is not the idiomatic way, but it"},{"DocComment":" // ensures consistent behavior between `PartialEq`, `PartialOrd` and `Ord` in this example."},{"DocComment":" impl PartialEq for Character {"},{"DocComment":" fn eq(&self, other: &Self) -> bool {"},{"DocComment":" self.cmp(other) == Ordering::Equal"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl Eq for Character {}"},{"DocComment":""},{"DocComment":" let a = Character {"},{"DocComment":" health: 3,"},{"DocComment":" experience: 5,"},{"DocComment":" };"},{"DocComment":" let b = Character {"},{"DocComment":" health: 10,"},{"DocComment":" experience: 77,"},{"DocComment":" };"},{"DocComment":" let c = Character {"},{"DocComment":" health: 143,"},{"DocComment":" experience: 2,"},{"DocComment":" };"},{"DocComment":""},{"DocComment":" // Mistake: The implementation of `Ord` compares different fields depending on the value of"},{"DocComment":" // `self.health`, the resulting order is not total."},{"DocComment":""},{"DocComment":" // Transitivity requirement of `Ord` is not given. If a is smaller than b and b is smaller than"},{"DocComment":" // c, by transitive property a must also be smaller than c."},{"DocComment":" assert!(a < b && b < c && c < a);"},{"DocComment":""},{"DocComment":" // Antisymmetry requirement of `Ord` is not given. Only one of a < c and c < a is allowed to be"},{"DocComment":" // true, not both or neither."},{"DocComment":" assert_eq!((a < c) as u8 + (c < a) as u8, 2);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" The documentation of [`PartialOrd`] contains further examples, for example it's wrong for"},{"DocComment":" [`PartialOrd`] and [`PartialEq`] to disagree."},{"DocComment":""},{"DocComment":" [`cmp`]: Ord::cmp"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"Ord"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":46,"beg":{"line":973,"col":21},"end":{"line":973,"col":31}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":27,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":46,"beg":{"line":973,"col":34},"end":{"line":973,"col":58}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":21,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[{"params":{"regions":[{"index":0,"name":null,"mutability":"Unknown"},{"index":1,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"cmp","attr_info":{"attributes":[{"DocComment":" This method returns an [`Ordering`] between `self` and `other`."},{"DocComment":""},{"DocComment":" By convention, `self.cmp(&other)` returns the ordering matching the expression"},{"DocComment":" `self other` if true."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::cmp::Ordering;"},{"DocComment":""},{"DocComment":" assert_eq!(5.cmp(&10), Ordering::Less);"},{"DocComment":" assert_eq!(10.cmp(&5), Ordering::Greater);"},{"DocComment":" assert_eq!(5.cmp(&5), Ordering::Equal);"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":1820},{"HashConsedValue":[6161,{"Ref":[{"Var":{"Bound":[0,1]}},{"Deduplicated":141},"Shared"]}]}],"output":{"Deduplicated":3569}},"item":{"id":201,"generics":{"regions":[{"Var":{"Bound":[0,0]}},{"Var":{"Bound":[0,1]}}],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[{"HashConsedValue":[5658,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":17,"generics":{"regions":[],"types":[{"Deduplicated":1577}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"kind":{"TraitMethod":[17,0]}},null,null,null],"vtable":null},{"def_id":18,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Ident":["Copy",0]}],"span":{"data":{"file_id":1,"beg":{"line":457,"col":0},"end":{"line":457,"col":21}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Types whose values can be duplicated simply by copying bits."},{"DocComment":""},{"DocComment":" By default, variable bindings have 'move semantics.' In other"},{"DocComment":" words:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #[derive(Debug)]"},{"DocComment":" struct Foo;"},{"DocComment":""},{"DocComment":" let x = Foo;"},{"DocComment":""},{"DocComment":" let y = x;"},{"DocComment":""},{"DocComment":" // `x` has moved into `y`, and so cannot be used"},{"DocComment":""},{"DocComment":" // println!(\"{x:?}\"); // error: use of moved value"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" However, if a type implements `Copy`, it instead has 'copy semantics':"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" // We can derive a `Copy` implementation. `Clone` is also required, as it's"},{"DocComment":" // a supertrait of `Copy`."},{"DocComment":" #[derive(Debug, Copy, Clone)]"},{"DocComment":" struct Foo;"},{"DocComment":""},{"DocComment":" let x = Foo;"},{"DocComment":""},{"DocComment":" let y = x;"},{"DocComment":""},{"DocComment":" // `y` is a copy of `x`"},{"DocComment":""},{"DocComment":" println!(\"{x:?}\"); // A-OK!"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" It's important to note that in these two examples, the only difference is whether you"},{"DocComment":" are allowed to access `x` after the assignment. Under the hood, both a copy and a move"},{"DocComment":" can result in bits being copied in memory, although this is sometimes optimized away."},{"DocComment":""},{"DocComment":" ## How can I implement `Copy`?"},{"DocComment":""},{"DocComment":" There are two ways to implement `Copy` on your type. The simplest is to use `derive`:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #[derive(Copy, Clone)]"},{"DocComment":" struct MyStruct;"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" You can also implement `Copy` and `Clone` manually:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" struct MyStruct;"},{"DocComment":""},{"DocComment":" impl Copy for MyStruct { }"},{"DocComment":""},{"DocComment":" impl Clone for MyStruct {"},{"DocComment":" fn clone(&self) -> MyStruct {"},{"DocComment":" *self"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" There is a small difference between the two. The `derive` strategy will also place a `Copy`"},{"DocComment":" bound on type parameters:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #[derive(Clone)]"},{"DocComment":" struct MyStruct(T);"},{"DocComment":""},{"DocComment":" impl Copy for MyStruct { }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" This isn't always desired. For example, shared references (`&T`) can be copied regardless of"},{"DocComment":" whether `T` is `Copy`. Likewise, a generic struct containing markers such as [`PhantomData`]"},{"DocComment":" could potentially be duplicated with a bit-wise copy."},{"DocComment":""},{"DocComment":" ## What's the difference between `Copy` and `Clone`?"},{"DocComment":""},{"DocComment":" Copies happen implicitly, for example as part of an assignment `y = x`. The behavior of"},{"DocComment":" `Copy` is not overloadable; it is always a simple bit-wise copy."},{"DocComment":""},{"DocComment":" Cloning is an explicit action, `x.clone()`. The implementation of [`Clone`] can"},{"DocComment":" provide any type-specific behavior necessary to duplicate values safely. For example,"},{"DocComment":" the implementation of [`Clone`] for [`String`] needs to copy the pointed-to string"},{"DocComment":" buffer in the heap. A simple bitwise copy of [`String`] values would merely copy the"},{"DocComment":" pointer, leading to a double free down the line. For this reason, [`String`] is [`Clone`]"},{"DocComment":" but not `Copy`."},{"DocComment":""},{"DocComment":" [`Clone`] is a supertrait of `Copy`, so everything which is `Copy` must also implement"},{"DocComment":" [`Clone`]. If a type is `Copy` then its [`Clone`] implementation only needs to return `*self`"},{"DocComment":" (see the example above)."},{"DocComment":""},{"DocComment":" ## When can my type be `Copy`?"},{"DocComment":""},{"DocComment":" A type can implement `Copy` if all of its components implement `Copy`. For example, this"},{"DocComment":" struct can be `Copy`:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #[allow(dead_code)]"},{"DocComment":" #[derive(Copy, Clone)]"},{"DocComment":" struct Point {"},{"DocComment":" x: i32,"},{"DocComment":" y: i32,"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" A struct can be `Copy`, and [`i32`] is `Copy`, therefore `Point` is eligible to be `Copy`."},{"DocComment":" By contrast, consider"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #![allow(dead_code)]"},{"DocComment":" # struct Point;"},{"DocComment":" struct PointList {"},{"DocComment":" points: Vec,"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" The struct `PointList` cannot implement `Copy`, because [`Vec`] is not `Copy`. If we"},{"DocComment":" attempt to derive a `Copy` implementation, we'll get an error:"},{"DocComment":""},{"DocComment":" ```text"},{"DocComment":" the trait `Copy` cannot be implemented for this type; field `points` does not implement `Copy`"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" Shared references (`&T`) are also `Copy`, so a type can be `Copy`, even when it holds"},{"DocComment":" shared references of types `T` that are *not* `Copy`. Consider the following struct,"},{"DocComment":" which can implement `Copy`, because it only holds a *shared reference* to our non-`Copy`"},{"DocComment":" type `PointList` from above:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #![allow(dead_code)]"},{"DocComment":" # struct PointList;"},{"DocComment":" #[derive(Copy, Clone)]"},{"DocComment":" struct PointListWrapper<'a> {"},{"DocComment":" point_list_ref: &'a PointList,"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" ## When *can't* my type be `Copy`?"},{"DocComment":""},{"DocComment":" Some types can't be copied safely. For example, copying `&mut T` would create an aliased"},{"DocComment":" mutable reference. Copying [`String`] would duplicate responsibility for managing the"},{"DocComment":" [`String`]'s buffer, leading to a double free."},{"DocComment":""},{"DocComment":" Generalizing the latter case, any type implementing [`Drop`] can't be `Copy`, because it's"},{"DocComment":" managing some resource besides its own [`size_of::`] bytes."},{"DocComment":""},{"DocComment":" If you try to implement `Copy` on a struct or enum containing non-`Copy` data, you will get"},{"DocComment":" the error [E0204]."},{"DocComment":""},{"DocComment":" [E0204]: ../../error_codes/E0204.html"},{"DocComment":""},{"DocComment":" ## When *should* my type be `Copy`?"},{"DocComment":""},{"DocComment":" Generally speaking, if your type _can_ implement `Copy`, it should. Keep in mind, though,"},{"DocComment":" that implementing `Copy` is part of the public API of your type. If the type might become"},{"DocComment":" non-`Copy` in the future, it could be prudent to omit the `Copy` implementation now, to"},{"DocComment":" avoid a breaking API change."},{"DocComment":""},{"DocComment":" ## Additional implementors"},{"DocComment":""},{"DocComment":" In addition to the [implementors listed below][impls],"},{"DocComment":" the following types also implement `Copy`:"},{"DocComment":""},{"DocComment":" * Function item types (i.e., the distinct types defined for each function)"},{"DocComment":" * Function pointer types (e.g., `fn() -> i32`)"},{"DocComment":" * Closure types, if they capture no value from the environment"},{"DocComment":" or if all such captured values implement `Copy` themselves."},{"DocComment":" Note that variables captured by shared reference always implement `Copy`"},{"DocComment":" (even if the referent doesn't),"},{"DocComment":" while variables captured by mutable reference never implement `Copy`."},{"DocComment":""},{"DocComment":" [`Vec`]: ../../std/vec/struct.Vec.html"},{"DocComment":" [`String`]: ../../std/string/struct.String.html"},{"DocComment":" [`size_of::`]: size_of"},{"DocComment":" [impls]: #implementors"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"copy"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":1,"beg":{"line":457,"col":0},"end":{"line":459,"col":1}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":1,"beg":{"line":457,"col":16},"end":{"line":457,"col":21}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":8,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[],"vtable":null},{"def_id":19,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["accum",0]},{"Ident":["Sum",0]}],"span":{"data":{"file_id":52,"beg":{"line":17,"col":0},"end":{"line":17,"col":30}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Trait to represent types that can be created by summing up an iterator."},{"DocComment":""},{"DocComment":" This trait is used to implement [`Iterator::sum()`]. Types which implement"},{"DocComment":" this trait can be generated by using the [`sum()`] method on an iterator."},{"DocComment":" Like [`FromIterator`], this trait should rarely be called directly."},{"DocComment":""},{"DocComment":" [`sum()`]: Iterator::sum"},{"DocComment":" [`FromIterator`]: iter::FromIterator"},{"Unknown":{"path":"diagnostic::on_unimplemented","args":"message =\n\"a value of type `{Self}` cannot be made by summing an iterator over elements of type `{A}`\",\nlabel =\n\"value of type `{Self}` cannot be made by summing a `std::iter::Iterator`\""}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"A"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":52,"beg":{"line":17,"col":25},"end":{"line":17,"col":30}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":52,"beg":{"line":17,"col":14},"end":{"line":17,"col":22}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[{"params":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":52,"beg":{"line":21,"col":11},"end":{"line":21,"col":12}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":52,"beg":{"line":21,"col":14},"end":{"line":21,"col":32}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[{"regions":[],"skip_binder":{"trait_ref":{"Deduplicated":4533},"type_id":0,"ty":{"Deduplicated":2212}}}]},"skip_binder":{"name":"sum","attr_info":{"attributes":[{"DocComment":" Takes an iterator and generates `Self` from the elements by \"summing up\""},{"DocComment":" the items."}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":173}],"output":{"Deduplicated":141}},"item":{"id":205,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551},{"Deduplicated":173}],"const_generics":[],"trait_refs":[{"HashConsedValue":[5659,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":19,"generics":{"regions":[],"types":[{"Deduplicated":1577},{"Deduplicated":2212}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":176},{"Deduplicated":4528}]}}},"kind":{"TraitMethod":[19,0]}}],"vtable":null},{"def_id":20,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["accum",0]},{"Ident":["Product",0]}],"span":{"data":{"file_id":52,"beg":{"line":38,"col":0},"end":{"line":38,"col":34}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Trait to represent types that can be created by multiplying elements of an"},{"DocComment":" iterator."},{"DocComment":""},{"DocComment":" This trait is used to implement [`Iterator::product()`]. Types which implement"},{"DocComment":" this trait can be generated by using the [`product()`] method on an iterator."},{"DocComment":" Like [`FromIterator`], this trait should rarely be called directly."},{"DocComment":""},{"DocComment":" [`product()`]: Iterator::product"},{"DocComment":" [`FromIterator`]: iter::FromIterator"},{"Unknown":{"path":"diagnostic::on_unimplemented","args":"message =\n\"a value of type `{Self}` cannot be made by multiplying all elements of type `{A}` from an iterator\",\nlabel =\n\"value of type `{Self}` cannot be made by multiplying all elements from a `std::iter::Iterator`\""}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"A"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":52,"beg":{"line":38,"col":29},"end":{"line":38,"col":34}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":52,"beg":{"line":38,"col":18},"end":{"line":38,"col":26}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[{"params":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":52,"beg":{"line":42,"col":15},"end":{"line":42,"col":16}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":52,"beg":{"line":42,"col":18},"end":{"line":42,"col":36}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[{"regions":[],"skip_binder":{"trait_ref":{"Deduplicated":4533},"type_id":0,"ty":{"Deduplicated":2212}}}]},"skip_binder":{"name":"product","attr_info":{"attributes":[{"DocComment":" Takes an iterator and generates `Self` from the elements by multiplying"},{"DocComment":" the items."}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":173}],"output":{"Deduplicated":141}},"item":{"id":206,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551},{"Deduplicated":173}],"const_generics":[],"trait_refs":[{"HashConsedValue":[5660,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":20,"generics":{"regions":[],"types":[{"Deduplicated":1577},{"Deduplicated":2212}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":176},{"Deduplicated":4528}]}}},"kind":{"TraitMethod":[20,0]}}],"vtable":null},{"def_id":21,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialOrd",0]}],"span":{"data":{"file_id":46,"beg":{"line":1358,"col":0},"end":{"line":1359,"col":41}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Trait for types that form a [partial order](https://en.wikipedia.org/wiki/Partial_order)."},{"DocComment":""},{"DocComment":" The `lt`, `le`, `gt`, and `ge` methods of this trait can be called using the `<`, `<=`, `>`, and"},{"DocComment":" `>=` operators, respectively."},{"DocComment":""},{"DocComment":" This trait should **only** contain the comparison logic for a type **if one plans on only"},{"DocComment":" implementing `PartialOrd` but not [`Ord`]**. Otherwise the comparison logic should be in [`Ord`]"},{"DocComment":" and this trait implemented with `Some(self.cmp(other))`."},{"DocComment":""},{"DocComment":" The methods of this trait must be consistent with each other and with those of [`PartialEq`]."},{"DocComment":" The following conditions must hold:"},{"DocComment":""},{"DocComment":" 1. `a == b` if and only if `partial_cmp(a, b) == Some(Equal)`."},{"DocComment":" 2. `a < b` if and only if `partial_cmp(a, b) == Some(Less)`"},{"DocComment":" 3. `a > b` if and only if `partial_cmp(a, b) == Some(Greater)`"},{"DocComment":" 4. `a <= b` if and only if `a < b || a == b`"},{"DocComment":" 5. `a >= b` if and only if `a > b || a == b`"},{"DocComment":" 6. `a != b` if and only if `!(a == b)`."},{"DocComment":""},{"DocComment":" Conditions 2–5 above are ensured by the default implementation. Condition 6 is already ensured"},{"DocComment":" by [`PartialEq`]."},{"DocComment":""},{"DocComment":" If [`Ord`] is also implemented for `Self` and `Rhs`, it must also be consistent with"},{"DocComment":" `partial_cmp` (see the documentation of that trait for the exact requirements). It's easy to"},{"DocComment":" accidentally make them disagree by deriving some of the traits and manually implementing others."},{"DocComment":""},{"DocComment":" The comparison relations must satisfy the following conditions (for all `a`, `b`, `c` of type"},{"DocComment":" `A`, `B`, `C`):"},{"DocComment":""},{"DocComment":" - **Transitivity**: if `A: PartialOrd` and `B: PartialOrd` and `A: PartialOrd`, then `a"},{"DocComment":" < b` and `b < c` implies `a < c`. The same must hold for both `==` and `>`. This must also"},{"DocComment":" work for longer chains, such as when `A: PartialOrd`, `B: PartialOrd`, `C:"},{"DocComment":" PartialOrd`, and `A: PartialOrd` all exist."},{"DocComment":" - **Duality**: if `A: PartialOrd` and `B: PartialOrd`, then `a < b` if and only if `b >"},{"DocComment":" a`."},{"DocComment":""},{"DocComment":" Note that the `B: PartialOrd` (dual) and `A: PartialOrd` (transitive) impls are not forced"},{"DocComment":" to exist, but these requirements apply whenever they do exist."},{"DocComment":""},{"DocComment":" Violating these requirements is a logic error. The behavior resulting from a logic error is not"},{"DocComment":" specified, but users of the trait must ensure that such logic errors do *not* result in"},{"DocComment":" undefined behavior. This means that `unsafe` code **must not** rely on the correctness of these"},{"DocComment":" methods."},{"DocComment":""},{"DocComment":" ## Cross-crate considerations"},{"DocComment":""},{"DocComment":" Upholding the requirements stated above can become tricky when one crate implements `PartialOrd`"},{"DocComment":" for a type of another crate (i.e., to allow comparing one of its own types with a type from the"},{"DocComment":" standard library). The recommendation is to never implement this trait for a foreign type. In"},{"DocComment":" other words, such a crate should do `impl PartialOrd for LocalType`, but it should"},{"DocComment":" *not* do `impl PartialOrd for ForeignType`."},{"DocComment":""},{"DocComment":" This avoids the problem of transitive chains that criss-cross crate boundaries: for all local"},{"DocComment":" types `T`, you may assume that no other crate will add `impl`s that allow comparing `T < U`. In"},{"DocComment":" other words, if other crates add `impl`s that allow building longer transitive chains `U1 < ..."},{"DocComment":" < T < V1 < ...`, then all the types that appear to the right of `T` must be types that the crate"},{"DocComment":" defining `T` already knows about. This rules out transitive chains where downstream crates can"},{"DocComment":" add new `impl`s that \"stitch together\" comparisons of foreign types in ways that violate"},{"DocComment":" transitivity."},{"DocComment":""},{"DocComment":" Not having such foreign `impl`s also avoids forward compatibility issues where one crate adding"},{"DocComment":" more `PartialOrd` implementations can cause build failures in downstream crates."},{"DocComment":""},{"DocComment":" ## Corollaries"},{"DocComment":""},{"DocComment":" The following corollaries follow from the above requirements:"},{"DocComment":""},{"DocComment":" - irreflexivity of `<` and `>`: `!(a < a)`, `!(a > a)`"},{"DocComment":" - transitivity of `>`: if `a > b` and `b > c` then `a > c`"},{"DocComment":" - duality of `partial_cmp`: `partial_cmp(a, b) == partial_cmp(b, a).map(Ordering::reverse)`"},{"DocComment":""},{"DocComment":" ## Strict and non-strict partial orders"},{"DocComment":""},{"DocComment":" The `<` and `>` operators behave according to a *strict* partial order. However, `<=` and `>=`"},{"DocComment":" do **not** behave according to a *non-strict* partial order. That is because mathematically, a"},{"DocComment":" non-strict partial order would require reflexivity, i.e. `a <= a` would need to be true for"},{"DocComment":" every `a`. This isn't always the case for types that implement `PartialOrd`, for example:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let a = f64::NAN;"},{"DocComment":" assert_eq!(a <= a, false);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" ## Derivable"},{"DocComment":""},{"DocComment":" This trait can be used with `#[derive]`."},{"DocComment":""},{"DocComment":" When `derive`d on structs, it will produce a"},{"DocComment":" [lexicographic](https://en.wikipedia.org/wiki/Lexicographic_order) ordering based on the"},{"DocComment":" top-to-bottom declaration order of the struct's members."},{"DocComment":""},{"DocComment":" When `derive`d on enums, variants are primarily ordered by their discriminants. Secondarily,"},{"DocComment":" they are ordered by their fields. By default, the discriminant is smallest for variants at the"},{"DocComment":" top, and largest for variants at the bottom. Here's an example:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #[derive(PartialEq, PartialOrd)]"},{"DocComment":" enum E {"},{"DocComment":" Top,"},{"DocComment":" Bottom,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" assert!(E::Top < E::Bottom);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" However, manually setting the discriminants can override this default behavior:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #[derive(PartialEq, PartialOrd)]"},{"DocComment":" enum E {"},{"DocComment":" Top = 2,"},{"DocComment":" Bottom = 1,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" assert!(E::Bottom < E::Top);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" ## How can I implement `PartialOrd`?"},{"DocComment":""},{"DocComment":" `PartialOrd` only requires implementation of the [`partial_cmp`] method, with the others"},{"DocComment":" generated from default implementations."},{"DocComment":""},{"DocComment":" However it remains possible to implement the others separately for types which do not have a"},{"DocComment":" total order. For example, for floating point numbers, `NaN < 0 == false` and `NaN >= 0 == false`"},{"DocComment":" (cf. IEEE 754-2008 section 5.11)."},{"DocComment":""},{"DocComment":" `PartialOrd` requires your type to be [`PartialEq`]."},{"DocComment":""},{"DocComment":" If your type is [`Ord`], you can implement [`partial_cmp`] by using [`cmp`]:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::cmp::Ordering;"},{"DocComment":""},{"DocComment":" struct Person {"},{"DocComment":" id: u32,"},{"DocComment":" name: String,"},{"DocComment":" height: u32,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialOrd for Person {"},{"DocComment":" fn partial_cmp(&self, other: &Self) -> Option {"},{"DocComment":" Some(self.cmp(other))"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl Ord for Person {"},{"DocComment":" fn cmp(&self, other: &Self) -> Ordering {"},{"DocComment":" self.height.cmp(&other.height)"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialEq for Person {"},{"DocComment":" fn eq(&self, other: &Self) -> bool {"},{"DocComment":" self.height == other.height"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl Eq for Person {}"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" You may also find it useful to use [`partial_cmp`] on your type's fields. Here is an example of"},{"DocComment":" `Person` types who have a floating-point `height` field that is the only field to be used for"},{"DocComment":" sorting:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::cmp::Ordering;"},{"DocComment":""},{"DocComment":" struct Person {"},{"DocComment":" id: u32,"},{"DocComment":" name: String,"},{"DocComment":" height: f64,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialOrd for Person {"},{"DocComment":" fn partial_cmp(&self, other: &Self) -> Option {"},{"DocComment":" self.height.partial_cmp(&other.height)"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialEq for Person {"},{"DocComment":" fn eq(&self, other: &Self) -> bool {"},{"DocComment":" self.height == other.height"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" ## Examples of incorrect `PartialOrd` implementations"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::cmp::Ordering;"},{"DocComment":""},{"DocComment":" #[derive(PartialEq, Debug)]"},{"DocComment":" struct Character {"},{"DocComment":" health: u32,"},{"DocComment":" experience: u32,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialOrd for Character {"},{"DocComment":" fn partial_cmp(&self, other: &Self) -> Option {"},{"DocComment":" Some(self.health.cmp(&other.health))"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" let a = Character {"},{"DocComment":" health: 10,"},{"DocComment":" experience: 5,"},{"DocComment":" };"},{"DocComment":" let b = Character {"},{"DocComment":" health: 10,"},{"DocComment":" experience: 77,"},{"DocComment":" };"},{"DocComment":""},{"DocComment":" // Mistake: `PartialEq` and `PartialOrd` disagree with each other."},{"DocComment":""},{"DocComment":" assert_eq!(a.partial_cmp(&b).unwrap(), Ordering::Equal); // a == b according to `PartialOrd`."},{"DocComment":" assert_ne!(a, b); // a != b according to `PartialEq`."},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let x: u32 = 0;"},{"DocComment":" let y: u32 = 1;"},{"DocComment":""},{"DocComment":" assert_eq!(x < y, true);"},{"DocComment":" assert_eq!(x.lt(&y), true);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" [`partial_cmp`]: PartialOrd::partial_cmp"},{"DocComment":" [`cmp`]: Ord::cmp"},{"Unknown":{"path":"rustc_on_unimplemented","args":"message = \"can't compare `{Self}` with `{Rhs}`\", label =\n\"no implementation for `{Self} < {Rhs}` and `{Self} > {Rhs}`\",\nappend_const_msg"}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"partial_ord"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"Rhs"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":46,"beg":{"line":1359,"col":4},"end":{"line":1359,"col":26}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":22,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[{"params":{"regions":[{"index":0,"name":null,"mutability":"Unknown"},{"index":1,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"partial_cmp","attr_info":{"attributes":[{"DocComment":" This method returns an ordering between `self` and `other` values if one exists."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::cmp::Ordering;"},{"DocComment":""},{"DocComment":" let result = 1.0.partial_cmp(&2.0);"},{"DocComment":" assert_eq!(result, Some(Ordering::Less));"},{"DocComment":""},{"DocComment":" let result = 1.0.partial_cmp(&1.0);"},{"DocComment":" assert_eq!(result, Some(Ordering::Equal));"},{"DocComment":""},{"DocComment":" let result = 2.0.partial_cmp(&1.0);"},{"DocComment":" assert_eq!(result, Some(Ordering::Greater));"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" When comparison is impossible:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let result = f64::NAN.partial_cmp(&1.0);"},{"DocComment":" assert_eq!(result, None);"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":1820},{"HashConsedValue":[5106,{"Ref":[{"Var":{"Bound":[0,1]}},{"Deduplicated":1551},"Shared"]}]}],"output":{"Deduplicated":3938}},"item":{"id":207,"generics":{"regions":[{"Var":{"Bound":[0,0]}},{"Var":{"Bound":[0,1]}}],"types":[{"Deduplicated":141},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[{"HashConsedValue":[5661,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":21,"generics":{"regions":[],"types":[{"Deduplicated":1577},{"Deduplicated":2212}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"kind":{"TraitMethod":[21,0]}},null,null,null,null,null,null,null,null],"vtable":{"id":{"Adt":46},"generics":{"regions":[],"types":[{"Deduplicated":1555}],"const_generics":[],"trait_refs":[]}}},{"def_id":22,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialEq",0]}],"span":{"data":{"file_id":46,"beg":{"line":251,"col":0},"end":{"line":251,"col":65}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Trait for comparisons using the equality operator."},{"DocComment":""},{"DocComment":" Implementing this trait for types provides the `==` and `!=` operators for"},{"DocComment":" those types."},{"DocComment":""},{"DocComment":" `x.eq(y)` can also be written `x == y`, and `x.ne(y)` can be written `x != y`."},{"DocComment":" We use the easier-to-read infix notation in the remainder of this documentation."},{"DocComment":""},{"DocComment":" This trait allows for comparisons using the equality operator, for types"},{"DocComment":" that do not have a full equivalence relation. For example, in floating point"},{"DocComment":" numbers `NaN != NaN`, so floating point types implement `PartialEq` but not"},{"DocComment":" [`trait@Eq`]. Formally speaking, when `Rhs == Self`, this trait corresponds"},{"DocComment":" to a [partial equivalence relation]."},{"DocComment":""},{"DocComment":" [partial equivalence relation]: https://en.wikipedia.org/wiki/Partial_equivalence_relation"},{"DocComment":""},{"DocComment":" Implementations must ensure that `eq` and `ne` are consistent with each other:"},{"DocComment":""},{"DocComment":" - `a != b` if and only if `!(a == b)`."},{"DocComment":""},{"DocComment":" The default implementation of `ne` provides this consistency and is almost"},{"DocComment":" always sufficient. It should not be overridden without very good reason."},{"DocComment":""},{"DocComment":" If [`PartialOrd`] or [`Ord`] are also implemented for `Self` and `Rhs`, their methods must also"},{"DocComment":" be consistent with `PartialEq` (see the documentation of those traits for the exact"},{"DocComment":" requirements). It's easy to accidentally make them disagree by deriving some of the traits and"},{"DocComment":" manually implementing others."},{"DocComment":""},{"DocComment":" The equality relation `==` must satisfy the following conditions"},{"DocComment":" (for all `a`, `b`, `c` of type `A`, `B`, `C`):"},{"DocComment":""},{"DocComment":" - **Symmetry**: if `A: PartialEq` and `B: PartialEq`, then **`a == b`"},{"DocComment":" implies `b == a`**; and"},{"DocComment":""},{"DocComment":" - **Transitivity**: if `A: PartialEq` and `B: PartialEq` and `A:"},{"DocComment":" PartialEq`, then **`a == b` and `b == c` implies `a == c`**."},{"DocComment":" This must also work for longer chains, such as when `A: PartialEq`, `B: PartialEq`,"},{"DocComment":" `C: PartialEq`, and `A: PartialEq` all exist."},{"DocComment":""},{"DocComment":" Note that the `B: PartialEq` (symmetric) and `A: PartialEq`"},{"DocComment":" (transitive) impls are not forced to exist, but these requirements apply"},{"DocComment":" whenever they do exist."},{"DocComment":""},{"DocComment":" Violating these requirements is a logic error. The behavior resulting from a logic error is not"},{"DocComment":" specified, but users of the trait must ensure that such logic errors do *not* result in"},{"DocComment":" undefined behavior. This means that `unsafe` code **must not** rely on the correctness of these"},{"DocComment":" methods."},{"DocComment":""},{"DocComment":" ## Cross-crate considerations"},{"DocComment":""},{"DocComment":" Upholding the requirements stated above can become tricky when one crate implements `PartialEq`"},{"DocComment":" for a type of another crate (i.e., to allow comparing one of its own types with a type from the"},{"DocComment":" standard library). The recommendation is to never implement this trait for a foreign type. In"},{"DocComment":" other words, such a crate should do `impl PartialEq for LocalType`, but it should"},{"DocComment":" *not* do `impl PartialEq for ForeignType`."},{"DocComment":""},{"DocComment":" This avoids the problem of transitive chains that criss-cross crate boundaries: for all local"},{"DocComment":" types `T`, you may assume that no other crate will add `impl`s that allow comparing `T == U`. In"},{"DocComment":" other words, if other crates add `impl`s that allow building longer transitive chains `U1 == ..."},{"DocComment":" == T == V1 == ...`, then all the types that appear to the right of `T` must be types that the"},{"DocComment":" crate defining `T` already knows about. This rules out transitive chains where downstream crates"},{"DocComment":" can add new `impl`s that \"stitch together\" comparisons of foreign types in ways that violate"},{"DocComment":" transitivity."},{"DocComment":""},{"DocComment":" Not having such foreign `impl`s also avoids forward compatibility issues where one crate adding"},{"DocComment":" more `PartialEq` implementations can cause build failures in downstream crates."},{"DocComment":""},{"DocComment":" ## Derivable"},{"DocComment":""},{"DocComment":" This trait can be used with `#[derive]`. When `derive`d on structs, two"},{"DocComment":" instances are equal if all fields are equal, and not equal if any fields"},{"DocComment":" are not equal. When `derive`d on enums, two instances are equal if they"},{"DocComment":" are the same variant and all fields are equal."},{"DocComment":""},{"DocComment":" ## How can I implement `PartialEq`?"},{"DocComment":""},{"DocComment":" An example implementation for a domain in which two books are considered"},{"DocComment":" the same book if their ISBN matches, even if the formats differ:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" enum BookFormat {"},{"DocComment":" Paperback,"},{"DocComment":" Hardback,"},{"DocComment":" Ebook,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" struct Book {"},{"DocComment":" isbn: i32,"},{"DocComment":" format: BookFormat,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialEq for Book {"},{"DocComment":" fn eq(&self, other: &Self) -> bool {"},{"DocComment":" self.isbn == other.isbn"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" let b1 = Book { isbn: 3, format: BookFormat::Paperback };"},{"DocComment":" let b2 = Book { isbn: 3, format: BookFormat::Ebook };"},{"DocComment":" let b3 = Book { isbn: 10, format: BookFormat::Paperback };"},{"DocComment":""},{"DocComment":" assert!(b1 == b2);"},{"DocComment":" assert!(b1 != b3);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" ## How can I compare two different types?"},{"DocComment":""},{"DocComment":" The type you can compare with is controlled by `PartialEq`'s type parameter."},{"DocComment":" For example, let's tweak our previous code a bit:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" // The derive implements == comparisons"},{"DocComment":" #[derive(PartialEq)]"},{"DocComment":" enum BookFormat {"},{"DocComment":" Paperback,"},{"DocComment":" Hardback,"},{"DocComment":" Ebook,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" struct Book {"},{"DocComment":" isbn: i32,"},{"DocComment":" format: BookFormat,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" // Implement == comparisons"},{"DocComment":" impl PartialEq for Book {"},{"DocComment":" fn eq(&self, other: &BookFormat) -> bool {"},{"DocComment":" self.format == *other"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" // Implement == comparisons"},{"DocComment":" impl PartialEq for BookFormat {"},{"DocComment":" fn eq(&self, other: &Book) -> bool {"},{"DocComment":" *self == other.format"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" let b1 = Book { isbn: 3, format: BookFormat::Paperback };"},{"DocComment":""},{"DocComment":" assert!(b1 == BookFormat::Paperback);"},{"DocComment":" assert!(BookFormat::Ebook != b1);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" By changing `impl PartialEq for Book` to `impl PartialEq for Book`,"},{"DocComment":" we allow `BookFormat`s to be compared with `Book`s."},{"DocComment":""},{"DocComment":" A comparison like the one above, which ignores some fields of the struct,"},{"DocComment":" can be dangerous. It can easily lead to an unintended violation of the"},{"DocComment":" requirements for a partial equivalence relation. For example, if we kept"},{"DocComment":" the above implementation of `PartialEq` for `BookFormat` and added an"},{"DocComment":" implementation of `PartialEq` for `Book` (either via a `#[derive]` or"},{"DocComment":" via the manual implementation from the first example) then the result would"},{"DocComment":" violate transitivity:"},{"DocComment":""},{"DocComment":" ```should_panic"},{"DocComment":" #[derive(PartialEq)]"},{"DocComment":" enum BookFormat {"},{"DocComment":" Paperback,"},{"DocComment":" Hardback,"},{"DocComment":" Ebook,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" #[derive(PartialEq)]"},{"DocComment":" struct Book {"},{"DocComment":" isbn: i32,"},{"DocComment":" format: BookFormat,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialEq for Book {"},{"DocComment":" fn eq(&self, other: &BookFormat) -> bool {"},{"DocComment":" self.format == *other"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialEq for BookFormat {"},{"DocComment":" fn eq(&self, other: &Book) -> bool {"},{"DocComment":" *self == other.format"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" fn main() {"},{"DocComment":" let b1 = Book { isbn: 1, format: BookFormat::Paperback };"},{"DocComment":" let b2 = Book { isbn: 2, format: BookFormat::Paperback };"},{"DocComment":""},{"DocComment":" assert!(b1 == BookFormat::Paperback);"},{"DocComment":" assert!(BookFormat::Paperback == b2);"},{"DocComment":""},{"DocComment":" // The following should hold by transitivity but doesn't."},{"DocComment":" assert!(b1 == b2); // <-- PANICS"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let x: u32 = 0;"},{"DocComment":" let y: u32 = 1;"},{"DocComment":""},{"DocComment":" assert_eq!(x == y, false);"},{"DocComment":" assert_eq!(x.eq(&y), false);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" [`eq`]: PartialEq::eq"},{"DocComment":" [`ne`]: PartialEq::ne"},{"Unknown":{"path":"rustc_on_unimplemented","args":"message = \"can't compare `{Self}` with `{Rhs}`\", label =\n\"no implementation for `{Self} == {Rhs}`\", append_const_msg"}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"eq"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"Rhs"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[],"consts":[],"types":[],"methods":[{"params":{"regions":[{"index":0,"name":null,"mutability":"Unknown"},{"index":1,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"eq","attr_info":{"attributes":[{"DocComment":" Tests for `self` and `other` values to be equal, and is used by `==`."}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":1820},{"Deduplicated":5106}],"output":{"Deduplicated":188}},"item":{"id":216,"generics":{"regions":[{"Var":{"Bound":[0,0]}},{"Var":{"Bound":[0,1]}}],"types":[{"Deduplicated":141},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[{"HashConsedValue":[6162,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":22,"generics":{"regions":[],"types":[{"Deduplicated":1577},{"Deduplicated":2212}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"kind":{"TraitMethod":[22,0]}},null],"vtable":{"id":{"Adt":47},"generics":{"regions":[],"types":[{"Deduplicated":1555}],"const_generics":[],"trait_refs":[]}}},{"def_id":23,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["zip",0]},{"Ident":["TrustedRandomAccessNoCoerce",0]}],"span":{"data":{"file_id":24,"beg":{"line":585,"col":0},"end":{"line":585,"col":51}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Like [`TrustedRandomAccess`] but without any of the requirements / guarantees around"},{"DocComment":" coercions to supertypes after `__iterator_get_unchecked` (they aren’t allowed here!), and"},{"DocComment":" without the requirement that subtypes / supertypes implement `TrustedRandomAccessNoCoerce`."},{"DocComment":""},{"DocComment":" This trait was created in PR #85874 to fix soundness issue #85873 without performance regressions."},{"DocComment":" It is subject to change as we might want to build a more generally useful (for performance"},{"DocComment":" optimizations) and more sophisticated trait or trait hierarchy that replaces or extends"},{"DocComment":" [`TrustedRandomAccess`] and `TrustedRandomAccessNoCoerce`."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":24,"beg":{"line":585,"col":46},"end":{"line":585,"col":51}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"consts":[{"name":"MAY_HAVE_SIDE_EFFECT","attr_info":{"attributes":[{"DocComment":" `true` if getting an iterator element may have side effects."},{"DocComment":" Remember to take inner iterators into account."}],"inline":null,"rename":null,"public":true},"ty":{"Deduplicated":188},"default":null}],"types":[],"methods":[null],"vtable":null},{"def_id":24,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["try_trait",0]},{"Ident":["FromResidual",0]}],"span":{"data":{"file_id":42,"beg":{"line":310,"col":0},"end":{"line":310,"col":57}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Used to specify which residuals can be converted into which [`crate::ops::Try`] types."},{"DocComment":""},{"DocComment":" Every `Try` type needs to be recreatable from its own associated"},{"DocComment":" `Residual` type, but can also have additional `FromResidual` implementations"},{"DocComment":" to support interconversion with other `Try` types."},{"Unknown":{"path":"rustc_on_unimplemented","args":"on(all(from_desugaring = \"QuestionMark\", Self = \"core::result::Result\",\nR = \"core::option::Option\",), message =\n\"the `?` operator can only be used on `Result`s, not `Option`s, \\\n in {ItemContext} that returns `Result`\",\nlabel = \"use `.ok_or(...)?` to provide an error compatible with `{Self}`\",\nparent_label = \"this function returns a `Result`\"),\non(all(from_desugaring = \"QuestionMark\", Self =\n\"core::result::Result\",), message =\n\"the `?` operator can only be used on `Result`s \\\n in {ItemContext} that returns `Result`\",\nlabel = \"this `?` produces `{R}`, which is incompatible with `{Self}`\",\nparent_label = \"this function returns a `Result`\"),\non(all(from_desugaring = \"QuestionMark\", Self = \"core::option::Option\", R =\n\"core::result::Result\",), message =\n\"the `?` operator can only be used on `Option`s, not `Result`s, \\\n in {ItemContext} that returns `Option`\",\nlabel = \"use `.ok()?` if you want to discard the `{R}` error information\",\nparent_label = \"this function returns an `Option`\"),\non(all(from_desugaring = \"QuestionMark\", Self = \"core::option::Option\",),\nmessage =\n\"the `?` operator can only be used on `Option`s \\\n in {ItemContext} that returns `Option`\",\nlabel = \"this `?` produces `{R}`, which is incompatible with `{Self}`\",\nparent_label = \"this function returns an `Option`\"),\non(all(from_desugaring = \"QuestionMark\", Self =\n\"core::ops::control_flow::ControlFlow\", R =\n\"core::ops::control_flow::ControlFlow\",), message =\n\"the `?` operator in {ItemContext} that returns `ControlFlow` \\\n can only be used on other `ControlFlow`s (with the same Break type)\",\nlabel = \"this `?` produces `{R}`, which is incompatible with `{Self}`\",\nparent_label = \"this function returns a `ControlFlow`\", note =\n\"unlike `Result`, there's no `From`-conversion performed for `ControlFlow`\"),\non(all(from_desugaring = \"QuestionMark\", Self =\n\"core::ops::control_flow::ControlFlow\",), message =\n\"the `?` operator can only be used on `ControlFlow`s \\\n in {ItemContext} that returns `ControlFlow`\",\nlabel = \"this `?` produces `{R}`, which is incompatible with `{Self}`\",\nparent_label = \"this function returns a `ControlFlow`\",),\non(all(from_desugaring = \"QuestionMark\"), message =\n\"the `?` operator can only be used in {ItemContext} \\\n that returns `Result` or `Option` \\\n (or another type that implements `{This}`)\",\nlabel = \"cannot use the `?` operator in {ItemContext} that returns `{Self}`\",\nparent_label =\n\"this function should return `Result` or `Option` to accept `?`\"),"}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"FromResidual"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"R"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":42,"beg":{"line":310,"col":0},"end":{"line":334,"col":1}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":42,"beg":{"line":310,"col":29},"end":{"line":310,"col":56}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"from_residual","attr_info":{"attributes":[{"DocComment":" Constructs the type from a compatible `Residual` type."},{"DocComment":""},{"DocComment":" This should be implemented consistently with the `branch` method such"},{"DocComment":" that applying the `?` operator will get back an equivalent residual:"},{"DocComment":" `FromResidual::from_residual(r).branch() --> ControlFlow::Break(r)`."},{"DocComment":" (The residual is not mandated to be *identical* when interconversion is involved.)"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #![feature(try_trait_v2)]"},{"DocComment":" use std::ops::{ControlFlow, FromResidual};"},{"DocComment":""},{"DocComment":" assert_eq!(Result::::from_residual(Err(3_u8)), Err(3));"},{"DocComment":" assert_eq!(Option::::from_residual(None), None);"},{"DocComment":" assert_eq!("},{"DocComment":" ControlFlow::<_, String>::from_residual(ControlFlow::Break(5)),"},{"DocComment":" ControlFlow::Break(5),"},{"DocComment":" );"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":1551}],"output":{"Deduplicated":141}},"item":{"id":219,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[{"HashConsedValue":[5663,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":24,"generics":{"regions":[],"types":[{"Deduplicated":1577},{"Deduplicated":2212}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"kind":{"TraitMethod":[24,0]}}],"vtable":null},{"def_id":25,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Ident":["Tuple",0]}],"span":{"data":{"file_id":1,"beg":{"line":1074,"col":0},"end":{"line":1074,"col":15}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" A marker for tuple types."},{"DocComment":""},{"DocComment":" The implementation of this trait is built-in and cannot be implemented"},{"DocComment":" for any user type."},{"Unknown":{"path":"diagnostic::on_unimplemented","args":"message = \"`{Self}` is not a tuple\""}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"tuple_trait"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":1,"beg":{"line":1074,"col":0},"end":{"line":1074,"col":18}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[],"vtable":null},{"def_id":26,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["nonzero",0]},{"Ident":["private",0]},{"Ident":["Sealed",0]}],"span":{"data":{"file_id":19,"beg":{"line":46,"col":12},"end":{"line":46,"col":28}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":19,"beg":{"line":46,"col":12},"end":{"line":46,"col":31}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[],"vtable":{"id":{"Adt":48},"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}},{"def_id":27,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["Eq",0]}],"span":{"data":{"file_id":46,"beg":{"line":338,"col":0},"end":{"line":338,"col":58}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Trait for comparisons corresponding to [equivalence relations]("},{"DocComment":" https://en.wikipedia.org/wiki/Equivalence_relation)."},{"DocComment":""},{"DocComment":" The primary difference to [`PartialEq`] is the additional requirement for reflexivity. A type"},{"DocComment":" that implements [`PartialEq`] guarantees that for all `a`, `b` and `c`:"},{"DocComment":""},{"DocComment":" - symmetric: `a == b` implies `b == a` and `a != b` implies `!(a == b)`"},{"DocComment":" - transitive: `a == b` and `b == c` implies `a == c`"},{"DocComment":""},{"DocComment":" `Eq`, which builds on top of [`PartialEq`] also implies:"},{"DocComment":""},{"DocComment":" - reflexive: `a == a`"},{"DocComment":""},{"DocComment":" This property cannot be checked by the compiler, and therefore `Eq` is a trait without methods."},{"DocComment":""},{"DocComment":" Violating this property is a logic error. The behavior resulting from a logic error is not"},{"DocComment":" specified, but users of the trait must ensure that such logic errors do *not* result in"},{"DocComment":" undefined behavior. This means that `unsafe` code **must not** rely on the correctness of these"},{"DocComment":" methods."},{"DocComment":""},{"DocComment":" Floating point types such as [`f32`] and [`f64`] implement only [`PartialEq`] but *not* `Eq`"},{"DocComment":" because `NaN` != `NaN`."},{"DocComment":""},{"DocComment":" ## Derivable"},{"DocComment":""},{"DocComment":" This trait can be used with `#[derive]`. When `derive`d, because `Eq` has no extra methods, it"},{"DocComment":" is only informing the compiler that this is an equivalence relation rather than a partial"},{"DocComment":" equivalence relation. Note that the `derive` strategy requires all fields are `Eq`, which isn't"},{"DocComment":" always desired."},{"DocComment":""},{"DocComment":" ## How can I implement `Eq`?"},{"DocComment":""},{"DocComment":" If you cannot use the `derive` strategy, specify that your type implements `Eq`, which has no"},{"DocComment":" extra methods:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" enum BookFormat {"},{"DocComment":" Paperback,"},{"DocComment":" Hardback,"},{"DocComment":" Ebook,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" struct Book {"},{"DocComment":" isbn: i32,"},{"DocComment":" format: BookFormat,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialEq for Book {"},{"DocComment":" fn eq(&self, other: &Self) -> bool {"},{"DocComment":" self.isbn == other.isbn"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl Eq for Book {}"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"Eq"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":46,"beg":{"line":338,"col":20},"end":{"line":338,"col":43}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":22,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[null],"vtable":null}],"trait_impls":[{"def_id":0,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":0}}],"span":{"data":{"file_id":4,"beg":{"line":21,"col":0},"end":{"line":21,"col":36}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":1568}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[{"index":0,"name":"'a","mutability":"Unknown"}],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":4,"beg":{"line":21,"col":9},"end":{"line":21,"col":10}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"HashConsedValue":[6170,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[6169,{"Ref":["Erased",{"HashConsedValue":[1569,{"Slice":{"Deduplicated":141}}]},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[5527,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"Deduplicated":5483}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":5482}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6171,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[5327,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[5326,{"Adt":{"id":{"Adt":4},"generics":{"regions":["Erased"],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[{"HashConsedValue":[1583,{"kind":{"Clause":{"Bound":[1,0]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1577}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":5326}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6172,{"kind":{"TraitImpl":{"id":1,"generics":{"regions":["Erased"],"types":[{"Deduplicated":173}],"const_generics":[],"trait_refs":[{"Deduplicated":176}]}}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":5326}],"const_generics":[],"trait_refs":[]}}}}]}],"consts":[],"types":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"value":{"Deduplicated":1576},"implied_trait_refs":[]},"kind":{"TraitType":[6,0]}},{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"value":{"HashConsedValue":[1584,{"Adt":{"id":{"Adt":4},"generics":{"regions":[{"Var":{"Bound":[1,0]}}],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[{"Deduplicated":1583}]}}}]},"implied_trait_refs":[]},"kind":{"TraitType":[6,1]}}],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":10,"generics":{"regions":[{"Var":{"Bound":[1,0]}}],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[{"Deduplicated":1583}]}},"kind":{"TraitMethod":[6,0]}}],"vtable":{"id":0,"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[{"Deduplicated":173}],"const_generics":[],"trait_refs":[{"Deduplicated":176}]}}},{"def_id":1,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}}],"span":{"data":{"file_id":7,"beg":{"line":153,"col":8},"end":{"line":153,"col":45}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":1591}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[{"index":0,"name":"'a","mutability":"Unknown"}],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":7,"beg":{"line":153,"col":17},"end":{"line":153,"col":18}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"Deduplicated":5327},{"Deduplicated":5527}],"consts":[],"types":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"value":{"Deduplicated":1576},"implied_trait_refs":[]},"kind":{"TraitType":[2,0]}}],"methods":[{"params":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":11,"generics":{"regions":[{"Var":{"Bound":[1,0]}},{"Var":{"Bound":[0,0]}}],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[{"Deduplicated":1583}]}},"kind":{"TraitMethod":[2,0]}},null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"vtable":{"id":1,"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[{"Deduplicated":173}],"const_generics":[],"trait_refs":[{"Deduplicated":176}]}}},{"def_id":2,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Impl":{"Trait":2}}],"span":{"data":{"file_id":11,"beg":{"line":317,"col":0},"end":{"line":317,"col":50}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":173}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":11,"beg":{"line":317,"col":5},"end":{"line":317,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":11,"beg":{"line":317,"col":8},"end":{"line":317,"col":24}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"HashConsedValue":[6179,{"kind":{"ParentClause":[{"Deduplicated":176},0]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6180,{"kind":{"ParentClause":[{"Deduplicated":4528},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":4534}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":176},{"Deduplicated":4528}],"consts":[],"types":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"value":{"Deduplicated":4534},"implied_trait_refs":[]},"kind":{"TraitType":[6,0]}},{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"value":{"Deduplicated":141},"implied_trait_refs":[]},"kind":{"TraitType":[6,1]}}],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":13,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[{"Deduplicated":1583},{"Deduplicated":4533}]}},"kind":{"TraitMethod":[6,0]}}],"vtable":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":173}],"const_generics":[],"trait_refs":[{"Deduplicated":176},{"Deduplicated":4528}]}}},{"def_id":3,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["result",0]},{"Impl":{"Trait":3}}],"span":{"data":{"file_id":3,"beg":{"line":2162,"col":0},"end":{"line":2162,"col":42}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":4540}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[],"types":[{"index":0,"name":"T"},{"index":1,"name":"E"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":3,"beg":{"line":2162,"col":5},"end":{"line":2162,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":3,"beg":{"line":2162,"col":8},"end":{"line":2162,"col":9}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"HashConsedValue":[6183,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[4541,{"Adt":{"id":{"Adt":3},"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[{"Deduplicated":1583},{"Deduplicated":2630}]}}}]}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6185,{"kind":{"TraitImpl":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":173},{"Deduplicated":1555},{"Deduplicated":1555}],"const_generics":[],"trait_refs":[{"Deduplicated":176},{"Deduplicated":2241},{"Deduplicated":2241},{"HashConsedValue":[6184,{"kind":{"TraitImpl":{"id":5,"generics":{"regions":[],"types":[{"Deduplicated":1555}],"const_generics":[],"trait_refs":[{"Deduplicated":2241}]}}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":3,"generics":{"regions":[],"types":[{"Deduplicated":1551},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":24,"generics":{"regions":[],"types":[{"Deduplicated":4541},{"Deduplicated":4546}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":176},{"Deduplicated":4562}],"consts":[],"types":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"value":{"Deduplicated":141},"implied_trait_refs":[]},"kind":{"TraitType":[11,0]}},{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"value":{"Deduplicated":4546},"implied_trait_refs":[]},"kind":{"TraitType":[11,1]}}],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":173,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[{"Deduplicated":1583},{"Deduplicated":2630}]}},"kind":{"TraitMethod":[11,0]}},{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":14,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[{"Deduplicated":1583},{"Deduplicated":2630}]}},"kind":{"TraitMethod":[11,1]}}],"vtable":null},{"def_id":4,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["result",0]},{"Impl":{"Trait":4}}],"span":{"data":{"file_id":3,"beg":{"line":2182,"col":0},"end":{"line":2183,"col":20}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":24,"generics":{"regions":[],"types":[{"Deduplicated":4564},{"Deduplicated":4560}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[],"types":[{"index":0,"name":"T"},{"index":1,"name":"E"},{"index":2,"name":"F"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":3,"beg":{"line":2182,"col":5},"end":{"line":2182,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":3,"beg":{"line":2182,"col":8},"end":{"line":2182,"col":9}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":3,"beg":{"line":2182,"col":11},"end":{"line":2182,"col":12}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":2592}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":3,"span":{"data":{"file_id":3,"beg":{"line":2182,"col":14},"end":{"line":2182,"col":29}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":3,"generics":{"regions":[],"types":[{"Deduplicated":2592},{"Deduplicated":1551}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"HashConsedValue":[6186,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[4567,{"Adt":{"id":{"Adt":3},"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":2592}],"const_generics":[],"trait_refs":[{"Deduplicated":1583},{"HashConsedValue":[4566,{"kind":{"Clause":{"Bound":[1,2]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":2596}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":4562}],"consts":[],"types":[],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":15,"generics":{"regions":[],"types":[{"Deduplicated":141},{"Deduplicated":1551},{"Deduplicated":2592}],"const_generics":[],"trait_refs":[{"Deduplicated":1583},{"Deduplicated":2630},{"Deduplicated":4566},{"HashConsedValue":[6187,{"kind":{"Clause":{"Bound":[1,3]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":3,"generics":{"regions":[],"types":[{"Deduplicated":2596},{"Deduplicated":2212}],"const_generics":[],"trait_refs":[]}}}}]}]}},"kind":{"TraitMethod":[24,0]}}],"vtable":null},{"def_id":5,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["convert",0]},{"Impl":{"Trait":5}}],"span":{"data":{"file_id":10,"beg":{"line":785,"col":0},"end":{"line":785,"col":27}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":3,"generics":{"regions":[],"types":[{"Deduplicated":173},{"Deduplicated":173}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":10,"beg":{"line":785,"col":5},"end":{"line":785,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"Deduplicated":176},{"Deduplicated":176}],"consts":[],"types":[],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":175,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[{"Deduplicated":1583}]}},"kind":{"TraitMethod":[3,0]}}],"vtable":null},{"def_id":6,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_closure",0]},{"Impl":{"Trait":6}}],"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"source_text":"|| x + 1","attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":true,"opacity":"Transparent","lang_item":null},"impl_trait":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":4580},{"Deduplicated":198}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"HashConsedValue":[6191,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[5675,{"Adt":{"id":{"Adt":8},"generics":{"regions":[{"Var":{"Bound":[1,0]}}],"types":[],"const_generics":[],"trait_refs":[]}}}]}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[1938,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[1937,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":198}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":198}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6192,{"kind":{"BuiltinOrAuto":{"builtin_data":"Tuple","parent_trait_refs":[{"Deduplicated":1937}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":25,"generics":{"regions":[],"types":[{"Deduplicated":198}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":309}],"consts":[],"types":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"value":{"Deduplicated":184},"implied_trait_refs":[]},"kind":{"TraitType":[4,0]}}],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":176,"generics":{"regions":[{"Var":{"Bound":[1,0]}}],"types":[],"const_generics":[],"trait_refs":[]}},"kind":{"TraitMethod":[4,0]}}],"vtable":null},{"def_id":7,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_closure",0]},{"Ident":["closure",0]},{"Impl":{"Trait":7}}],"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"source_text":"|| x + 1","attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":true,"opacity":"Transparent","lang_item":null},"impl_trait":{"id":5,"generics":{"regions":[],"types":[{"Deduplicated":4580}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[],"consts":[],"types":[],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":179,"generics":{"regions":[{"Var":{"Bound":[1,0]}}],"types":[],"const_generics":[],"trait_refs":[]}},"kind":{"TraitMethod":[5,0]}}],"vtable":null},{"def_id":8,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["option",0]},{"Impl":{"Trait":8}}],"span":{"data":{"file_id":6,"beg":{"line":2755,"col":0},"end":{"line":2755,"col":36}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":3327}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":6,"beg":{"line":2755,"col":5},"end":{"line":2755,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"HashConsedValue":[4600,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[2361,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[{"Deduplicated":1583}]}}}]}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6195,{"kind":{"TraitImpl":{"id":9,"generics":{"regions":[],"types":[{"Deduplicated":173}],"const_generics":[],"trait_refs":[{"Deduplicated":176}]}}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":24,"generics":{"regions":[],"types":[{"Deduplicated":2361},{"Deduplicated":1510}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":176},{"Deduplicated":1512}],"consts":[],"types":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"value":{"Deduplicated":141},"implied_trait_refs":[]},"kind":{"TraitType":[11,0]}},{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"value":{"Deduplicated":1510},"implied_trait_refs":[]},"kind":{"TraitType":[11,1]}}],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":180,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[{"Deduplicated":1583}]}},"kind":{"TraitMethod":[11,0]}},{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":18,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[{"Deduplicated":1583}]}},"kind":{"TraitMethod":[11,1]}}],"vtable":null},{"def_id":9,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["option",0]},{"Impl":{"Trait":9}}],"span":{"data":{"file_id":6,"beg":{"line":2777,"col":0},"end":{"line":2777,"col":74}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":24,"generics":{"regions":[],"types":[{"Deduplicated":3327},{"Deduplicated":1510}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":6,"beg":{"line":2777,"col":5},"end":{"line":2777,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"Deduplicated":4600},{"Deduplicated":1512}],"consts":[],"types":[],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":19,"generics":{"regions":[],"types":[{"Deduplicated":141}],"const_generics":[],"trait_refs":[{"Deduplicated":1583}]}},"kind":{"TraitMethod":[24,0]}}],"vtable":null},{"def_id":10,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["nonzero",0]},{"Impl":{"Trait":10}}],"span":{"data":{"file_id":19,"beg":{"line":62,"col":12},"end":{"line":62,"col":56}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":7,"generics":{"regions":[],"types":[{"Deduplicated":591}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"HashConsedValue":[1822,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[1821,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":591}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":591}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6197,{"kind":{"TraitImpl":{"id":11,"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":18,"generics":{"regions":[],"types":[{"Deduplicated":591}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6198,{"kind":{"TraitImpl":{"id":12,"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":26,"generics":{"regions":[],"types":[{"Deduplicated":591}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[4708,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[4707,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":4706}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":4706}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6199,{"kind":{"TraitImpl":{"id":13,"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":18,"generics":{"regions":[],"types":[{"Deduplicated":4706}],"const_generics":[],"trait_refs":[]}}}}]}],"consts":[],"types":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"value":{"Deduplicated":4706},"implied_trait_refs":[]},"kind":{"TraitType":[7,0]}}],"methods":[],"vtable":null},{"def_id":11,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Impl":{"Trait":11}}],"span":{"data":{"file_id":1,"beg":{"line":60,"col":25},"end":{"line":60,"col":62}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":18,"generics":{"regions":[],"types":[{"Deduplicated":591}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"Deduplicated":1821},{"HashConsedValue":[6200,{"kind":{"TraitImpl":{"id":14,"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":8,"generics":{"regions":[],"types":[{"Deduplicated":591}],"const_generics":[],"trait_refs":[]}}}}]}],"consts":[],"types":[],"methods":[],"vtable":null},{"def_id":12,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["nonzero",0]},{"Impl":{"Trait":12}}],"span":{"data":{"file_id":19,"beg":{"line":55,"col":12},"end":{"line":55,"col":47}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":26,"generics":{"regions":[],"types":[{"Deduplicated":591}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"Deduplicated":1821}],"consts":[],"types":[],"methods":[],"vtable":{"id":3,"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}},{"def_id":13,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["niche_types",0]},{"Impl":{"Trait":13}}],"span":{"data":{"file_id":53,"beg":{"line":17,"col":24},"end":{"line":17,"col":28}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":18,"generics":{"regions":[],"types":[{"Deduplicated":4706}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"Deduplicated":4707},{"HashConsedValue":[6201,{"kind":{"TraitImpl":{"id":15,"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":8,"generics":{"regions":[],"types":[{"Deduplicated":4706}],"const_generics":[],"trait_refs":[]}}}}]}],"consts":[],"types":[],"methods":[],"vtable":null},{"def_id":14,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["clone",0]},{"Ident":["impls",0]},{"Impl":{"Trait":14}}],"span":{"data":{"file_id":25,"beg":{"line":612,"col":16},"end":{"line":612,"col":39}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":8,"generics":{"regions":[],"types":[{"Deduplicated":591}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"Deduplicated":1822}],"consts":[],"types":[],"methods":[{"params":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":221,"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[],"const_generics":[],"trait_refs":[]}},"kind":{"TraitMethod":[8,0]}},null],"vtable":null},{"def_id":15,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["niche_types",0]},{"Impl":{"Trait":15}}],"span":{"data":{"file_id":53,"beg":{"line":17,"col":17},"end":{"line":17,"col":22}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":8,"generics":{"regions":[],"types":[{"Deduplicated":4706}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"Deduplicated":4708}],"consts":[],"types":[],"methods":[{"params":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":223,"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[],"const_generics":[],"trait_refs":[]}},"kind":{"TraitMethod":[8,0]}},null],"vtable":null}],"ordered_decls":[{"TraitDecl":{"NonRec":1}},{"TraitDecl":{"NonRec":0}},{"Type":{"NonRec":5}},{"TraitDecl":{"NonRec":5}},{"Fun":{"NonRec":17}},{"TraitDecl":{"NonRec":25}},{"TraitDecl":{"NonRec":4}},{"Fun":{"NonRec":16}},{"TraitDecl":{"NonRec":3}},{"Fun":{"NonRec":174}},{"Fun":{"NonRec":175}},{"TraitImpl":{"NonRec":5}},{"Type":{"NonRec":7}},{"TraitDecl":{"NonRec":2}},{"Fun":{"NonRec":13}},{"Fun":{"NonRec":27}},{"Fun":{"NonRec":178}},{"Type":{"NonRec":6}},{"Fun":{"NonRec":177}},{"Fun":{"NonRec":18}},{"Fun":{"NonRec":19}},{"Type":{"NonRec":3}},{"Fun":{"NonRec":14}},{"Fun":{"NonRec":15}},{"Type":{"NonRec":4}},{"Fun":{"NonRec":11}},{"TraitImpl":{"NonRec":1}},{"Fun":{"NonRec":10}},{"Fun":{"NonRec":12}},{"Type":{"NonRec":0}},{"Fun":{"NonRec":0}},{"Fun":{"NonRec":1}},{"Type":{"NonRec":1}},{"Fun":{"NonRec":2}},{"Type":{"NonRec":2}},{"Fun":{"NonRec":3}},{"Fun":{"NonRec":4}},{"Fun":{"NonRec":5}},{"Type":{"NonRec":8}},{"Fun":{"NonRec":179}},{"TraitImpl":{"NonRec":7}},{"Fun":{"NonRec":176}},{"TraitImpl":{"NonRec":6}},{"Fun":{"NonRec":6}},{"Fun":{"NonRec":7}},{"Fun":{"NonRec":8}},{"Fun":{"NonRec":9}}]},"has_errors":false} \ No newline at end of file +{"charon_version":"0.1.201","translated":{"crate_name":"charon_corpus","options":{"ullbc":true,"precise_drops":false,"skip_borrowck":false,"mir":null,"rustc_args":[],"targets":[],"monomorphize":false,"monomorphize_mut":null,"start_from":[],"start_from_if_exists":[],"start_from_attribute":null,"start_from_pub":false,"include":[],"opaque":[],"exclude":[],"extract_opaque_bodies":false,"translate_all_methods":false,"lift_associated_types":[],"hide_marker_traits":false,"remove_adt_clauses":false,"hide_allocator":false,"remove_unused_self_clauses":false,"desugar_drops":false,"ops_to_function_calls":false,"index_to_function_calls":false,"treat_box_as_builtin":false,"raw_consts":false,"unsized_strings":false,"reconstruct_fallible_operations":false,"reconstruct_asserts":false,"unbind_item_vars":false,"print_original_ullbc":false,"print_ullbc":false,"print_built_llbc":false,"print_llbc":false,"dest_dir":null,"dest_file":"/Users/youknowone/Projects/pyre-majit-general/build/llbc/corpus.ullbc","no_dedup_serialized_ast":false,"format":null,"no_serialize":false,"no_typecheck":false,"no_normalize":false,"abort_on_error":false,"error_on_warnings":false,"preset":null},"target_information":[{"key":"aarch64-apple-darwin","value":{"target_pointer_size":8,"is_little_endian":true}}],"files":[{"id":0,"name":{"Local":"src/lib.rs"},"crate_name":"charon_corpus","contents":"//! Charon fixture corpus: representative shapes from issue #97.\n//!\n//! 1. `straight_line_add` — straight-line interpreter-shaped function.\n//! 2. `branch_loop_sum` — branch + loop, like opcode dispatch fragments.\n//! 3. `strategy_dispatch` — enum-as-strategy (dict-strategy stand-in).\n//! 4. `desugar_mix` — `?`, `match`, and iterator desugaring together.\n\n#![allow(dead_code)]\n\npub type PyResult = Result;\n\n// --- 1. Straight-line ---------------------------------------------------\n\n#[inline(never)]\npub fn straight_line_add(a: i64, b: i64, c: i64) -> i64 {\n let s = a + b;\n let t = s * 2;\n t + c\n}\n\n// --- 2. Branch + loop ---------------------------------------------------\n\n#[inline(never)]\npub fn branch_loop_sum(slice: &[i64], threshold: i64) -> i64 {\n let mut acc: i64 = 0;\n for &v in slice {\n if v > threshold {\n acc += v;\n } else {\n acc -= v;\n }\n }\n acc\n}\n\n// --- 3. Strategy dispatch (dict-strategy stand-in) ----------------------\n\npub enum Strategy {\n Empty,\n IntKeyed { len: usize },\n StrKeyed { len: usize, capacity: usize },\n}\n\n#[inline(never)]\npub fn strategy_len(s: &Strategy) -> usize {\n match s {\n Strategy::Empty => 0,\n Strategy::IntKeyed { len } => *len,\n Strategy::StrKeyed { len, capacity: _ } => *len,\n }\n}\n\n// --- 4. Desugar mix: ?, match, iterator --------------------------------\n\npub enum Token {\n Add(i64),\n Sub(i64),\n Halt,\n}\n\nfn parse_one(raw: i64) -> PyResult {\n match raw {\n i64::MIN => Ok(Token::Halt),\n 0 => Err(\"halt-zero forbidden\"),\n v if v > 0 => Ok(Token::Add(v)),\n v => Ok(Token::Sub(-v)),\n }\n}\n\n#[inline(never)]\npub fn desugar_mix(input: &[i64]) -> PyResult {\n let mut acc: i64 = 0;\n for &raw in input.iter() {\n let tok = parse_one(raw)?;\n match tok {\n Token::Add(v) => acc += v,\n Token::Sub(v) => acc -= v,\n Token::Halt => break,\n }\n }\n Ok(acc)\n}\n\n// --- 5. Tuple round-trip: construct a tuple, read .0/.1 in same fn ------\n//\n// Exercises `Rvalue::Aggregate` for a *non-Adt* (tuple) value paired\n// with `Field` projection reads of that same local. The lowering must\n// emit a `__pos_` `FieldRead` symmetric to the construction-side\n// `FieldWrite` chain rather than collapsing every `.N` to the base.\n\n#[inline(never)]\npub fn tuple_roundtrip(a: i64, b: i64) -> i64 {\n let pair = (a + b, a - b);\n pair.0 * pair.1\n}\n\n// --- 6. Closures --------------------------------------------------------\n//\n// `bool_then_closure` is the exact `core::bool::::then` census shape:\n// an opaque combinator taking a `FnOnce` closure that captures a value from\n// the enclosing scope. Charon extracts the closure's `call_once` body as a\n// transparent inherent method of the closure type.\n\n#[inline(never)]\npub fn bool_then_closure(c: bool, x: i64) -> Option {\n c.then(|| x + 1)\n}\n\n// `then_some` is the eager sibling of `then`: it takes an already-evaluated\n// value rather than a closure, so the diamond's `then` arm wraps it in `Some`\n// directly (no `call_once`). Same Opaque-core-combinator residual shape.\n#[inline(never)]\npub fn bool_then_some(c: bool, x: i64) -> Option {\n c.then_some(x + 1)\n}\n\n// --- 7. Option question mark -------------------------------------------\n//\n// Exercises `Try::branch` on `Option`: `Some(v)` continues with `v`, while\n// `None` returns `None` normally from the enclosing Option-returning function.\n\n#[inline(never)]\nfn option_source(keep: bool, value: i64) -> Option {\n if keep { Some(value) } else { None }\n}\n\n#[inline(never)]\npub fn option_question_mark(keep: bool, value: i64, addend: i64) -> Option {\n let v = option_source(keep, value)?;\n Some(v + addend)\n}\n\n// ---------------------------------------------------------------------------\n// A host-registered callback table.\n// ---------------------------------------------------------------------------\n\n/// The callback a host installs at run time. A bare `fn` pointer, so the set\n/// of addresses that can reach a call through it is not recoverable from this\n/// artifact — the shape used by host-settable callback hooks.\npub type HostCallback = fn(i64) -> i64;\n\npub struct HostRegistry {\n pub slot: HostCallback,\n pub maybe_slot: Option,\n}\n\n/// Call through the registered callback. `front::mir` lowers this to\n/// `OpKind::IndirectCall { graphs: None }` — `indirect_call` with an\n/// unknown PBC family, which `guess_call_kind` answers `residual` for\n/// (`call.py:105`/`137`, `jtransform.py:410-412`). The `__dyn_call`\n/// placeholder it used to reach is an unregistered synthetic path with no\n/// continuation.\n#[inline(never)]\npub fn host_registry_dispatch(reg: &HostRegistry, x: i64) -> i64 {\n (reg.slot)(x)\n}\n\n/// The one-hop `Option` spelling of the same shape.\n#[inline(never)]\npub fn host_registry_dispatch_optional(reg: &HostRegistry, x: i64) -> i64 {\n match reg.maybe_slot {\n Some(f) => f(x),\n None => 0,\n }\n}\n"},{"id":1,"name":{"Local":"/rustc/library/core/src/marker.rs"},"crate_name":"core","contents":null},{"id":2,"name":{"Local":"/rustc/library/core/src/lib.rs"},"crate_name":"core","contents":null},{"id":3,"name":{"Local":"/rustc/library/core/src/result.rs"},"crate_name":"core","contents":null},{"id":4,"name":{"Local":"/rustc/library/core/src/slice/iter.rs"},"crate_name":"core","contents":null},{"id":5,"name":{"Local":"/rustc/library/core/src/slice/mod.rs"},"crate_name":"core","contents":null},{"id":6,"name":{"Local":"/rustc/library/core/src/option.rs"},"crate_name":"core","contents":null},{"id":7,"name":{"Local":"/rustc/library/core/src/slice/iter/macros.rs"},"crate_name":"core","contents":null},{"id":8,"name":{"Local":"/rustc/library/core/src/ops/control_flow.rs"},"crate_name":"core","contents":null},{"id":9,"name":{"Local":"/rustc/library/core/src/ops/mod.rs"},"crate_name":"core","contents":null},{"id":10,"name":{"Local":"/rustc/library/core/src/convert/mod.rs"},"crate_name":"core","contents":null},{"id":11,"name":{"Local":"/rustc/library/core/src/iter/traits/collect.rs"},"crate_name":"core","contents":null},{"id":12,"name":{"Local":"/rustc/library/core/src/iter/traits/mod.rs"},"crate_name":"core","contents":null},{"id":13,"name":{"Local":"/rustc/library/core/src/iter/mod.rs"},"crate_name":"core","contents":null},{"id":14,"name":{"Local":"/rustc/library/core/src/iter/traits/iterator.rs"},"crate_name":"core","contents":null},{"id":15,"name":{"Local":"/rustc/library/core/src/bool.rs"},"crate_name":"core","contents":null},{"id":16,"name":{"Local":"/rustc/library/core/src/ops/function.rs"},"crate_name":"core","contents":null},{"id":17,"name":{"Local":"/rustc/library/core/src/array/iter.rs"},"crate_name":"core","contents":null},{"id":18,"name":{"Local":"/rustc/library/core/src/array/mod.rs"},"crate_name":"core","contents":null},{"id":19,"name":{"Local":"/rustc/library/core/src/num/nonzero.rs"},"crate_name":"core","contents":null},{"id":20,"name":{"Local":"/rustc/library/core/src/num/mod.rs"},"crate_name":"core","contents":null},{"id":21,"name":{"Local":"/rustc/library/core/src/iter/adapters/step_by.rs"},"crate_name":"core","contents":null},{"id":22,"name":{"Local":"/rustc/library/core/src/iter/adapters/mod.rs"},"crate_name":"core","contents":null},{"id":23,"name":{"Local":"/rustc/library/core/src/iter/adapters/chain.rs"},"crate_name":"core","contents":null},{"id":24,"name":{"Local":"/rustc/library/core/src/iter/adapters/zip.rs"},"crate_name":"core","contents":null},{"id":25,"name":{"Local":"/rustc/library/core/src/clone.rs"},"crate_name":"core","contents":null},{"id":26,"name":{"Local":"/rustc/library/core/src/iter/adapters/intersperse.rs"},"crate_name":"core","contents":null},{"id":27,"name":{"Local":"/rustc/library/core/src/iter/adapters/map.rs"},"crate_name":"core","contents":null},{"id":28,"name":{"Local":"/rustc/library/core/src/iter/adapters/filter.rs"},"crate_name":"core","contents":null},{"id":29,"name":{"Local":"/rustc/library/core/src/iter/adapters/filter_map.rs"},"crate_name":"core","contents":null},{"id":30,"name":{"Local":"/rustc/library/core/src/iter/adapters/enumerate.rs"},"crate_name":"core","contents":null},{"id":31,"name":{"Local":"/rustc/library/core/src/iter/adapters/peekable.rs"},"crate_name":"core","contents":null},{"id":32,"name":{"Local":"/rustc/library/core/src/iter/adapters/skip_while.rs"},"crate_name":"core","contents":null},{"id":33,"name":{"Local":"/rustc/library/core/src/iter/adapters/take_while.rs"},"crate_name":"core","contents":null},{"id":34,"name":{"Local":"/rustc/library/core/src/iter/adapters/map_while.rs"},"crate_name":"core","contents":null},{"id":35,"name":{"Local":"/rustc/library/core/src/iter/adapters/skip.rs"},"crate_name":"core","contents":null},{"id":36,"name":{"Local":"/rustc/library/core/src/iter/adapters/take.rs"},"crate_name":"core","contents":null},{"id":37,"name":{"Local":"/rustc/library/core/src/iter/adapters/scan.rs"},"crate_name":"core","contents":null},{"id":38,"name":{"Local":"/rustc/library/core/src/iter/adapters/flatten.rs"},"crate_name":"core","contents":null},{"id":39,"name":{"Local":"/rustc/library/core/src/iter/adapters/map_windows.rs"},"crate_name":"core","contents":null},{"id":40,"name":{"Local":"/rustc/library/core/src/iter/adapters/fuse.rs"},"crate_name":"core","contents":null},{"id":41,"name":{"Local":"/rustc/library/core/src/iter/adapters/inspect.rs"},"crate_name":"core","contents":null},{"id":42,"name":{"Local":"/rustc/library/core/src/ops/try_trait.rs"},"crate_name":"core","contents":null},{"id":43,"name":{"Local":"/rustc/library/core/src/default.rs"},"crate_name":"core","contents":null},{"id":44,"name":{"Local":"/rustc/library/core/src/iter/traits/double_ended.rs"},"crate_name":"core","contents":null},{"id":45,"name":{"Local":"/rustc/library/core/src/iter/traits/exact_size.rs"},"crate_name":"core","contents":null},{"id":46,"name":{"Local":"/rustc/library/core/src/cmp.rs"},"crate_name":"core","contents":null},{"id":47,"name":{"Local":"/rustc/library/core/src/iter/adapters/rev.rs"},"crate_name":"core","contents":null},{"id":48,"name":{"Local":"/rustc/library/core/src/iter/adapters/copied.rs"},"crate_name":"core","contents":null},{"id":49,"name":{"Local":"/rustc/library/core/src/iter/adapters/cloned.rs"},"crate_name":"core","contents":null},{"id":50,"name":{"Local":"/rustc/library/core/src/iter/adapters/cycle.rs"},"crate_name":"core","contents":null},{"id":51,"name":{"Local":"/rustc/library/core/src/iter/adapters/array_chunks.rs"},"crate_name":"core","contents":null},{"id":52,"name":{"Local":"/rustc/library/core/src/iter/traits/accum.rs"},"crate_name":"core","contents":null},{"id":53,"name":{"Local":"/rustc/library/core/src/num/niche_types.rs"},"crate_name":"core","contents":null}],"item_names":[{"key":{"Type":0},"value":[{"Ident":["charon_corpus",0]},{"Ident":["PyResult",0]}]},{"key":{"Fun":0},"value":[{"Ident":["charon_corpus",0]},{"Ident":["straight_line_add",0]}]},{"key":{"Fun":1},"value":[{"Ident":["charon_corpus",0]},{"Ident":["branch_loop_sum",0]}]},{"key":{"Type":1},"value":[{"Ident":["charon_corpus",0]},{"Ident":["Strategy",0]}]},{"key":{"Fun":2},"value":[{"Ident":["charon_corpus",0]},{"Ident":["strategy_len",0]}]},{"key":{"Type":2},"value":[{"Ident":["charon_corpus",0]},{"Ident":["Token",0]}]},{"key":{"Fun":3},"value":[{"Ident":["charon_corpus",0]},{"Ident":["parse_one",0]}]},{"key":{"Fun":4},"value":[{"Ident":["charon_corpus",0]},{"Ident":["desugar_mix",0]}]},{"key":{"Fun":5},"value":[{"Ident":["charon_corpus",0]},{"Ident":["tuple_roundtrip",0]}]},{"key":{"Fun":6},"value":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_closure",0]}]},{"key":{"Fun":7},"value":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_some",0]}]},{"key":{"Fun":8},"value":[{"Ident":["charon_corpus",0]},{"Ident":["option_source",0]}]},{"key":{"Fun":9},"value":[{"Ident":["charon_corpus",0]},{"Ident":["option_question_mark",0]}]},{"key":{"Type":3},"value":[{"Ident":["charon_corpus",0]},{"Ident":["HostCallback",0]}]},{"key":{"Type":4},"value":[{"Ident":["charon_corpus",0]},{"Ident":["HostRegistry",0]}]},{"key":{"Fun":10},"value":[{"Ident":["charon_corpus",0]},{"Ident":["host_registry_dispatch",0]}]},{"key":{"Fun":11},"value":[{"Ident":["charon_corpus",0]},{"Ident":["host_registry_dispatch_optional",0]}]},{"key":{"TraitDecl":0},"value":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Ident":["Sized",0]}]},{"key":{"Type":5},"value":[{"Ident":["core",0]},{"Ident":["result",0]},{"Ident":["Result",0]}]},{"key":{"TraitDecl":1},"value":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Ident":["MetaSized",0]}]},{"key":{"Type":6},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Ident":["Iter",0]}]},{"key":{"Type":7},"value":[{"Ident":["core",0]},{"Ident":["option",0]},{"Ident":["Option",0]}]},{"key":{"TraitImpl":0},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":0}}]},{"key":{"Fun":12},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":0}},{"Ident":["into_iter",0]}]},{"key":{"TraitImpl":1},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}}]},{"key":{"Fun":13},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["next",0]}]},{"key":{"Type":8},"value":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["control_flow",0]},{"Ident":["ControlFlow",0]}]},{"key":{"Type":9},"value":[{"Ident":["core",0]},{"Ident":["convert",0]},{"Ident":["Infallible",0]}]},{"key":{"Fun":14},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Impl":{"Ty":{"params":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":5,"beg":{"line":101,"col":5},"end":{"line":101,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[164,{"TypeVar":{"Bound":[1,0]}}]}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"HashConsedValue":[1063,{"Slice":{"HashConsedValue":[196,{"TypeVar":{"Bound":[0,0]}}]}}]},"kind":"InherentImplBlock"}}},{"Ident":["iter",0]}]},{"key":{"TraitImpl":2},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Impl":{"Trait":2}}]},{"key":{"Fun":15},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Impl":{"Trait":2}},{"Ident":["into_iter",0]}]},{"key":{"TraitDecl":2},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]}]},{"key":{"TraitImpl":3},"value":[{"Ident":["core",0]},{"Ident":["result",0]},{"Impl":{"Trait":3}}]},{"key":{"Fun":16},"value":[{"Ident":["core",0]},{"Ident":["result",0]},{"Impl":{"Trait":3}},{"Ident":["branch",0]}]},{"key":{"TraitImpl":4},"value":[{"Ident":["core",0]},{"Ident":["result",0]},{"Impl":{"Trait":4}}]},{"key":{"Fun":17},"value":[{"Ident":["core",0]},{"Ident":["result",0]},{"Impl":{"Trait":4}},{"Ident":["from_residual",0]}]},{"key":{"TraitDecl":3},"value":[{"Ident":["core",0]},{"Ident":["convert",0]},{"Ident":["From",0]}]},{"key":{"TraitImpl":5},"value":[{"Ident":["core",0]},{"Ident":["convert",0]},{"Impl":{"Trait":5}}]},{"key":{"Type":10},"value":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_closure",0]},{"Ident":["closure",0]}]},{"key":{"TraitImpl":6},"value":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_closure",0]},{"Impl":{"Trait":6}}]},{"key":{"Fun":18},"value":[{"Ident":["core",0]},{"Ident":["bool",0]},{"Impl":{"Ty":{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"HashConsedValue":[211,{"Literal":"Bool"}]},"kind":"InherentImplBlock"}}},{"Ident":["then",0]}]},{"key":{"TraitDecl":4},"value":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["function",0]},{"Ident":["FnOnce",0]}]},{"key":{"TraitDecl":5},"value":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Ident":["Destruct",0]}]},{"key":{"TraitImpl":7},"value":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_closure",0]},{"Ident":["closure",0]},{"Impl":{"Trait":7}}]},{"key":{"Fun":19},"value":[{"Ident":["core",0]},{"Ident":["bool",0]},{"Impl":{"Ty":{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"Deduplicated":211},"kind":"InherentImplBlock"}}},{"Ident":["then_some",0]}]},{"key":{"TraitImpl":8},"value":[{"Ident":["core",0]},{"Ident":["option",0]},{"Impl":{"Trait":8}}]},{"key":{"Fun":20},"value":[{"Ident":["core",0]},{"Ident":["option",0]},{"Impl":{"Trait":8}},{"Ident":["branch",0]}]},{"key":{"TraitImpl":9},"value":[{"Ident":["core",0]},{"Ident":["option",0]},{"Impl":{"Trait":9}}]},{"key":{"Fun":21},"value":[{"Ident":["core",0]},{"Ident":["option",0]},{"Impl":{"Trait":9}},{"Ident":["from_residual",0]}]},{"key":{"Type":11},"value":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Ident":["MetaSized",0]},{"Ident":["{vtable}",0]}]},{"key":{"TraitDecl":6},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["IntoIterator",0]}]},{"key":{"Global":0},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":0}},{"Ident":["{vtable}",0]}]},{"key":{"Global":1},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["{vtable}",0]}]},{"key":{"Fun":22},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["next_chunk",0]}]},{"key":{"Fun":23},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["size_hint",0]}]},{"key":{"Fun":24},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["count",0]}]},{"key":{"Fun":25},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["last",0]}]},{"key":{"Fun":26},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["advance_by",0]}]},{"key":{"Fun":27},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["nth",0]}]},{"key":{"Fun":28},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["step_by",0]}]},{"key":{"Type":12},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["{vtable}",0]}]},{"key":{"Fun":29},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["next",0]}]},{"key":{"Fun":30},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["next_chunk",0]}]},{"key":{"Type":13},"value":[{"Ident":["core",0]},{"Ident":["array",0]},{"Ident":["iter",0]},{"Ident":["IntoIter",0]}]},{"key":{"Fun":31},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["size_hint",0]}]},{"key":{"Fun":32},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["count",0]}]},{"key":{"Fun":33},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["last",0]}]},{"key":{"Fun":34},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["advance_by",0]}]},{"key":{"Type":14},"value":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["nonzero",0]},{"Ident":["NonZero",0]}]},{"key":{"TraitDecl":7},"value":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["nonzero",0]},{"Ident":["ZeroablePrimitive",0]}]},{"key":{"TraitImpl":10},"value":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["nonzero",0]},{"Impl":{"Trait":10}}]},{"key":{"Fun":35},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["nth",0]}]},{"key":{"Fun":36},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["step_by",0]}]},{"key":{"Type":15},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["step_by",0]},{"Ident":["StepBy",0]}]},{"key":{"Fun":37},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["chain",0]}]},{"key":{"Type":16},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["chain",0]},{"Ident":["Chain",0]}]},{"key":{"Fun":38},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["zip",0]}]},{"key":{"Type":17},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["zip",0]},{"Ident":["Zip",0]}]},{"key":{"Fun":39},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["intersperse",0]}]},{"key":{"TraitDecl":8},"value":[{"Ident":["core",0]},{"Ident":["clone",0]},{"Ident":["Clone",0]}]},{"key":{"Type":18},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["intersperse",0]},{"Ident":["Intersperse",0]}]},{"key":{"Fun":40},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["intersperse_with",0]}]},{"key":{"TraitDecl":9},"value":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["function",0]},{"Ident":["FnMut",0]}]},{"key":{"Type":19},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["intersperse",0]},{"Ident":["IntersperseWith",0]}]},{"key":{"Fun":41},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["map",0]}]},{"key":{"Type":20},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["map",0]},{"Ident":["Map",0]}]},{"key":{"Fun":42},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["for_each",0]}]},{"key":{"Fun":43},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["filter",0]}]},{"key":{"Type":21},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["filter",0]},{"Ident":["Filter",0]}]},{"key":{"Fun":44},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["filter_map",0]}]},{"key":{"Type":22},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["filter_map",0]},{"Ident":["FilterMap",0]}]},{"key":{"Fun":45},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["enumerate",0]}]},{"key":{"Type":23},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["enumerate",0]},{"Ident":["Enumerate",0]}]},{"key":{"Fun":46},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["peekable",0]}]},{"key":{"Type":24},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["peekable",0]},{"Ident":["Peekable",0]}]},{"key":{"Fun":47},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["skip_while",0]}]},{"key":{"Type":25},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["skip_while",0]},{"Ident":["SkipWhile",0]}]},{"key":{"Fun":48},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["take_while",0]}]},{"key":{"Type":26},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["take_while",0]},{"Ident":["TakeWhile",0]}]},{"key":{"Fun":49},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["map_while",0]}]},{"key":{"Type":27},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["map_while",0]},{"Ident":["MapWhile",0]}]},{"key":{"Fun":50},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["skip",0]}]},{"key":{"Type":28},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["skip",0]},{"Ident":["Skip",0]}]},{"key":{"Fun":51},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["take",0]}]},{"key":{"Type":29},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["take",0]},{"Ident":["Take",0]}]},{"key":{"Fun":52},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["scan",0]}]},{"key":{"Type":30},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["scan",0]},{"Ident":["Scan",0]}]},{"key":{"Fun":53},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["flat_map",0]}]},{"key":{"Type":31},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["flatten",0]},{"Ident":["FlatMap",0]}]},{"key":{"Fun":54},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["flatten",0]}]},{"key":{"Type":32},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["flatten",0]},{"Ident":["Flatten",0]}]},{"key":{"Fun":55},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["map_windows",0]}]},{"key":{"Type":33},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["map_windows",0]},{"Ident":["MapWindows",0]}]},{"key":{"Fun":56},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["fuse",0]}]},{"key":{"Type":34},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["fuse",0]},{"Ident":["Fuse",0]}]},{"key":{"Fun":57},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["inspect",0]}]},{"key":{"Type":35},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["inspect",0]},{"Ident":["Inspect",0]}]},{"key":{"Fun":58},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["by_ref",0]}]},{"key":{"Fun":59},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["collect",0]}]},{"key":{"TraitDecl":10},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["FromIterator",0]}]},{"key":{"Fun":60},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["try_collect",0]}]},{"key":{"TraitDecl":11},"value":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["try_trait",0]},{"Ident":["Try",0]}]},{"key":{"TraitDecl":12},"value":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["try_trait",0]},{"Ident":["Residual",0]}]},{"key":{"Fun":61},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["collect_into",0]}]},{"key":{"TraitDecl":13},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["Extend",0]}]},{"key":{"Fun":62},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["partition",0]}]},{"key":{"TraitDecl":14},"value":[{"Ident":["core",0]},{"Ident":["default",0]},{"Ident":["Default",0]}]},{"key":{"Fun":63},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["partition_in_place",0]}]},{"key":{"TraitDecl":15},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["double_ended",0]},{"Ident":["DoubleEndedIterator",0]}]},{"key":{"Fun":64},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["is_partitioned",0]}]},{"key":{"Fun":65},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["try_fold",0]}]},{"key":{"Fun":66},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["try_for_each",0]}]},{"key":{"Fun":67},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["fold",0]}]},{"key":{"Fun":68},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["reduce",0]}]},{"key":{"Fun":69},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["try_reduce",0]}]},{"key":{"Fun":70},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["all",0]}]},{"key":{"Fun":71},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["any",0]}]},{"key":{"Fun":72},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["find",0]}]},{"key":{"Fun":73},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["find_map",0]}]},{"key":{"Fun":74},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["try_find",0]}]},{"key":{"Fun":75},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["position",0]}]},{"key":{"Fun":76},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["rposition",0]}]},{"key":{"TraitDecl":16},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["exact_size",0]},{"Ident":["ExactSizeIterator",0]}]},{"key":{"Fun":77},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["max",0]}]},{"key":{"TraitDecl":17},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["Ord",0]}]},{"key":{"Fun":78},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["min",0]}]},{"key":{"Fun":79},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["max_by_key",0]}]},{"key":{"Fun":80},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["max_by",0]}]},{"key":{"Type":36},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["Ordering",0]}]},{"key":{"Fun":81},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["min_by_key",0]}]},{"key":{"Fun":82},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["min_by",0]}]},{"key":{"Fun":83},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["rev",0]}]},{"key":{"Type":37},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["rev",0]},{"Ident":["Rev",0]}]},{"key":{"Fun":84},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["unzip",0]}]},{"key":{"Fun":85},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["copied",0]}]},{"key":{"TraitDecl":18},"value":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Ident":["Copy",0]}]},{"key":{"Type":38},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["copied",0]},{"Ident":["Copied",0]}]},{"key":{"Fun":86},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["cloned",0]}]},{"key":{"Type":39},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["cloned",0]},{"Ident":["Cloned",0]}]},{"key":{"Fun":87},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["cycle",0]}]},{"key":{"Type":40},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["cycle",0]},{"Ident":["Cycle",0]}]},{"key":{"Fun":88},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["array_chunks",0]}]},{"key":{"Type":41},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["array_chunks",0]},{"Ident":["ArrayChunks",0]}]},{"key":{"Fun":89},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["sum",0]}]},{"key":{"TraitDecl":19},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["accum",0]},{"Ident":["Sum",0]}]},{"key":{"Fun":90},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["product",0]}]},{"key":{"TraitDecl":20},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["accum",0]},{"Ident":["Product",0]}]},{"key":{"Fun":91},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["cmp",0]}]},{"key":{"Fun":92},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["cmp_by",0]}]},{"key":{"Fun":93},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["partial_cmp",0]}]},{"key":{"TraitDecl":21},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialOrd",0]}]},{"key":{"Fun":94},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["partial_cmp_by",0]}]},{"key":{"Fun":95},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["eq",0]}]},{"key":{"TraitDecl":22},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialEq",0]}]},{"key":{"Fun":96},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["eq_by",0]}]},{"key":{"Fun":97},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["ne",0]}]},{"key":{"Fun":98},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["lt",0]}]},{"key":{"Fun":99},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["le",0]}]},{"key":{"Fun":100},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["gt",0]}]},{"key":{"Fun":101},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["ge",0]}]},{"key":{"Fun":102},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["is_sorted",0]}]},{"key":{"Fun":103},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["is_sorted_by",0]}]},{"key":{"Fun":104},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["is_sorted_by_key",0]}]},{"key":{"Fun":105},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["__iterator_get_unchecked",0]}]},{"key":{"TraitDecl":23},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["zip",0]},{"Ident":["TrustedRandomAccessNoCoerce",0]}]},{"key":{"Fun":106},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["chain",0]}]},{"key":{"Fun":107},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["zip",0]}]},{"key":{"Fun":108},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["intersperse",0]}]},{"key":{"Fun":109},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["intersperse_with",0]}]},{"key":{"Fun":110},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["map",0]}]},{"key":{"Fun":111},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["for_each",0]}]},{"key":{"Fun":112},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["filter",0]}]},{"key":{"Fun":113},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["filter_map",0]}]},{"key":{"Fun":114},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["enumerate",0]}]},{"key":{"Fun":115},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["peekable",0]}]},{"key":{"Fun":116},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["skip_while",0]}]},{"key":{"Fun":117},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["take_while",0]}]},{"key":{"Fun":118},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["map_while",0]}]},{"key":{"Fun":119},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["skip",0]}]},{"key":{"Fun":120},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["take",0]}]},{"key":{"Fun":121},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["scan",0]}]},{"key":{"Fun":122},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["flat_map",0]}]},{"key":{"Fun":123},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["flatten",0]}]},{"key":{"Fun":124},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["map_windows",0]}]},{"key":{"Fun":125},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["fuse",0]}]},{"key":{"Fun":126},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["inspect",0]}]},{"key":{"Fun":127},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["by_ref",0]}]},{"key":{"Fun":128},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["collect",0]}]},{"key":{"Fun":129},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["try_collect",0]}]},{"key":{"Fun":130},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["collect_into",0]}]},{"key":{"Fun":131},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["partition",0]}]},{"key":{"Fun":132},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["partition_in_place",0]}]},{"key":{"Fun":133},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["is_partitioned",0]}]},{"key":{"Fun":134},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["try_fold",0]}]},{"key":{"Fun":135},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["try_for_each",0]}]},{"key":{"Fun":136},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["fold",0]}]},{"key":{"Fun":137},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["reduce",0]}]},{"key":{"Fun":138},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["try_reduce",0]}]},{"key":{"Fun":139},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["all",0]}]},{"key":{"Fun":140},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["any",0]}]},{"key":{"Fun":141},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["find",0]}]},{"key":{"Fun":142},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["find_map",0]}]},{"key":{"Fun":143},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["try_find",0]}]},{"key":{"Fun":144},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["position",0]}]},{"key":{"Fun":145},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["rposition",0]}]},{"key":{"Fun":146},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["max",0]}]},{"key":{"Fun":147},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["min",0]}]},{"key":{"Fun":148},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["max_by_key",0]}]},{"key":{"Fun":149},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["max_by",0]}]},{"key":{"Fun":150},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["min_by_key",0]}]},{"key":{"Fun":151},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["min_by",0]}]},{"key":{"Fun":152},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["rev",0]}]},{"key":{"Fun":153},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["unzip",0]}]},{"key":{"Fun":154},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["copied",0]}]},{"key":{"Fun":155},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["cloned",0]}]},{"key":{"Fun":156},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["cycle",0]}]},{"key":{"Fun":157},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["array_chunks",0]}]},{"key":{"Fun":158},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["sum",0]}]},{"key":{"Fun":159},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["product",0]}]},{"key":{"Fun":160},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["cmp",0]}]},{"key":{"Fun":161},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["cmp_by",0]}]},{"key":{"Fun":162},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["partial_cmp",0]}]},{"key":{"Fun":163},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["partial_cmp_by",0]}]},{"key":{"Fun":164},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["eq",0]}]},{"key":{"Fun":165},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["eq_by",0]}]},{"key":{"Fun":166},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["ne",0]}]},{"key":{"Fun":167},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["lt",0]}]},{"key":{"Fun":168},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["le",0]}]},{"key":{"Fun":169},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["gt",0]}]},{"key":{"Fun":170},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["ge",0]}]},{"key":{"Fun":171},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["is_sorted",0]}]},{"key":{"Fun":172},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["is_sorted_by",0]}]},{"key":{"Fun":173},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["is_sorted_by_key",0]}]},{"key":{"Fun":174},"value":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["__iterator_get_unchecked",0]}]},{"key":{"Global":2},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Impl":{"Trait":2}},{"Ident":["{vtable}",0]}]},{"key":{"TraitDecl":24},"value":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["try_trait",0]},{"Ident":["FromResidual",0]}]},{"key":{"Fun":175},"value":[{"Ident":["core",0]},{"Ident":["result",0]},{"Impl":{"Trait":3}},{"Ident":["from_output",0]}]},{"key":{"Fun":176},"value":[{"Ident":["core",0]},{"Ident":["convert",0]},{"Ident":["From",0]},{"Ident":["from",0]}]},{"key":{"Fun":177},"value":[{"Ident":["core",0]},{"Ident":["convert",0]},{"Impl":{"Trait":5}},{"Ident":["from",0]}]},{"key":{"TraitDecl":25},"value":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Ident":["Tuple",0]}]},{"key":{"Fun":178},"value":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_closure",0]},{"Impl":{"Trait":6}},{"Ident":["call_once",0]}]},{"key":{"Type":42},"value":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["function",0]},{"Ident":["FnOnce",0]},{"Ident":["{vtable}",0]}]},{"key":{"Fun":179},"value":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["function",0]},{"Ident":["FnOnce",0]},{"Ident":["call_once",0]}]},{"key":{"Fun":180},"value":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Ident":["Destruct",0]},{"Ident":["drop_in_place",0]}]},{"key":{"Fun":181},"value":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_closure",0]},{"Ident":["closure",0]},{"Impl":{"Trait":7}},{"Ident":["drop_in_place",0]}]},{"key":{"Fun":182},"value":[{"Ident":["core",0]},{"Ident":["option",0]},{"Impl":{"Trait":8}},{"Ident":["from_output",0]}]},{"key":{"Type":43},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["IntoIterator",0]},{"Ident":["{vtable}",0]}]},{"key":{"Fun":183},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["IntoIterator",0]},{"Ident":["into_iter",0]}]},{"key":{"TraitDecl":26},"value":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["nonzero",0]},{"Ident":["private",0]},{"Ident":["Sealed",0]}]},{"key":{"TraitImpl":11},"value":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Impl":{"Trait":11}}]},{"key":{"TraitImpl":12},"value":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["nonzero",0]},{"Impl":{"Trait":12}}]},{"key":{"Type":44},"value":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["niche_types",0]},{"Ident":["NonZeroUsizeInner",0]}]},{"key":{"TraitImpl":13},"value":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["niche_types",0]},{"Impl":{"Trait":13}}]},{"key":{"Fun":184},"value":[{"Ident":["core",0]},{"Ident":["clone",0]},{"Ident":["Clone",0]},{"Ident":["clone",0]}]},{"key":{"Fun":185},"value":[{"Ident":["core",0]},{"Ident":["clone",0]},{"Ident":["Clone",0]},{"Ident":["clone_from",0]}]},{"key":{"Type":45},"value":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["function",0]},{"Ident":["FnMut",0]},{"Ident":["{vtable}",0]}]},{"key":{"Fun":186},"value":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["function",0]},{"Ident":["FnMut",0]},{"Ident":["call_mut",0]}]},{"key":{"Fun":187},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["FromIterator",0]},{"Ident":["from_iter",0]}]},{"key":{"Fun":188},"value":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["try_trait",0]},{"Ident":["Try",0]},{"Ident":["from_output",0]}]},{"key":{"Fun":189},"value":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["try_trait",0]},{"Ident":["Try",0]},{"Ident":["branch",0]}]},{"key":{"Fun":190},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["Extend",0]},{"Ident":["extend",0]}]},{"key":{"Fun":191},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["Extend",0]},{"Ident":["extend_one",0]}]},{"key":{"Fun":192},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["Extend",0]},{"Ident":["extend_reserve",0]}]},{"key":{"Fun":193},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["Extend",0]},{"Ident":["extend_one_unchecked",0]}]},{"key":{"Fun":194},"value":[{"Ident":["core",0]},{"Ident":["default",0]},{"Ident":["Default",0]},{"Ident":["default",0]}]},{"key":{"Type":46},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["double_ended",0]},{"Ident":["DoubleEndedIterator",0]},{"Ident":["{vtable}",0]}]},{"key":{"Fun":195},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["double_ended",0]},{"Ident":["DoubleEndedIterator",0]},{"Ident":["next_back",0]}]},{"key":{"Fun":196},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["double_ended",0]},{"Ident":["DoubleEndedIterator",0]},{"Ident":["advance_back_by",0]}]},{"key":{"Fun":197},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["double_ended",0]},{"Ident":["DoubleEndedIterator",0]},{"Ident":["nth_back",0]}]},{"key":{"Fun":198},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["double_ended",0]},{"Ident":["DoubleEndedIterator",0]},{"Ident":["try_rfold",0]}]},{"key":{"Fun":199},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["double_ended",0]},{"Ident":["DoubleEndedIterator",0]},{"Ident":["rfold",0]}]},{"key":{"Fun":200},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["double_ended",0]},{"Ident":["DoubleEndedIterator",0]},{"Ident":["rfind",0]}]},{"key":{"Type":47},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["exact_size",0]},{"Ident":["ExactSizeIterator",0]},{"Ident":["{vtable}",0]}]},{"key":{"Fun":201},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["exact_size",0]},{"Ident":["ExactSizeIterator",0]},{"Ident":["len",0]}]},{"key":{"Fun":202},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["exact_size",0]},{"Ident":["ExactSizeIterator",0]},{"Ident":["is_empty",0]}]},{"key":{"TraitDecl":27},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["Eq",0]}]},{"key":{"Fun":203},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["Ord",0]},{"Ident":["cmp",0]}]},{"key":{"Fun":204},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["Ord",0]},{"Ident":["max",0]}]},{"key":{"Fun":205},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["Ord",0]},{"Ident":["min",0]}]},{"key":{"Fun":206},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["Ord",0]},{"Ident":["clamp",0]}]},{"key":{"Fun":207},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["accum",0]},{"Ident":["Sum",0]},{"Ident":["sum",0]}]},{"key":{"Fun":208},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["accum",0]},{"Ident":["Product",0]},{"Ident":["product",0]}]},{"key":{"Type":48},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialOrd",0]},{"Ident":["{vtable}",0]}]},{"key":{"Fun":209},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialOrd",0]},{"Ident":["partial_cmp",0]}]},{"key":{"Fun":210},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialOrd",0]},{"Ident":["lt",0]}]},{"key":{"Fun":211},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialOrd",0]},{"Ident":["le",0]}]},{"key":{"Fun":212},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialOrd",0]},{"Ident":["gt",0]}]},{"key":{"Fun":213},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialOrd",0]},{"Ident":["ge",0]}]},{"key":{"Fun":214},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialOrd",0]},{"Ident":["__chaining_lt",0]}]},{"key":{"Fun":215},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialOrd",0]},{"Ident":["__chaining_le",0]}]},{"key":{"Fun":216},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialOrd",0]},{"Ident":["__chaining_gt",0]}]},{"key":{"Fun":217},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialOrd",0]},{"Ident":["__chaining_ge",0]}]},{"key":{"Type":49},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialEq",0]},{"Ident":["{vtable}",0]}]},{"key":{"Fun":218},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialEq",0]},{"Ident":["eq",0]}]},{"key":{"Fun":219},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialEq",0]},{"Ident":["ne",0]}]},{"key":{"Fun":220},"value":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["zip",0]},{"Ident":["TrustedRandomAccessNoCoerce",0]},{"Ident":["size",0]}]},{"key":{"Fun":221},"value":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["try_trait",0]},{"Ident":["FromResidual",0]},{"Ident":["from_residual",0]}]},{"key":{"Type":50},"value":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["nonzero",0]},{"Ident":["private",0]},{"Ident":["Sealed",0]},{"Ident":["{vtable}",0]}]},{"key":{"TraitImpl":14},"value":[{"Ident":["core",0]},{"Ident":["clone",0]},{"Ident":["impls",0]},{"Impl":{"Trait":14}}]},{"key":{"Global":3},"value":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["nonzero",0]},{"Impl":{"Trait":12}},{"Ident":["{vtable}",0]}]},{"key":{"TraitImpl":15},"value":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["niche_types",0]},{"Impl":{"Trait":15}}]},{"key":{"Fun":222},"value":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["Eq",0]},{"Ident":["assert_receiver_is_total_eq",0]}]},{"key":{"Fun":223},"value":[{"Ident":["core",0]},{"Ident":["clone",0]},{"Ident":["impls",0]},{"Impl":{"Trait":14}},{"Ident":["clone",0]}]},{"key":{"Fun":224},"value":[{"Ident":["core",0]},{"Ident":["clone",0]},{"Ident":["impls",0]},{"Impl":{"Trait":14}},{"Ident":["clone_from",0]}]},{"key":{"Fun":225},"value":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["niche_types",0]},{"Impl":{"Trait":15}},{"Ident":["clone",0]}]},{"key":{"Fun":226},"value":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["niche_types",0]},{"Impl":{"Trait":15}},{"Ident":["clone_from",0]}]}],"assoc_item_names":[{"types":[],"methods":[],"consts":[]},{"types":[],"methods":[],"consts":[]},{"types":["Item"],"methods":["next","next_chunk","size_hint","count","last","advance_by","nth","step_by","chain","zip","intersperse","intersperse_with","map","for_each","filter","filter_map","enumerate","peekable","skip_while","take_while","map_while","skip","take","scan","flat_map","flatten","map_windows","fuse","inspect","by_ref","collect","try_collect","collect_into","partition","partition_in_place","is_partitioned","try_fold","try_for_each","fold","reduce","try_reduce","all","any","find","find_map","try_find","position","rposition","max","min","max_by_key","max_by","min_by_key","min_by","rev","unzip","copied","cloned","cycle","array_chunks","sum","product","cmp","cmp_by","partial_cmp","partial_cmp_by","eq","eq_by","ne","lt","le","gt","ge","is_sorted","is_sorted_by","is_sorted_by_key","__iterator_get_unchecked"],"consts":[]},{"types":[],"methods":["from"],"consts":[]},{"types":["Output"],"methods":["call_once"],"consts":[]},{"types":[],"methods":["drop_in_place"],"consts":[]},{"types":["Item","IntoIter"],"methods":["into_iter"],"consts":[]},{"types":["NonZeroInner"],"methods":[],"consts":[]},{"types":[],"methods":["clone","clone_from"],"consts":[]},{"types":[],"methods":["call_mut"],"consts":[]},{"types":[],"methods":["from_iter"],"consts":[]},{"types":["Output","Residual"],"methods":["from_output","branch"],"consts":[]},{"types":["TryType"],"methods":[],"consts":[]},{"types":[],"methods":["extend","extend_one","extend_reserve","extend_one_unchecked"],"consts":[]},{"types":[],"methods":["default"],"consts":[]},{"types":[],"methods":["next_back","advance_back_by","nth_back","try_rfold","rfold","rfind"],"consts":[]},{"types":[],"methods":["len","is_empty"],"consts":[]},{"types":[],"methods":["cmp","max","min","clamp"],"consts":[]},{"types":[],"methods":[],"consts":[]},{"types":[],"methods":["sum"],"consts":[]},{"types":[],"methods":["product"],"consts":[]},{"types":[],"methods":["partial_cmp","lt","le","gt","ge","__chaining_lt","__chaining_le","__chaining_gt","__chaining_ge"],"consts":[]},{"types":[],"methods":["eq","ne"],"consts":[]},{"types":[],"methods":["size"],"consts":["MAY_HAVE_SIDE_EFFECT"]},{"types":[],"methods":["from_residual"],"consts":[]},{"types":[],"methods":[],"consts":[]},{"types":[],"methods":[],"consts":[]},{"types":[],"methods":["assert_receiver_is_total_eq"],"consts":[]}],"short_names":[{"key":{"TraitImpl":0},"value":[{"Impl":{"Trait":0}}]},{"key":{"Fun":12},"value":[{"Impl":{"Trait":0}},{"Ident":["into_iter",0]}]},{"key":{"TraitImpl":1},"value":[{"Impl":{"Trait":1}}]},{"key":{"Fun":13},"value":[{"Impl":{"Trait":1}},{"Ident":["next",0]}]},{"key":{"TraitImpl":2},"value":[{"Impl":{"Trait":2}}]},{"key":{"Fun":15},"value":[{"Impl":{"Trait":2}},{"Ident":["into_iter",0]}]},{"key":{"TraitImpl":3},"value":[{"Impl":{"Trait":3}}]},{"key":{"Fun":16},"value":[{"Impl":{"Trait":3}},{"Ident":["branch",0]}]},{"key":{"TraitImpl":4},"value":[{"Impl":{"Trait":4}}]},{"key":{"Fun":17},"value":[{"Impl":{"Trait":4}},{"Ident":["from_residual",0]}]},{"key":{"TraitImpl":5},"value":[{"Impl":{"Trait":5}}]},{"key":{"TraitImpl":6},"value":[{"Impl":{"Trait":6}}]},{"key":{"TraitImpl":7},"value":[{"Impl":{"Trait":7}}]},{"key":{"TraitImpl":8},"value":[{"Impl":{"Trait":8}}]},{"key":{"Fun":20},"value":[{"Impl":{"Trait":8}},{"Ident":["branch",0]}]},{"key":{"TraitImpl":9},"value":[{"Impl":{"Trait":9}}]},{"key":{"Fun":21},"value":[{"Impl":{"Trait":9}},{"Ident":["from_residual",0]}]},{"key":{"Global":0},"value":[{"Impl":{"Trait":0}},{"Ident":["{vtable}",0]}]},{"key":{"Global":1},"value":[{"Impl":{"Trait":1}},{"Ident":["{vtable}",0]}]},{"key":{"Fun":22},"value":[{"Impl":{"Trait":1}},{"Ident":["next_chunk",0]}]},{"key":{"Fun":23},"value":[{"Impl":{"Trait":1}},{"Ident":["size_hint",0]}]},{"key":{"Fun":24},"value":[{"Impl":{"Trait":1}},{"Ident":["count",0]}]},{"key":{"Fun":25},"value":[{"Impl":{"Trait":1}},{"Ident":["last",0]}]},{"key":{"Fun":26},"value":[{"Impl":{"Trait":1}},{"Ident":["advance_by",0]}]},{"key":{"Fun":27},"value":[{"Impl":{"Trait":1}},{"Ident":["nth",0]}]},{"key":{"Fun":28},"value":[{"Impl":{"Trait":1}},{"Ident":["step_by",0]}]},{"key":{"TraitImpl":10},"value":[{"Impl":{"Trait":10}}]},{"key":{"Fun":106},"value":[{"Impl":{"Trait":1}},{"Ident":["chain",0]}]},{"key":{"Fun":107},"value":[{"Impl":{"Trait":1}},{"Ident":["zip",0]}]},{"key":{"Fun":108},"value":[{"Impl":{"Trait":1}},{"Ident":["intersperse",0]}]},{"key":{"Fun":109},"value":[{"Impl":{"Trait":1}},{"Ident":["intersperse_with",0]}]},{"key":{"Fun":110},"value":[{"Impl":{"Trait":1}},{"Ident":["map",0]}]},{"key":{"Fun":111},"value":[{"Impl":{"Trait":1}},{"Ident":["for_each",0]}]},{"key":{"Fun":112},"value":[{"Impl":{"Trait":1}},{"Ident":["filter",0]}]},{"key":{"Fun":113},"value":[{"Impl":{"Trait":1}},{"Ident":["filter_map",0]}]},{"key":{"Fun":114},"value":[{"Impl":{"Trait":1}},{"Ident":["enumerate",0]}]},{"key":{"Fun":115},"value":[{"Impl":{"Trait":1}},{"Ident":["peekable",0]}]},{"key":{"Fun":116},"value":[{"Impl":{"Trait":1}},{"Ident":["skip_while",0]}]},{"key":{"Fun":117},"value":[{"Impl":{"Trait":1}},{"Ident":["take_while",0]}]},{"key":{"Fun":118},"value":[{"Impl":{"Trait":1}},{"Ident":["map_while",0]}]},{"key":{"Fun":119},"value":[{"Impl":{"Trait":1}},{"Ident":["skip",0]}]},{"key":{"Fun":120},"value":[{"Impl":{"Trait":1}},{"Ident":["take",0]}]},{"key":{"Fun":121},"value":[{"Impl":{"Trait":1}},{"Ident":["scan",0]}]},{"key":{"Fun":122},"value":[{"Impl":{"Trait":1}},{"Ident":["flat_map",0]}]},{"key":{"Fun":123},"value":[{"Impl":{"Trait":1}},{"Ident":["flatten",0]}]},{"key":{"Fun":124},"value":[{"Impl":{"Trait":1}},{"Ident":["map_windows",0]}]},{"key":{"Fun":125},"value":[{"Impl":{"Trait":1}},{"Ident":["fuse",0]}]},{"key":{"Fun":126},"value":[{"Impl":{"Trait":1}},{"Ident":["inspect",0]}]},{"key":{"Fun":127},"value":[{"Impl":{"Trait":1}},{"Ident":["by_ref",0]}]},{"key":{"Fun":128},"value":[{"Impl":{"Trait":1}},{"Ident":["collect",0]}]},{"key":{"Fun":129},"value":[{"Impl":{"Trait":1}},{"Ident":["try_collect",0]}]},{"key":{"Fun":130},"value":[{"Impl":{"Trait":1}},{"Ident":["collect_into",0]}]},{"key":{"Fun":131},"value":[{"Impl":{"Trait":1}},{"Ident":["partition",0]}]},{"key":{"Fun":132},"value":[{"Impl":{"Trait":1}},{"Ident":["partition_in_place",0]}]},{"key":{"Fun":133},"value":[{"Impl":{"Trait":1}},{"Ident":["is_partitioned",0]}]},{"key":{"Fun":134},"value":[{"Impl":{"Trait":1}},{"Ident":["try_fold",0]}]},{"key":{"Fun":135},"value":[{"Impl":{"Trait":1}},{"Ident":["try_for_each",0]}]},{"key":{"Fun":136},"value":[{"Impl":{"Trait":1}},{"Ident":["fold",0]}]},{"key":{"Fun":137},"value":[{"Impl":{"Trait":1}},{"Ident":["reduce",0]}]},{"key":{"Fun":138},"value":[{"Impl":{"Trait":1}},{"Ident":["try_reduce",0]}]},{"key":{"Fun":139},"value":[{"Impl":{"Trait":1}},{"Ident":["all",0]}]},{"key":{"Fun":140},"value":[{"Impl":{"Trait":1}},{"Ident":["any",0]}]},{"key":{"Fun":141},"value":[{"Impl":{"Trait":1}},{"Ident":["find",0]}]},{"key":{"Fun":142},"value":[{"Impl":{"Trait":1}},{"Ident":["find_map",0]}]},{"key":{"Fun":143},"value":[{"Impl":{"Trait":1}},{"Ident":["try_find",0]}]},{"key":{"Fun":144},"value":[{"Impl":{"Trait":1}},{"Ident":["position",0]}]},{"key":{"Fun":145},"value":[{"Impl":{"Trait":1}},{"Ident":["rposition",0]}]},{"key":{"Fun":146},"value":[{"Impl":{"Trait":1}},{"Ident":["max",0]}]},{"key":{"Fun":147},"value":[{"Impl":{"Trait":1}},{"Ident":["min",0]}]},{"key":{"Fun":148},"value":[{"Impl":{"Trait":1}},{"Ident":["max_by_key",0]}]},{"key":{"Fun":149},"value":[{"Impl":{"Trait":1}},{"Ident":["max_by",0]}]},{"key":{"Fun":150},"value":[{"Impl":{"Trait":1}},{"Ident":["min_by_key",0]}]},{"key":{"Fun":151},"value":[{"Impl":{"Trait":1}},{"Ident":["min_by",0]}]},{"key":{"Fun":152},"value":[{"Impl":{"Trait":1}},{"Ident":["rev",0]}]},{"key":{"Fun":153},"value":[{"Impl":{"Trait":1}},{"Ident":["unzip",0]}]},{"key":{"Fun":154},"value":[{"Impl":{"Trait":1}},{"Ident":["copied",0]}]},{"key":{"Fun":155},"value":[{"Impl":{"Trait":1}},{"Ident":["cloned",0]}]},{"key":{"Fun":156},"value":[{"Impl":{"Trait":1}},{"Ident":["cycle",0]}]},{"key":{"Fun":157},"value":[{"Impl":{"Trait":1}},{"Ident":["array_chunks",0]}]},{"key":{"Fun":158},"value":[{"Impl":{"Trait":1}},{"Ident":["sum",0]}]},{"key":{"Fun":159},"value":[{"Impl":{"Trait":1}},{"Ident":["product",0]}]},{"key":{"Fun":160},"value":[{"Impl":{"Trait":1}},{"Ident":["cmp",0]}]},{"key":{"Fun":161},"value":[{"Impl":{"Trait":1}},{"Ident":["cmp_by",0]}]},{"key":{"Fun":162},"value":[{"Impl":{"Trait":1}},{"Ident":["partial_cmp",0]}]},{"key":{"Fun":163},"value":[{"Impl":{"Trait":1}},{"Ident":["partial_cmp_by",0]}]},{"key":{"Fun":164},"value":[{"Impl":{"Trait":1}},{"Ident":["eq",0]}]},{"key":{"Fun":165},"value":[{"Impl":{"Trait":1}},{"Ident":["eq_by",0]}]},{"key":{"Fun":166},"value":[{"Impl":{"Trait":1}},{"Ident":["ne",0]}]},{"key":{"Fun":167},"value":[{"Impl":{"Trait":1}},{"Ident":["lt",0]}]},{"key":{"Fun":168},"value":[{"Impl":{"Trait":1}},{"Ident":["le",0]}]},{"key":{"Fun":169},"value":[{"Impl":{"Trait":1}},{"Ident":["gt",0]}]},{"key":{"Fun":170},"value":[{"Impl":{"Trait":1}},{"Ident":["ge",0]}]},{"key":{"Fun":171},"value":[{"Impl":{"Trait":1}},{"Ident":["is_sorted",0]}]},{"key":{"Fun":172},"value":[{"Impl":{"Trait":1}},{"Ident":["is_sorted_by",0]}]},{"key":{"Fun":173},"value":[{"Impl":{"Trait":1}},{"Ident":["is_sorted_by_key",0]}]},{"key":{"Fun":174},"value":[{"Impl":{"Trait":1}},{"Ident":["__iterator_get_unchecked",0]}]},{"key":{"Global":2},"value":[{"Impl":{"Trait":2}},{"Ident":["{vtable}",0]}]},{"key":{"Fun":175},"value":[{"Impl":{"Trait":3}},{"Ident":["from_output",0]}]},{"key":{"Fun":177},"value":[{"Impl":{"Trait":5}},{"Ident":["from",0]}]},{"key":{"Fun":178},"value":[{"Impl":{"Trait":6}},{"Ident":["call_once",0]}]},{"key":{"Fun":181},"value":[{"Impl":{"Trait":7}},{"Ident":["drop_in_place",0]}]},{"key":{"Fun":182},"value":[{"Impl":{"Trait":8}},{"Ident":["from_output",0]}]},{"key":{"TraitImpl":11},"value":[{"Impl":{"Trait":11}}]},{"key":{"TraitImpl":12},"value":[{"Impl":{"Trait":12}}]},{"key":{"TraitImpl":13},"value":[{"Impl":{"Trait":13}}]},{"key":{"TraitImpl":14},"value":[{"Impl":{"Trait":14}}]},{"key":{"Global":3},"value":[{"Impl":{"Trait":12}},{"Ident":["{vtable}",0]}]},{"key":{"TraitImpl":15},"value":[{"Impl":{"Trait":15}}]},{"key":{"Fun":223},"value":[{"Impl":{"Trait":14}},{"Ident":["clone",0]}]},{"key":{"Fun":224},"value":[{"Impl":{"Trait":14}},{"Ident":["clone_from",0]}]},{"key":{"Fun":225},"value":[{"Impl":{"Trait":15}},{"Ident":["clone",0]}]},{"key":{"Fun":226},"value":[{"Impl":{"Trait":15}},{"Ident":["clone_from",0]}]},{"key":{"Type":6},"value":[{"Ident":["Iter",0]}]},{"key":{"Type":5},"value":[{"Ident":["Result",0]}]},{"key":{"TraitDecl":9},"value":[{"Ident":["FnMut",0]}]},{"key":{"TraitDecl":19},"value":[{"Ident":["Sum",0]}]},{"key":{"Fun":192},"value":[{"Ident":["extend_reserve",0]}]},{"key":{"Fun":1},"value":[{"Ident":["branch_loop_sum",0]}]},{"key":{"Type":40},"value":[{"Ident":["Cycle",0]}]},{"key":{"TraitDecl":22},"value":[{"Ident":["PartialEq",0]}]},{"key":{"Type":31},"value":[{"Ident":["FlatMap",0]}]},{"key":{"Type":23},"value":[{"Ident":["Enumerate",0]}]},{"key":{"Fun":197},"value":[{"Ident":["nth_back",0]}]},{"key":{"Fun":222},"value":[{"Ident":["assert_receiver_is_total_eq",0]}]},{"key":{"Type":39},"value":[{"Ident":["Cloned",0]}]},{"key":{"TraitDecl":15},"value":[{"Ident":["DoubleEndedIterator",0]}]},{"key":{"TraitDecl":20},"value":[{"Ident":["Product",0]}]},{"key":{"Fun":18},"value":[{"Ident":["then",0]}]},{"key":{"Type":9},"value":[{"Ident":["Infallible",0]}]},{"key":{"Type":36},"value":[{"Ident":["Ordering",0]}]},{"key":{"Fun":6},"value":[{"Ident":["bool_then_closure",0]}]},{"key":{"TraitDecl":5},"value":[{"Ident":["Destruct",0]}]},{"key":{"Type":2},"value":[{"Ident":["Token",0]}]},{"key":{"Fun":11},"value":[{"Ident":["host_registry_dispatch_optional",0]}]},{"key":{"Type":20},"value":[{"Ident":["Map",0]}]},{"key":{"Type":18},"value":[{"Ident":["Intersperse",0]}]},{"key":{"Fun":196},"value":[{"Ident":["advance_back_by",0]}]},{"key":{"Type":17},"value":[{"Ident":["Zip",0]}]},{"key":{"Type":7},"value":[{"Ident":["Option",0]}]},{"key":{"Type":38},"value":[{"Ident":["Copied",0]}]},{"key":{"Type":35},"value":[{"Ident":["Inspect",0]}]},{"key":{"Type":41},"value":[{"Ident":["ArrayChunks",0]}]},{"key":{"Fun":206},"value":[{"Ident":["clamp",0]}]},{"key":{"Type":10},"value":[{"Ident":["closure",0]}]},{"key":{"Fun":198},"value":[{"Ident":["try_rfold",0]}]},{"key":{"Fun":214},"value":[{"Ident":["__chaining_lt",0]}]},{"key":{"Type":32},"value":[{"Ident":["Flatten",0]}]},{"key":{"TraitDecl":10},"value":[{"Ident":["FromIterator",0]}]},{"key":{"Type":13},"value":[{"Ident":["IntoIter",0]}]},{"key":{"Type":16},"value":[{"Ident":["Chain",0]}]},{"key":{"Fun":217},"value":[{"Ident":["__chaining_ge",0]}]},{"key":{"Type":21},"value":[{"Ident":["Filter",0]}]},{"key":{"Fun":10},"value":[{"Ident":["host_registry_dispatch",0]}]},{"key":{"Type":25},"value":[{"Ident":["SkipWhile",0]}]},{"key":{"Type":30},"value":[{"Ident":["Scan",0]}]},{"key":{"TraitDecl":23},"value":[{"Ident":["TrustedRandomAccessNoCoerce",0]}]},{"key":{"TraitDecl":24},"value":[{"Ident":["FromResidual",0]}]},{"key":{"Type":14},"value":[{"Ident":["NonZero",0]}]},{"key":{"Fun":9},"value":[{"Ident":["option_question_mark",0]}]},{"key":{"TraitDecl":7},"value":[{"Ident":["ZeroablePrimitive",0]}]},{"key":{"Type":24},"value":[{"Ident":["Peekable",0]}]},{"key":{"Fun":215},"value":[{"Ident":["__chaining_le",0]}]},{"key":{"Fun":2},"value":[{"Ident":["strategy_len",0]}]},{"key":{"TraitDecl":18},"value":[{"Ident":["Copy",0]}]},{"key":{"Fun":200},"value":[{"Ident":["rfind",0]}]},{"key":{"Fun":220},"value":[{"Ident":["size",0]}]},{"key":{"TraitDecl":17},"value":[{"Ident":["Ord",0]}]},{"key":{"TraitDecl":21},"value":[{"Ident":["PartialOrd",0]}]},{"key":{"Type":0},"value":[{"Ident":["PyResult",0]}]},{"key":{"Type":34},"value":[{"Ident":["Fuse",0]}]},{"key":{"Fun":4},"value":[{"Ident":["desugar_mix",0]}]},{"key":{"TraitDecl":12},"value":[{"Ident":["Residual",0]}]},{"key":{"TraitDecl":6},"value":[{"Ident":["IntoIterator",0]}]},{"key":{"Type":22},"value":[{"Ident":["FilterMap",0]}]},{"key":{"TraitDecl":14},"value":[{"Ident":["Default",0]}]},{"key":{"Fun":199},"value":[{"Ident":["rfold",0]}]},{"key":{"TraitDecl":8},"value":[{"Ident":["Clone",0]}]},{"key":{"Fun":19},"value":[{"Ident":["then_some",0]}]},{"key":{"TraitDecl":11},"value":[{"Ident":["Try",0]}]},{"key":{"TraitDecl":0},"value":[{"Ident":["Sized",0]}]},{"key":{"Type":33},"value":[{"Ident":["MapWindows",0]}]},{"key":{"Fun":193},"value":[{"Ident":["extend_one_unchecked",0]}]},{"key":{"Type":44},"value":[{"Ident":["NonZeroUsizeInner",0]}]},{"key":{"Fun":195},"value":[{"Ident":["next_back",0]}]},{"key":{"Fun":202},"value":[{"Ident":["is_empty",0]}]},{"key":{"TraitDecl":27},"value":[{"Ident":["Eq",0]}]},{"key":{"Type":1},"value":[{"Ident":["Strategy",0]}]},{"key":{"Type":27},"value":[{"Ident":["MapWhile",0]}]},{"key":{"TraitDecl":1},"value":[{"Ident":["MetaSized",0]}]},{"key":{"Type":8},"value":[{"Ident":["ControlFlow",0]}]},{"key":{"Fun":14},"value":[{"Ident":["iter",0]}]},{"key":{"TraitDecl":2},"value":[{"Ident":["Iterator",0]}]},{"key":{"TraitDecl":26},"value":[{"Ident":["Sealed",0]}]},{"key":{"Type":19},"value":[{"Ident":["IntersperseWith",0]}]},{"key":{"Fun":8},"value":[{"Ident":["option_source",0]}]},{"key":{"Type":28},"value":[{"Ident":["Skip",0]}]},{"key":{"Fun":186},"value":[{"Ident":["call_mut",0]}]},{"key":{"TraitDecl":16},"value":[{"Ident":["ExactSizeIterator",0]}]},{"key":{"TraitDecl":25},"value":[{"Ident":["Tuple",0]}]},{"key":{"Type":4},"value":[{"Ident":["HostRegistry",0]}]},{"key":{"TraitDecl":13},"value":[{"Ident":["Extend",0]}]},{"key":{"Fun":7},"value":[{"Ident":["bool_then_some",0]}]},{"key":{"TraitDecl":4},"value":[{"Ident":["FnOnce",0]}]},{"key":{"Fun":187},"value":[{"Ident":["from_iter",0]}]},{"key":{"Fun":190},"value":[{"Ident":["extend",0]}]},{"key":{"Type":15},"value":[{"Ident":["StepBy",0]}]},{"key":{"Fun":201},"value":[{"Ident":["len",0]}]},{"key":{"Type":3},"value":[{"Ident":["HostCallback",0]}]},{"key":{"Fun":191},"value":[{"Ident":["extend_one",0]}]},{"key":{"Fun":216},"value":[{"Ident":["__chaining_gt",0]}]},{"key":{"TraitDecl":3},"value":[{"Ident":["From",0]}]},{"key":{"Type":37},"value":[{"Ident":["Rev",0]}]},{"key":{"Fun":5},"value":[{"Ident":["tuple_roundtrip",0]}]},{"key":{"Fun":194},"value":[{"Ident":["default",0]}]},{"key":{"Fun":3},"value":[{"Ident":["parse_one",0]}]},{"key":{"Fun":0},"value":[{"Ident":["straight_line_add",0]}]},{"key":{"Type":26},"value":[{"Ident":["TakeWhile",0]}]},{"key":{"Type":29},"value":[{"Ident":["Take",0]}]}],"type_decls":[{"def_id":0,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["PyResult",0]}],"span":{"data":{"file_id":0,"beg":{"line":10,"col":0},"end":{"line":10,"col":47}},"generated_from_span":null},"source_text":"pub type PyResult = Result;","attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":10,"col":18},"end":{"line":10,"col":19}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":{"Alias":{"HashConsedValue":[6165,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":196},{"HashConsedValue":[198,{"Ref":["Static",{"HashConsedValue":[197,{"Adt":{"id":{"Builtin":"Str"},"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}}]},"Shared"]}]}],"const_generics":[],"trait_refs":[{"HashConsedValue":[199,{"kind":{"Clause":{"Bound":[0,0]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[205,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[204,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[203,{"Ref":["Erased",{"Deduplicated":197},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":198}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},"layout":[],"ptr_metadata":"None"},{"def_id":1,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["Strategy",0]}],"span":{"data":{"file_id":0,"beg":{"line":38,"col":0},"end":{"line":42,"col":1}},"generated_from_span":null},"source_text":"pub enum Strategy {\n Empty,\n IntKeyed { len: usize },\n StrKeyed { len: usize, capacity: usize },\n}","attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":{"Enum":[{"id":0,"span":{"data":{"file_id":0,"beg":{"line":39,"col":4},"end":{"line":39,"col":9}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":"Empty","fields":[],"discriminant":{"Scalar":{"Signed":["Isize","0"]}}},{"id":1,"span":{"data":{"file_id":0,"beg":{"line":40,"col":4},"end":{"line":40,"col":12}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":"IntKeyed","fields":[{"span":{"data":{"file_id":0,"beg":{"line":40,"col":15},"end":{"line":40,"col":25}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":"len","ty":{"HashConsedValue":[614,{"Literal":{"UInt":"Usize"}}]}}],"discriminant":{"Scalar":{"Signed":["Isize","1"]}}},{"id":2,"span":{"data":{"file_id":0,"beg":{"line":41,"col":4},"end":{"line":41,"col":12}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":"StrKeyed","fields":[{"span":{"data":{"file_id":0,"beg":{"line":41,"col":15},"end":{"line":41,"col":25}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":"len","ty":{"Deduplicated":614}},{"span":{"data":{"file_id":0,"beg":{"line":41,"col":27},"end":{"line":41,"col":42}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":"capacity","ty":{"Deduplicated":614}}],"discriminant":{"Scalar":{"Signed":["Isize","2"]}}}]},"layout":[{"key":"aarch64-apple-darwin","value":{"size":24,"align":8,"discriminator":{"Branch":{"offset":0,"int_ty":{"Unsigned":"U64"},"children":[[{"start":{"Unsigned":["U64","0"]},"end":{"Unsigned":["U64","0"]}},{"Known":0}],[{"start":{"Unsigned":["U64","1"]},"end":{"Unsigned":["U64","1"]}},{"Known":1}],[{"start":{"Unsigned":["U64","2"]},"end":{"Unsigned":["U64","2"]}},{"Known":2}]],"fallback":"Invalid"}},"uninhabited":false,"variant_layouts":[{"field_offsets":[],"uninhabited":false,"tagger":[[0,{"Unsigned":["U64","0"]}]]},{"field_offsets":[8],"uninhabited":false,"tagger":[[0,{"Unsigned":["U64","1"]}]]},{"field_offsets":[8,16],"uninhabited":false,"tagger":[[0,{"Unsigned":["U64","2"]}]]}],"repr":{"repr_algo":"Rust","align_modif":null,"transparent":false,"explicit_discr_type":false}}}],"ptr_metadata":"None"},{"def_id":2,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["Token",0]}],"span":{"data":{"file_id":0,"beg":{"line":55,"col":0},"end":{"line":59,"col":1}},"generated_from_span":null},"source_text":"pub enum Token {\n Add(i64),\n Sub(i64),\n Halt,\n}","attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":{"Enum":[{"id":0,"span":{"data":{"file_id":0,"beg":{"line":56,"col":4},"end":{"line":56,"col":7}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":"Add","fields":[{"span":{"data":{"file_id":0,"beg":{"line":56,"col":8},"end":{"line":56,"col":11}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":null,"ty":{"HashConsedValue":[207,{"Literal":{"Int":"I64"}}]}}],"discriminant":{"Scalar":{"Signed":["Isize","0"]}}},{"id":1,"span":{"data":{"file_id":0,"beg":{"line":57,"col":4},"end":{"line":57,"col":7}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":"Sub","fields":[{"span":{"data":{"file_id":0,"beg":{"line":57,"col":8},"end":{"line":57,"col":11}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":null,"ty":{"Deduplicated":207}}],"discriminant":{"Scalar":{"Signed":["Isize","1"]}}},{"id":2,"span":{"data":{"file_id":0,"beg":{"line":58,"col":4},"end":{"line":58,"col":8}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":"Halt","fields":[],"discriminant":{"Scalar":{"Signed":["Isize","2"]}}}]},"layout":[{"key":"aarch64-apple-darwin","value":{"size":16,"align":8,"discriminator":{"Branch":{"offset":0,"int_ty":{"Unsigned":"U64"},"children":[[{"start":{"Unsigned":["U64","0"]},"end":{"Unsigned":["U64","0"]}},{"Known":0}],[{"start":{"Unsigned":["U64","1"]},"end":{"Unsigned":["U64","1"]}},{"Known":1}],[{"start":{"Unsigned":["U64","2"]},"end":{"Unsigned":["U64","2"]}},{"Known":2}]],"fallback":"Invalid"}},"uninhabited":false,"variant_layouts":[{"field_offsets":[8],"uninhabited":false,"tagger":[[0,{"Unsigned":["U64","0"]}]]},{"field_offsets":[8],"uninhabited":false,"tagger":[[0,{"Unsigned":["U64","1"]}]]},{"field_offsets":[],"uninhabited":false,"tagger":[[0,{"Unsigned":["U64","2"]}]]}],"repr":{"repr_algo":"Rust","align_modif":null,"transparent":false,"explicit_discr_type":false}}}],"ptr_metadata":"None"},{"def_id":3,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["HostCallback",0]}],"span":{"data":{"file_id":0,"beg":{"line":140,"col":0},"end":{"line":140,"col":39}},"generated_from_span":null},"source_text":"pub type HostCallback = fn(i64) -> i64;","attr_info":{"attributes":[{"DocComment":" The callback a host installs at run time. A bare `fn` pointer, so the set"},{"DocComment":" of addresses that can reach a call through it is not recoverable from this"},{"DocComment":" artifact — the shape used by host-settable callback hooks."}],"inline":null,"rename":null,"public":true},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":{"Alias":{"HashConsedValue":[1573,{"FnPtr":{"regions":[],"skip_binder":{"is_unsafe":false,"inputs":[{"Deduplicated":207}],"output":{"Deduplicated":207}}}}]}},"layout":[{"key":"aarch64-apple-darwin","value":{"size":8,"align":8,"discriminator":{"Known":0},"uninhabited":false,"variant_layouts":[],"repr":{"repr_algo":"Rust","align_modif":null,"transparent":false,"explicit_discr_type":false}}}],"ptr_metadata":"None"},{"def_id":4,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["HostRegistry",0]}],"span":{"data":{"file_id":0,"beg":{"line":142,"col":0},"end":{"line":145,"col":1}},"generated_from_span":null},"source_text":"pub struct HostRegistry {\n pub slot: HostCallback,\n pub maybe_slot: Option,\n}","attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":{"Struct":[{"span":{"data":{"file_id":0,"beg":{"line":143,"col":4},"end":{"line":143,"col":26}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":"slot","ty":{"Deduplicated":1573}},{"span":{"data":{"file_id":0,"beg":{"line":144,"col":4},"end":{"line":144,"col":40}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":"maybe_slot","ty":{"HashConsedValue":[1577,{"Adt":{"id":{"Adt":7},"generics":{"regions":[],"types":[{"Deduplicated":1573}],"const_generics":[],"trait_refs":[{"HashConsedValue":[1576,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[1575,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":1573}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1573}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}}]},"layout":[{"key":"aarch64-apple-darwin","value":{"size":16,"align":8,"discriminator":{"Known":0},"uninhabited":false,"variant_layouts":[{"field_offsets":[0,8],"uninhabited":false,"tagger":[]}],"repr":{"repr_algo":"Rust","align_modif":null,"transparent":false,"explicit_discr_type":false}}}],"ptr_metadata":"None"},{"def_id":5,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["result",0]},{"Ident":["Result",0]}],"span":{"data":{"file_id":3,"beg":{"line":557,"col":0},"end":{"line":557,"col":21}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" `Result` is a type that represents either success ([`Ok`]) or failure ([`Err`])."},{"DocComment":""},{"DocComment":" See the [module documentation](self) for details."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"Result"},"generics":{"regions":[],"types":[{"index":0,"name":"T"},{"index":1,"name":"E"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":3,"beg":{"line":557,"col":16},"end":{"line":557,"col":17}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":3,"beg":{"line":557,"col":19},"end":{"line":557,"col":20}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[1585,{"TypeVar":{"Bound":[1,1]}}]}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":{"Enum":[{"id":0,"span":{"data":{"file_id":3,"beg":{"line":561,"col":4},"end":{"line":561,"col":6}},"generated_from_span":null},"attr_info":{"attributes":[{"DocComment":" Contains the success value"}],"inline":null,"rename":null,"public":true},"name":"Ok","fields":[{"span":{"data":{"file_id":3,"beg":{"line":561,"col":53},"end":{"line":561,"col":54}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":null,"ty":{"Deduplicated":196}}],"discriminant":{"Scalar":{"Signed":["Isize","0"]}}},{"id":1,"span":{"data":{"file_id":3,"beg":{"line":566,"col":4},"end":{"line":566,"col":7}},"generated_from_span":null},"attr_info":{"attributes":[{"DocComment":" Contains the error value"}],"inline":null,"rename":null,"public":true},"name":"Err","fields":[{"span":{"data":{"file_id":3,"beg":{"line":566,"col":54},"end":{"line":566,"col":55}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":null,"ty":{"HashConsedValue":[1589,{"TypeVar":{"Bound":[0,1]}}]}}],"discriminant":{"Scalar":{"Signed":["Isize","1"]}}}]},"layout":[],"ptr_metadata":"None"},{"def_id":6,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Ident":["Iter",0]}],"span":{"data":{"file_id":4,"beg":{"line":69,"col":0},"end":{"line":69,"col":26}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Immutable slice iterator"},{"DocComment":""},{"DocComment":" This struct is created by the [`iter`] method on [slices]."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" Basic usage:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" // First, we need a slice to call the `iter` method on:"},{"DocComment":" let slice = &[1, 2, 3];"},{"DocComment":""},{"DocComment":" // Then we call `iter` on the slice to get the `Iter` iterator,"},{"DocComment":" // and iterate over it:"},{"DocComment":" for element in slice.iter() {"},{"DocComment":" println!(\"{element}\");"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" // This for loop actually already works without calling `iter`:"},{"DocComment":" for element in slice {"},{"DocComment":" println!(\"{element}\");"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" [`iter`]: slice::iter"},{"DocComment":" [slices]: slice"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"SliceIter"},"generics":{"regions":[{"index":0,"name":"'a","mutability":"Shared"}],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":4,"beg":{"line":69,"col":20},"end":{"line":69,"col":21}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[{"regions":[],"skip_binder":[{"Deduplicated":164},{"Var":{"Bound":[1,0]}}]},{"regions":[],"skip_binder":[{"Deduplicated":164},{"Var":{"Bound":[1,0]}}]}],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[{"key":"aarch64-apple-darwin","value":{"size":16,"align":8,"discriminator":{"Known":0},"uninhabited":false,"variant_layouts":[{"field_offsets":[0,8,16],"uninhabited":false,"tagger":[]}],"repr":{"repr_algo":"Rust","align_modif":null,"transparent":false,"explicit_discr_type":false}}}],"ptr_metadata":"None"},{"def_id":7,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["option",0]},{"Ident":["Option",0]}],"span":{"data":{"file_id":6,"beg":{"line":600,"col":0},"end":{"line":600,"col":18}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" The `Option` type. See [the module level documentation](self) for more."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"Option"},"generics":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":6,"beg":{"line":600,"col":16},"end":{"line":600,"col":17}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":{"Enum":[{"id":0,"span":{"data":{"file_id":6,"beg":{"line":604,"col":4},"end":{"line":604,"col":8}},"generated_from_span":null},"attr_info":{"attributes":[{"DocComment":" No value."}],"inline":null,"rename":null,"public":true},"name":"None","fields":[],"discriminant":{"Scalar":{"Signed":["Isize","0"]}}},{"id":1,"span":{"data":{"file_id":6,"beg":{"line":608,"col":4},"end":{"line":608,"col":8}},"generated_from_span":null},"attr_info":{"attributes":[{"DocComment":" Some value of type `T`."}],"inline":null,"rename":null,"public":true},"name":"Some","fields":[{"span":{"data":{"file_id":6,"beg":{"line":608,"col":55},"end":{"line":608,"col":56}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":null,"ty":{"Deduplicated":196}}],"discriminant":{"Scalar":{"Signed":["Isize","1"]}}}]},"layout":[],"ptr_metadata":"None"},{"def_id":8,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["control_flow",0]},{"Ident":["ControlFlow",0]}],"span":{"data":{"file_id":8,"beg":{"line":89,"col":0},"end":{"line":89,"col":31}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Used to tell an operation whether it should exit early or go on as usual."},{"DocComment":""},{"DocComment":" This is used when exposing things (like graph traversals or visitors) where"},{"DocComment":" you want the user to be able to choose whether to exit early."},{"DocComment":" Having the enum makes it clearer -- no more wondering \"wait, what did `false`"},{"DocComment":" mean again?\" -- and allows including a value."},{"DocComment":""},{"DocComment":" Similar to [`Option`] and [`Result`], this enum can be used with the `?` operator"},{"DocComment":" to return immediately if the [`Break`] variant is present or otherwise continue normally"},{"DocComment":" with the value inside the [`Continue`] variant."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" Early-exiting from [`Iterator::try_for_each`]:"},{"DocComment":" ```"},{"DocComment":" use std::ops::ControlFlow;"},{"DocComment":""},{"DocComment":" let r = (2..100).try_for_each(|x| {"},{"DocComment":" if 403 % x == 0 {"},{"DocComment":" return ControlFlow::Break(x)"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" ControlFlow::Continue(())"},{"DocComment":" });"},{"DocComment":" assert_eq!(r, ControlFlow::Break(13));"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" A basic tree traversal:"},{"DocComment":" ```"},{"DocComment":" use std::ops::ControlFlow;"},{"DocComment":""},{"DocComment":" pub struct TreeNode {"},{"DocComment":" value: T,"},{"DocComment":" left: Option>>,"},{"DocComment":" right: Option>>,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl TreeNode {"},{"DocComment":" pub fn traverse_inorder(&self, f: &mut impl FnMut(&T) -> ControlFlow) -> ControlFlow {"},{"DocComment":" if let Some(left) = &self.left {"},{"DocComment":" left.traverse_inorder(f)?;"},{"DocComment":" }"},{"DocComment":" f(&self.value)?;"},{"DocComment":" if let Some(right) = &self.right {"},{"DocComment":" right.traverse_inorder(f)?;"},{"DocComment":" }"},{"DocComment":" ControlFlow::Continue(())"},{"DocComment":" }"},{"DocComment":" fn leaf(value: T) -> Option>> {"},{"DocComment":" Some(Box::new(Self { value, left: None, right: None }))"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" let node = TreeNode {"},{"DocComment":" value: 0,"},{"DocComment":" left: TreeNode::leaf(1),"},{"DocComment":" right: Some(Box::new(TreeNode {"},{"DocComment":" value: -1,"},{"DocComment":" left: TreeNode::leaf(5),"},{"DocComment":" right: TreeNode::leaf(2),"},{"DocComment":" }))"},{"DocComment":" };"},{"DocComment":" let mut sum = 0;"},{"DocComment":""},{"DocComment":" let res = node.traverse_inorder(&mut |val| {"},{"DocComment":" if *val < 0 {"},{"DocComment":" ControlFlow::Break(*val)"},{"DocComment":" } else {"},{"DocComment":" sum += *val;"},{"DocComment":" ControlFlow::Continue(())"},{"DocComment":" }"},{"DocComment":" });"},{"DocComment":" assert_eq!(res, ControlFlow::Break(-1));"},{"DocComment":" assert_eq!(sum, 6);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" [`Break`]: ControlFlow::Break"},{"DocComment":" [`Continue`]: ControlFlow::Continue"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"ControlFlow"},"generics":{"regions":[],"types":[{"index":0,"name":"B"},{"index":1,"name":"C"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":8,"beg":{"line":89,"col":21},"end":{"line":89,"col":22}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":8,"beg":{"line":89,"col":24},"end":{"line":89,"col":30}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":{"Enum":[{"id":0,"span":{"data":{"file_id":8,"beg":{"line":93,"col":4},"end":{"line":93,"col":12}},"generated_from_span":null},"attr_info":{"attributes":[{"DocComment":" Move on to the next phase of the operation as normal."}],"inline":null,"rename":null,"public":true},"name":"Continue","fields":[{"span":{"data":{"file_id":8,"beg":{"line":93,"col":13},"end":{"line":93,"col":14}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":null,"ty":{"Deduplicated":1589}}],"discriminant":{"Scalar":{"Signed":["Isize","0"]}}},{"id":1,"span":{"data":{"file_id":8,"beg":{"line":97,"col":4},"end":{"line":97,"col":9}},"generated_from_span":null},"attr_info":{"attributes":[{"DocComment":" Exit the operation without running subsequent phases."}],"inline":null,"rename":null,"public":true},"name":"Break","fields":[{"span":{"data":{"file_id":8,"beg":{"line":97,"col":10},"end":{"line":97,"col":11}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"name":null,"ty":{"Deduplicated":196}}],"discriminant":{"Scalar":{"Signed":["Isize","1"]}}}]},"layout":[],"ptr_metadata":"None"},{"def_id":9,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["convert",0]},{"Ident":["Infallible",0]}],"span":{"data":{"file_id":10,"beg":{"line":930,"col":0},"end":{"line":930,"col":19}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" The error type for errors that can never happen."},{"DocComment":""},{"DocComment":" Since this enum has no variant, a value of this type can never actually exist."},{"DocComment":" This can be useful for generic APIs that use [`Result`] and parameterize the error type,"},{"DocComment":" to indicate that the result is always [`Ok`]."},{"DocComment":""},{"DocComment":" For example, the [`TryFrom`] trait (conversion that returns a [`Result`])"},{"DocComment":" has a blanket implementation for all types where a reverse [`Into`] implementation exists."},{"DocComment":""},{"DocComment":" ```ignore (illustrates std code, duplicating the impl in a doctest would be an error)"},{"DocComment":" impl TryFrom for T where U: Into {"},{"DocComment":" type Error = Infallible;"},{"DocComment":""},{"DocComment":" fn try_from(value: U) -> Result {"},{"DocComment":" Ok(U::into(value)) // Never returns `Err`"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" # Future compatibility"},{"DocComment":""},{"DocComment":" This enum has the same role as [the `!` “never” type][never],"},{"DocComment":" which is unstable in this version of Rust."},{"DocComment":" When `!` is stabilized, we plan to make `Infallible` a type alias to it:"},{"DocComment":""},{"DocComment":" ```ignore (illustrates future std change)"},{"DocComment":" pub type Infallible = !;"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" … and eventually deprecate `Infallible`."},{"DocComment":""},{"DocComment":" However there is one case where `!` syntax can be used"},{"DocComment":" before `!` is stabilized as a full-fledged type: in the position of a function’s return type."},{"DocComment":" Specifically, it is possible to have implementations for two different function pointer types:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" trait MyTrait {}"},{"DocComment":" impl MyTrait for fn() -> ! {}"},{"DocComment":" impl MyTrait for fn() -> std::convert::Infallible {}"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" With `Infallible` being an enum, this code is valid."},{"DocComment":" However when `Infallible` becomes an alias for the never type,"},{"DocComment":" the two `impl`s will start to overlap"},{"DocComment":" and therefore will be disallowed by the language’s trait coherence rules."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":{"Enum":[]},"layout":[{"key":"aarch64-apple-darwin","value":{"size":0,"align":1,"discriminator":null,"uninhabited":true,"variant_layouts":[],"repr":{"repr_algo":"Rust","align_modif":null,"transparent":false,"explicit_discr_type":false}}}],"ptr_metadata":"None"},{"def_id":10,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_closure",0]},{"Ident":["closure",0]}],"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"source_text":"|| x + 1","attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":{"Closure":{"info":{"kind":"FnOnce","fn_once_impl":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[{"Var":{"Bound":[1,0]}}],"types":[],"const_generics":[],"trait_refs":[]}}},"fn_mut_impl":null,"fn_impl":null,"signature":{"regions":[],"skip_binder":{"is_unsafe":false,"inputs":[],"output":{"Deduplicated":207}}}}}},"kind":{"Struct":[{"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"name":null,"ty":{"HashConsedValue":[6166,{"Ref":[{"Var":{"Bound":[0,0]}},{"Deduplicated":207},"Shared"]}]}}]},"layout":[{"key":"aarch64-apple-darwin","value":{"size":8,"align":8,"discriminator":{"Known":0},"uninhabited":false,"variant_layouts":[{"field_offsets":[0],"uninhabited":false,"tagger":[]}],"repr":{"repr_algo":"Rust","align_modif":null,"transparent":false,"explicit_discr_type":false}}}],"ptr_metadata":"None"},null,null,{"def_id":13,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["array",0]},{"Ident":["iter",0]},{"Ident":["IntoIter",0]}],"span":{"data":{"file_id":17,"beg":{"line":20,"col":0},"end":{"line":20,"col":38}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" A by-value [array] iterator."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"ArrayIntoIter"},"generics":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[{"index":0,"name":"N","ty":{"Deduplicated":614}}],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":17,"beg":{"line":20,"col":20},"end":{"line":20,"col":21}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":14,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["nonzero",0]},{"Ident":["NonZero",0]}],"span":{"data":{"file_id":19,"beg":{"line":127,"col":0},"end":{"line":127,"col":40}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" A value that is known not to equal zero."},{"DocComment":""},{"DocComment":" This enables some memory layout optimization."},{"DocComment":" For example, `Option>` is the same size as `u32`:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use core::{num::NonZero};"},{"DocComment":""},{"DocComment":" assert_eq!(size_of::>>(), size_of::());"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" # Layout"},{"DocComment":""},{"DocComment":" `NonZero` is guaranteed to have the same layout and bit validity as `T`"},{"DocComment":" with the exception that the all-zero bit pattern is invalid."},{"DocComment":" `Option>` is guaranteed to be compatible with `T`, including in"},{"DocComment":" FFI."},{"DocComment":""},{"DocComment":" Thanks to the [null pointer optimization], `NonZero` and"},{"DocComment":" `Option>` are guaranteed to have the same size and alignment:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::num::NonZero;"},{"DocComment":""},{"DocComment":" assert_eq!(size_of::>(), size_of::>>());"},{"DocComment":" assert_eq!(align_of::>(), align_of::>>());"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" [null pointer optimization]: crate::option#representation"},{"DocComment":""},{"DocComment":" # Note on generic usage"},{"DocComment":""},{"DocComment":" `NonZero` can only be used with some standard library primitive types"},{"DocComment":" (such as `u8`, `i32`, and etc.). The type parameter `T` must implement the"},{"DocComment":" internal trait [`ZeroablePrimitive`], which is currently permanently unstable"},{"DocComment":" and cannot be implemented by users. Therefore, you cannot use `NonZero`"},{"DocComment":" with your own types, nor can you implement traits for all `NonZero`,"},{"DocComment":" only for concrete types."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"NonZero"},"generics":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":19,"beg":{"line":127,"col":19},"end":{"line":127,"col":20}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":19,"beg":{"line":127,"col":22},"end":{"line":127,"col":39}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":7,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":15,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["step_by",0]},{"Ident":["StepBy",0]}],"span":{"data":{"file_id":21,"beg":{"line":16,"col":0},"end":{"line":16,"col":20}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator for stepping iterators by a custom amount."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`step_by`] method on [`Iterator`]. See"},{"DocComment":" its documentation for more."},{"DocComment":""},{"DocComment":" [`step_by`]: Iterator::step_by"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":21,"beg":{"line":16,"col":18},"end":{"line":16,"col":19}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":16,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["chain",0]},{"Ident":["Chain",0]}],"span":{"data":{"file_id":23,"beg":{"line":23,"col":0},"end":{"line":23,"col":22}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that links two iterators together, in a chain."},{"DocComment":""},{"DocComment":" This `struct` is created by [`chain`] or [`Iterator::chain`]. See their"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::iter::Chain;"},{"DocComment":" use std::slice::Iter;"},{"DocComment":""},{"DocComment":" let a1 = [1, 2, 3];"},{"DocComment":" let a2 = [4, 5, 6];"},{"DocComment":" let iter: Chain, Iter<'_, _>> = a1.iter().chain(a2.iter());"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"A"},{"index":1,"name":"B"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":23,"beg":{"line":23,"col":17},"end":{"line":23,"col":18}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":23,"beg":{"line":23,"col":20},"end":{"line":23,"col":21}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":17,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["zip",0]},{"Ident":["Zip",0]}],"span":{"data":{"file_id":24,"beg":{"line":15,"col":0},"end":{"line":15,"col":20}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that iterates two other iterators simultaneously."},{"DocComment":""},{"DocComment":" This `struct` is created by [`zip`] or [`Iterator::zip`]."},{"DocComment":" See their documentation for more."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"A"},{"index":1,"name":"B"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":24,"beg":{"line":15,"col":15},"end":{"line":15,"col":16}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":24,"beg":{"line":15,"col":18},"end":{"line":15,"col":19}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":18,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["intersperse",0]},{"Ident":["Intersperse",0]}],"span":{"data":{"file_id":26,"beg":{"line":10,"col":0},"end":{"line":10,"col":35}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator adapter that places a separator between all elements."},{"DocComment":""},{"DocComment":" This `struct` is created by [`Iterator::intersperse`]. See its documentation"},{"DocComment":" for more information."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":26,"beg":{"line":10,"col":23},"end":{"line":10,"col":24}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":26,"beg":{"line":10,"col":26},"end":{"line":10,"col":34}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":26,"beg":{"line":12,"col":13},"end":{"line":12,"col":18}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":8,"generics":{"regions":[],"types":[{"HashConsedValue":[4568,{"TraitType":[{"HashConsedValue":[4567,{"kind":{"Clause":{"Bound":[1,1]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"HashConsedValue":[1611,{"TypeVar":{"Bound":[2,0]}}]}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":19,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["intersperse",0]},{"Ident":["IntersperseWith",0]}],"span":{"data":{"file_id":26,"beg":{"line":91,"col":0},"end":{"line":91,"col":32}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator adapter that places a separator between all elements."},{"DocComment":""},{"DocComment":" This `struct` is created by [`Iterator::intersperse_with`]. See its"},{"DocComment":" documentation for more information."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"},{"index":1,"name":"G"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":26,"beg":{"line":91,"col":27},"end":{"line":91,"col":28}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":26,"beg":{"line":91,"col":30},"end":{"line":91,"col":31}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":26,"beg":{"line":93,"col":7},"end":{"line":93,"col":15}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":20,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["map",0]},{"Ident":["Map",0]}],"span":{"data":{"file_id":27,"beg":{"line":61,"col":0},"end":{"line":61,"col":20}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that maps the values of `iter` with `f`."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`map`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`map`]: Iterator::map"},{"DocComment":" [`Iterator`]: trait.Iterator.html"},{"DocComment":""},{"DocComment":" # Notes about side effects"},{"DocComment":""},{"DocComment":" The [`map`] iterator implements [`DoubleEndedIterator`], meaning that"},{"DocComment":" you can also [`map`] backwards:"},{"DocComment":""},{"DocComment":" ```rust"},{"DocComment":" let v: Vec = [1, 2, 3].into_iter().map(|x| x + 1).rev().collect();"},{"DocComment":""},{"DocComment":" assert_eq!(v, [4, 3, 2]);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" [`DoubleEndedIterator`]: trait.DoubleEndedIterator.html"},{"DocComment":""},{"DocComment":" But if your closure has state, iterating backwards may act in a way you do"},{"DocComment":" not expect. Let's go through an example. First, in the forward direction:"},{"DocComment":""},{"DocComment":" ```rust"},{"DocComment":" let mut c = 0;"},{"DocComment":""},{"DocComment":" for pair in ['a', 'b', 'c'].into_iter()"},{"DocComment":" .map(|letter| { c += 1; (letter, c) }) {"},{"DocComment":" println!(\"{pair:?}\");"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" This will print `('a', 1), ('b', 2), ('c', 3)`."},{"DocComment":""},{"DocComment":" Now consider this twist where we add a call to `rev`. This version will"},{"DocComment":" print `('c', 1), ('b', 2), ('a', 3)`. Note that the letters are reversed,"},{"DocComment":" but the values of the counter still go in order. This is because `map()` is"},{"DocComment":" still being called lazily on each item, but we are popping items off the"},{"DocComment":" back of the vector now, instead of shifting them from the front."},{"DocComment":""},{"DocComment":" ```rust"},{"DocComment":" let mut c = 0;"},{"DocComment":""},{"DocComment":" for pair in ['a', 'b', 'c'].into_iter()"},{"DocComment":" .map(|letter| { c += 1; (letter, c) })"},{"DocComment":" .rev() {"},{"DocComment":" println!(\"{pair:?}\");"},{"DocComment":" }"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"},{"index":1,"name":"F"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":27,"beg":{"line":61,"col":15},"end":{"line":61,"col":16}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":27,"beg":{"line":61,"col":18},"end":{"line":61,"col":19}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":21,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["filter",0]},{"Ident":["Filter",0]}],"span":{"data":{"file_id":28,"beg":{"line":21,"col":0},"end":{"line":21,"col":23}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that filters the elements of `iter` with `predicate`."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`filter`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`filter`]: Iterator::filter"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"},{"index":1,"name":"P"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":28,"beg":{"line":21,"col":18},"end":{"line":21,"col":19}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":28,"beg":{"line":21,"col":21},"end":{"line":21,"col":22}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":22,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["filter_map",0]},{"Ident":["FilterMap",0]}],"span":{"data":{"file_id":29,"beg":{"line":18,"col":0},"end":{"line":18,"col":26}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that uses `f` to both filter and map elements from `iter`."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`filter_map`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`filter_map`]: Iterator::filter_map"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"},{"index":1,"name":"F"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":29,"beg":{"line":18,"col":21},"end":{"line":18,"col":22}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":29,"beg":{"line":18,"col":24},"end":{"line":18,"col":25}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":23,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["enumerate",0]},{"Ident":["Enumerate",0]}],"span":{"data":{"file_id":30,"beg":{"line":18,"col":0},"end":{"line":18,"col":23}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that yields the current count and the element during iteration."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`enumerate`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`enumerate`]: Iterator::enumerate"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"Enumerate"},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":30,"beg":{"line":18,"col":21},"end":{"line":18,"col":22}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":24,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["peekable",0]},{"Ident":["Peekable",0]}],"span":{"data":{"file_id":31,"beg":{"line":17,"col":0},"end":{"line":17,"col":32}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator with a `peek()` that returns an optional reference to the next"},{"DocComment":" element."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`peekable`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`peekable`]: Iterator::peekable"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"IterPeekable"},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":31,"beg":{"line":17,"col":20},"end":{"line":17,"col":21}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":31,"beg":{"line":17,"col":23},"end":{"line":17,"col":31}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":25,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["skip_while",0]},{"Ident":["SkipWhile",0]}],"span":{"data":{"file_id":32,"beg":{"line":17,"col":0},"end":{"line":17,"col":26}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that rejects elements while `predicate` returns `true`."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`skip_while`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`skip_while`]: Iterator::skip_while"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"},{"index":1,"name":"P"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":32,"beg":{"line":17,"col":21},"end":{"line":17,"col":22}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":32,"beg":{"line":17,"col":24},"end":{"line":17,"col":25}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":26,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["take_while",0]},{"Ident":["TakeWhile",0]}],"span":{"data":{"file_id":33,"beg":{"line":17,"col":0},"end":{"line":17,"col":26}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that only accepts elements while `predicate` returns `true`."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`take_while`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`take_while`]: Iterator::take_while"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"},{"index":1,"name":"P"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":33,"beg":{"line":17,"col":21},"end":{"line":17,"col":22}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":33,"beg":{"line":17,"col":24},"end":{"line":17,"col":25}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":27,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["map_while",0]},{"Ident":["MapWhile",0]}],"span":{"data":{"file_id":34,"beg":{"line":17,"col":0},"end":{"line":17,"col":25}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that only accepts elements while `predicate` returns `Some(_)`."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`map_while`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`map_while`]: Iterator::map_while"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"},{"index":1,"name":"P"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":34,"beg":{"line":17,"col":20},"end":{"line":17,"col":21}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":34,"beg":{"line":17,"col":23},"end":{"line":17,"col":24}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":28,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["skip",0]},{"Ident":["Skip",0]}],"span":{"data":{"file_id":35,"beg":{"line":21,"col":0},"end":{"line":21,"col":18}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that skips over `n` elements of `iter`."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`skip`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`skip`]: Iterator::skip"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":35,"beg":{"line":21,"col":16},"end":{"line":21,"col":17}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":29,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["take",0]},{"Ident":["Take",0]}],"span":{"data":{"file_id":36,"beg":{"line":17,"col":0},"end":{"line":17,"col":18}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that only iterates over the first `n` iterations of `iter`."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`take`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`take`]: Iterator::take"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":36,"beg":{"line":17,"col":16},"end":{"line":17,"col":17}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":30,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["scan",0]},{"Ident":["Scan",0]}],"span":{"data":{"file_id":37,"beg":{"line":17,"col":0},"end":{"line":17,"col":25}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator to maintain state while iterating another iterator."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`scan`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`scan`]: Iterator::scan"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"},{"index":1,"name":"St"},{"index":2,"name":"F"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":37,"beg":{"line":17,"col":16},"end":{"line":17,"col":17}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":37,"beg":{"line":17,"col":19},"end":{"line":17,"col":21}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":37,"beg":{"line":17,"col":23},"end":{"line":17,"col":24}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[2626,{"TypeVar":{"Bound":[1,2]}}]}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":31,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["flatten",0]},{"Ident":["FlatMap",0]}],"span":{"data":{"file_id":38,"beg":{"line":17,"col":0},"end":{"line":17,"col":41}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that maps each element to an iterator, and yields the elements"},{"DocComment":" of the produced iterators."},{"DocComment":""},{"DocComment":" This `struct` is created by [`Iterator::flat_map`]. See its documentation"},{"DocComment":" for more."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"},{"index":1,"name":"U"},{"index":2,"name":"F"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":38,"beg":{"line":17,"col":19},"end":{"line":17,"col":20}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":38,"beg":{"line":17,"col":22},"end":{"line":17,"col":23}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":38,"beg":{"line":17,"col":39},"end":{"line":17,"col":40}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":2626}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":3,"span":{"data":{"file_id":38,"beg":{"line":17,"col":25},"end":{"line":17,"col":37}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":32,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["flatten",0]},{"Ident":["Flatten",0]}],"span":{"data":{"file_id":38,"beg":{"line":184,"col":0},"end":{"line":184,"col":51}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that flattens one level of nesting in an iterator of things"},{"DocComment":" that can be turned into iterators."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`flatten`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`flatten`]: Iterator::flatten()"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":38,"beg":{"line":184,"col":19},"end":{"line":184,"col":20}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":38,"beg":{"line":184,"col":22},"end":{"line":184,"col":50}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":38,"beg":{"line":184,"col":37},"end":{"line":184,"col":49}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":4568}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":33,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["map_windows",0]},{"Ident":["MapWindows",0]}],"span":{"data":{"file_id":39,"beg":{"line":11,"col":0},"end":{"line":11,"col":53}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator over the mapped windows of another iterator."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`Iterator::map_windows`]. See its"},{"DocComment":" documentation for more information."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"},{"index":1,"name":"F"}],"const_generics":[{"index":0,"name":"N","ty":{"Deduplicated":614}}],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":39,"beg":{"line":11,"col":22},"end":{"line":11,"col":23}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":39,"beg":{"line":11,"col":35},"end":{"line":11,"col":36}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":39,"beg":{"line":11,"col":25},"end":{"line":11,"col":33}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":34,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["fuse",0]},{"Ident":["Fuse",0]}],"span":{"data":{"file_id":40,"beg":{"line":17,"col":0},"end":{"line":17,"col":18}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that yields `None` forever after the underlying iterator"},{"DocComment":" yields `None` once."},{"DocComment":""},{"DocComment":" This `struct` is created by [`Iterator::fuse`]. See its documentation"},{"DocComment":" for more."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":40,"beg":{"line":17,"col":16},"end":{"line":17,"col":17}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":35,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["inspect",0]},{"Ident":["Inspect",0]}],"span":{"data":{"file_id":41,"beg":{"line":18,"col":0},"end":{"line":18,"col":24}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that calls a function with a reference to each element before"},{"DocComment":" yielding it."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`inspect`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`inspect`]: Iterator::inspect"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"},{"index":1,"name":"F"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":41,"beg":{"line":18,"col":19},"end":{"line":18,"col":20}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":41,"beg":{"line":18,"col":22},"end":{"line":18,"col":23}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":36,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["Ordering",0]}],"span":{"data":{"file_id":46,"beg":{"line":396,"col":0},"end":{"line":396,"col":17}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An `Ordering` is the result of a comparison between two values."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::cmp::Ordering;"},{"DocComment":""},{"DocComment":" assert_eq!(1.cmp(&2), Ordering::Less);"},{"DocComment":""},{"DocComment":" assert_eq!(1.cmp(&1), Ordering::Equal);"},{"DocComment":""},{"DocComment":" assert_eq!(2.cmp(&1), Ordering::Greater);"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"Ordering"},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":{"Enum":[{"id":0,"span":{"data":{"file_id":46,"beg":{"line":399,"col":4},"end":{"line":399,"col":8}},"generated_from_span":null},"attr_info":{"attributes":[{"DocComment":" An ordering where a compared value is less than another."}],"inline":null,"rename":null,"public":true},"name":"Less","fields":[],"discriminant":{"Scalar":{"Signed":["I8","-1"]}}},{"id":1,"span":{"data":{"file_id":46,"beg":{"line":402,"col":4},"end":{"line":402,"col":9}},"generated_from_span":null},"attr_info":{"attributes":[{"DocComment":" An ordering where a compared value is equal to another."}],"inline":null,"rename":null,"public":true},"name":"Equal","fields":[],"discriminant":{"Scalar":{"Signed":["I8","0"]}}},{"id":2,"span":{"data":{"file_id":46,"beg":{"line":405,"col":4},"end":{"line":405,"col":11}},"generated_from_span":null},"attr_info":{"attributes":[{"DocComment":" An ordering where a compared value is greater than another."}],"inline":null,"rename":null,"public":true},"name":"Greater","fields":[],"discriminant":{"Scalar":{"Signed":["I8","1"]}}}]},"layout":[{"key":"aarch64-apple-darwin","value":{"size":1,"align":1,"discriminator":{"Branch":{"offset":0,"int_ty":{"Signed":"I8"},"children":[[{"start":{"Signed":["I8","-1"]},"end":{"Signed":["I8","-1"]}},{"Known":0}],[{"start":{"Signed":["I8","0"]},"end":{"Signed":["I8","0"]}},{"Known":1}],[{"start":{"Signed":["I8","1"]},"end":{"Signed":["I8","1"]}},{"Known":2}]],"fallback":"Invalid"}},"uninhabited":false,"variant_layouts":[{"field_offsets":[],"uninhabited":false,"tagger":[[0,{"Signed":["I8","-1"]}]]},{"field_offsets":[],"uninhabited":false,"tagger":[[0,{"Signed":["I8","0"]}]]},{"field_offsets":[],"uninhabited":false,"tagger":[[0,{"Signed":["I8","1"]}]]}],"repr":{"repr_algo":"Rust","align_modif":null,"transparent":false,"explicit_discr_type":true}}}],"ptr_metadata":"None"},{"def_id":37,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["rev",0]},{"Ident":["Rev",0]}],"span":{"data":{"file_id":47,"beg":{"line":15,"col":0},"end":{"line":15,"col":17}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" A double-ended iterator with the direction inverted."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`rev`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`rev`]: Iterator::rev"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":47,"beg":{"line":15,"col":15},"end":{"line":15,"col":16}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":38,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["copied",0]},{"Ident":["Copied",0]}],"span":{"data":{"file_id":48,"beg":{"line":19,"col":0},"end":{"line":19,"col":20}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that copies the elements of an underlying iterator."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`copied`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`copied`]: Iterator::copied"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":48,"beg":{"line":19,"col":18},"end":{"line":19,"col":19}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":39,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["cloned",0]},{"Ident":["Cloned",0]}],"span":{"data":{"file_id":49,"beg":{"line":18,"col":0},"end":{"line":18,"col":20}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that clones the elements of an underlying iterator."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`cloned`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`cloned`]: Iterator::cloned"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":49,"beg":{"line":18,"col":18},"end":{"line":18,"col":19}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":40,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["cycle",0]},{"Ident":["Cycle",0]}],"span":{"data":{"file_id":50,"beg":{"line":15,"col":0},"end":{"line":15,"col":19}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that repeats endlessly."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`cycle`] method on [`Iterator`]. See its"},{"DocComment":" documentation for more."},{"DocComment":""},{"DocComment":" [`cycle`]: Iterator::cycle"},{"DocComment":" [`Iterator`]: trait.Iterator.html"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":50,"beg":{"line":15,"col":17},"end":{"line":15,"col":18}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},{"def_id":41,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["array_chunks",0]},{"Ident":["ArrayChunks",0]}],"span":{"data":{"file_id":51,"beg":{"line":19,"col":0},"end":{"line":19,"col":51}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator over `N` elements of the iterator at a time."},{"DocComment":""},{"DocComment":" The chunks do not overlap. If `N` does not divide the length of the"},{"DocComment":" iterator, then the last up to `N-1` elements will be omitted."},{"DocComment":""},{"DocComment":" This `struct` is created by the [`array_chunks`][Iterator::array_chunks]"},{"DocComment":" method on [`Iterator`]. See its documentation for more."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[{"index":0,"name":"N","ty":{"Deduplicated":614}}],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":51,"beg":{"line":19,"col":23},"end":{"line":19,"col":24}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":51,"beg":{"line":19,"col":26},"end":{"line":19,"col":34}},"generated_from_span":null},"origin":"WhereClauseOnType","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[],"ptr_metadata":"None"},null,null,{"def_id":44,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["niche_types",0]},{"Ident":["NonZeroUsizeInner",0]}],"span":{"data":{"file_id":53,"beg":{"line":20,"col":8},"end":{"line":20,"col":55}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"src":"TopLevel","kind":"Opaque","layout":[{"key":"aarch64-apple-darwin","value":{"size":8,"align":8,"discriminator":{"Known":0},"uninhabited":false,"variant_layouts":[{"field_offsets":[0],"uninhabited":false,"tagger":[]}],"repr":{"repr_algo":"Rust","align_modif":null,"transparent":true,"explicit_discr_type":false}}}],"ptr_metadata":"None"},null,null,null,null,null,null],"fun_decls":[{"def_id":0,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["straight_line_add",0]}],"span":{"data":{"file_id":0,"beg":{"line":15,"col":0},"end":{"line":19,"col":1}},"generated_from_span":null},"source_text":"pub fn straight_line_add(a: i64, b: i64, c: i64) -> i64 {\n let s = a + b;\n let t = s * 2;\n t + c\n}","attr_info":{"attributes":[],"inline":"Never","rename":null,"public":true},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":207},{"Deduplicated":207},{"Deduplicated":207}],"output":{"Deduplicated":207}},"src":"TopLevel","is_global_initializer":null,"body":{"Unstructured":{"span":{"data":{"file_id":0,"beg":{"line":15,"col":0},"end":{"line":19,"col":1}},"generated_from_span":null},"bound_body_regions":0,"locals":{"arg_count":3,"locals":[{"index":0,"name":null,"span":{"data":{"file_id":0,"beg":{"line":15,"col":52},"end":{"line":15,"col":55}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":1,"name":"a","span":{"data":{"file_id":0,"beg":{"line":15,"col":25},"end":{"line":15,"col":26}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":2,"name":"b","span":{"data":{"file_id":0,"beg":{"line":15,"col":33},"end":{"line":15,"col":34}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":3,"name":"c","span":{"data":{"file_id":0,"beg":{"line":15,"col":41},"end":{"line":15,"col":42}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":4,"name":"s","span":{"data":{"file_id":0,"beg":{"line":16,"col":8},"end":{"line":16,"col":9}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":5,"name":null,"span":{"data":{"file_id":0,"beg":{"line":16,"col":12},"end":{"line":16,"col":13}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":6,"name":null,"span":{"data":{"file_id":0,"beg":{"line":16,"col":16},"end":{"line":16,"col":17}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":7,"name":null,"span":{"data":{"file_id":0,"beg":{"line":16,"col":12},"end":{"line":16,"col":17}},"generated_from_span":null},"ty":{"HashConsedValue":[212,{"Adt":{"id":"Tuple","generics":{"regions":[],"types":[{"Deduplicated":207},{"Deduplicated":211}],"const_generics":[],"trait_refs":[]}}}]}},{"index":8,"name":"t","span":{"data":{"file_id":0,"beg":{"line":17,"col":8},"end":{"line":17,"col":9}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":9,"name":null,"span":{"data":{"file_id":0,"beg":{"line":17,"col":12},"end":{"line":17,"col":13}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":10,"name":null,"span":{"data":{"file_id":0,"beg":{"line":17,"col":12},"end":{"line":17,"col":17}},"generated_from_span":null},"ty":{"Deduplicated":212}},{"index":11,"name":null,"span":{"data":{"file_id":0,"beg":{"line":18,"col":4},"end":{"line":18,"col":5}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":12,"name":null,"span":{"data":{"file_id":0,"beg":{"line":18,"col":8},"end":{"line":18,"col":9}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":13,"name":null,"span":{"data":{"file_id":0,"beg":{"line":18,"col":4},"end":{"line":18,"col":9}},"generated_from_span":null},"ty":{"Deduplicated":212}}]},"body":[{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":16,"col":8},"end":{"line":16,"col":9}},"generated_from_span":null},"kind":{"StorageLive":7},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":16,"col":8},"end":{"line":16,"col":9}},"generated_from_span":null},"kind":{"StorageLive":10},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":16,"col":8},"end":{"line":16,"col":9}},"generated_from_span":null},"kind":{"StorageLive":13},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":16,"col":8},"end":{"line":16,"col":9}},"generated_from_span":null},"kind":{"StorageLive":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":16,"col":12},"end":{"line":16,"col":13}},"generated_from_span":null},"kind":{"StorageLive":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":16,"col":12},"end":{"line":16,"col":13}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":5},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":1},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":16,"col":16},"end":{"line":16,"col":17}},"generated_from_span":null},"kind":{"StorageLive":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":16,"col":16},"end":{"line":16,"col":17}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":6},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":2},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":16,"col":12},"end":{"line":16,"col":17}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":7},"ty":{"Deduplicated":212}},{"BinaryOp":["AddChecked",{"Copy":{"kind":{"Local":5},"ty":{"Deduplicated":207}}},{"Copy":{"kind":{"Local":6},"ty":{"Deduplicated":207}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":16,"col":12},"end":{"line":16,"col":17}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Projection":[{"kind":{"Local":7},"ty":{"Deduplicated":212}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":211}}},"expected":false,"check_kind":{"Overflow":[{"Add":"Wrap"},{"Move":{"kind":{"Local":5},"ty":{"Deduplicated":207}}},{"Move":{"kind":{"Local":6},"ty":{"Deduplicated":207}}}]}},"target":2,"on_unwind":1}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":15,"col":0},"end":{"line":19,"col":1}},"generated_from_span":null},"kind":"UnwindResume","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":16,"col":12},"end":{"line":16,"col":17}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":4},"ty":{"Deduplicated":207}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":7},"ty":{"Deduplicated":212}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":16,"col":16},"end":{"line":16,"col":17}},"generated_from_span":null},"kind":{"StorageDead":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":16,"col":16},"end":{"line":16,"col":17}},"generated_from_span":null},"kind":{"StorageDead":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":17,"col":8},"end":{"line":17,"col":9}},"generated_from_span":null},"kind":{"StorageLive":8},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":17,"col":12},"end":{"line":17,"col":13}},"generated_from_span":null},"kind":{"StorageLive":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":17,"col":12},"end":{"line":17,"col":13}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":9},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":4},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":17,"col":12},"end":{"line":17,"col":17}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":10},"ty":{"Deduplicated":212}},{"BinaryOp":["MulChecked",{"Copy":{"kind":{"Local":9},"ty":{"Deduplicated":207}}},{"Const":{"kind":{"Literal":{"Scalar":{"Signed":["I64","2"]}}},"ty":{"Deduplicated":207}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":17,"col":12},"end":{"line":17,"col":17}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Projection":[{"kind":{"Local":10},"ty":{"Deduplicated":212}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":211}}},"expected":false,"check_kind":{"Overflow":[{"Mul":"Wrap"},{"Move":{"kind":{"Local":9},"ty":{"Deduplicated":207}}},{"Const":{"kind":{"Literal":{"Scalar":{"Signed":["I64","2"]}}},"ty":{"Deduplicated":207}}}]}},"target":3,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":17,"col":12},"end":{"line":17,"col":17}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":8},"ty":{"Deduplicated":207}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":10},"ty":{"Deduplicated":212}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":17,"col":16},"end":{"line":17,"col":17}},"generated_from_span":null},"kind":{"StorageDead":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":18,"col":4},"end":{"line":18,"col":5}},"generated_from_span":null},"kind":{"StorageLive":11},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":18,"col":4},"end":{"line":18,"col":5}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":11},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":8},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":18,"col":8},"end":{"line":18,"col":9}},"generated_from_span":null},"kind":{"StorageLive":12},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":18,"col":8},"end":{"line":18,"col":9}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":12},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":3},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":18,"col":4},"end":{"line":18,"col":9}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":13},"ty":{"Deduplicated":212}},{"BinaryOp":["AddChecked",{"Copy":{"kind":{"Local":11},"ty":{"Deduplicated":207}}},{"Copy":{"kind":{"Local":12},"ty":{"Deduplicated":207}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":18,"col":4},"end":{"line":18,"col":9}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Projection":[{"kind":{"Local":13},"ty":{"Deduplicated":212}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":211}}},"expected":false,"check_kind":{"Overflow":[{"Add":"Wrap"},{"Move":{"kind":{"Local":11},"ty":{"Deduplicated":207}}},{"Move":{"kind":{"Local":12},"ty":{"Deduplicated":207}}}]}},"target":4,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":18,"col":4},"end":{"line":18,"col":9}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":207}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":13},"ty":{"Deduplicated":212}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":18,"col":8},"end":{"line":18,"col":9}},"generated_from_span":null},"kind":{"StorageDead":12},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":18,"col":8},"end":{"line":18,"col":9}},"generated_from_span":null},"kind":{"StorageDead":11},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":19,"col":0},"end":{"line":19,"col":1}},"generated_from_span":null},"kind":{"StorageDead":8},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":19,"col":0},"end":{"line":19,"col":1}},"generated_from_span":null},"kind":{"StorageDead":4},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":19,"col":1},"end":{"line":19,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}}],"comments":[]}}},{"def_id":1,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["branch_loop_sum",0]}],"span":{"data":{"file_id":0,"beg":{"line":24,"col":0},"end":{"line":34,"col":1}},"generated_from_span":null},"source_text":"pub fn branch_loop_sum(slice: &[i64], threshold: i64) -> i64 {\n let mut acc: i64 = 0;\n for &v in slice {\n if v > threshold {\n acc += v;\n } else {\n acc -= v;\n }\n }\n acc\n}","attr_info":{"attributes":[],"inline":"Never","rename":null,"public":true},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"HashConsedValue":[214,{"Ref":[{"Var":{"Bound":[0,0]}},{"HashConsedValue":[213,{"Slice":{"Deduplicated":207}}]},"Shared"]}]},{"Deduplicated":207}],"output":{"Deduplicated":207}},"src":"TopLevel","is_global_initializer":null,"body":{"Unstructured":{"span":{"data":{"file_id":0,"beg":{"line":24,"col":0},"end":{"line":34,"col":1}},"generated_from_span":null},"bound_body_regions":30,"locals":{"arg_count":2,"locals":[{"index":0,"name":null,"span":{"data":{"file_id":0,"beg":{"line":24,"col":57},"end":{"line":24,"col":60}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":1,"name":"slice","span":{"data":{"file_id":0,"beg":{"line":24,"col":23},"end":{"line":24,"col":28}},"generated_from_span":null},"ty":{"HashConsedValue":[217,{"Ref":[{"Body":1},{"Deduplicated":213},"Shared"]}]}},{"index":2,"name":"threshold","span":{"data":{"file_id":0,"beg":{"line":24,"col":38},"end":{"line":24,"col":47}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":3,"name":"acc","span":{"data":{"file_id":0,"beg":{"line":25,"col":8},"end":{"line":25,"col":15}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":4,"name":null,"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"ty":{"HashConsedValue":[334,{"Adt":{"id":{"Adt":6},"generics":{"regions":[{"Body":3}],"types":[{"Deduplicated":207}],"const_generics":[],"trait_refs":[{"HashConsedValue":[332,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[331,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":207}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":207}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},{"index":5,"name":null,"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"ty":{"HashConsedValue":[335,{"Ref":[{"Body":4},{"Deduplicated":213},"Shared"]}]}},{"index":6,"name":"iter","span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"ty":{"HashConsedValue":[336,{"Adt":{"id":{"Adt":6},"generics":{"regions":[{"Body":5}],"types":[{"Deduplicated":207}],"const_generics":[],"trait_refs":[{"Deduplicated":332}]}}}]}},{"index":7,"name":null,"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"ty":{"HashConsedValue":[5392,{"Adt":{"id":{"Adt":7},"generics":{"regions":[],"types":[{"HashConsedValue":[377,{"Ref":[{"Body":12},{"Deduplicated":207},"Shared"]}]}],"const_generics":[],"trait_refs":[{"HashConsedValue":[5391,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[5390,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":377}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":377}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},{"index":8,"name":null,"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"ty":{"HashConsedValue":[387,{"Ref":[{"Body":17},{"HashConsedValue":[386,{"Adt":{"id":{"Adt":6},"generics":{"regions":[{"Body":18}],"types":[{"Deduplicated":207}],"const_generics":[],"trait_refs":[{"Deduplicated":332}]}}}]},"Mut"]}]}},{"index":9,"name":null,"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"ty":{"HashConsedValue":[389,{"Ref":[{"Body":19},{"HashConsedValue":[388,{"Adt":{"id":{"Adt":6},"generics":{"regions":[{"Body":20}],"types":[{"Deduplicated":207}],"const_generics":[],"trait_refs":[{"Deduplicated":332}]}}}]},"Mut"]}]}},{"index":10,"name":null,"span":{"data":{"file_id":0,"beg":{"line":26,"col":4},"end":{"line":32,"col":5}},"generated_from_span":null},"ty":{"HashConsedValue":[390,{"Literal":{"Int":"Isize"}}]}},{"index":11,"name":"v","span":{"data":{"file_id":0,"beg":{"line":26,"col":9},"end":{"line":26,"col":10}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":12,"name":null,"span":{"data":{"file_id":0,"beg":{"line":27,"col":11},"end":{"line":27,"col":24}},"generated_from_span":null},"ty":{"Deduplicated":211}},{"index":13,"name":null,"span":{"data":{"file_id":0,"beg":{"line":27,"col":11},"end":{"line":27,"col":12}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":14,"name":null,"span":{"data":{"file_id":0,"beg":{"line":27,"col":15},"end":{"line":27,"col":24}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":15,"name":null,"span":{"data":{"file_id":0,"beg":{"line":28,"col":19},"end":{"line":28,"col":20}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":16,"name":null,"span":{"data":{"file_id":0,"beg":{"line":28,"col":12},"end":{"line":28,"col":20}},"generated_from_span":null},"ty":{"Deduplicated":212}},{"index":17,"name":null,"span":{"data":{"file_id":0,"beg":{"line":30,"col":19},"end":{"line":30,"col":20}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":18,"name":null,"span":{"data":{"file_id":0,"beg":{"line":30,"col":12},"end":{"line":30,"col":20}},"generated_from_span":null},"ty":{"Deduplicated":212}}]},"body":[{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":25,"col":8},"end":{"line":25,"col":15}},"generated_from_span":null},"kind":{"StorageLive":10},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":25,"col":8},"end":{"line":25,"col":15}},"generated_from_span":null},"kind":{"StorageLive":16},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":25,"col":8},"end":{"line":25,"col":15}},"generated_from_span":null},"kind":{"StorageLive":18},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":25,"col":8},"end":{"line":25,"col":15}},"generated_from_span":null},"kind":{"StorageLive":3},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":25,"col":23},"end":{"line":25,"col":24}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":3},"ty":{"Deduplicated":207}},{"Use":{"Const":{"kind":{"Literal":{"Scalar":{"Signed":["I64","0"]}}},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"StorageLive":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"StorageLive":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":5},"ty":{"Deduplicated":335}},{"Use":{"Copy":{"kind":{"Local":1},"ty":{"Deduplicated":217}}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Regular":{"kind":{"Fun":{"Regular":12}},"generics":{"regions":[{"Body":21}],"types":[{"Deduplicated":207}],"const_generics":[],"trait_refs":[{"Deduplicated":332}]}}},"args":[{"Move":{"kind":{"Local":5},"ty":{"Deduplicated":335}}}],"dest":{"kind":{"Local":4},"ty":{"Deduplicated":334}}},"target":2,"on_unwind":1}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":24,"col":0},"end":{"line":34,"col":1}},"generated_from_span":null},"kind":"UnwindResume","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":26,"col":18},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"StorageDead":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"StorageLive":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":6},"ty":{"Deduplicated":336}},{"Use":{"Move":{"kind":{"Local":4},"ty":{"Deduplicated":334}}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":26,"col":4},"end":{"line":32,"col":5}},"generated_from_span":null},"kind":{"Goto":{"target":3}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"StorageLive":7},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"StorageLive":8},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"StorageLive":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":9},"ty":{"Deduplicated":389}},{"Ref":{"place":{"kind":{"Local":6},"ty":{"Deduplicated":336}},"kind":"Mut","ptr_metadata":{"Const":{"kind":{"Adt":[null,[]]},"ty":{"HashConsedValue":[221,{"Adt":{"id":"Tuple","generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}}]}}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":8},"ty":{"Deduplicated":387}},{"Ref":{"place":{"kind":{"Projection":[{"kind":{"Local":9},"ty":{"Deduplicated":389}},"Deref"]},"ty":{"HashConsedValue":[6236,{"Adt":{"id":{"Adt":6},"generics":{"regions":[{"Body":22}],"types":[{"Deduplicated":207}],"const_generics":[],"trait_refs":[{"Deduplicated":332}]}}}]}},"kind":"TwoPhaseMut","ptr_metadata":{"Const":{"kind":{"Adt":[null,[]]},"ty":{"Deduplicated":221}}}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Regular":{"kind":{"Fun":{"Regular":13}},"generics":{"regions":[{"Body":23},{"Body":25}],"types":[{"Deduplicated":207}],"const_generics":[],"trait_refs":[{"Deduplicated":332}]}}},"args":[{"Move":{"kind":{"Local":8},"ty":{"Deduplicated":387}}}],"dest":{"kind":{"Local":7},"ty":{"Deduplicated":5392}}},"target":4,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":26,"col":18},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"StorageDead":8},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":10},"ty":{"Deduplicated":390}},{"Discriminant":{"kind":{"Local":7},"ty":{"Deduplicated":5392}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"Switch":{"discr":{"Move":{"kind":{"Local":10},"ty":{"Deduplicated":390}}},"targets":{"SwitchInt":[{"Int":"Isize"},[[{"Scalar":{"Signed":["Isize","0"]}},5],[{"Scalar":{"Signed":["Isize","1"]}},6]],7]}}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":32,"col":4},"end":{"line":32,"col":5}},"generated_from_span":null},"kind":{"StorageDead":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":32,"col":4},"end":{"line":32,"col":5}},"generated_from_span":null},"kind":{"StorageDead":7},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":32,"col":4},"end":{"line":32,"col":5}},"generated_from_span":null},"kind":{"StorageDead":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":32,"col":4},"end":{"line":32,"col":5}},"generated_from_span":null},"kind":{"StorageDead":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":33,"col":4},"end":{"line":33,"col":7}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":3},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":34,"col":0},"end":{"line":34,"col":1}},"generated_from_span":null},"kind":{"StorageDead":3},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":34,"col":1},"end":{"line":34,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":26,"col":9},"end":{"line":26,"col":10}},"generated_from_span":null},"kind":{"StorageLive":11},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":26,"col":9},"end":{"line":26,"col":10}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":11},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Projection":[{"kind":{"Local":7},"ty":{"Deduplicated":5392}},{"Field":[{"Adt":[7,1]},0]}]},"ty":{"HashConsedValue":[6237,{"Ref":[{"Body":29},{"Deduplicated":207},"Shared"]}]}},"Deref"]},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":27,"col":11},"end":{"line":27,"col":24}},"generated_from_span":null},"kind":{"StorageLive":12},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":27,"col":11},"end":{"line":27,"col":12}},"generated_from_span":null},"kind":{"StorageLive":13},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":27,"col":11},"end":{"line":27,"col":12}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":13},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":11},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":27,"col":15},"end":{"line":27,"col":24}},"generated_from_span":null},"kind":{"StorageLive":14},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":27,"col":15},"end":{"line":27,"col":24}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":14},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":2},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":27,"col":11},"end":{"line":27,"col":24}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":12},"ty":{"Deduplicated":211}},{"BinaryOp":["Gt",{"Move":{"kind":{"Local":13},"ty":{"Deduplicated":207}}},{"Move":{"kind":{"Local":14},"ty":{"Deduplicated":207}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":27,"col":11},"end":{"line":27,"col":24}},"generated_from_span":null},"kind":{"Switch":{"discr":{"Move":{"kind":{"Local":12},"ty":{"Deduplicated":211}}},"targets":{"If":[8,9]}}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":26,"col":14},"end":{"line":26,"col":19}},"generated_from_span":null},"kind":{"Abort":"UndefinedBehavior"},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":27,"col":23},"end":{"line":27,"col":24}},"generated_from_span":null},"kind":{"StorageDead":14},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":27,"col":23},"end":{"line":27,"col":24}},"generated_from_span":null},"kind":{"StorageDead":13},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":28,"col":19},"end":{"line":28,"col":20}},"generated_from_span":null},"kind":{"StorageLive":15},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":28,"col":19},"end":{"line":28,"col":20}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":15},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":11},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":28,"col":12},"end":{"line":28,"col":20}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":16},"ty":{"Deduplicated":212}},{"BinaryOp":["AddChecked",{"Copy":{"kind":{"Local":3},"ty":{"Deduplicated":207}}},{"Copy":{"kind":{"Local":15},"ty":{"Deduplicated":207}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":28,"col":12},"end":{"line":28,"col":20}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Projection":[{"kind":{"Local":16},"ty":{"Deduplicated":212}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":211}}},"expected":false,"check_kind":{"Overflow":[{"Add":"Wrap"},{"Copy":{"kind":{"Local":3},"ty":{"Deduplicated":207}}},{"Move":{"kind":{"Local":19},"ty":{"Deduplicated":207}}}]}},"target":10,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":27,"col":23},"end":{"line":27,"col":24}},"generated_from_span":null},"kind":{"StorageDead":14},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":27,"col":23},"end":{"line":27,"col":24}},"generated_from_span":null},"kind":{"StorageDead":13},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":30,"col":19},"end":{"line":30,"col":20}},"generated_from_span":null},"kind":{"StorageLive":17},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":30,"col":19},"end":{"line":30,"col":20}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":17},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":11},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":30,"col":12},"end":{"line":30,"col":20}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":18},"ty":{"Deduplicated":212}},{"BinaryOp":["SubChecked",{"Copy":{"kind":{"Local":3},"ty":{"Deduplicated":207}}},{"Copy":{"kind":{"Local":17},"ty":{"Deduplicated":207}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":30,"col":12},"end":{"line":30,"col":20}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Projection":[{"kind":{"Local":18},"ty":{"Deduplicated":212}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":211}}},"expected":false,"check_kind":{"Overflow":[{"Sub":"Wrap"},{"Copy":{"kind":{"Local":3},"ty":{"Deduplicated":207}}},{"Move":{"kind":{"Local":21},"ty":{"Deduplicated":207}}}]}},"target":11,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":28,"col":12},"end":{"line":28,"col":20}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":3},"ty":{"Deduplicated":207}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":16},"ty":{"Deduplicated":212}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":28,"col":19},"end":{"line":28,"col":20}},"generated_from_span":null},"kind":{"StorageDead":15},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":27,"col":8},"end":{"line":31,"col":9}},"generated_from_span":null},"kind":{"Goto":{"target":12}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":30,"col":12},"end":{"line":30,"col":20}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":3},"ty":{"Deduplicated":207}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":18},"ty":{"Deduplicated":212}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":30,"col":19},"end":{"line":30,"col":20}},"generated_from_span":null},"kind":{"StorageDead":17},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":27,"col":8},"end":{"line":31,"col":9}},"generated_from_span":null},"kind":{"Goto":{"target":12}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":31,"col":8},"end":{"line":31,"col":9}},"generated_from_span":null},"kind":{"StorageDead":12},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":32,"col":4},"end":{"line":32,"col":5}},"generated_from_span":null},"kind":{"StorageDead":11},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":32,"col":4},"end":{"line":32,"col":5}},"generated_from_span":null},"kind":{"StorageDead":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":32,"col":4},"end":{"line":32,"col":5}},"generated_from_span":null},"kind":{"StorageDead":7},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":26,"col":4},"end":{"line":32,"col":5}},"generated_from_span":null},"kind":{"Goto":{"target":3}},"comments_before":[]}}],"comments":[]}}},{"def_id":2,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["strategy_len",0]}],"span":{"data":{"file_id":0,"beg":{"line":45,"col":0},"end":{"line":51,"col":1}},"generated_from_span":null},"source_text":"pub fn strategy_len(s: &Strategy) -> usize {\n match s {\n Strategy::Empty => 0,\n Strategy::IntKeyed { len } => *len,\n Strategy::StrKeyed { len, capacity: _ } => *len,\n }\n}","attr_info":{"attributes":[],"inline":"Never","rename":null,"public":true},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"HashConsedValue":[6238,{"Ref":[{"Var":{"Bound":[0,0]}},{"HashConsedValue":[620,{"Adt":{"id":{"Adt":1},"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}}]},"Shared"]}]}],"output":{"Deduplicated":614}},"src":"TopLevel","is_global_initializer":null,"body":{"Unstructured":{"span":{"data":{"file_id":0,"beg":{"line":45,"col":0},"end":{"line":51,"col":1}},"generated_from_span":null},"bound_body_regions":5,"locals":{"arg_count":1,"locals":[{"index":0,"name":null,"span":{"data":{"file_id":0,"beg":{"line":45,"col":37},"end":{"line":45,"col":42}},"generated_from_span":null},"ty":{"Deduplicated":614}},{"index":1,"name":"s","span":{"data":{"file_id":0,"beg":{"line":45,"col":20},"end":{"line":45,"col":21}},"generated_from_span":null},"ty":{"HashConsedValue":[624,{"Ref":[{"Body":1},{"Deduplicated":620},"Shared"]}]}},{"index":2,"name":null,"span":{"data":{"file_id":0,"beg":{"line":47,"col":8},"end":{"line":47,"col":23}},"generated_from_span":null},"ty":{"Deduplicated":390}},{"index":3,"name":"len","span":{"data":{"file_id":0,"beg":{"line":48,"col":29},"end":{"line":48,"col":32}},"generated_from_span":null},"ty":{"HashConsedValue":[627,{"Ref":[{"Body":3},{"Deduplicated":614},"Shared"]}]}},{"index":4,"name":"len","span":{"data":{"file_id":0,"beg":{"line":49,"col":29},"end":{"line":49,"col":32}},"generated_from_span":null},"ty":{"HashConsedValue":[628,{"Ref":[{"Body":4},{"Deduplicated":614},"Shared"]}]}}]},"body":[{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":46,"col":10},"end":{"line":46,"col":11}},"generated_from_span":null},"kind":{"StorageLive":2},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":46,"col":10},"end":{"line":46,"col":11}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":2},"ty":{"Deduplicated":390}},{"Discriminant":{"kind":{"Projection":[{"kind":{"Local":1},"ty":{"Deduplicated":624}},"Deref"]},"ty":{"Deduplicated":620}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":46,"col":4},"end":{"line":46,"col":11}},"generated_from_span":null},"kind":{"Switch":{"discr":{"Move":{"kind":{"Local":2},"ty":{"Deduplicated":390}}},"targets":{"SwitchInt":[{"Int":"Isize"},[[{"Scalar":{"Signed":["Isize","0"]}},1],[{"Scalar":{"Signed":["Isize","1"]}},2],[{"Scalar":{"Signed":["Isize","2"]}},3]],4]}}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":47,"col":27},"end":{"line":47,"col":28}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":614}},{"Use":{"Const":{"kind":{"Literal":{"Scalar":{"Unsigned":["Usize","0"]}}},"ty":{"Deduplicated":614}}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":51,"col":1},"end":{"line":51,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":48,"col":29},"end":{"line":48,"col":32}},"generated_from_span":null},"kind":{"StorageLive":3},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":48,"col":29},"end":{"line":48,"col":32}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":3},"ty":{"Deduplicated":627}},{"Ref":{"place":{"kind":{"Projection":[{"kind":{"Projection":[{"kind":{"Local":1},"ty":{"Deduplicated":624}},"Deref"]},"ty":{"Deduplicated":620}},{"Field":[{"Adt":[1,1]},0]}]},"ty":{"Deduplicated":614}},"kind":"Shared","ptr_metadata":{"Const":{"kind":{"Adt":[null,[]]},"ty":{"Deduplicated":221}}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":48,"col":38},"end":{"line":48,"col":42}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":614}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Local":3},"ty":{"Deduplicated":627}},"Deref"]},"ty":{"Deduplicated":614}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":48,"col":41},"end":{"line":48,"col":42}},"generated_from_span":null},"kind":{"StorageDead":3},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":51,"col":1},"end":{"line":51,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":49,"col":29},"end":{"line":49,"col":32}},"generated_from_span":null},"kind":{"StorageLive":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":49,"col":29},"end":{"line":49,"col":32}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":4},"ty":{"Deduplicated":628}},{"Ref":{"place":{"kind":{"Projection":[{"kind":{"Projection":[{"kind":{"Local":1},"ty":{"Deduplicated":624}},"Deref"]},"ty":{"Deduplicated":620}},{"Field":[{"Adt":[1,2]},0]}]},"ty":{"Deduplicated":614}},"kind":"Shared","ptr_metadata":{"Const":{"kind":{"Adt":[null,[]]},"ty":{"Deduplicated":221}}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":49,"col":51},"end":{"line":49,"col":55}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":614}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Local":4},"ty":{"Deduplicated":628}},"Deref"]},"ty":{"Deduplicated":614}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":49,"col":54},"end":{"line":49,"col":55}},"generated_from_span":null},"kind":{"StorageDead":4},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":51,"col":1},"end":{"line":51,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":46,"col":10},"end":{"line":46,"col":11}},"generated_from_span":null},"kind":{"Abort":"UndefinedBehavior"},"comments_before":[]}}],"comments":[]}}},{"def_id":3,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["parse_one",0]}],"span":{"data":{"file_id":0,"beg":{"line":61,"col":0},"end":{"line":68,"col":1}},"generated_from_span":null},"source_text":"fn parse_one(raw: i64) -> PyResult {\n match raw {\n i64::MIN => Ok(Token::Halt),\n 0 => Err(\"halt-zero forbidden\"),\n v if v > 0 => Ok(Token::Add(v)),\n v => Ok(Token::Sub(-v)),\n }\n}","attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":207}],"output":{"HashConsedValue":[6239,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"HashConsedValue":[634,{"Adt":{"id":{"Adt":2},"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}}]},{"Deduplicated":198}],"const_generics":[],"trait_refs":[{"HashConsedValue":[636,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[635,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":634}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":634}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":205}]}}}]}},"src":"TopLevel","is_global_initializer":null,"body":{"Unstructured":{"span":{"data":{"file_id":0,"beg":{"line":61,"col":0},"end":{"line":68,"col":1}},"generated_from_span":null},"bound_body_regions":35,"locals":{"arg_count":1,"locals":[{"index":0,"name":null,"span":{"data":{"file_id":0,"beg":{"line":61,"col":26},"end":{"line":61,"col":41}},"generated_from_span":null},"ty":{"HashConsedValue":[5399,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":634},{"HashConsedValue":[651,{"Ref":[{"Body":6},{"Deduplicated":197},"Shared"]}]}],"const_generics":[],"trait_refs":[{"Deduplicated":636},{"HashConsedValue":[5398,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[5397,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":651}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":651}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},{"index":1,"name":"raw","span":{"data":{"file_id":0,"beg":{"line":61,"col":13},"end":{"line":61,"col":16}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":2,"name":null,"span":{"data":{"file_id":0,"beg":{"line":62,"col":10},"end":{"line":62,"col":13}},"generated_from_span":null},"ty":{"HashConsedValue":[372,{"Ref":[{"Body":10},{"Deduplicated":207},"Shared"]}]}},{"index":3,"name":null,"span":{"data":{"file_id":0,"beg":{"line":63,"col":23},"end":{"line":63,"col":34}},"generated_from_span":null},"ty":{"Deduplicated":634}},{"index":4,"name":null,"span":{"data":{"file_id":0,"beg":{"line":64,"col":17},"end":{"line":64,"col":38}},"generated_from_span":null},"ty":{"HashConsedValue":[657,{"Ref":[{"Body":11},{"Deduplicated":197},"Shared"]}]}},{"index":5,"name":null,"span":{"data":{"file_id":0,"beg":{"line":64,"col":17},"end":{"line":64,"col":38}},"generated_from_span":null},"ty":{"HashConsedValue":[658,{"Ref":[{"Body":12},{"Deduplicated":197},"Shared"]}]}},{"index":6,"name":"v","span":{"data":{"file_id":0,"beg":{"line":65,"col":8},"end":{"line":65,"col":9}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":7,"name":"v","span":{"data":{"file_id":0,"beg":{"line":65,"col":8},"end":{"line":65,"col":9}},"generated_from_span":null},"ty":{"HashConsedValue":[378,{"Ref":[{"Body":13},{"Deduplicated":207},"Shared"]}]}},{"index":8,"name":null,"span":{"data":{"file_id":0,"beg":{"line":65,"col":13},"end":{"line":65,"col":18}},"generated_from_span":null},"ty":{"Deduplicated":211}},{"index":9,"name":null,"span":{"data":{"file_id":0,"beg":{"line":65,"col":13},"end":{"line":65,"col":14}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":10,"name":null,"span":{"data":{"file_id":0,"beg":{"line":65,"col":25},"end":{"line":65,"col":38}},"generated_from_span":null},"ty":{"Deduplicated":634}},{"index":11,"name":null,"span":{"data":{"file_id":0,"beg":{"line":65,"col":36},"end":{"line":65,"col":37}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":12,"name":"v","span":{"data":{"file_id":0,"beg":{"line":66,"col":8},"end":{"line":66,"col":9}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":13,"name":null,"span":{"data":{"file_id":0,"beg":{"line":66,"col":16},"end":{"line":66,"col":30}},"generated_from_span":null},"ty":{"Deduplicated":634}},{"index":14,"name":null,"span":{"data":{"file_id":0,"beg":{"line":66,"col":27},"end":{"line":66,"col":29}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":15,"name":null,"span":{"data":{"file_id":0,"beg":{"line":66,"col":28},"end":{"line":66,"col":29}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":16,"name":null,"span":{"data":{"file_id":0,"beg":{"line":66,"col":27},"end":{"line":66,"col":29}},"generated_from_span":null},"ty":{"Deduplicated":211}}]},"body":[{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":62,"col":4},"end":{"line":62,"col":13}},"generated_from_span":null},"kind":{"StorageLive":2},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":62,"col":4},"end":{"line":62,"col":13}},"generated_from_span":null},"kind":{"StorageLive":16},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":62,"col":4},"end":{"line":62,"col":13}},"generated_from_span":null},"kind":{"Switch":{"discr":{"Copy":{"kind":{"Local":1},"ty":{"Deduplicated":207}}},"targets":{"SwitchInt":[{"Int":"I64"},[[{"Scalar":{"Signed":["I64","-9223372036854775808"]}},1],[{"Scalar":{"Signed":["I64","0"]}},2]],3]}}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":63,"col":23},"end":{"line":63,"col":34}},"generated_from_span":null},"kind":{"StorageLive":3},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":63,"col":23},"end":{"line":63,"col":34}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":3},"ty":{"Deduplicated":634}},{"Aggregate":[{"Adt":[{"id":{"Adt":2},"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}},2,null]},[]]}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":63,"col":20},"end":{"line":63,"col":35}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":5399}},{"Aggregate":[{"Adt":[{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":634},{"HashConsedValue":[6240,{"Ref":[{"Body":14},{"Deduplicated":197},"Shared"]}]}],"const_generics":[],"trait_refs":[{"Deduplicated":636},{"HashConsedValue":[6244,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[6242,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[6241,{"Ref":[{"Body":18},{"Deduplicated":197},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[6243,{"Ref":[{"Body":16},{"Deduplicated":197},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}]}},0,null]},[{"Move":{"kind":{"Local":3},"ty":{"Deduplicated":634}}}]]}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":63,"col":34},"end":{"line":63,"col":35}},"generated_from_span":null},"kind":{"StorageDead":3},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":68,"col":1},"end":{"line":68,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":64,"col":17},"end":{"line":64,"col":38}},"generated_from_span":null},"kind":{"StorageLive":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":64,"col":17},"end":{"line":64,"col":38}},"generated_from_span":null},"kind":{"StorageLive":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":64,"col":17},"end":{"line":64,"col":38}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":5},"ty":{"Deduplicated":658}},{"Use":{"Const":{"kind":{"Literal":{"Str":"halt-zero forbidden"}},"ty":{"HashConsedValue":[6245,{"Ref":[{"Body":19},{"Deduplicated":197},"Shared"]}]}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":64,"col":17},"end":{"line":64,"col":38}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":4},"ty":{"Deduplicated":657}},{"Ref":{"place":{"kind":{"Projection":[{"kind":{"Local":5},"ty":{"Deduplicated":658}},"Deref"]},"ty":{"Deduplicated":197}},"kind":"Shared","ptr_metadata":{"Copy":{"kind":{"Projection":[{"kind":{"Local":5},"ty":{"Deduplicated":658}},"PtrMetadata"]},"ty":{"Deduplicated":614}}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":64,"col":13},"end":{"line":64,"col":39}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":5399}},{"Aggregate":[{"Adt":[{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":634},{"HashConsedValue":[6246,{"Ref":[{"Body":20},{"Deduplicated":197},"Shared"]}]}],"const_generics":[],"trait_refs":[{"Deduplicated":636},{"HashConsedValue":[6250,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[6248,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[6247,{"Ref":[{"Body":24},{"Deduplicated":197},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[6249,{"Ref":[{"Body":22},{"Deduplicated":197},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}]}},1,null]},[{"Move":{"kind":{"Local":4},"ty":{"Deduplicated":657}}}]]}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":64,"col":38},"end":{"line":64,"col":39}},"generated_from_span":null},"kind":{"StorageDead":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":64,"col":38},"end":{"line":64,"col":39}},"generated_from_span":null},"kind":{"StorageDead":5},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":68,"col":1},"end":{"line":68,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":65,"col":8},"end":{"line":65,"col":9}},"generated_from_span":null},"kind":{"StorageLive":7},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":8},"end":{"line":65,"col":9}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":7},"ty":{"Deduplicated":378}},{"Ref":{"place":{"kind":{"Local":1},"ty":{"Deduplicated":207}},"kind":"Shared","ptr_metadata":{"Const":{"kind":{"Adt":[null,[]]},"ty":{"Deduplicated":221}}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":62,"col":10},"end":{"line":62,"col":13}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":2},"ty":{"Deduplicated":372}},{"Ref":{"place":{"kind":{"Local":1},"ty":{"Deduplicated":207}},"kind":"Shallow","ptr_metadata":{"Const":{"kind":{"Adt":[null,[]]},"ty":{"Deduplicated":221}}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":13},"end":{"line":65,"col":18}},"generated_from_span":null},"kind":{"StorageLive":8},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":13},"end":{"line":65,"col":14}},"generated_from_span":null},"kind":{"StorageLive":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":13},"end":{"line":65,"col":14}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":9},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Local":7},"ty":{"Deduplicated":378}},"Deref"]},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":13},"end":{"line":65,"col":18}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":8},"ty":{"Deduplicated":211}},{"BinaryOp":["Gt",{"Move":{"kind":{"Local":9},"ty":{"Deduplicated":207}}},{"Const":{"kind":{"Literal":{"Scalar":{"Signed":["I64","0"]}}},"ty":{"Deduplicated":207}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":65,"col":13},"end":{"line":65,"col":18}},"generated_from_span":null},"kind":{"Switch":{"discr":{"Move":{"kind":{"Local":8},"ty":{"Deduplicated":211}}},"targets":{"If":[4,5]}}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":65,"col":17},"end":{"line":65,"col":18}},"generated_from_span":null},"kind":{"StorageDead":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":17},"end":{"line":65,"col":18}},"generated_from_span":null},"kind":{"StorageDead":8},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":8},"end":{"line":65,"col":9}},"generated_from_span":null},"kind":{"StorageLive":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":8},"end":{"line":65,"col":9}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":6},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":1},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":25},"end":{"line":65,"col":38}},"generated_from_span":null},"kind":{"StorageLive":10},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":36},"end":{"line":65,"col":37}},"generated_from_span":null},"kind":{"StorageLive":11},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":36},"end":{"line":65,"col":37}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":11},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":6},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":25},"end":{"line":65,"col":38}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":10},"ty":{"Deduplicated":634}},{"Aggregate":[{"Adt":[{"id":{"Adt":2},"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}},0,null]},[{"Move":{"kind":{"Local":11},"ty":{"Deduplicated":207}}}]]}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":37},"end":{"line":65,"col":38}},"generated_from_span":null},"kind":{"StorageDead":11},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":22},"end":{"line":65,"col":39}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":5399}},{"Aggregate":[{"Adt":[{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":634},{"HashConsedValue":[6251,{"Ref":[{"Body":25},{"Deduplicated":197},"Shared"]}]}],"const_generics":[],"trait_refs":[{"Deduplicated":636},{"HashConsedValue":[6255,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[6253,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[6252,{"Ref":[{"Body":29},{"Deduplicated":197},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[6254,{"Ref":[{"Body":27},{"Deduplicated":197},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}]}},0,null]},[{"Move":{"kind":{"Local":10},"ty":{"Deduplicated":634}}}]]}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":38},"end":{"line":65,"col":39}},"generated_from_span":null},"kind":{"StorageDead":10},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":38},"end":{"line":65,"col":39}},"generated_from_span":null},"kind":{"StorageDead":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":38},"end":{"line":65,"col":39}},"generated_from_span":null},"kind":{"StorageDead":7},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":68,"col":1},"end":{"line":68,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":65,"col":17},"end":{"line":65,"col":18}},"generated_from_span":null},"kind":{"StorageDead":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":17},"end":{"line":65,"col":18}},"generated_from_span":null},"kind":{"StorageDead":8},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":65,"col":38},"end":{"line":65,"col":39}},"generated_from_span":null},"kind":{"StorageDead":7},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":66,"col":8},"end":{"line":66,"col":9}},"generated_from_span":null},"kind":{"StorageLive":12},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":66,"col":8},"end":{"line":66,"col":9}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":12},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":1},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":66,"col":16},"end":{"line":66,"col":30}},"generated_from_span":null},"kind":{"StorageLive":13},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":66,"col":27},"end":{"line":66,"col":29}},"generated_from_span":null},"kind":{"StorageLive":14},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":66,"col":28},"end":{"line":66,"col":29}},"generated_from_span":null},"kind":{"StorageLive":15},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":66,"col":28},"end":{"line":66,"col":29}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":15},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":12},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":66,"col":27},"end":{"line":66,"col":29}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":16},"ty":{"Deduplicated":211}},{"BinaryOp":["Eq",{"Copy":{"kind":{"Local":15},"ty":{"Deduplicated":207}}},{"Const":{"kind":{"Literal":{"Scalar":{"Signed":["I64","-9223372036854775808"]}}},"ty":{"Deduplicated":207}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":66,"col":27},"end":{"line":66,"col":29}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Local":16},"ty":{"Deduplicated":211}}},"expected":false,"check_kind":{"OverflowNeg":{"Copy":{"kind":{"Local":15},"ty":{"Deduplicated":207}}}}},"target":7,"on_unwind":6}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":61,"col":0},"end":{"line":68,"col":1}},"generated_from_span":null},"kind":"UnwindResume","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":66,"col":27},"end":{"line":66,"col":29}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":14},"ty":{"Deduplicated":207}},{"UnaryOp":[{"Neg":"Wrap"},{"Move":{"kind":{"Local":15},"ty":{"Deduplicated":207}}}]}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":66,"col":28},"end":{"line":66,"col":29}},"generated_from_span":null},"kind":{"StorageDead":15},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":66,"col":16},"end":{"line":66,"col":30}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":13},"ty":{"Deduplicated":634}},{"Aggregate":[{"Adt":[{"id":{"Adt":2},"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}},1,null]},[{"Move":{"kind":{"Local":14},"ty":{"Deduplicated":207}}}]]}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":66,"col":29},"end":{"line":66,"col":30}},"generated_from_span":null},"kind":{"StorageDead":14},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":66,"col":13},"end":{"line":66,"col":31}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":5399}},{"Aggregate":[{"Adt":[{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":634},{"HashConsedValue":[6256,{"Ref":[{"Body":30},{"Deduplicated":197},"Shared"]}]}],"const_generics":[],"trait_refs":[{"Deduplicated":636},{"HashConsedValue":[6260,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[6258,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[6257,{"Ref":[{"Body":34},{"Deduplicated":197},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[6259,{"Ref":[{"Body":32},{"Deduplicated":197},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}]}},0,null]},[{"Move":{"kind":{"Local":13},"ty":{"Deduplicated":634}}}]]}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":66,"col":30},"end":{"line":66,"col":31}},"generated_from_span":null},"kind":{"StorageDead":13},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":66,"col":30},"end":{"line":66,"col":31}},"generated_from_span":null},"kind":{"StorageDead":12},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":68,"col":1},"end":{"line":68,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}}],"comments":[]}}},{"def_id":4,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["desugar_mix",0]}],"span":{"data":{"file_id":0,"beg":{"line":71,"col":0},"end":{"line":82,"col":1}},"generated_from_span":null},"source_text":"pub fn desugar_mix(input: &[i64]) -> PyResult {\n let mut acc: i64 = 0;\n for &raw in input.iter() {\n let tok = parse_one(raw)?;\n match tok {\n Token::Add(v) => acc += v,\n Token::Sub(v) => acc -= v,\n Token::Halt => break,\n }\n }\n Ok(acc)\n}","attr_info":{"attributes":[],"inline":"Never","rename":null,"public":true},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":214}],"output":{"HashConsedValue":[6261,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":207},{"Deduplicated":198}],"const_generics":[],"trait_refs":[{"Deduplicated":332},{"Deduplicated":205}]}}}]}},"src":"TopLevel","is_global_initializer":null,"body":{"Unstructured":{"span":{"data":{"file_id":0,"beg":{"line":71,"col":0},"end":{"line":82,"col":1}},"generated_from_span":null},"bound_body_regions":147,"locals":{"arg_count":1,"locals":[{"index":0,"name":null,"span":{"data":{"file_id":0,"beg":{"line":71,"col":37},"end":{"line":71,"col":50}},"generated_from_span":null},"ty":{"HashConsedValue":[5453,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":207},{"Deduplicated":651}],"const_generics":[],"trait_refs":[{"Deduplicated":332},{"Deduplicated":5398}]}}}]}},{"index":1,"name":"input","span":{"data":{"file_id":0,"beg":{"line":71,"col":19},"end":{"line":71,"col":24}},"generated_from_span":null},"ty":{"HashConsedValue":[694,{"Ref":[{"Body":10},{"Deduplicated":213},"Shared"]}]}},{"index":2,"name":"acc","span":{"data":{"file_id":0,"beg":{"line":72,"col":8},"end":{"line":72,"col":15}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":3,"name":null,"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"ty":{"HashConsedValue":[696,{"Adt":{"id":{"Adt":6},"generics":{"regions":[{"Body":12}],"types":[{"Deduplicated":207}],"const_generics":[],"trait_refs":[{"Deduplicated":332}]}}}]}},{"index":4,"name":null,"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"ty":{"HashConsedValue":[697,{"Adt":{"id":{"Adt":6},"generics":{"regions":[{"Body":13}],"types":[{"Deduplicated":207}],"const_generics":[],"trait_refs":[{"Deduplicated":332}]}}}]}},{"index":5,"name":null,"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":21}},"generated_from_span":null},"ty":{"HashConsedValue":[698,{"Ref":[{"Body":14},{"Deduplicated":213},"Shared"]}]}},{"index":6,"name":"iter","span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"ty":{"HashConsedValue":[699,{"Adt":{"id":{"Adt":6},"generics":{"regions":[{"Body":15}],"types":[{"Deduplicated":207}],"const_generics":[],"trait_refs":[{"Deduplicated":332}]}}}]}},{"index":7,"name":null,"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"ty":{"HashConsedValue":[5456,{"Adt":{"id":{"Adt":7},"generics":{"regions":[],"types":[{"HashConsedValue":[709,{"Ref":[{"Body":22},{"Deduplicated":207},"Shared"]}]}],"const_generics":[],"trait_refs":[{"HashConsedValue":[5455,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[5454,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":709}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":709}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},{"index":8,"name":null,"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"ty":{"HashConsedValue":[718,{"Ref":[{"Body":27},{"HashConsedValue":[717,{"Adt":{"id":{"Adt":6},"generics":{"regions":[{"Body":28}],"types":[{"Deduplicated":207}],"const_generics":[],"trait_refs":[{"Deduplicated":332}]}}}]},"Mut"]}]}},{"index":9,"name":null,"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"ty":{"HashConsedValue":[720,{"Ref":[{"Body":29},{"HashConsedValue":[719,{"Adt":{"id":{"Adt":6},"generics":{"regions":[{"Body":30}],"types":[{"Deduplicated":207}],"const_generics":[],"trait_refs":[{"Deduplicated":332}]}}}]},"Mut"]}]}},{"index":10,"name":null,"span":{"data":{"file_id":0,"beg":{"line":73,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"ty":{"Deduplicated":390}},{"index":11,"name":"raw","span":{"data":{"file_id":0,"beg":{"line":73,"col":9},"end":{"line":73,"col":12}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":12,"name":"tok","span":{"data":{"file_id":0,"beg":{"line":74,"col":12},"end":{"line":74,"col":15}},"generated_from_span":null},"ty":{"Deduplicated":634}},{"index":13,"name":null,"span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":33}},"generated_from_span":null},"ty":{"HashConsedValue":[5462,{"Adt":{"id":{"Adt":8},"generics":{"regions":[],"types":[{"HashConsedValue":[5459,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"HashConsedValue":[835,{"Adt":{"id":{"Adt":9},"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}}]},{"HashConsedValue":[881,{"Ref":[{"Body":57},{"Deduplicated":197},"Shared"]}]}],"const_generics":[],"trait_refs":[{"HashConsedValue":[837,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[836,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":835}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":835}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[5458,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[5457,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":881}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":881}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]},{"Deduplicated":634}],"const_generics":[],"trait_refs":[{"HashConsedValue":[5461,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[5460,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":5459}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":5459}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":636}]}}}]}},{"index":14,"name":null,"span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":32}},"generated_from_span":null},"ty":{"HashConsedValue":[5465,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":634},{"HashConsedValue":[910,{"Ref":[{"Body":71},{"Deduplicated":197},"Shared"]}]}],"const_generics":[],"trait_refs":[{"Deduplicated":636},{"HashConsedValue":[5464,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[5463,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":910}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":910}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},{"index":15,"name":null,"span":{"data":{"file_id":0,"beg":{"line":74,"col":28},"end":{"line":74,"col":31}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":16,"name":null,"span":{"data":{"file_id":0,"beg":{"line":74,"col":32},"end":{"line":74,"col":33}},"generated_from_span":null},"ty":{"Deduplicated":390}},{"index":17,"name":"residual","span":{"data":{"file_id":0,"beg":{"line":74,"col":32},"end":{"line":74,"col":33}},"generated_from_span":null},"ty":{"HashConsedValue":[5468,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":835},{"HashConsedValue":[916,{"Ref":[{"Body":74},{"Deduplicated":197},"Shared"]}]}],"const_generics":[],"trait_refs":[{"Deduplicated":837},{"HashConsedValue":[5467,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[5466,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":916}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":916}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},{"index":18,"name":null,"span":{"data":{"file_id":0,"beg":{"line":74,"col":32},"end":{"line":74,"col":33}},"generated_from_span":null},"ty":{"HashConsedValue":[5471,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":835},{"HashConsedValue":[922,{"Ref":[{"Body":77},{"Deduplicated":197},"Shared"]}]}],"const_generics":[],"trait_refs":[{"Deduplicated":837},{"HashConsedValue":[5470,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[5469,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":922}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":922}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},{"index":19,"name":"val","span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":33}},"generated_from_span":null},"ty":{"Deduplicated":634}},{"index":20,"name":null,"span":{"data":{"file_id":0,"beg":{"line":76,"col":12},"end":{"line":76,"col":25}},"generated_from_span":null},"ty":{"Deduplicated":390}},{"index":21,"name":"v","span":{"data":{"file_id":0,"beg":{"line":76,"col":23},"end":{"line":76,"col":24}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":22,"name":null,"span":{"data":{"file_id":0,"beg":{"line":76,"col":36},"end":{"line":76,"col":37}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":23,"name":null,"span":{"data":{"file_id":0,"beg":{"line":76,"col":29},"end":{"line":76,"col":37}},"generated_from_span":null},"ty":{"Deduplicated":212}},{"index":24,"name":"v","span":{"data":{"file_id":0,"beg":{"line":77,"col":23},"end":{"line":77,"col":24}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":25,"name":null,"span":{"data":{"file_id":0,"beg":{"line":77,"col":36},"end":{"line":77,"col":37}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":26,"name":null,"span":{"data":{"file_id":0,"beg":{"line":77,"col":29},"end":{"line":77,"col":37}},"generated_from_span":null},"ty":{"Deduplicated":212}},{"index":27,"name":null,"span":{"data":{"file_id":0,"beg":{"line":81,"col":7},"end":{"line":81,"col":10}},"generated_from_span":null},"ty":{"Deduplicated":207}}]},"body":[{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":72,"col":8},"end":{"line":72,"col":15}},"generated_from_span":null},"kind":{"StorageLive":10},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":72,"col":8},"end":{"line":72,"col":15}},"generated_from_span":null},"kind":{"StorageLive":16},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":72,"col":8},"end":{"line":72,"col":15}},"generated_from_span":null},"kind":{"StorageLive":20},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":72,"col":8},"end":{"line":72,"col":15}},"generated_from_span":null},"kind":{"StorageLive":23},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":72,"col":8},"end":{"line":72,"col":15}},"generated_from_span":null},"kind":{"StorageLive":26},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":72,"col":8},"end":{"line":72,"col":15}},"generated_from_span":null},"kind":{"StorageLive":2},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":72,"col":23},"end":{"line":72,"col":24}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":2},"ty":{"Deduplicated":207}},{"Use":{"Const":{"kind":{"Literal":{"Scalar":{"Signed":["I64","0"]}}},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"StorageLive":3},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"StorageLive":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":21}},"generated_from_span":null},"kind":{"StorageLive":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":21}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":5},"ty":{"Deduplicated":698}},{"Ref":{"place":{"kind":{"Projection":[{"kind":{"Local":1},"ty":{"Deduplicated":694}},"Deref"]},"ty":{"Deduplicated":213}},"kind":"Shared","ptr_metadata":{"Copy":{"kind":{"Projection":[{"kind":{"Local":1},"ty":{"Deduplicated":694}},"PtrMetadata"]},"ty":{"Deduplicated":614}}}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Regular":{"kind":{"Fun":{"Regular":14}},"generics":{"regions":[{"Body":81}],"types":[{"Deduplicated":207}],"const_generics":[],"trait_refs":[{"Deduplicated":332}]}}},"args":[{"Move":{"kind":{"Local":5},"ty":{"Deduplicated":698}}}],"dest":{"kind":{"Local":4},"ty":{"Deduplicated":697}}},"target":2,"on_unwind":1}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":71,"col":0},"end":{"line":82,"col":1}},"generated_from_span":null},"kind":"UnwindResume","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":73,"col":27},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"StorageDead":5},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Regular":{"kind":{"Fun":{"Regular":15}},"generics":{"regions":[],"types":[{"HashConsedValue":[5472,{"Adt":{"id":{"Adt":6},"generics":{"regions":[{"Body":82}],"types":[{"Deduplicated":207}],"const_generics":[],"trait_refs":[{"Deduplicated":332}]}}}]}],"const_generics":[],"trait_refs":[{"HashConsedValue":[6269,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[6268,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":5472}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":5472}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6270,{"kind":{"TraitImpl":{"id":1,"generics":{"regions":[{"Body":82}],"types":[{"Deduplicated":207}],"const_generics":[],"trait_refs":[{"Deduplicated":332}]}}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":5472}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"args":[{"Move":{"kind":{"Local":4},"ty":{"Deduplicated":697}}}],"dest":{"kind":{"Local":3},"ty":{"Deduplicated":696}}},"target":3,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":73,"col":27},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"StorageDead":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"StorageLive":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":6},"ty":{"Deduplicated":699}},{"Use":{"Move":{"kind":{"Local":3},"ty":{"Deduplicated":696}}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":73,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"Goto":{"target":4}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"StorageLive":7},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"StorageLive":8},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"StorageLive":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":9},"ty":{"Deduplicated":720}},{"Ref":{"place":{"kind":{"Local":6},"ty":{"Deduplicated":699}},"kind":"Mut","ptr_metadata":{"Const":{"kind":{"Adt":[null,[]]},"ty":{"Deduplicated":221}}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":8},"ty":{"Deduplicated":718}},{"Ref":{"place":{"kind":{"Projection":[{"kind":{"Local":9},"ty":{"Deduplicated":720}},"Deref"]},"ty":{"HashConsedValue":[6271,{"Adt":{"id":{"Adt":6},"generics":{"regions":[{"Body":90}],"types":[{"Deduplicated":207}],"const_generics":[],"trait_refs":[{"Deduplicated":332}]}}}]}},"kind":"TwoPhaseMut","ptr_metadata":{"Const":{"kind":{"Adt":[null,[]]},"ty":{"Deduplicated":221}}}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Regular":{"kind":{"Fun":{"Regular":13}},"generics":{"regions":[{"Body":91},{"Body":93}],"types":[{"Deduplicated":207}],"const_generics":[],"trait_refs":[{"Deduplicated":332}]}}},"args":[{"Move":{"kind":{"Local":8},"ty":{"Deduplicated":718}}}],"dest":{"kind":{"Local":7},"ty":{"Deduplicated":5456}}},"target":5,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":73,"col":27},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"StorageDead":8},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":10},"ty":{"Deduplicated":390}},{"Discriminant":{"kind":{"Local":7},"ty":{"Deduplicated":5456}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"Switch":{"discr":{"Move":{"kind":{"Local":10},"ty":{"Deduplicated":390}}},"targets":{"SwitchInt":[{"Int":"Isize"},[[{"Scalar":{"Signed":["Isize","0"]}},9],[{"Scalar":{"Signed":["Isize","1"]}},6]],7]}}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":73,"col":9},"end":{"line":73,"col":12}},"generated_from_span":null},"kind":{"StorageLive":11},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":73,"col":9},"end":{"line":73,"col":12}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":11},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Projection":[{"kind":{"Local":7},"ty":{"Deduplicated":5456}},{"Field":[{"Adt":[7,1]},0]}]},"ty":{"HashConsedValue":[6272,{"Ref":[{"Body":97},{"Deduplicated":207},"Shared"]}]}},"Deref"]},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":12},"end":{"line":74,"col":15}},"generated_from_span":null},"kind":{"StorageLive":12},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"StorageLive":13},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":32}},"generated_from_span":null},"kind":{"StorageLive":14},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":28},"end":{"line":74,"col":31}},"generated_from_span":null},"kind":{"StorageLive":15},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":28},"end":{"line":74,"col":31}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":15},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":11},"ty":{"Deduplicated":207}}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":32}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Regular":{"kind":{"Fun":{"Regular":3}},"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}},"args":[{"Move":{"kind":{"Local":15},"ty":{"Deduplicated":207}}}],"dest":{"kind":{"Local":14},"ty":{"Deduplicated":5465}}},"target":8,"on_unwind":1}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":73,"col":16},"end":{"line":73,"col":28}},"generated_from_span":null},"kind":{"Abort":"UndefinedBehavior"},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":74,"col":31},"end":{"line":74,"col":32}},"generated_from_span":null},"kind":{"StorageDead":15},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Regular":{"kind":{"Fun":{"Regular":16}},"generics":{"regions":[],"types":[{"Deduplicated":634},{"HashConsedValue":[5478,{"Ref":[{"Body":98},{"Deduplicated":197},"Shared"]}]}],"const_generics":[],"trait_refs":[{"Deduplicated":636},{"HashConsedValue":[6274,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[6273,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":5478}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":5478}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"args":[{"Move":{"kind":{"Local":14},"ty":{"Deduplicated":5465}}}],"dest":{"kind":{"Local":13},"ty":{"Deduplicated":5462}}},"target":10,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":7},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":3},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":81,"col":7},"end":{"line":81,"col":10}},"generated_from_span":null},"kind":{"StorageLive":27},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":81,"col":7},"end":{"line":81,"col":10}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":27},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":2},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":81,"col":4},"end":{"line":81,"col":11}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":5453}},{"Aggregate":[{"Adt":[{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":207},{"HashConsedValue":[6275,{"Ref":[{"Body":103},{"Deduplicated":197},"Shared"]}]}],"const_generics":[],"trait_refs":[{"Deduplicated":332},{"HashConsedValue":[6279,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[6277,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[6276,{"Ref":[{"Body":107},{"Deduplicated":197},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[6278,{"Ref":[{"Body":105},{"Deduplicated":197},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}]}},0,null]},[{"Move":{"kind":{"Local":27},"ty":{"Deduplicated":207}}}]]}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":81,"col":10},"end":{"line":81,"col":11}},"generated_from_span":null},"kind":{"StorageDead":27},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":82,"col":0},"end":{"line":82,"col":1}},"generated_from_span":null},"kind":{"StorageDead":2},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":82,"col":1},"end":{"line":82,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":74,"col":32},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"StorageDead":14},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":16},"ty":{"Deduplicated":390}},{"Discriminant":{"kind":{"Local":13},"ty":{"Deduplicated":5462}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"Switch":{"discr":{"Move":{"kind":{"Local":16},"ty":{"Deduplicated":390}}},"targets":{"SwitchInt":[{"Int":"Isize"},[[{"Scalar":{"Signed":["Isize","0"]}},11],[{"Scalar":{"Signed":["Isize","1"]}},12]],13]}}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"StorageLive":19},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":19},"ty":{"Deduplicated":634}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":13},"ty":{"Deduplicated":5462}},{"Field":[{"Adt":[8,0]},0]}]},"ty":{"Deduplicated":634}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":12},"ty":{"Deduplicated":634}},{"Use":{"Move":{"kind":{"Local":19},"ty":{"Deduplicated":634}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":32},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"StorageDead":19},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":33},"end":{"line":74,"col":34}},"generated_from_span":null},"kind":{"StorageDead":13},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":75,"col":14},"end":{"line":75,"col":17}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":20},"ty":{"Deduplicated":390}},{"Discriminant":{"kind":{"Local":12},"ty":{"Deduplicated":634}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":75,"col":8},"end":{"line":75,"col":17}},"generated_from_span":null},"kind":{"Switch":{"discr":{"Move":{"kind":{"Local":20},"ty":{"Deduplicated":390}}},"targets":{"SwitchInt":[{"Int":"Isize"},[[{"Scalar":{"Signed":["Isize","0"]}},14],[{"Scalar":{"Signed":["Isize","1"]}},15],[{"Scalar":{"Signed":["Isize","2"]}},16]],17]}}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":74,"col":32},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"StorageLive":17},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":32},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":17},"ty":{"Deduplicated":5468}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Local":13},"ty":{"Deduplicated":5462}},{"Field":[{"Adt":[8,1]},0]}]},"ty":{"HashConsedValue":[6285,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":835},{"HashConsedValue":[6280,{"Ref":[{"Body":126},{"Deduplicated":197},"Shared"]}]}],"const_generics":[],"trait_refs":[{"Deduplicated":837},{"HashConsedValue":[6284,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[6282,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[6281,{"Ref":[{"Body":127},{"Deduplicated":197},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[6283,{"Ref":[{"Body":128},{"Deduplicated":197},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":32},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"StorageLive":18},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":32},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":18},"ty":{"Deduplicated":5471}},{"Use":{"Copy":{"kind":{"Local":17},"ty":{"Deduplicated":5468}}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Regular":{"kind":{"Fun":{"Regular":17}},"generics":{"regions":[],"types":[{"Deduplicated":207},{"HashConsedValue":[5492,{"Ref":[{"Body":129},{"Deduplicated":197},"Shared"]}]},{"Deduplicated":5492}],"const_generics":[],"trait_refs":[{"Deduplicated":332},{"HashConsedValue":[5494,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[5493,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":5492}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":5492}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":5494},{"HashConsedValue":[6286,{"kind":{"TraitImpl":{"id":5,"generics":{"regions":[],"types":[{"Deduplicated":5492}],"const_generics":[],"trait_refs":[{"Deduplicated":5494}]}}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":3,"generics":{"regions":[],"types":[{"Deduplicated":5492},{"Deduplicated":5492}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"args":[{"Move":{"kind":{"Local":18},"ty":{"Deduplicated":5471}}}],"dest":{"kind":{"Local":0},"ty":{"Deduplicated":5453}}},"target":18,"on_unwind":1}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":74,"col":18},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"Abort":"UndefinedBehavior"},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":76,"col":23},"end":{"line":76,"col":24}},"generated_from_span":null},"kind":{"StorageLive":21},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":76,"col":23},"end":{"line":76,"col":24}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":21},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Local":12},"ty":{"Deduplicated":634}},{"Field":[{"Adt":[2,0]},0]}]},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":76,"col":36},"end":{"line":76,"col":37}},"generated_from_span":null},"kind":{"StorageLive":22},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":76,"col":36},"end":{"line":76,"col":37}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":22},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":21},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":76,"col":29},"end":{"line":76,"col":37}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":23},"ty":{"Deduplicated":212}},{"BinaryOp":["AddChecked",{"Copy":{"kind":{"Local":2},"ty":{"Deduplicated":207}}},{"Copy":{"kind":{"Local":22},"ty":{"Deduplicated":207}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":76,"col":29},"end":{"line":76,"col":37}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Projection":[{"kind":{"Local":23},"ty":{"Deduplicated":212}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":211}}},"expected":false,"check_kind":{"Overflow":[{"Add":"Wrap"},{"Copy":{"kind":{"Local":2},"ty":{"Deduplicated":207}}},{"Move":{"kind":{"Local":27},"ty":{"Deduplicated":207}}}]}},"target":19,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":77,"col":23},"end":{"line":77,"col":24}},"generated_from_span":null},"kind":{"StorageLive":24},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":77,"col":23},"end":{"line":77,"col":24}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":24},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Local":12},"ty":{"Deduplicated":634}},{"Field":[{"Adt":[2,1]},0]}]},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":77,"col":36},"end":{"line":77,"col":37}},"generated_from_span":null},"kind":{"StorageLive":25},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":77,"col":36},"end":{"line":77,"col":37}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":25},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":24},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":77,"col":29},"end":{"line":77,"col":37}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":26},"ty":{"Deduplicated":212}},{"BinaryOp":["SubChecked",{"Copy":{"kind":{"Local":2},"ty":{"Deduplicated":207}}},{"Copy":{"kind":{"Local":25},"ty":{"Deduplicated":207}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":77,"col":29},"end":{"line":77,"col":37}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Projection":[{"kind":{"Local":26},"ty":{"Deduplicated":212}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":211}}},"expected":false,"check_kind":{"Overflow":[{"Sub":"Wrap"},{"Copy":{"kind":{"Local":2},"ty":{"Deduplicated":207}}},{"Move":{"kind":{"Local":30},"ty":{"Deduplicated":207}}}]}},"target":20,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":12},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":11},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"kind":{"Goto":{"target":9}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":75,"col":14},"end":{"line":75,"col":17}},"generated_from_span":null},"kind":{"Abort":"UndefinedBehavior"},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":74,"col":32},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"StorageDead":18},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":32},"end":{"line":74,"col":33}},"generated_from_span":null},"kind":{"StorageDead":17},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":74,"col":33},"end":{"line":74,"col":34}},"generated_from_span":null},"kind":{"StorageDead":13},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":12},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":11},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":7},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":3},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":82,"col":0},"end":{"line":82,"col":1}},"generated_from_span":null},"kind":{"StorageDead":2},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":82,"col":1},"end":{"line":82,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":76,"col":29},"end":{"line":76,"col":37}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":2},"ty":{"Deduplicated":207}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":23},"ty":{"Deduplicated":212}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":76,"col":36},"end":{"line":76,"col":37}},"generated_from_span":null},"kind":{"StorageDead":22},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":76,"col":36},"end":{"line":76,"col":37}},"generated_from_span":null},"kind":{"StorageDead":21},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":76,"col":36},"end":{"line":76,"col":37}},"generated_from_span":null},"kind":{"Goto":{"target":21}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":77,"col":29},"end":{"line":77,"col":37}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":2},"ty":{"Deduplicated":207}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":26},"ty":{"Deduplicated":212}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":77,"col":36},"end":{"line":77,"col":37}},"generated_from_span":null},"kind":{"StorageDead":25},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":77,"col":36},"end":{"line":77,"col":37}},"generated_from_span":null},"kind":{"StorageDead":24},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":77,"col":36},"end":{"line":77,"col":37}},"generated_from_span":null},"kind":{"Goto":{"target":21}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":12},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":11},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":80,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"StorageDead":7},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":73,"col":4},"end":{"line":80,"col":5}},"generated_from_span":null},"kind":{"Goto":{"target":4}},"comments_before":[]}}],"comments":[]}}},{"def_id":5,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["tuple_roundtrip",0]}],"span":{"data":{"file_id":0,"beg":{"line":92,"col":0},"end":{"line":95,"col":1}},"generated_from_span":null},"source_text":"pub fn tuple_roundtrip(a: i64, b: i64) -> i64 {\n let pair = (a + b, a - b);\n pair.0 * pair.1\n}","attr_info":{"attributes":[],"inline":"Never","rename":null,"public":true},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":207},{"Deduplicated":207}],"output":{"Deduplicated":207}},"src":"TopLevel","is_global_initializer":null,"body":{"Unstructured":{"span":{"data":{"file_id":0,"beg":{"line":92,"col":0},"end":{"line":95,"col":1}},"generated_from_span":null},"bound_body_regions":0,"locals":{"arg_count":2,"locals":[{"index":0,"name":null,"span":{"data":{"file_id":0,"beg":{"line":92,"col":42},"end":{"line":92,"col":45}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":1,"name":"a","span":{"data":{"file_id":0,"beg":{"line":92,"col":23},"end":{"line":92,"col":24}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":2,"name":"b","span":{"data":{"file_id":0,"beg":{"line":92,"col":31},"end":{"line":92,"col":32}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":3,"name":"pair","span":{"data":{"file_id":0,"beg":{"line":93,"col":8},"end":{"line":93,"col":12}},"generated_from_span":null},"ty":{"HashConsedValue":[1419,{"Adt":{"id":"Tuple","generics":{"regions":[],"types":[{"Deduplicated":207},{"Deduplicated":207}],"const_generics":[],"trait_refs":[]}}}]}},{"index":4,"name":null,"span":{"data":{"file_id":0,"beg":{"line":93,"col":16},"end":{"line":93,"col":21}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":5,"name":null,"span":{"data":{"file_id":0,"beg":{"line":93,"col":16},"end":{"line":93,"col":17}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":6,"name":null,"span":{"data":{"file_id":0,"beg":{"line":93,"col":20},"end":{"line":93,"col":21}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":7,"name":null,"span":{"data":{"file_id":0,"beg":{"line":93,"col":16},"end":{"line":93,"col":21}},"generated_from_span":null},"ty":{"Deduplicated":212}},{"index":8,"name":null,"span":{"data":{"file_id":0,"beg":{"line":93,"col":23},"end":{"line":93,"col":28}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":9,"name":null,"span":{"data":{"file_id":0,"beg":{"line":93,"col":23},"end":{"line":93,"col":24}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":10,"name":null,"span":{"data":{"file_id":0,"beg":{"line":93,"col":27},"end":{"line":93,"col":28}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":11,"name":null,"span":{"data":{"file_id":0,"beg":{"line":93,"col":23},"end":{"line":93,"col":28}},"generated_from_span":null},"ty":{"Deduplicated":212}},{"index":12,"name":null,"span":{"data":{"file_id":0,"beg":{"line":94,"col":4},"end":{"line":94,"col":10}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":13,"name":null,"span":{"data":{"file_id":0,"beg":{"line":94,"col":13},"end":{"line":94,"col":19}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":14,"name":null,"span":{"data":{"file_id":0,"beg":{"line":94,"col":4},"end":{"line":94,"col":19}},"generated_from_span":null},"ty":{"Deduplicated":212}}]},"body":[{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":93,"col":8},"end":{"line":93,"col":12}},"generated_from_span":null},"kind":{"StorageLive":7},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":8},"end":{"line":93,"col":12}},"generated_from_span":null},"kind":{"StorageLive":11},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":8},"end":{"line":93,"col":12}},"generated_from_span":null},"kind":{"StorageLive":14},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":8},"end":{"line":93,"col":12}},"generated_from_span":null},"kind":{"StorageLive":3},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":16},"end":{"line":93,"col":21}},"generated_from_span":null},"kind":{"StorageLive":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":16},"end":{"line":93,"col":17}},"generated_from_span":null},"kind":{"StorageLive":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":16},"end":{"line":93,"col":17}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":5},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":1},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":20},"end":{"line":93,"col":21}},"generated_from_span":null},"kind":{"StorageLive":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":20},"end":{"line":93,"col":21}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":6},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":2},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":16},"end":{"line":93,"col":21}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":7},"ty":{"Deduplicated":212}},{"BinaryOp":["AddChecked",{"Copy":{"kind":{"Local":5},"ty":{"Deduplicated":207}}},{"Copy":{"kind":{"Local":6},"ty":{"Deduplicated":207}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":93,"col":16},"end":{"line":93,"col":21}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Projection":[{"kind":{"Local":7},"ty":{"Deduplicated":212}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":211}}},"expected":false,"check_kind":{"Overflow":[{"Add":"Wrap"},{"Move":{"kind":{"Local":5},"ty":{"Deduplicated":207}}},{"Move":{"kind":{"Local":6},"ty":{"Deduplicated":207}}}]}},"target":2,"on_unwind":1}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":92,"col":0},"end":{"line":95,"col":1}},"generated_from_span":null},"kind":"UnwindResume","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":93,"col":16},"end":{"line":93,"col":21}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":4},"ty":{"Deduplicated":207}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":7},"ty":{"Deduplicated":212}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":20},"end":{"line":93,"col":21}},"generated_from_span":null},"kind":{"StorageDead":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":20},"end":{"line":93,"col":21}},"generated_from_span":null},"kind":{"StorageDead":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":23},"end":{"line":93,"col":28}},"generated_from_span":null},"kind":{"StorageLive":8},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":23},"end":{"line":93,"col":24}},"generated_from_span":null},"kind":{"StorageLive":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":23},"end":{"line":93,"col":24}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":9},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":1},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":27},"end":{"line":93,"col":28}},"generated_from_span":null},"kind":{"StorageLive":10},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":27},"end":{"line":93,"col":28}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":10},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":2},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":23},"end":{"line":93,"col":28}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":11},"ty":{"Deduplicated":212}},{"BinaryOp":["SubChecked",{"Copy":{"kind":{"Local":9},"ty":{"Deduplicated":207}}},{"Copy":{"kind":{"Local":10},"ty":{"Deduplicated":207}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":93,"col":23},"end":{"line":93,"col":28}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Projection":[{"kind":{"Local":11},"ty":{"Deduplicated":212}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":211}}},"expected":false,"check_kind":{"Overflow":[{"Sub":"Wrap"},{"Move":{"kind":{"Local":9},"ty":{"Deduplicated":207}}},{"Move":{"kind":{"Local":10},"ty":{"Deduplicated":207}}}]}},"target":3,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":93,"col":23},"end":{"line":93,"col":28}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":8},"ty":{"Deduplicated":207}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":11},"ty":{"Deduplicated":212}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":27},"end":{"line":93,"col":28}},"generated_from_span":null},"kind":{"StorageDead":10},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":27},"end":{"line":93,"col":28}},"generated_from_span":null},"kind":{"StorageDead":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":15},"end":{"line":93,"col":29}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":3},"ty":{"Deduplicated":1419}},{"Aggregate":[{"Adt":[{"id":"Tuple","generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}},null,null]},[{"Move":{"kind":{"Local":4},"ty":{"Deduplicated":207}}},{"Move":{"kind":{"Local":8},"ty":{"Deduplicated":207}}}]]}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":28},"end":{"line":93,"col":29}},"generated_from_span":null},"kind":{"StorageDead":8},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":93,"col":28},"end":{"line":93,"col":29}},"generated_from_span":null},"kind":{"StorageDead":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":94,"col":4},"end":{"line":94,"col":10}},"generated_from_span":null},"kind":{"StorageLive":12},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":94,"col":4},"end":{"line":94,"col":10}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":12},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Local":3},"ty":{"Deduplicated":1419}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":94,"col":13},"end":{"line":94,"col":19}},"generated_from_span":null},"kind":{"StorageLive":13},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":94,"col":13},"end":{"line":94,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":13},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Local":3},"ty":{"Deduplicated":1419}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":94,"col":4},"end":{"line":94,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":14},"ty":{"Deduplicated":212}},{"BinaryOp":["MulChecked",{"Copy":{"kind":{"Local":12},"ty":{"Deduplicated":207}}},{"Copy":{"kind":{"Local":13},"ty":{"Deduplicated":207}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":94,"col":4},"end":{"line":94,"col":19}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Projection":[{"kind":{"Local":14},"ty":{"Deduplicated":212}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":211}}},"expected":false,"check_kind":{"Overflow":[{"Mul":"Wrap"},{"Move":{"kind":{"Local":12},"ty":{"Deduplicated":207}}},{"Move":{"kind":{"Local":13},"ty":{"Deduplicated":207}}}]}},"target":4,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":94,"col":4},"end":{"line":94,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":207}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":14},"ty":{"Deduplicated":212}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":94,"col":18},"end":{"line":94,"col":19}},"generated_from_span":null},"kind":{"StorageDead":13},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":94,"col":18},"end":{"line":94,"col":19}},"generated_from_span":null},"kind":{"StorageDead":12},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":95,"col":0},"end":{"line":95,"col":1}},"generated_from_span":null},"kind":{"StorageDead":3},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":95,"col":1},"end":{"line":95,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}}],"comments":[]}}},{"def_id":6,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_closure",0]}],"span":{"data":{"file_id":0,"beg":{"line":105,"col":0},"end":{"line":107,"col":1}},"generated_from_span":null},"source_text":"pub fn bool_then_closure(c: bool, x: i64) -> Option {\n c.then(|| x + 1)\n}","attr_info":{"attributes":[],"inline":"Never","rename":null,"public":true},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":211},{"Deduplicated":207}],"output":{"HashConsedValue":[1420,{"Adt":{"id":{"Adt":7},"generics":{"regions":[],"types":[{"Deduplicated":207}],"const_generics":[],"trait_refs":[{"Deduplicated":332}]}}}]}},"src":"TopLevel","is_global_initializer":null,"body":{"Unstructured":{"span":{"data":{"file_id":0,"beg":{"line":105,"col":0},"end":{"line":107,"col":1}},"generated_from_span":null},"bound_body_regions":16,"locals":{"arg_count":2,"locals":[{"index":0,"name":null,"span":{"data":{"file_id":0,"beg":{"line":105,"col":45},"end":{"line":105,"col":56}},"generated_from_span":null},"ty":{"Deduplicated":1420}},{"index":1,"name":"c","span":{"data":{"file_id":0,"beg":{"line":105,"col":25},"end":{"line":105,"col":26}},"generated_from_span":null},"ty":{"Deduplicated":211}},{"index":2,"name":"x","span":{"data":{"file_id":0,"beg":{"line":105,"col":34},"end":{"line":105,"col":35}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":3,"name":null,"span":{"data":{"file_id":0,"beg":{"line":106,"col":4},"end":{"line":106,"col":5}},"generated_from_span":null},"ty":{"Deduplicated":211}},{"index":4,"name":null,"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"ty":{"HashConsedValue":[1438,{"Adt":{"id":{"Adt":10},"generics":{"regions":[{"Body":1}],"types":[],"const_generics":[],"trait_refs":[]}}}]}},{"index":5,"name":null,"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"ty":{"HashConsedValue":[1440,{"Ref":[{"Body":3},{"Deduplicated":207},"Shared"]}]}}]},"body":[{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":106,"col":4},"end":{"line":106,"col":5}},"generated_from_span":null},"kind":{"StorageLive":3},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":106,"col":4},"end":{"line":106,"col":5}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":3},"ty":{"Deduplicated":211}},{"Use":{"Copy":{"kind":{"Local":1},"ty":{"Deduplicated":211}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"kind":{"StorageLive":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"kind":{"StorageLive":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":5},"ty":{"Deduplicated":1440}},{"Ref":{"place":{"kind":{"Local":2},"ty":{"Deduplicated":207}},"kind":"Shared","ptr_metadata":{"Const":{"kind":{"Adt":[null,[]]},"ty":{"Deduplicated":221}}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":4},"ty":{"Deduplicated":1438}},{"Aggregate":[{"Adt":[{"id":{"Adt":10},"generics":{"regions":[{"Body":4}],"types":[],"const_generics":[],"trait_refs":[]}},null,null]},[{"Move":{"kind":{"Local":5},"ty":{"Deduplicated":1440}}}]]}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":106,"col":12},"end":{"line":106,"col":13}},"generated_from_span":null},"kind":{"StorageDead":5},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":106,"col":4},"end":{"line":106,"col":20}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Regular":{"kind":{"Fun":{"Regular":18}},"generics":{"regions":[],"types":[{"Deduplicated":207},{"HashConsedValue":[5507,{"Adt":{"id":{"Adt":10},"generics":{"regions":[{"Body":5}],"types":[],"const_generics":[],"trait_refs":[]}}}]}],"const_generics":[],"trait_refs":[{"Deduplicated":332},{"HashConsedValue":[6292,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[6291,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":5507}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":5507}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6293,{"kind":{"TraitImpl":{"id":6,"generics":{"regions":[{"Body":5}],"types":[],"const_generics":[],"trait_refs":[]}}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":5507},{"Deduplicated":221}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6294,{"kind":{"TraitImpl":{"id":7,"generics":{"regions":[{"Body":5}],"types":[],"const_generics":[],"trait_refs":[]}}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":5,"generics":{"regions":[],"types":[{"Deduplicated":5507}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"args":[{"Move":{"kind":{"Local":3},"ty":{"Deduplicated":211}}},{"Move":{"kind":{"Local":4},"ty":{"Deduplicated":1438}}}],"dest":{"kind":{"Local":0},"ty":{"Deduplicated":1420}}},"target":2,"on_unwind":1}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":105,"col":0},"end":{"line":107,"col":1}},"generated_from_span":null},"kind":"UnwindResume","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":106,"col":19},"end":{"line":106,"col":20}},"generated_from_span":null},"kind":{"StorageDead":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":106,"col":19},"end":{"line":106,"col":20}},"generated_from_span":null},"kind":{"StorageDead":3},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":107,"col":1},"end":{"line":107,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}}],"comments":[]}}},{"def_id":7,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_some",0]}],"span":{"data":{"file_id":0,"beg":{"line":113,"col":0},"end":{"line":115,"col":1}},"generated_from_span":null},"source_text":"pub fn bool_then_some(c: bool, x: i64) -> Option {\n c.then_some(x + 1)\n}","attr_info":{"attributes":[],"inline":"Never","rename":null,"public":true},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":211},{"Deduplicated":207}],"output":{"Deduplicated":1420}},"src":"TopLevel","is_global_initializer":null,"body":{"Unstructured":{"span":{"data":{"file_id":0,"beg":{"line":113,"col":0},"end":{"line":115,"col":1}},"generated_from_span":null},"bound_body_regions":0,"locals":{"arg_count":2,"locals":[{"index":0,"name":null,"span":{"data":{"file_id":0,"beg":{"line":113,"col":42},"end":{"line":113,"col":53}},"generated_from_span":null},"ty":{"Deduplicated":1420}},{"index":1,"name":"c","span":{"data":{"file_id":0,"beg":{"line":113,"col":22},"end":{"line":113,"col":23}},"generated_from_span":null},"ty":{"Deduplicated":211}},{"index":2,"name":"x","span":{"data":{"file_id":0,"beg":{"line":113,"col":31},"end":{"line":113,"col":32}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":3,"name":null,"span":{"data":{"file_id":0,"beg":{"line":114,"col":4},"end":{"line":114,"col":5}},"generated_from_span":null},"ty":{"Deduplicated":211}},{"index":4,"name":null,"span":{"data":{"file_id":0,"beg":{"line":114,"col":16},"end":{"line":114,"col":21}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":5,"name":null,"span":{"data":{"file_id":0,"beg":{"line":114,"col":16},"end":{"line":114,"col":17}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":6,"name":null,"span":{"data":{"file_id":0,"beg":{"line":114,"col":16},"end":{"line":114,"col":21}},"generated_from_span":null},"ty":{"Deduplicated":212}}]},"body":[{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":114,"col":4},"end":{"line":114,"col":5}},"generated_from_span":null},"kind":{"StorageLive":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":114,"col":4},"end":{"line":114,"col":5}},"generated_from_span":null},"kind":{"StorageLive":3},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":114,"col":4},"end":{"line":114,"col":5}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":3},"ty":{"Deduplicated":211}},{"Use":{"Copy":{"kind":{"Local":1},"ty":{"Deduplicated":211}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":114,"col":16},"end":{"line":114,"col":21}},"generated_from_span":null},"kind":{"StorageLive":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":114,"col":16},"end":{"line":114,"col":17}},"generated_from_span":null},"kind":{"StorageLive":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":114,"col":16},"end":{"line":114,"col":17}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":5},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":2},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":114,"col":16},"end":{"line":114,"col":21}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":6},"ty":{"Deduplicated":212}},{"BinaryOp":["AddChecked",{"Copy":{"kind":{"Local":5},"ty":{"Deduplicated":207}}},{"Const":{"kind":{"Literal":{"Scalar":{"Signed":["I64","1"]}}},"ty":{"Deduplicated":207}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":114,"col":16},"end":{"line":114,"col":21}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Projection":[{"kind":{"Local":6},"ty":{"Deduplicated":212}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":211}}},"expected":false,"check_kind":{"Overflow":[{"Add":"Wrap"},{"Move":{"kind":{"Local":5},"ty":{"Deduplicated":207}}},{"Const":{"kind":{"Literal":{"Scalar":{"Signed":["I64","1"]}}},"ty":{"Deduplicated":207}}}]}},"target":2,"on_unwind":1}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":113,"col":0},"end":{"line":115,"col":1}},"generated_from_span":null},"kind":"UnwindResume","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":114,"col":16},"end":{"line":114,"col":21}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":4},"ty":{"Deduplicated":207}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":6},"ty":{"Deduplicated":212}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":114,"col":20},"end":{"line":114,"col":21}},"generated_from_span":null},"kind":{"StorageDead":5},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":114,"col":4},"end":{"line":114,"col":22}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Regular":{"kind":{"Fun":{"Regular":19}},"generics":{"regions":[],"types":[{"Deduplicated":207}],"const_generics":[],"trait_refs":[{"Deduplicated":332},{"HashConsedValue":[6296,{"kind":{"BuiltinOrAuto":{"builtin_data":"NoopDestruct","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":5,"generics":{"regions":[],"types":[{"Deduplicated":207}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"args":[{"Move":{"kind":{"Local":3},"ty":{"Deduplicated":211}}},{"Move":{"kind":{"Local":4},"ty":{"Deduplicated":207}}}],"dest":{"kind":{"Local":0},"ty":{"Deduplicated":1420}}},"target":3,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":114,"col":21},"end":{"line":114,"col":22}},"generated_from_span":null},"kind":{"StorageDead":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":114,"col":21},"end":{"line":114,"col":22}},"generated_from_span":null},"kind":{"StorageDead":3},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":115,"col":1},"end":{"line":115,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}}],"comments":[]}}},{"def_id":8,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["option_source",0]}],"span":{"data":{"file_id":0,"beg":{"line":123,"col":0},"end":{"line":125,"col":1}},"generated_from_span":null},"source_text":"fn option_source(keep: bool, value: i64) -> Option {\n if keep { Some(value) } else { None }\n}","attr_info":{"attributes":[],"inline":"Never","rename":null,"public":false},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":211},{"Deduplicated":207}],"output":{"Deduplicated":1420}},"src":"TopLevel","is_global_initializer":null,"body":{"Unstructured":{"span":{"data":{"file_id":0,"beg":{"line":123,"col":0},"end":{"line":125,"col":1}},"generated_from_span":null},"bound_body_regions":0,"locals":{"arg_count":2,"locals":[{"index":0,"name":null,"span":{"data":{"file_id":0,"beg":{"line":123,"col":44},"end":{"line":123,"col":55}},"generated_from_span":null},"ty":{"Deduplicated":1420}},{"index":1,"name":"keep","span":{"data":{"file_id":0,"beg":{"line":123,"col":17},"end":{"line":123,"col":21}},"generated_from_span":null},"ty":{"Deduplicated":211}},{"index":2,"name":"value","span":{"data":{"file_id":0,"beg":{"line":123,"col":29},"end":{"line":123,"col":34}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":3,"name":null,"span":{"data":{"file_id":0,"beg":{"line":124,"col":7},"end":{"line":124,"col":11}},"generated_from_span":null},"ty":{"Deduplicated":211}},{"index":4,"name":null,"span":{"data":{"file_id":0,"beg":{"line":124,"col":19},"end":{"line":124,"col":24}},"generated_from_span":null},"ty":{"Deduplicated":207}}]},"body":[{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":124,"col":7},"end":{"line":124,"col":11}},"generated_from_span":null},"kind":{"StorageLive":3},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":124,"col":7},"end":{"line":124,"col":11}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":3},"ty":{"Deduplicated":211}},{"Use":{"Copy":{"kind":{"Local":1},"ty":{"Deduplicated":211}}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":124,"col":7},"end":{"line":124,"col":11}},"generated_from_span":null},"kind":{"Switch":{"discr":{"Move":{"kind":{"Local":3},"ty":{"Deduplicated":211}}},"targets":{"If":[1,2]}}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":124,"col":19},"end":{"line":124,"col":24}},"generated_from_span":null},"kind":{"StorageLive":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":124,"col":19},"end":{"line":124,"col":24}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":4},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":2},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":124,"col":14},"end":{"line":124,"col":25}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":1420}},{"Aggregate":[{"Adt":[{"id":{"Adt":7},"generics":{"regions":[],"types":[{"Deduplicated":207}],"const_generics":[],"trait_refs":[{"Deduplicated":332}]}},1,null]},[{"Move":{"kind":{"Local":4},"ty":{"Deduplicated":207}}}]]}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":124,"col":24},"end":{"line":124,"col":25}},"generated_from_span":null},"kind":{"StorageDead":4},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":124,"col":4},"end":{"line":124,"col":41}},"generated_from_span":null},"kind":{"Goto":{"target":3}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":124,"col":35},"end":{"line":124,"col":39}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":1420}},{"Aggregate":[{"Adt":[{"id":{"Adt":7},"generics":{"regions":[],"types":[{"Deduplicated":207}],"const_generics":[],"trait_refs":[{"Deduplicated":332}]}},0,null]},[]]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":124,"col":4},"end":{"line":124,"col":41}},"generated_from_span":null},"kind":{"Goto":{"target":3}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":124,"col":40},"end":{"line":124,"col":41}},"generated_from_span":null},"kind":{"StorageDead":3},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":125,"col":1},"end":{"line":125,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}}],"comments":[]}}},{"def_id":9,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["option_question_mark",0]}],"span":{"data":{"file_id":0,"beg":{"line":128,"col":0},"end":{"line":131,"col":1}},"generated_from_span":null},"source_text":"pub fn option_question_mark(keep: bool, value: i64, addend: i64) -> Option {\n let v = option_source(keep, value)?;\n Some(v + addend)\n}","attr_info":{"attributes":[],"inline":"Never","rename":null,"public":true},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":211},{"Deduplicated":207},{"Deduplicated":207}],"output":{"Deduplicated":1420}},"src":"TopLevel","is_global_initializer":null,"body":{"Unstructured":{"span":{"data":{"file_id":0,"beg":{"line":128,"col":0},"end":{"line":131,"col":1}},"generated_from_span":null},"bound_body_regions":0,"locals":{"arg_count":3,"locals":[{"index":0,"name":null,"span":{"data":{"file_id":0,"beg":{"line":128,"col":68},"end":{"line":128,"col":79}},"generated_from_span":null},"ty":{"Deduplicated":1420}},{"index":1,"name":"keep","span":{"data":{"file_id":0,"beg":{"line":128,"col":28},"end":{"line":128,"col":32}},"generated_from_span":null},"ty":{"Deduplicated":211}},{"index":2,"name":"value","span":{"data":{"file_id":0,"beg":{"line":128,"col":40},"end":{"line":128,"col":45}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":3,"name":"addend","span":{"data":{"file_id":0,"beg":{"line":128,"col":52},"end":{"line":128,"col":58}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":4,"name":"v","span":{"data":{"file_id":0,"beg":{"line":129,"col":8},"end":{"line":129,"col":9}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":5,"name":null,"span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":39}},"generated_from_span":null},"ty":{"HashConsedValue":[1536,{"Adt":{"id":{"Adt":8},"generics":{"regions":[],"types":[{"HashConsedValue":[1533,{"Adt":{"id":{"Adt":7},"generics":{"regions":[],"types":[{"Deduplicated":835}],"const_generics":[],"trait_refs":[{"Deduplicated":837}]}}}]},{"Deduplicated":207}],"const_generics":[],"trait_refs":[{"HashConsedValue":[1535,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[1534,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":1533}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1533}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":332}]}}}]}},{"index":6,"name":null,"span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":38}},"generated_from_span":null},"ty":{"Deduplicated":1420}},{"index":7,"name":null,"span":{"data":{"file_id":0,"beg":{"line":129,"col":26},"end":{"line":129,"col":30}},"generated_from_span":null},"ty":{"Deduplicated":211}},{"index":8,"name":null,"span":{"data":{"file_id":0,"beg":{"line":129,"col":32},"end":{"line":129,"col":37}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":9,"name":null,"span":{"data":{"file_id":0,"beg":{"line":129,"col":38},"end":{"line":129,"col":39}},"generated_from_span":null},"ty":{"Deduplicated":390}},{"index":10,"name":"residual","span":{"data":{"file_id":0,"beg":{"line":129,"col":38},"end":{"line":129,"col":39}},"generated_from_span":null},"ty":{"Deduplicated":1533}},{"index":11,"name":null,"span":{"data":{"file_id":0,"beg":{"line":129,"col":38},"end":{"line":129,"col":39}},"generated_from_span":null},"ty":{"Deduplicated":1533}},{"index":12,"name":"val","span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":39}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":13,"name":null,"span":{"data":{"file_id":0,"beg":{"line":130,"col":9},"end":{"line":130,"col":19}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":14,"name":null,"span":{"data":{"file_id":0,"beg":{"line":130,"col":9},"end":{"line":130,"col":10}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":15,"name":null,"span":{"data":{"file_id":0,"beg":{"line":130,"col":13},"end":{"line":130,"col":19}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":16,"name":null,"span":{"data":{"file_id":0,"beg":{"line":130,"col":9},"end":{"line":130,"col":19}},"generated_from_span":null},"ty":{"Deduplicated":212}}]},"body":[{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":129,"col":8},"end":{"line":129,"col":9}},"generated_from_span":null},"kind":{"StorageLive":9},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":8},"end":{"line":129,"col":9}},"generated_from_span":null},"kind":{"StorageLive":16},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":8},"end":{"line":129,"col":9}},"generated_from_span":null},"kind":{"StorageLive":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"StorageLive":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":38}},"generated_from_span":null},"kind":{"StorageLive":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":26},"end":{"line":129,"col":30}},"generated_from_span":null},"kind":{"StorageLive":7},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":26},"end":{"line":129,"col":30}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":7},"ty":{"Deduplicated":211}},{"Use":{"Copy":{"kind":{"Local":1},"ty":{"Deduplicated":211}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":32},"end":{"line":129,"col":37}},"generated_from_span":null},"kind":{"StorageLive":8},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":32},"end":{"line":129,"col":37}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":8},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":2},"ty":{"Deduplicated":207}}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":38}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Regular":{"kind":{"Fun":{"Regular":8}},"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}},"args":[{"Move":{"kind":{"Local":7},"ty":{"Deduplicated":211}}},{"Move":{"kind":{"Local":8},"ty":{"Deduplicated":207}}}],"dest":{"kind":{"Local":6},"ty":{"Deduplicated":1420}}},"target":2,"on_unwind":1}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":128,"col":0},"end":{"line":131,"col":1}},"generated_from_span":null},"kind":"UnwindResume","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":129,"col":37},"end":{"line":129,"col":38}},"generated_from_span":null},"kind":{"StorageDead":8},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":37},"end":{"line":129,"col":38}},"generated_from_span":null},"kind":{"StorageDead":7},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Regular":{"kind":{"Fun":{"Regular":20}},"generics":{"regions":[],"types":[{"Deduplicated":207}],"const_generics":[],"trait_refs":[{"Deduplicated":332}]}}},"args":[{"Move":{"kind":{"Local":6},"ty":{"Deduplicated":1420}}}],"dest":{"kind":{"Local":5},"ty":{"Deduplicated":1536}}},"target":3,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":129,"col":38},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"StorageDead":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":9},"ty":{"Deduplicated":390}},{"Discriminant":{"kind":{"Local":5},"ty":{"Deduplicated":1536}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"Switch":{"discr":{"Move":{"kind":{"Local":9},"ty":{"Deduplicated":390}}},"targets":{"SwitchInt":[{"Int":"Isize"},[[{"Scalar":{"Signed":["Isize","0"]}},4],[{"Scalar":{"Signed":["Isize","1"]}},5]],6]}}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"StorageLive":12},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":12},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Local":5},"ty":{"Deduplicated":1536}},{"Field":[{"Adt":[8,0]},0]}]},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":4},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":12},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":38},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"StorageDead":12},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":39},"end":{"line":129,"col":40}},"generated_from_span":null},"kind":{"StorageDead":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":130,"col":9},"end":{"line":130,"col":19}},"generated_from_span":null},"kind":{"StorageLive":13},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":130,"col":9},"end":{"line":130,"col":10}},"generated_from_span":null},"kind":{"StorageLive":14},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":130,"col":9},"end":{"line":130,"col":10}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":14},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":4},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":130,"col":13},"end":{"line":130,"col":19}},"generated_from_span":null},"kind":{"StorageLive":15},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":130,"col":13},"end":{"line":130,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":15},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":3},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":130,"col":9},"end":{"line":130,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":16},"ty":{"Deduplicated":212}},{"BinaryOp":["AddChecked",{"Copy":{"kind":{"Local":14},"ty":{"Deduplicated":207}}},{"Copy":{"kind":{"Local":15},"ty":{"Deduplicated":207}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":130,"col":9},"end":{"line":130,"col":19}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Projection":[{"kind":{"Local":16},"ty":{"Deduplicated":212}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":211}}},"expected":false,"check_kind":{"Overflow":[{"Add":"Wrap"},{"Move":{"kind":{"Local":15},"ty":{"Deduplicated":207}}},{"Move":{"kind":{"Local":16},"ty":{"Deduplicated":207}}}]}},"target":7,"on_unwind":1}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":129,"col":38},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"StorageLive":10},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":38},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":10},"ty":{"Deduplicated":1533}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Local":5},"ty":{"Deduplicated":1536}},{"Field":[{"Adt":[8,1]},0]}]},"ty":{"Deduplicated":1533}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":38},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"StorageLive":11},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":38},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":11},"ty":{"Deduplicated":1533}},{"Use":{"Copy":{"kind":{"Local":10},"ty":{"Deduplicated":1533}}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Regular":{"kind":{"Fun":{"Regular":21}},"generics":{"regions":[],"types":[{"Deduplicated":207}],"const_generics":[],"trait_refs":[{"Deduplicated":332}]}}},"args":[{"Move":{"kind":{"Local":11},"ty":{"Deduplicated":1533}}}],"dest":{"kind":{"Local":0},"ty":{"Deduplicated":1420}}},"target":8,"on_unwind":1}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":129,"col":12},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"Abort":"UndefinedBehavior"},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":130,"col":9},"end":{"line":130,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":13},"ty":{"Deduplicated":207}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":16},"ty":{"Deduplicated":212}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":130,"col":18},"end":{"line":130,"col":19}},"generated_from_span":null},"kind":{"StorageDead":15},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":130,"col":18},"end":{"line":130,"col":19}},"generated_from_span":null},"kind":{"StorageDead":14},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":130,"col":4},"end":{"line":130,"col":20}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":1420}},{"Aggregate":[{"Adt":[{"id":{"Adt":7},"generics":{"regions":[],"types":[{"Deduplicated":207}],"const_generics":[],"trait_refs":[{"Deduplicated":332}]}},1,null]},[{"Move":{"kind":{"Local":13},"ty":{"Deduplicated":207}}}]]}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":130,"col":19},"end":{"line":130,"col":20}},"generated_from_span":null},"kind":{"StorageDead":13},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":131,"col":0},"end":{"line":131,"col":1}},"generated_from_span":null},"kind":{"StorageDead":4},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":131,"col":1},"end":{"line":131,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":129,"col":38},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"StorageDead":11},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":38},"end":{"line":129,"col":39}},"generated_from_span":null},"kind":{"StorageDead":10},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":129,"col":39},"end":{"line":129,"col":40}},"generated_from_span":null},"kind":{"StorageDead":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":131,"col":0},"end":{"line":131,"col":1}},"generated_from_span":null},"kind":{"StorageDead":4},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":131,"col":1},"end":{"line":131,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}}],"comments":[]}}},{"def_id":10,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["host_registry_dispatch",0]}],"span":{"data":{"file_id":0,"beg":{"line":154,"col":0},"end":{"line":156,"col":1}},"generated_from_span":null},"source_text":"pub fn host_registry_dispatch(reg: &HostRegistry, x: i64) -> i64 {\n (reg.slot)(x)\n}","attr_info":{"attributes":[{"DocComment":" Call through the registered callback. `front::mir` lowers this to"},{"DocComment":" `OpKind::IndirectCall { graphs: None }` — `indirect_call` with an"},{"DocComment":" unknown PBC family, which `guess_call_kind` answers `residual` for"},{"DocComment":" (`call.py:105`/`137`, `jtransform.py:410-412`). The `__dyn_call`"},{"DocComment":" placeholder it used to reach is an unregistered synthetic path with no"},{"DocComment":" continuation."}],"inline":"Never","rename":null,"public":true},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"HashConsedValue":[1580,{"Ref":[{"Var":{"Bound":[0,0]}},{"HashConsedValue":[1579,{"Adt":{"id":{"Adt":4},"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}}]},"Shared"]}]},{"Deduplicated":207}],"output":{"Deduplicated":207}},"src":"TopLevel","is_global_initializer":null,"body":{"Unstructured":{"span":{"data":{"file_id":0,"beg":{"line":154,"col":0},"end":{"line":156,"col":1}},"generated_from_span":null},"bound_body_regions":2,"locals":{"arg_count":2,"locals":[{"index":0,"name":null,"span":{"data":{"file_id":0,"beg":{"line":154,"col":61},"end":{"line":154,"col":64}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":1,"name":"reg","span":{"data":{"file_id":0,"beg":{"line":154,"col":30},"end":{"line":154,"col":33}},"generated_from_span":null},"ty":{"HashConsedValue":[1583,{"Ref":[{"Body":1},{"Deduplicated":1579},"Shared"]}]}},{"index":2,"name":"x","span":{"data":{"file_id":0,"beg":{"line":154,"col":50},"end":{"line":154,"col":51}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":3,"name":null,"span":{"data":{"file_id":0,"beg":{"line":155,"col":4},"end":{"line":155,"col":14}},"generated_from_span":null},"ty":{"Deduplicated":1573}},{"index":4,"name":null,"span":{"data":{"file_id":0,"beg":{"line":155,"col":15},"end":{"line":155,"col":16}},"generated_from_span":null},"ty":{"Deduplicated":207}}]},"body":[{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":155,"col":4},"end":{"line":155,"col":14}},"generated_from_span":null},"kind":{"StorageLive":3},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":155,"col":4},"end":{"line":155,"col":14}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":3},"ty":{"Deduplicated":1573}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Projection":[{"kind":{"Local":1},"ty":{"Deduplicated":1583}},"Deref"]},"ty":{"Deduplicated":1579}},{"Field":[{"Adt":[4,null]},0]}]},"ty":{"Deduplicated":1573}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":155,"col":15},"end":{"line":155,"col":16}},"generated_from_span":null},"kind":{"StorageLive":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":155,"col":15},"end":{"line":155,"col":16}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":4},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":2},"ty":{"Deduplicated":207}}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":155,"col":4},"end":{"line":155,"col":17}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Dynamic":{"Move":{"kind":{"Local":3},"ty":{"Deduplicated":1573}}}},"args":[{"Move":{"kind":{"Local":4},"ty":{"Deduplicated":207}}}],"dest":{"kind":{"Local":0},"ty":{"Deduplicated":207}}},"target":2,"on_unwind":1}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":154,"col":0},"end":{"line":156,"col":1}},"generated_from_span":null},"kind":"UnwindResume","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":155,"col":16},"end":{"line":155,"col":17}},"generated_from_span":null},"kind":{"StorageDead":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":155,"col":16},"end":{"line":155,"col":17}},"generated_from_span":null},"kind":{"StorageDead":3},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":156,"col":1},"end":{"line":156,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}}],"comments":[]}}},{"def_id":11,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["host_registry_dispatch_optional",0]}],"span":{"data":{"file_id":0,"beg":{"line":160,"col":0},"end":{"line":165,"col":1}},"generated_from_span":null},"source_text":"pub fn host_registry_dispatch_optional(reg: &HostRegistry, x: i64) -> i64 {\n match reg.maybe_slot {\n Some(f) => f(x),\n None => 0,\n }\n}","attr_info":{"attributes":[{"DocComment":" The one-hop `Option` spelling of the same shape."}],"inline":"Never","rename":null,"public":true},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":1580},{"Deduplicated":207}],"output":{"Deduplicated":207}},"src":"TopLevel","is_global_initializer":null,"body":{"Unstructured":{"span":{"data":{"file_id":0,"beg":{"line":160,"col":0},"end":{"line":165,"col":1}},"generated_from_span":null},"bound_body_regions":2,"locals":{"arg_count":2,"locals":[{"index":0,"name":null,"span":{"data":{"file_id":0,"beg":{"line":160,"col":70},"end":{"line":160,"col":73}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":1,"name":"reg","span":{"data":{"file_id":0,"beg":{"line":160,"col":39},"end":{"line":160,"col":42}},"generated_from_span":null},"ty":{"Deduplicated":1583}},{"index":2,"name":"x","span":{"data":{"file_id":0,"beg":{"line":160,"col":59},"end":{"line":160,"col":60}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":3,"name":null,"span":{"data":{"file_id":0,"beg":{"line":162,"col":8},"end":{"line":162,"col":15}},"generated_from_span":null},"ty":{"Deduplicated":390}},{"index":4,"name":"f","span":{"data":{"file_id":0,"beg":{"line":162,"col":13},"end":{"line":162,"col":14}},"generated_from_span":null},"ty":{"Deduplicated":1573}},{"index":5,"name":null,"span":{"data":{"file_id":0,"beg":{"line":162,"col":19},"end":{"line":162,"col":20}},"generated_from_span":null},"ty":{"Deduplicated":1573}},{"index":6,"name":null,"span":{"data":{"file_id":0,"beg":{"line":162,"col":21},"end":{"line":162,"col":22}},"generated_from_span":null},"ty":{"Deduplicated":207}}]},"body":[{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":161,"col":10},"end":{"line":161,"col":24}},"generated_from_span":null},"kind":{"StorageLive":3},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":161,"col":10},"end":{"line":161,"col":24}},"generated_from_span":null},"kind":{"PlaceMention":{"kind":{"Projection":[{"kind":{"Projection":[{"kind":{"Local":1},"ty":{"Deduplicated":1583}},"Deref"]},"ty":{"Deduplicated":1579}},{"Field":[{"Adt":[4,null]},1]}]},"ty":{"Deduplicated":1577}}},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":161,"col":10},"end":{"line":161,"col":24}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":3},"ty":{"Deduplicated":390}},{"Discriminant":{"kind":{"Projection":[{"kind":{"Projection":[{"kind":{"Local":1},"ty":{"Deduplicated":1583}},"Deref"]},"ty":{"Deduplicated":1579}},{"Field":[{"Adt":[4,null]},1]}]},"ty":{"Deduplicated":1577}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":161,"col":4},"end":{"line":161,"col":24}},"generated_from_span":null},"kind":{"Switch":{"discr":{"Move":{"kind":{"Local":3},"ty":{"Deduplicated":390}}},"targets":{"SwitchInt":[{"Int":"Isize"},[[{"Scalar":{"Signed":["Isize","0"]}},1],[{"Scalar":{"Signed":["Isize","1"]}},2]],3]}}},"comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":163,"col":16},"end":{"line":163,"col":17}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":207}},{"Use":{"Const":{"kind":{"Literal":{"Scalar":{"Signed":["I64","0"]}}},"ty":{"Deduplicated":207}}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":165,"col":1},"end":{"line":165,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":162,"col":13},"end":{"line":162,"col":14}},"generated_from_span":null},"kind":{"StorageLive":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":162,"col":13},"end":{"line":162,"col":14}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":4},"ty":{"Deduplicated":1573}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Projection":[{"kind":{"Projection":[{"kind":{"Local":1},"ty":{"Deduplicated":1583}},"Deref"]},"ty":{"Deduplicated":1579}},{"Field":[{"Adt":[4,null]},1]}]},"ty":{"Deduplicated":1577}},{"Field":[{"Adt":[7,1]},0]}]},"ty":{"Deduplicated":1573}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":162,"col":19},"end":{"line":162,"col":20}},"generated_from_span":null},"kind":{"StorageLive":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":162,"col":19},"end":{"line":162,"col":20}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":5},"ty":{"Deduplicated":1573}},{"Use":{"Copy":{"kind":{"Local":4},"ty":{"Deduplicated":1573}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":162,"col":21},"end":{"line":162,"col":22}},"generated_from_span":null},"kind":{"StorageLive":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":162,"col":21},"end":{"line":162,"col":22}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":6},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Local":2},"ty":{"Deduplicated":207}}}}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":162,"col":19},"end":{"line":162,"col":23}},"generated_from_span":null},"kind":{"Call":{"call":{"func":{"Dynamic":{"Move":{"kind":{"Local":5},"ty":{"Deduplicated":1573}}}},"args":[{"Move":{"kind":{"Local":6},"ty":{"Deduplicated":207}}}],"dest":{"kind":{"Local":0},"ty":{"Deduplicated":207}}},"target":5,"on_unwind":4}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":161,"col":10},"end":{"line":161,"col":24}},"generated_from_span":null},"kind":{"Abort":"UndefinedBehavior"},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":160,"col":0},"end":{"line":165,"col":1}},"generated_from_span":null},"kind":"UnwindResume","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":162,"col":22},"end":{"line":162,"col":23}},"generated_from_span":null},"kind":{"StorageDead":6},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":162,"col":22},"end":{"line":162,"col":23}},"generated_from_span":null},"kind":{"StorageDead":5},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":162,"col":22},"end":{"line":162,"col":23}},"generated_from_span":null},"kind":{"StorageDead":4},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":165,"col":1},"end":{"line":165,"col":1}},"generated_from_span":null},"kind":"Return","comments_before":[]}}],"comments":[]}}},{"def_id":12,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":0}},{"Ident":["into_iter",0]}],"span":{"data":{"file_id":4,"beg":{"line":25,"col":4},"end":{"line":25,"col":37}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[{"index":0,"name":"'a","mutability":"Unknown"}],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":4,"beg":{"line":21,"col":9},"end":{"line":21,"col":10}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"HashConsedValue":[1602,{"Ref":[{"Var":{"Bound":[0,0]}},{"Deduplicated":1063},"Shared"]}]}],"output":{"HashConsedValue":[1625,{"Adt":{"id":{"Adt":6},"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[{"Deduplicated":196}],"const_generics":[],"trait_refs":[{"Deduplicated":199}]}}}]}},"src":{"TraitImpl":{"impl_ref":{"id":0,"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[{"Deduplicated":196}],"const_generics":[],"trait_refs":[{"Deduplicated":199}]}},"trait_ref":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":1602}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"reuses_default":true}},"is_global_initializer":null,"body":"Opaque"},{"def_id":13,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}},{"Ident":["next",0]}],"span":{"data":{"file_id":7,"beg":{"line":157,"col":12},"end":{"line":157,"col":47}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":"Hint","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[{"index":0,"name":"'a","mutability":"Unknown"},{"index":1,"name":null,"mutability":"Unknown"}],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":7,"beg":{"line":153,"col":17},"end":{"line":153,"col":18}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"HashConsedValue":[6297,{"Ref":[{"Var":{"Bound":[0,1]}},{"Deduplicated":1625},"Mut"]}]}],"output":{"HashConsedValue":[6299,{"Adt":{"id":{"Adt":7},"generics":{"regions":[],"types":[{"HashConsedValue":[4551,{"Ref":[{"Var":{"Bound":[0,0]}},{"Deduplicated":196},"Shared"]}]}],"const_generics":[],"trait_refs":[{"HashConsedValue":[6298,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[5517,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[5516,{"Ref":["Erased",{"Deduplicated":164},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[1610,{"Ref":[{"Var":{"Bound":[1,0]}},{"Deduplicated":164},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},"src":{"TraitImpl":{"impl_ref":{"id":1,"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[{"Deduplicated":196}],"const_generics":[],"trait_refs":[{"Deduplicated":199}]}},"trait_ref":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":1625}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"reuses_default":true}},"is_global_initializer":null,"body":"Opaque"},{"def_id":14,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Impl":{"Ty":{"params":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":5,"beg":{"line":101,"col":5},"end":{"line":101,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"Deduplicated":1063},"kind":"InherentImplBlock"}}},{"Ident":["iter",0]}],"span":{"data":{"file_id":5,"beg":{"line":1040,"col":4},"end":{"line":1040,"col":43}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Returns an iterator over the slice."},{"DocComment":""},{"DocComment":" The iterator yields all items from start to end."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let x = &[1, 2, 4];"},{"DocComment":" let mut iterator = x.iter();"},{"DocComment":""},{"DocComment":" assert_eq!(iterator.next(), Some(&1));"},{"DocComment":" assert_eq!(iterator.next(), Some(&2));"},{"DocComment":" assert_eq!(iterator.next(), Some(&4));"},{"DocComment":" assert_eq!(iterator.next(), None);"},{"DocComment":" ```"}],"inline":"Hint","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"slice_iter"},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":5,"beg":{"line":101,"col":5},"end":{"line":101,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":1602}],"output":{"Deduplicated":1625}},"src":"TopLevel","is_global_initializer":null,"body":"Opaque"},{"def_id":15,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Impl":{"Trait":2}},{"Ident":["into_iter",0]}],"span":{"data":{"file_id":11,"beg":{"line":322,"col":4},"end":{"line":322,"col":27}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":"Hint","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":11,"beg":{"line":317,"col":5},"end":{"line":317,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":11,"beg":{"line":317,"col":8},"end":{"line":317,"col":24}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":196}],"output":{"Deduplicated":196}},"src":{"TraitImpl":{"impl_ref":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":196}],"const_generics":[],"trait_refs":[{"Deduplicated":199},{"HashConsedValue":[4562,{"kind":{"Clause":{"Bound":[0,1]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}]}]}},"trait_ref":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":196}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"reuses_default":true}},"is_global_initializer":null,"body":"Opaque"},{"def_id":16,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["result",0]},{"Impl":{"Trait":3}},{"Ident":["branch",0]}],"span":{"data":{"file_id":3,"beg":{"line":2172,"col":4},"end":{"line":2172,"col":64}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":"Hint","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"T"},{"index":1,"name":"E"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":3,"beg":{"line":2162,"col":5},"end":{"line":2162,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":3,"beg":{"line":2162,"col":8},"end":{"line":2162,"col":9}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"HashConsedValue":[4574,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":196},{"Deduplicated":1589}],"const_generics":[],"trait_refs":[{"Deduplicated":199},{"HashConsedValue":[2275,{"kind":{"Clause":{"Bound":[0,1]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}],"output":{"HashConsedValue":[6300,{"Adt":{"id":{"Adt":8},"generics":{"regions":[],"types":[{"HashConsedValue":[4594,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":835},{"Deduplicated":1589}],"const_generics":[],"trait_refs":[{"Deduplicated":837},{"Deduplicated":2275}]}}}]},{"Deduplicated":196}],"const_generics":[],"trait_refs":[{"HashConsedValue":[4596,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[4595,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[4580,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":835},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[{"Deduplicated":837},{"HashConsedValue":[2664,{"kind":{"Clause":{"Bound":[1,1]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[2246,{"TypeVar":{"Bound":[2,1]}}]}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":4580}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":199}]}}}]}},"src":{"TraitImpl":{"impl_ref":{"id":3,"generics":{"regions":[],"types":[{"Deduplicated":196},{"Deduplicated":1589}],"const_generics":[],"trait_refs":[{"Deduplicated":199},{"Deduplicated":2275}]}},"trait_ref":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":4574}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":1},"reuses_default":true}},"is_global_initializer":null,"body":"Opaque"},{"def_id":17,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["result",0]},{"Impl":{"Trait":4}},{"Ident":["from_residual",0]}],"span":{"data":{"file_id":3,"beg":{"line":2187,"col":4},"end":{"line":2187,"col":70}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":"Hint","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"T"},{"index":1,"name":"E"},{"index":2,"name":"F"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":3,"beg":{"line":2182,"col":5},"end":{"line":2182,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":3,"beg":{"line":2182,"col":8},"end":{"line":2182,"col":9}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":3,"beg":{"line":2182,"col":11},"end":{"line":2182,"col":12}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":2626}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":3,"span":{"data":{"file_id":3,"beg":{"line":2182,"col":14},"end":{"line":2182,"col":29}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":3,"generics":{"regions":[],"types":[{"Deduplicated":2626},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":4594}],"output":{"HashConsedValue":[4598,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":196},{"HashConsedValue":[2639,{"TypeVar":{"Bound":[0,2]}}]}],"const_generics":[],"trait_refs":[{"Deduplicated":199},{"HashConsedValue":[2667,{"kind":{"Clause":{"Bound":[0,2]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":2626}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},"src":{"TraitImpl":{"impl_ref":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":196},{"Deduplicated":1589},{"Deduplicated":2639}],"const_generics":[],"trait_refs":[{"Deduplicated":199},{"Deduplicated":2275},{"Deduplicated":2667},{"HashConsedValue":[4599,{"kind":{"Clause":{"Bound":[0,3]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":3,"generics":{"regions":[],"types":[{"Deduplicated":2626},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}]}]}},"trait_ref":{"id":24,"generics":{"regions":[],"types":[{"Deduplicated":4598},{"Deduplicated":4594}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"reuses_default":true}},"is_global_initializer":null,"body":"Opaque"},{"def_id":18,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["bool",0]},{"Impl":{"Ty":{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"Deduplicated":211},"kind":"InherentImplBlock"}}},{"Ident":["then",0]}],"span":{"data":{"file_id":15,"beg":{"line":65,"col":4},"end":{"line":65,"col":94}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Returns `Some(f())` if the `bool` is [`true`](../std/keyword.true.html),"},{"DocComment":" or `None` otherwise."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" assert_eq!(false.then(|| 0), None);"},{"DocComment":" assert_eq!(true.then(|| 0), Some(0));"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let mut a = 0;"},{"DocComment":""},{"DocComment":" true.then(|| { a += 1; });"},{"DocComment":" false.then(|| { a += 1; });"},{"DocComment":""},{"DocComment":" // `a` is incremented once because the closure is evaluated lazily by"},{"DocComment":" // `then`."},{"DocComment":" assert_eq!(a, 1);"},{"DocComment":" ```"}],"inline":"Hint","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"bool_then"},"generics":{"regions":[],"types":[{"index":0,"name":"T"},{"index":1,"name":"F"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":15,"beg":{"line":65,"col":22},"end":{"line":65,"col":23}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":15,"beg":{"line":65,"col":25},"end":{"line":65,"col":26}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":15,"beg":{"line":65,"col":28},"end":{"line":65,"col":49}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":1585},{"Deduplicated":221}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":3,"span":{"data":{"file_id":15,"beg":{"line":65,"col":52},"end":{"line":65,"col":68}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":5,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[{"regions":[],"skip_binder":{"trait_ref":{"HashConsedValue":[6137,{"kind":{"Clause":{"Bound":[1,2]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":2246},{"Deduplicated":221}],"const_generics":[],"trait_refs":[]}}}}]},"type_id":0,"ty":{"Deduplicated":164}}}]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":211},{"Deduplicated":1589}],"output":{"HashConsedValue":[3361,{"Adt":{"id":{"Adt":7},"generics":{"regions":[],"types":[{"Deduplicated":196}],"const_generics":[],"trait_refs":[{"Deduplicated":199}]}}}]}},"src":"TopLevel","is_global_initializer":null,"body":"Opaque"},{"def_id":19,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["bool",0]},{"Impl":{"Ty":{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"Deduplicated":211},"kind":"InherentImplBlock"}}},{"Ident":["then_some",0]}],"span":{"data":{"file_id":15,"beg":{"line":36,"col":4},"end":{"line":36,"col":72}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Returns `Some(t)` if the `bool` is [`true`](../std/keyword.true.html),"},{"DocComment":" or `None` otherwise."},{"DocComment":""},{"DocComment":" Arguments passed to `then_some` are eagerly evaluated; if you are"},{"DocComment":" passing the result of a function call, it is recommended to use"},{"DocComment":" [`then`], which is lazily evaluated."},{"DocComment":""},{"DocComment":" [`then`]: bool::then"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" assert_eq!(false.then_some(0), None);"},{"DocComment":" assert_eq!(true.then_some(0), Some(0));"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let mut a = 0;"},{"DocComment":" let mut function_with_side_effects = || { a += 1; };"},{"DocComment":""},{"DocComment":" true.then_some(function_with_side_effects());"},{"DocComment":" false.then_some(function_with_side_effects());"},{"DocComment":""},{"DocComment":" // `a` is incremented twice because the value passed to `then_some` is"},{"DocComment":" // evaluated eagerly."},{"DocComment":" assert_eq!(a, 2);"},{"DocComment":" ```"}],"inline":"Hint","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":15,"beg":{"line":36,"col":27},"end":{"line":36,"col":28}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":15,"beg":{"line":36,"col":30},"end":{"line":36,"col":46}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":5,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":211},{"Deduplicated":196}],"output":{"Deduplicated":3361}},"src":"TopLevel","is_global_initializer":null,"body":"Opaque"},{"def_id":20,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["option",0]},{"Impl":{"Trait":8}},{"Ident":["branch",0]}],"span":{"data":{"file_id":6,"beg":{"line":2765,"col":4},"end":{"line":2765,"col":64}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":"Hint","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":6,"beg":{"line":2755,"col":5},"end":{"line":2755,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":3361}],"output":{"HashConsedValue":[6301,{"Adt":{"id":{"Adt":8},"generics":{"regions":[],"types":[{"Deduplicated":1533},{"Deduplicated":196}],"const_generics":[],"trait_refs":[{"Deduplicated":1535},{"Deduplicated":199}]}}}]}},"src":{"TraitImpl":{"impl_ref":{"id":8,"generics":{"regions":[],"types":[{"Deduplicated":196}],"const_generics":[],"trait_refs":[{"Deduplicated":199}]}},"trait_ref":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":3361}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":1},"reuses_default":true}},"is_global_initializer":null,"body":"Opaque"},{"def_id":21,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["option",0]},{"Impl":{"Trait":9}},{"Ident":["from_residual",0]}],"span":{"data":{"file_id":6,"beg":{"line":2779,"col":4},"end":{"line":2779,"col":67}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":"Hint","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":6,"beg":{"line":2777,"col":5},"end":{"line":2777,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":1533}],"output":{"Deduplicated":3361}},"src":{"TraitImpl":{"impl_ref":{"id":9,"generics":{"regions":[],"types":[{"Deduplicated":196}],"const_generics":[],"trait_refs":[{"Deduplicated":199}]}},"trait_ref":{"id":24,"generics":{"regions":[],"types":[{"Deduplicated":3361},{"Deduplicated":1533}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"reuses_default":true}},"is_global_initializer":null,"body":"Opaque"},null,null,null,null,null,null,null,{"def_id":29,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]},{"Ident":["next",0]}],"span":{"data":{"file_id":14,"beg":{"line":77,"col":4},"end":{"line":77,"col":45}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Advances the iterator and returns the next value."},{"DocComment":""},{"DocComment":" Returns [`None`] when iteration is finished. Individual iterator"},{"DocComment":" implementations may choose to resume iteration, and so calling `next()`"},{"DocComment":" again may or may not eventually start returning [`Some(Item)`] again at some"},{"DocComment":" point."},{"DocComment":""},{"DocComment":" [`Some(Item)`]: Some"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let a = [1, 2, 3];"},{"DocComment":""},{"DocComment":" let mut iter = a.into_iter();"},{"DocComment":""},{"DocComment":" // A call to next() returns the next value..."},{"DocComment":" assert_eq!(Some(1), iter.next());"},{"DocComment":" assert_eq!(Some(2), iter.next());"},{"DocComment":" assert_eq!(Some(3), iter.next());"},{"DocComment":""},{"DocComment":" // ... and then None once it's over."},{"DocComment":" assert_eq!(None, iter.next());"},{"DocComment":""},{"DocComment":" // More calls may or may not return `None`. Here, they always will."},{"DocComment":" assert_eq!(None, iter.next());"},{"DocComment":" assert_eq!(None, iter.next());"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"next"},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"HashConsedValue":[2979,{"Ref":[{"Var":{"Bound":[0,0]}},{"Deduplicated":196},"Mut"]}]}],"output":{"HashConsedValue":[6305,{"Adt":{"id":{"Adt":7},"generics":{"regions":[],"types":[{"HashConsedValue":[6302,{"TraitType":[{"HashConsedValue":[4665,{"kind":{"Clause":{"Bound":[0,0]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[{"HashConsedValue":[6304,{"kind":{"ParentClause":[{"Deduplicated":4665},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[6303,{"TraitType":[{"HashConsedValue":[5625,{"kind":{"Clause":{"Bound":[1,0]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":1611}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},"src":{"TraitDecl":{"trait_ref":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":196}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,{"def_id":175,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["result",0]},{"Impl":{"Trait":3}},{"Ident":["from_output",0]}],"span":{"data":{"file_id":3,"beg":{"line":2167,"col":4},"end":{"line":2167,"col":48}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":"Hint","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"T"},{"index":1,"name":"E"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":3,"beg":{"line":2162,"col":5},"end":{"line":2162,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":3,"beg":{"line":2162,"col":8},"end":{"line":2162,"col":9}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":196}],"output":{"Deduplicated":4574}},"src":{"TraitImpl":{"impl_ref":{"id":3,"generics":{"regions":[],"types":[{"Deduplicated":196},{"Deduplicated":1589}],"const_generics":[],"trait_refs":[{"Deduplicated":199},{"Deduplicated":2275}]}},"trait_ref":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":4574}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"reuses_default":true}},"is_global_initializer":null,"body":"Opaque"},{"def_id":176,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["convert",0]},{"Ident":["From",0]},{"Ident":["from",0]}],"span":{"data":{"file_id":10,"beg":{"line":592,"col":4},"end":{"line":592,"col":30}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Converts to this type from the input type."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"from_fn"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":3,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":1589}],"output":{"Deduplicated":196}},"src":{"TraitDecl":{"trait_ref":{"id":3,"generics":{"regions":[],"types":[{"Deduplicated":196},{"Deduplicated":1589}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},{"def_id":177,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["convert",0]},{"Impl":{"Trait":5}},{"Ident":["from",0]}],"span":{"data":{"file_id":10,"beg":{"line":788,"col":4},"end":{"line":788,"col":22}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Returns the argument unchanged."}],"inline":"Always","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":10,"beg":{"line":785,"col":5},"end":{"line":785,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":196}],"output":{"Deduplicated":196}},"src":{"TraitImpl":{"impl_ref":{"id":5,"generics":{"regions":[],"types":[{"Deduplicated":196}],"const_generics":[],"trait_refs":[{"Deduplicated":199}]}},"trait_ref":{"id":3,"generics":{"regions":[],"types":[{"Deduplicated":196},{"Deduplicated":196}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"reuses_default":true}},"is_global_initializer":null,"body":"Opaque"},{"def_id":178,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_closure",0]},{"Impl":{"Trait":6}},{"Ident":["call_once",0]}],"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"source_text":"|| x + 1","attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"HashConsedValue":[4614,{"Adt":{"id":{"Adt":10},"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[],"const_generics":[],"trait_refs":[]}}}]},{"Deduplicated":221}],"output":{"Deduplicated":207}},"src":{"TraitImpl":{"impl_ref":{"id":6,"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[],"const_generics":[],"trait_refs":[]}},"trait_ref":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":4614},{"Deduplicated":221}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"reuses_default":false}},"is_global_initializer":null,"body":{"Unstructured":{"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"bound_body_regions":1,"locals":{"arg_count":2,"locals":[{"index":0,"name":null,"span":{"data":{"file_id":0,"beg":{"line":106,"col":13},"end":{"line":106,"col":13}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":1,"name":null,"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"ty":{"Deduplicated":4614}},{"index":2,"name":"tupled_args","span":{"data":{"file_id":0,"beg":{"line":0,"col":0},"end":{"line":0,"col":0}},"generated_from_span":null},"ty":{"Deduplicated":221}},{"index":3,"name":null,"span":{"data":{"file_id":0,"beg":{"line":106,"col":14},"end":{"line":106,"col":15}},"generated_from_span":null},"ty":{"Deduplicated":207}},{"index":4,"name":null,"span":{"data":{"file_id":0,"beg":{"line":106,"col":14},"end":{"line":106,"col":19}},"generated_from_span":null},"ty":{"Deduplicated":212}}]},"body":[{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":106,"col":14},"end":{"line":106,"col":15}},"generated_from_span":null},"kind":{"StorageLive":4},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":106,"col":14},"end":{"line":106,"col":15}},"generated_from_span":null},"kind":{"StorageLive":3},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":106,"col":14},"end":{"line":106,"col":15}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":3},"ty":{"Deduplicated":207}},{"Use":{"Copy":{"kind":{"Projection":[{"kind":{"Projection":[{"kind":{"Local":1},"ty":{"Deduplicated":4614}},{"Field":[{"Adt":[10,null]},0]}]},"ty":{"HashConsedValue":[6306,{"Ref":[{"Body":0},{"Deduplicated":207},"Shared"]}]}},"Deref"]},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":106,"col":14},"end":{"line":106,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":4},"ty":{"Deduplicated":212}},{"BinaryOp":["AddChecked",{"Copy":{"kind":{"Local":3},"ty":{"Deduplicated":207}}},{"Const":{"kind":{"Literal":{"Scalar":{"Signed":["I64","1"]}}},"ty":{"Deduplicated":207}}}]}]},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":106,"col":14},"end":{"line":106,"col":19}},"generated_from_span":null},"kind":{"Assert":{"assert":{"cond":{"Move":{"kind":{"Projection":[{"kind":{"Local":4},"ty":{"Deduplicated":212}},{"Field":[{"Tuple":2},1]}]},"ty":{"Deduplicated":211}}},"expected":false,"check_kind":{"Overflow":[{"Add":"Wrap"},{"Move":{"kind":{"Local":3},"ty":{"Deduplicated":207}}},{"Const":{"kind":{"Literal":{"Scalar":{"Signed":["I64","1"]}}},"ty":{"Deduplicated":207}}}]}},"target":2,"on_unwind":1}},"comments_before":[]}},{"statements":[],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"kind":"UnwindResume","comments_before":[]}},{"statements":[{"span":{"data":{"file_id":0,"beg":{"line":106,"col":14},"end":{"line":106,"col":19}},"generated_from_span":null},"kind":{"Assign":[{"kind":{"Local":0},"ty":{"Deduplicated":207}},{"Use":{"Move":{"kind":{"Projection":[{"kind":{"Local":4},"ty":{"Deduplicated":212}},{"Field":[{"Tuple":2},0]}]},"ty":{"Deduplicated":207}}}}]},"comments_before":[]},{"span":{"data":{"file_id":0,"beg":{"line":106,"col":18},"end":{"line":106,"col":19}},"generated_from_span":null},"kind":{"StorageDead":3},"comments_before":[]}],"terminator":{"span":{"data":{"file_id":0,"beg":{"line":106,"col":19},"end":{"line":106,"col":19}},"generated_from_span":null},"kind":"Return","comments_before":[]}}],"comments":[]}}},{"def_id":179,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["function",0]},{"Ident":["FnOnce",0]},{"Ident":["call_once",0]}],"span":{"data":{"file_id":16,"beg":{"line":250,"col":4},"end":{"line":250,"col":70}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Performs the call operation."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"Args"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":196},{"Deduplicated":1589}],"output":{"HashConsedValue":[6308,{"TraitType":[{"HashConsedValue":[6307,{"kind":{"Clause":{"Bound":[0,0]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}},"src":{"TraitDecl":{"trait_ref":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":196},{"Deduplicated":1589}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},{"def_id":180,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Ident":["Destruct",0]},{"Ident":["drop_in_place",0]}],"span":{"data":{"file_id":1,"beg":{"line":1063,"col":0},"end":{"line":1063,"col":38}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":true,"inputs":[{"HashConsedValue":[6309,{"RawPtr":[{"Deduplicated":196},"Mut"]}]}],"output":{"Deduplicated":221}},"src":{"TraitDecl":{"trait_ref":{"id":5,"generics":{"regions":[],"types":[{"Deduplicated":196}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},{"def_id":181,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_closure",0]},{"Ident":["closure",0]},{"Impl":{"Trait":7}},{"Ident":["drop_in_place",0]}],"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"source_text":"|| x + 1","attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":true,"opacity":"Transparent","lang_item":null},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":true,"inputs":[{"HashConsedValue":[6310,{"RawPtr":[{"Deduplicated":4614},"Mut"]}]}],"output":{"Deduplicated":221}},"src":{"TraitImpl":{"impl_ref":{"id":7,"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[],"const_generics":[],"trait_refs":[]}},"trait_ref":{"id":5,"generics":{"regions":[],"types":[{"Deduplicated":4614}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"reuses_default":false}},"is_global_initializer":null,"body":"Missing"},{"def_id":182,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["option",0]},{"Impl":{"Trait":8}},{"Ident":["from_output",0]}],"span":{"data":{"file_id":6,"beg":{"line":2760,"col":4},"end":{"line":2760,"col":48}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":"Hint","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":6,"beg":{"line":2755,"col":5},"end":{"line":2755,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":196}],"output":{"Deduplicated":3361}},"src":{"TraitImpl":{"impl_ref":{"id":8,"generics":{"regions":[],"types":[{"Deduplicated":196}],"const_generics":[],"trait_refs":[{"Deduplicated":199}]}},"trait_ref":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":3361}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"reuses_default":true}},"is_global_initializer":null,"body":"Opaque"},{"def_id":183,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["IntoIterator",0]},{"Ident":["into_iter",0]}],"span":{"data":{"file_id":11,"beg":{"line":312,"col":4},"end":{"line":312,"col":41}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Creates an iterator from a value."},{"DocComment":""},{"DocComment":" See the [module-level documentation] for more."},{"DocComment":""},{"DocComment":" [module-level documentation]: crate::iter"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let v = [1, 2, 3];"},{"DocComment":" let mut iter = v.into_iter();"},{"DocComment":""},{"DocComment":" assert_eq!(Some(1), iter.next());"},{"DocComment":" assert_eq!(Some(2), iter.next());"},{"DocComment":" assert_eq!(Some(3), iter.next());"},{"DocComment":" assert_eq!(None, iter.next());"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"into_iter"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":196}],"output":{"HashConsedValue":[6311,{"TraitType":[{"HashConsedValue":[5634,{"kind":{"Clause":{"Bound":[0,0]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}]},1]}]}},"src":{"TraitDecl":{"trait_ref":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":196}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},{"def_id":184,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["clone",0]},{"Ident":["Clone",0]},{"Ident":["clone",0]}],"span":{"data":{"file_id":25,"beg":{"line":236,"col":4},"end":{"line":236,"col":28}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Returns a duplicate of the value."},{"DocComment":""},{"DocComment":" Note that what \"duplicate\" means varies by type:"},{"DocComment":" - For most types, this creates a deep, independent copy"},{"DocComment":" - For reference types like `&T`, this creates another reference to the same value"},{"DocComment":" - For smart pointers like [`Arc`] or [`Rc`], this increments the reference count"},{"DocComment":" but still points to the same underlying data"},{"DocComment":""},{"DocComment":" [`Arc`]: ../../std/sync/struct.Arc.html"},{"DocComment":" [`Rc`]: ../../std/rc/struct.Rc.html"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #![allow(noop_method_call)]"},{"DocComment":" let hello = \"Hello\"; // &str implements Clone"},{"DocComment":""},{"DocComment":" assert_eq!(\"Hello\", hello.clone());"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" Example with a reference-counted type:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::sync::{Arc, Mutex};"},{"DocComment":""},{"DocComment":" let data = Arc::new(Mutex::new(vec![1, 2, 3]));"},{"DocComment":" let data_clone = data.clone(); // Creates another Arc pointing to the same Mutex"},{"DocComment":""},{"DocComment":" {"},{"DocComment":" let mut lock = data.lock().unwrap();"},{"DocComment":" lock.push(4);"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" // Changes are visible through the clone because they share the same underlying data"},{"DocComment":" assert_eq!(*data_clone.lock().unwrap(), vec![1, 2, 3, 4]);"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"clone_fn"},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":8,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":4551}],"output":{"Deduplicated":196}},"src":{"TraitDecl":{"trait_ref":{"id":8,"generics":{"regions":[],"types":[{"Deduplicated":196}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},null,{"def_id":186,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["function",0]},{"Ident":["FnMut",0]},{"Ident":["call_mut",0]}],"span":{"data":{"file_id":16,"beg":{"line":166,"col":4},"end":{"line":166,"col":74}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Performs the call operation."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[{"index":0,"name":"Self"},{"index":1,"name":"Args"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":9,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":2979},{"Deduplicated":1589}],"output":{"HashConsedValue":[6314,{"TraitType":[{"HashConsedValue":[6313,{"kind":{"ParentClause":[{"HashConsedValue":[6312,{"kind":{"Clause":{"Bound":[0,0]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":9,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}]},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}},"src":{"TraitDecl":{"trait_ref":{"id":9,"generics":{"regions":[],"types":[{"Deduplicated":196},{"Deduplicated":1589}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},{"def_id":187,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["FromIterator",0]},{"Ident":["from_iter",0]}],"span":{"data":{"file_id":11,"beg":{"line":152,"col":4},"end":{"line":152,"col":61}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Creates a value from an iterator."},{"DocComment":""},{"DocComment":" See the [module-level documentation] for more."},{"DocComment":""},{"DocComment":" [module-level documentation]: crate::iter"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let five_fives = std::iter::repeat(5).take(5);"},{"DocComment":""},{"DocComment":" let v = Vec::from_iter(five_fives);"},{"DocComment":""},{"DocComment":" assert_eq!(v, vec![5, 5, 5, 5, 5]);"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"from_iter_fn"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"A"},{"index":2,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":10,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":11,"beg":{"line":152,"col":17},"end":{"line":152,"col":18}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":2626}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":11,"beg":{"line":152,"col":20},"end":{"line":152,"col":42}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":2626}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[{"regions":[],"skip_binder":{"trait_ref":{"HashConsedValue":[5264,{"kind":{"Clause":{"Bound":[1,2]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"HashConsedValue":[2630,{"TypeVar":{"Bound":[2,2]}}]}],"const_generics":[],"trait_refs":[]}}}}]},"type_id":0,"ty":{"Deduplicated":1585}}}]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":2639}],"output":{"Deduplicated":196}},"src":{"TraitDecl":{"trait_ref":{"id":10,"generics":{"regions":[],"types":[{"Deduplicated":196},{"Deduplicated":1589}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},{"def_id":188,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["try_trait",0]},{"Ident":["Try",0]},{"Ident":["from_output",0]}],"span":{"data":{"file_id":42,"beg":{"line":192,"col":4},"end":{"line":192,"col":49}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Constructs the type from its `Output` type."},{"DocComment":""},{"DocComment":" This should be implemented consistently with the `branch` method"},{"DocComment":" such that applying the `?` operator will get back the original value:"},{"DocComment":" `Try::from_output(x).branch() --> ControlFlow::Continue(x)`."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #![feature(try_trait_v2)]"},{"DocComment":" use std::ops::Try;"},{"DocComment":""},{"DocComment":" assert_eq!( as Try>::from_output(3), Ok(3));"},{"DocComment":" assert_eq!( as Try>::from_output(4), Some(4));"},{"DocComment":" assert_eq!("},{"DocComment":" as Try>::from_output(5),"},{"DocComment":" std::ops::ControlFlow::Continue(5),"},{"DocComment":" );"},{"DocComment":""},{"DocComment":" # fn make_question_mark_work() -> Option<()> {"},{"DocComment":" assert_eq!(Option::from_output(4)?, 4);"},{"DocComment":" # None }"},{"DocComment":" # make_question_mark_work();"},{"DocComment":""},{"DocComment":" // This is used, for example, on the accumulator in `try_fold`:"},{"DocComment":" let r = std::iter::empty().try_fold(4, |_, ()| -> Option<_> { unreachable!() });"},{"DocComment":" assert_eq!(r, Some(4));"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"from_output"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"HashConsedValue":[5266,{"TraitType":[{"HashConsedValue":[5265,{"kind":{"Clause":{"Bound":[0,0]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"output":{"Deduplicated":196}},"src":{"TraitDecl":{"trait_ref":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":196}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},{"def_id":189,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["try_trait",0]},{"Ident":["Try",0]},{"Ident":["branch",0]}],"span":{"data":{"file_id":42,"beg":{"line":219,"col":4},"end":{"line":219,"col":65}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Used in `?` to decide whether the operator should produce a value"},{"DocComment":" (because this returned [`ControlFlow::Continue`])"},{"DocComment":" or propagate a value back to the caller"},{"DocComment":" (because this returned [`ControlFlow::Break`])."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #![feature(try_trait_v2)]"},{"DocComment":" use std::ops::{ControlFlow, Try};"},{"DocComment":""},{"DocComment":" assert_eq!(Ok::<_, String>(3).branch(), ControlFlow::Continue(3));"},{"DocComment":" assert_eq!(Err::(3).branch(), ControlFlow::Break(Err(3)));"},{"DocComment":""},{"DocComment":" assert_eq!(Some(3).branch(), ControlFlow::Continue(3));"},{"DocComment":" assert_eq!(None::.branch(), ControlFlow::Break(None));"},{"DocComment":""},{"DocComment":" assert_eq!(ControlFlow::::Continue(3).branch(), ControlFlow::Continue(3));"},{"DocComment":" assert_eq!("},{"DocComment":" ControlFlow::<_, String>::Break(3).branch(),"},{"DocComment":" ControlFlow::Break(ControlFlow::Break(3)),"},{"DocComment":" );"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"branch"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":196}],"output":{"HashConsedValue":[6320,{"Adt":{"id":{"Adt":8},"generics":{"regions":[],"types":[{"HashConsedValue":[6315,{"TraitType":[{"Deduplicated":5265},1]}]},{"Deduplicated":5266}],"const_generics":[],"trait_refs":[{"HashConsedValue":[6317,{"kind":{"ParentClause":[{"HashConsedValue":[6316,{"kind":{"ParentClause":[{"Deduplicated":5265},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":24,"generics":{"regions":[],"types":[{"Deduplicated":164},{"HashConsedValue":[5269,{"TraitType":[{"HashConsedValue":[5268,{"kind":{"Clause":{"Bound":[1,0]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":1611}],"const_generics":[],"trait_refs":[]}}}}]},1]}]}],"const_generics":[],"trait_refs":[]}}}}]},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":5269}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6319,{"kind":{"ParentClause":[{"Deduplicated":5265},2]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[6318,{"TraitType":[{"Deduplicated":5268},0]}]}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},"src":{"TraitDecl":{"trait_ref":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":196}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":1},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},{"def_id":190,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["Extend",0]},{"Ident":["extend",0]}],"span":{"data":{"file_id":11,"beg":{"line":416,"col":4},"end":{"line":416,"col":61}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Extends a collection with the contents of an iterator."},{"DocComment":""},{"DocComment":" As this is the only required method for this trait, the [trait-level] docs"},{"DocComment":" contain more details."},{"DocComment":""},{"DocComment":" [trait-level]: Extend"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" // You can extend a String with some chars:"},{"DocComment":" let mut message = String::from(\"abc\");"},{"DocComment":""},{"DocComment":" message.extend(['d', 'e', 'f'].iter());"},{"DocComment":""},{"DocComment":" assert_eq!(\"abcdef\", &message);"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[{"index":0,"name":"Self"},{"index":1,"name":"A"},{"index":2,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":13,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":11,"beg":{"line":416,"col":14},"end":{"line":416,"col":15}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":2626}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":11,"beg":{"line":416,"col":17},"end":{"line":416,"col":39}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":2626}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[{"regions":[],"skip_binder":{"trait_ref":{"Deduplicated":5264},"type_id":0,"ty":{"Deduplicated":1585}}}]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":2979},{"Deduplicated":2639}],"output":{"Deduplicated":221}},"src":{"TraitDecl":{"trait_ref":{"id":13,"generics":{"regions":[],"types":[{"Deduplicated":196},{"Deduplicated":1589}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},null,null,null,{"def_id":194,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["default",0]},{"Ident":["Default",0]},{"Ident":["default",0]}],"span":{"data":{"file_id":43,"beg":{"line":139,"col":4},"end":{"line":139,"col":25}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Returns the \"default value\" for a type."},{"DocComment":""},{"DocComment":" Default values are often some kind of initial value, identity value, or anything else that"},{"DocComment":" may make sense as a default."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" Using built-in default values:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let i: i8 = Default::default();"},{"DocComment":" let (x, y): (Option, f64) = Default::default();"},{"DocComment":" let (a, b, (c, d)): (i32, u32, (bool, bool)) = Default::default();"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" Making your own:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #[allow(dead_code)]"},{"DocComment":" enum Kind {"},{"DocComment":" A,"},{"DocComment":" B,"},{"DocComment":" C,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl Default for Kind {"},{"DocComment":" fn default() -> Self { Kind::A }"},{"DocComment":" }"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"default_fn"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":14,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[],"output":{"Deduplicated":196}},"src":{"TraitDecl":{"trait_ref":{"id":14,"generics":{"regions":[],"types":[{"Deduplicated":196}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},{"def_id":195,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["double_ended",0]},{"Ident":["DoubleEndedIterator",0]},{"Ident":["next_back",0]}],"span":{"data":{"file_id":44,"beg":{"line":94,"col":4},"end":{"line":94,"col":50}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Removes and returns an element from the end of the iterator."},{"DocComment":""},{"DocComment":" Returns `None` when there are no more elements."},{"DocComment":""},{"DocComment":" The [trait-level] docs contain more details."},{"DocComment":""},{"DocComment":" [trait-level]: DoubleEndedIterator"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" Basic usage:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let numbers = vec![1, 2, 3, 4, 5, 6];"},{"DocComment":""},{"DocComment":" let mut iter = numbers.iter();"},{"DocComment":""},{"DocComment":" assert_eq!(Some(&1), iter.next());"},{"DocComment":" assert_eq!(Some(&6), iter.next_back());"},{"DocComment":" assert_eq!(Some(&5), iter.next_back());"},{"DocComment":" assert_eq!(Some(&2), iter.next());"},{"DocComment":" assert_eq!(Some(&3), iter.next());"},{"DocComment":" assert_eq!(Some(&4), iter.next());"},{"DocComment":" assert_eq!(None, iter.next());"},{"DocComment":" assert_eq!(None, iter.next_back());"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" # Remarks"},{"DocComment":""},{"DocComment":" The elements yielded by `DoubleEndedIterator`'s methods may differ from"},{"DocComment":" the ones yielded by [`Iterator`]'s methods:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let vec = vec![(1, 'a'), (1, 'b'), (1, 'c'), (2, 'a'), (2, 'b')];"},{"DocComment":" let uniq_by_fst_comp = || {"},{"DocComment":" let mut seen = std::collections::HashSet::new();"},{"DocComment":" vec.iter().copied().filter(move |x| seen.insert(x.0))"},{"DocComment":" };"},{"DocComment":""},{"DocComment":" assert_eq!(uniq_by_fst_comp().last(), Some((2, 'a')));"},{"DocComment":" assert_eq!(uniq_by_fst_comp().next_back(), Some((2, 'b')));"},{"DocComment":""},{"DocComment":" assert_eq!("},{"DocComment":" uniq_by_fst_comp().fold(vec![], |mut v, x| {v.push(x); v}),"},{"DocComment":" vec![(1, 'a'), (2, 'a')]"},{"DocComment":" );"},{"DocComment":" assert_eq!("},{"DocComment":" uniq_by_fst_comp().rfold(vec![], |mut v, x| {v.push(x); v}),"},{"DocComment":" vec![(2, 'b'), (1, 'c')]"},{"DocComment":" );"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":15,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":2979}],"output":{"HashConsedValue":[6324,{"Adt":{"id":{"Adt":7},"generics":{"regions":[],"types":[{"HashConsedValue":[6321,{"TraitType":[{"HashConsedValue":[5276,{"kind":{"ParentClause":[{"HashConsedValue":[5275,{"kind":{"Clause":{"Bound":[0,0]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":15,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}]},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[{"HashConsedValue":[6323,{"kind":{"ParentClause":[{"Deduplicated":5276},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[6322,{"TraitType":[{"HashConsedValue":[5647,{"kind":{"ParentClause":[{"HashConsedValue":[5646,{"kind":{"Clause":{"Bound":[1,0]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":15,"generics":{"regions":[],"types":[{"Deduplicated":1611}],"const_generics":[],"trait_refs":[]}}}}]},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":1611}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},"src":{"TraitDecl":{"trait_ref":{"id":15,"generics":{"regions":[],"types":[{"Deduplicated":196}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},null,null,null,null,null,null,null,{"def_id":203,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["Ord",0]},{"Ident":["cmp",0]}],"span":{"data":{"file_id":46,"beg":{"line":991,"col":4},"end":{"line":991,"col":44}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" This method returns an [`Ordering`] between `self` and `other`."},{"DocComment":""},{"DocComment":" By convention, `self.cmp(&other)` returns the ordering matching the expression"},{"DocComment":" `self other` if true."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::cmp::Ordering;"},{"DocComment":""},{"DocComment":" assert_eq!(5.cmp(&10), Ordering::Less);"},{"DocComment":" assert_eq!(10.cmp(&5), Ordering::Greater);"},{"DocComment":" assert_eq!(5.cmp(&5), Ordering::Equal);"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"ord_cmp_method"},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"},{"index":1,"name":null,"mutability":"Unknown"}],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":17,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":4551},{"HashConsedValue":[6325,{"Ref":[{"Var":{"Bound":[0,1]}},{"Deduplicated":196},"Shared"]}]}],"output":{"HashConsedValue":[3603,{"Adt":{"id":{"Adt":36},"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}}]}},"src":{"TraitDecl":{"trait_ref":{"id":17,"generics":{"regions":[],"types":[{"Deduplicated":196}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},null,null,null,{"def_id":207,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["accum",0]},{"Ident":["Sum",0]},{"Ident":["sum",0]}],"span":{"data":{"file_id":52,"beg":{"line":21,"col":4},"end":{"line":21,"col":51}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Takes an iterator and generates `Self` from the elements by \"summing up\""},{"DocComment":" the items."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"A"},{"index":2,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":19,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":52,"beg":{"line":21,"col":11},"end":{"line":21,"col":12}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":2626}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":52,"beg":{"line":21,"col":14},"end":{"line":21,"col":32}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":2626}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[{"regions":[],"skip_binder":{"trait_ref":{"HashConsedValue":[5291,{"kind":{"Clause":{"Bound":[1,2]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":2630}],"const_generics":[],"trait_refs":[]}}}}]},"type_id":0,"ty":{"Deduplicated":1585}}}]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":2639}],"output":{"Deduplicated":196}},"src":{"TraitDecl":{"trait_ref":{"id":19,"generics":{"regions":[],"types":[{"Deduplicated":196},{"Deduplicated":1589}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},{"def_id":208,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["accum",0]},{"Ident":["Product",0]},{"Ident":["product",0]}],"span":{"data":{"file_id":52,"beg":{"line":42,"col":4},"end":{"line":42,"col":55}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Takes an iterator and generates `Self` from the elements by multiplying"},{"DocComment":" the items."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"A"},{"index":2,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":20,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":52,"beg":{"line":42,"col":15},"end":{"line":42,"col":16}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":2626}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":52,"beg":{"line":42,"col":18},"end":{"line":42,"col":36}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":2626}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[{"regions":[],"skip_binder":{"trait_ref":{"Deduplicated":5291},"type_id":0,"ty":{"Deduplicated":1585}}}]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":2639}],"output":{"Deduplicated":196}},"src":{"TraitDecl":{"trait_ref":{"id":20,"generics":{"regions":[],"types":[{"Deduplicated":196},{"Deduplicated":1589}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},{"def_id":209,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialOrd",0]},{"Ident":["partial_cmp",0]}],"span":{"data":{"file_id":46,"beg":{"line":1387,"col":4},"end":{"line":1387,"col":59}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" This method returns an ordering between `self` and `other` values if one exists."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::cmp::Ordering;"},{"DocComment":""},{"DocComment":" let result = 1.0.partial_cmp(&2.0);"},{"DocComment":" assert_eq!(result, Some(Ordering::Less));"},{"DocComment":""},{"DocComment":" let result = 1.0.partial_cmp(&1.0);"},{"DocComment":" assert_eq!(result, Some(Ordering::Equal));"},{"DocComment":""},{"DocComment":" let result = 2.0.partial_cmp(&1.0);"},{"DocComment":" assert_eq!(result, Some(Ordering::Greater));"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" When comparison is impossible:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let result = f64::NAN.partial_cmp(&1.0);"},{"DocComment":" assert_eq!(result, None);"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"cmp_partialord_cmp"},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"},{"index":1,"name":null,"mutability":"Unknown"}],"types":[{"index":0,"name":"Self"},{"index":1,"name":"Rhs"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":21,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":4551},{"HashConsedValue":[5292,{"Ref":[{"Var":{"Bound":[0,1]}},{"Deduplicated":1589},"Shared"]}]}],"output":{"HashConsedValue":[3972,{"Adt":{"id":{"Adt":7},"generics":{"regions":[],"types":[{"Deduplicated":3603}],"const_generics":[],"trait_refs":[{"HashConsedValue":[3971,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[3970,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":3603}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":3603}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},"src":{"TraitDecl":{"trait_ref":{"id":21,"generics":{"regions":[],"types":[{"Deduplicated":196},{"Deduplicated":1589}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},null,null,null,null,null,null,null,null,{"def_id":218,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialEq",0]},{"Ident":["eq",0]}],"span":{"data":{"file_id":46,"beg":{"line":256,"col":4},"end":{"line":256,"col":38}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Tests for `self` and `other` values to be equal, and is used by `==`."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"cmp_partialeq_eq"},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"},{"index":1,"name":null,"mutability":"Unknown"}],"types":[{"index":0,"name":"Self"},{"index":1,"name":"Rhs"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":22,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":4551},{"Deduplicated":5292}],"output":{"Deduplicated":211}},"src":{"TraitDecl":{"trait_ref":{"id":22,"generics":{"regions":[],"types":[{"Deduplicated":196},{"Deduplicated":1589}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},null,null,{"def_id":221,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["try_trait",0]},{"Ident":["FromResidual",0]},{"Ident":["from_residual",0]}],"span":{"data":{"file_id":42,"beg":{"line":333,"col":4},"end":{"line":333,"col":42}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Constructs the type from a compatible `Residual` type."},{"DocComment":""},{"DocComment":" This should be implemented consistently with the `branch` method such"},{"DocComment":" that applying the `?` operator will get back an equivalent residual:"},{"DocComment":" `FromResidual::from_residual(r).branch() --> ControlFlow::Break(r)`."},{"DocComment":" (The residual is not mandated to be *identical* when interconversion is involved.)"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #![feature(try_trait_v2)]"},{"DocComment":" use std::ops::{ControlFlow, FromResidual};"},{"DocComment":""},{"DocComment":" assert_eq!(Result::::from_residual(Err(3_u8)), Err(3));"},{"DocComment":" assert_eq!(Option::::from_residual(None), None);"},{"DocComment":" assert_eq!("},{"DocComment":" ControlFlow::<_, String>::from_residual(ControlFlow::Break(5)),"},{"DocComment":" ControlFlow::Break(5),"},{"DocComment":" );"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"from_residual"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"R"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":0,"beg":{"line":1,"col":0},"end":{"line":1,"col":0}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":24,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":1589}],"output":{"Deduplicated":196}},"src":{"TraitDecl":{"trait_ref":{"id":24,"generics":{"regions":[],"types":[{"Deduplicated":196},{"Deduplicated":1589}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"has_default":false}},"is_global_initializer":null,"body":"Opaque"},null,{"def_id":223,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["clone",0]},{"Ident":["impls",0]},{"Impl":{"Trait":14}},{"Ident":["clone",0]}],"span":{"data":{"file_id":25,"beg":{"line":614,"col":20},"end":{"line":614,"col":43}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":"Always","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"HashConsedValue":[6326,{"Ref":[{"Var":{"Bound":[0,0]}},{"Deduplicated":614},"Shared"]}]}],"output":{"Deduplicated":614}},"src":{"TraitImpl":{"impl_ref":{"id":14,"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}},"trait_ref":{"id":8,"generics":{"regions":[],"types":[{"Deduplicated":614}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"reuses_default":true}},"is_global_initializer":null,"body":"Opaque"},null,{"def_id":225,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["niche_types",0]},{"Impl":{"Trait":15}},{"Ident":["clone",0]}],"span":{"data":{"file_id":53,"beg":{"line":17,"col":17},"end":{"line":17,"col":22}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":"Hint","rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"signature":{"is_unsafe":false,"inputs":[{"HashConsedValue":[6327,{"Ref":[{"Var":{"Bound":[0,0]}},{"HashConsedValue":[4740,{"Adt":{"id":{"Adt":44},"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}}]},"Shared"]}]}],"output":{"Deduplicated":4740}},"src":{"TraitImpl":{"impl_ref":{"id":15,"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}},"trait_ref":{"id":8,"generics":{"regions":[],"types":[{"Deduplicated":4740}],"const_generics":[],"trait_refs":[]}},"item_id":{"Method":0},"reuses_default":true}},"is_global_initializer":null,"body":"Opaque"},null],"global_decls":[null,null,null,null],"trait_decls":[{"def_id":0,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Ident":["Sized",0]}],"span":{"data":{"file_id":1,"beg":{"line":161,"col":0},"end":{"line":161,"col":26}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Types with a constant size known at compile time."},{"DocComment":""},{"DocComment":" All type parameters have an implicit bound of `Sized`. The special syntax"},{"DocComment":" `?Sized` can be used to remove this bound if it's not appropriate."},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #![allow(dead_code)]"},{"DocComment":" struct Foo(T);"},{"DocComment":" struct Bar(T);"},{"DocComment":""},{"DocComment":" // struct FooUse(Foo<[i32]>); // error: Sized is not implemented for [i32]"},{"DocComment":" struct BarUse(Bar<[i32]>); // OK"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" The one exception is the implicit `Self` type of a trait. A trait does not"},{"DocComment":" have an implicit `Sized` bound as this is incompatible with [trait object]s"},{"DocComment":" where, by definition, the trait needs to work with all possible implementors,"},{"DocComment":" and thus could be any size."},{"DocComment":""},{"DocComment":" Although Rust will let you bind `Sized` to a trait, you won't"},{"DocComment":" be able to use it to form a trait object later:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #![allow(unused_variables)]"},{"DocComment":" trait Foo { }"},{"DocComment":" trait Bar: Sized { }"},{"DocComment":""},{"DocComment":" struct Impl;"},{"DocComment":" impl Foo for Impl { }"},{"DocComment":" impl Bar for Impl { }"},{"DocComment":""},{"DocComment":" let x: &dyn Foo = &Impl; // OK"},{"DocComment":" // let y: &dyn Bar = &Impl; // error: the trait `Bar` cannot be made into an object"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" [trait object]: ../../book/ch17-02-trait-objects.html"},{"Unknown":{"path":"diagnostic::on_unimplemented","args":"message =\n\"the size for values of type `{Self}` cannot be known at compilation time\",\nlabel = \"doesn't have a size known at compile-time\""}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"sized"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":1,"beg":{"line":161,"col":17},"end":{"line":161,"col":26}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[],"vtable":null},{"def_id":1,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Ident":["MetaSized",0]}],"span":{"data":{"file_id":1,"beg":{"line":178,"col":0},"end":{"line":178,"col":33}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Types with a size that can be determined from pointer metadata."},{"Unknown":{"path":"diagnostic::on_unimplemented","args":"message = \"the size for values of type `{Self}` cannot be known\", label =\n\"doesn't have a known size\""}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"meta_sized"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[],"consts":[],"types":[],"methods":[],"vtable":{"id":{"Adt":11},"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}},{"def_id":2,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["iterator",0]},{"Ident":["Iterator",0]}],"span":{"data":{"file_id":14,"beg":{"line":41,"col":0},"end":{"line":41,"col":24}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" A trait for dealing with iterators."},{"DocComment":""},{"DocComment":" This is the main iterator trait. For more about the concept of iterators"},{"DocComment":" generally, please see the [module-level documentation]. In particular, you"},{"DocComment":" may want to know how to [implement `Iterator`][impl]."},{"DocComment":""},{"DocComment":" [module-level documentation]: crate::iter"},{"DocComment":" [impl]: crate::iter#implementing-iterator"},{"Unknown":{"path":"rustc_on_unimplemented","args":"on(Self = \"core::ops::range::RangeTo\", note =\n\"you might have meant to use a bounded `Range`\"),\non(Self = \"core::ops::range::RangeToInclusive\", note =\n\"you might have meant to use a bounded `RangeInclusive`\"), label =\n\"`{Self}` is not an iterator\", message = \"`{Self}` is not an iterator\""}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"iterator"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":14,"beg":{"line":41,"col":0},"end":{"line":4130,"col":1}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":14,"beg":{"line":45,"col":4},"end":{"line":45,"col":14}},"generated_from_span":null},"origin":{"TraitItem":0},"trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[1731,{"TraitType":[{"HashConsedValue":[1730,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":1611}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"Item","attr_info":{"attributes":[{"DocComment":" The type of the elements being iterated over."}],"inline":null,"rename":null,"public":false},"default":null,"implied_clauses":[]},"kind":{"TraitType":[2,0]}}],"methods":[{"params":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"next","attr_info":{"attributes":[{"DocComment":" Advances the iterator and returns the next value."},{"DocComment":""},{"DocComment":" Returns [`None`] when iteration is finished. Individual iterator"},{"DocComment":" implementations may choose to resume iteration, and so calling `next()`"},{"DocComment":" again may or may not eventually start returning [`Some(Item)`] again at some"},{"DocComment":" point."},{"DocComment":""},{"DocComment":" [`Some(Item)`]: Some"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let a = [1, 2, 3];"},{"DocComment":""},{"DocComment":" let mut iter = a.into_iter();"},{"DocComment":""},{"DocComment":" // A call to next() returns the next value..."},{"DocComment":" assert_eq!(Some(1), iter.next());"},{"DocComment":" assert_eq!(Some(2), iter.next());"},{"DocComment":" assert_eq!(Some(3), iter.next());"},{"DocComment":""},{"DocComment":" // ... and then None once it's over."},{"DocComment":" assert_eq!(None, iter.next());"},{"DocComment":""},{"DocComment":" // More calls may or may not return `None`. Here, they always will."},{"DocComment":" assert_eq!(None, iter.next());"},{"DocComment":" assert_eq!(None, iter.next());"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"HashConsedValue":[1729,{"Ref":[{"Var":{"Bound":[0,0]}},{"Deduplicated":164},"Mut"]}]}],"output":{"HashConsedValue":[6168,{"Adt":{"id":{"Adt":7},"generics":{"regions":[],"types":[{"Deduplicated":1731}],"const_generics":[],"trait_refs":[{"HashConsedValue":[6167,{"kind":{"ParentClause":[{"Deduplicated":1730},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[1709,{"TraitType":[{"HashConsedValue":[1708,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"HashConsedValue":[1619,{"TypeVar":{"Bound":[3,0]}}]}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},"item":{"id":29,"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[{"Deduplicated":1730}]}}},"kind":{"TraitMethod":[2,0]}},null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"vtable":{"id":{"Adt":12},"generics":{"regions":[],"types":[{"HashConsedValue":[6170,{"TraitType":[{"HashConsedValue":[6169,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}},{"def_id":3,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["convert",0]},{"Ident":["From",0]}],"span":{"data":{"file_id":10,"beg":{"line":587,"col":0},"end":{"line":587,"col":30}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Used to do value-to-value conversions while consuming the input value. It is the reciprocal of"},{"DocComment":" [`Into`]."},{"DocComment":""},{"DocComment":" One should always prefer implementing `From` over [`Into`]"},{"DocComment":" because implementing `From` automatically provides one with an implementation of [`Into`]"},{"DocComment":" thanks to the blanket implementation in the standard library."},{"DocComment":""},{"DocComment":" Only implement [`Into`] when targeting a version prior to Rust 1.41 and converting to a type"},{"DocComment":" outside the current crate."},{"DocComment":" `From` was not able to do these types of conversions in earlier versions because of Rust's"},{"DocComment":" orphaning rules."},{"DocComment":" See [`Into`] for more details."},{"DocComment":""},{"DocComment":" Prefer using [`Into`] over [`From`] when specifying trait bounds on a generic function"},{"DocComment":" to ensure that types that only implement [`Into`] can be used as well."},{"DocComment":""},{"DocComment":" The `From` trait is also very useful when performing error handling. When constructing a function"},{"DocComment":" that is capable of failing, the return type will generally be of the form `Result`."},{"DocComment":" `From` simplifies error handling by allowing a function to return a single error type"},{"DocComment":" that encapsulates multiple error types. See the \"Examples\" section and [the book][book] for more"},{"DocComment":" details."},{"DocComment":""},{"DocComment":" **Note: This trait must not fail**. The `From` trait is intended for perfect conversions."},{"DocComment":" If the conversion can fail or is not perfect, use [`TryFrom`]."},{"DocComment":""},{"DocComment":" # Generic Implementations"},{"DocComment":""},{"DocComment":" - `From for U` implies [`Into`]` for T`"},{"DocComment":" - `From` is reflexive, which means that `From for T` is implemented"},{"DocComment":""},{"DocComment":" # When to implement `From`"},{"DocComment":""},{"DocComment":" While there's no technical restrictions on which conversions can be done using"},{"DocComment":" a `From` implementation, the general expectation is that the conversions"},{"DocComment":" should typically be restricted as follows:"},{"DocComment":""},{"DocComment":" * The conversion is *infallible*: if the conversion can fail, use [`TryFrom`]"},{"DocComment":" instead; don't provide a `From` impl that panics."},{"DocComment":""},{"DocComment":" * The conversion is *lossless*: semantically, it should not lose or discard"},{"DocComment":" information. For example, `i32: From` exists, where the original"},{"DocComment":" value can be recovered using `u16: TryFrom`. And `String: From<&str>`"},{"DocComment":" exists, where you can get something equivalent to the original value via"},{"DocComment":" `Deref`. But `From` cannot be used to convert from `u32` to `u16`, since"},{"DocComment":" that cannot succeed in a lossless way. (There's some wiggle room here for"},{"DocComment":" information not considered semantically relevant. For example,"},{"DocComment":" `Box<[T]>: From>` exists even though it might not preserve capacity,"},{"DocComment":" like how two vectors can be equal despite differing capacities.)"},{"DocComment":""},{"DocComment":" * The conversion is *value-preserving*: the conceptual kind and meaning of"},{"DocComment":" the resulting value is the same, even though the Rust type and technical"},{"DocComment":" representation might be different. For example `-1_i8 as u8` is *lossless*,"},{"DocComment":" since `as` casting back can recover the original value, but that conversion"},{"DocComment":" is *not* available via `From` because `-1` and `255` are different conceptual"},{"DocComment":" values (despite being identical bit patterns technically). But"},{"DocComment":" `f32: From` *is* available because `1_i16` and `1.0_f32` are conceptually"},{"DocComment":" the same real number (despite having very different bit patterns technically)."},{"DocComment":" `String: From` is available because they're both *text*, but"},{"DocComment":" `String: From` is *not* available, since `1` (a number) and `\"1\"`"},{"DocComment":" (text) are too different. (Converting values to text is instead covered"},{"DocComment":" by the [`Display`](crate::fmt::Display) trait.)"},{"DocComment":""},{"DocComment":" * The conversion is *obvious*: it's the only reasonable conversion between"},{"DocComment":" the two types. Otherwise it's better to have it be a named method or"},{"DocComment":" constructor, like how [`str::as_bytes`] is a method and how integers have"},{"DocComment":" methods like [`u32::from_ne_bytes`], [`u32::from_le_bytes`], and"},{"DocComment":" [`u32::from_be_bytes`], none of which are `From` implementations. Whereas"},{"DocComment":" there's only one reasonable way to wrap an [`Ipv6Addr`](crate::net::Ipv6Addr)"},{"DocComment":" into an [`IpAddr`](crate::net::IpAddr), thus `IpAddr: From` exists."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" [`String`] implements `From<&str>`:"},{"DocComment":""},{"DocComment":" An explicit conversion from a `&str` to a String is done as follows:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let string = \"hello\".to_string();"},{"DocComment":" let other_string = String::from(\"hello\");"},{"DocComment":""},{"DocComment":" assert_eq!(string, other_string);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" While performing error handling it is often useful to implement `From` for your own error type."},{"DocComment":" By converting underlying error types to our own custom error type that encapsulates the"},{"DocComment":" underlying error type, we can return a single error type without losing information on the"},{"DocComment":" underlying cause. The '?' operator automatically converts the underlying error type to our"},{"DocComment":" custom error type with `From::from`."},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::fs;"},{"DocComment":" use std::io;"},{"DocComment":" use std::num;"},{"DocComment":""},{"DocComment":" enum CliError {"},{"DocComment":" IoError(io::Error),"},{"DocComment":" ParseError(num::ParseIntError),"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl From for CliError {"},{"DocComment":" fn from(error: io::Error) -> Self {"},{"DocComment":" CliError::IoError(error)"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl From for CliError {"},{"DocComment":" fn from(error: num::ParseIntError) -> Self {"},{"DocComment":" CliError::ParseError(error)"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" fn open_and_parse_file(file_name: &str) -> Result {"},{"DocComment":" let mut contents = fs::read_to_string(&file_name)?;"},{"DocComment":" let num: i32 = contents.trim().parse()?;"},{"DocComment":" Ok(num)"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" [`String`]: ../../std/string/struct.String.html"},{"DocComment":" [`from`]: From::from"},{"DocComment":" [book]: ../../book/ch09-00-error-handling.html"},{"Unknown":{"path":"rustc_on_unimplemented","args":"on(all(Self = \"&str\", T = \"alloc::string::String\"), note =\n\"to coerce a `{T}` into a `{Self}`, use `&*` as a prefix\",)"}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"From"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"T"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":10,"beg":{"line":587,"col":25},"end":{"line":587,"col":30}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":10,"beg":{"line":587,"col":21},"end":{"line":587,"col":22}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"from","attr_info":{"attributes":[{"DocComment":" Converts to this type from the input type."}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":1585}],"output":{"Deduplicated":164}},"item":{"id":176,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[{"HashConsedValue":[5658,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":3,"generics":{"regions":[],"types":[{"Deduplicated":1611},{"Deduplicated":2246}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"kind":{"TraitMethod":[3,0]}}],"vtable":null},{"def_id":4,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["function",0]},{"Ident":["FnOnce",0]}],"span":{"data":{"file_id":16,"beg":{"line":242,"col":0},"end":{"line":242,"col":35}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" The version of the call operator that takes a by-value receiver."},{"DocComment":""},{"DocComment":" Instances of `FnOnce` can be called, but might not be callable multiple"},{"DocComment":" times. Because of this, if the only thing known about a type is that it"},{"DocComment":" implements `FnOnce`, it can only be called once."},{"DocComment":""},{"DocComment":" `FnOnce` is implemented automatically by closures that might consume captured"},{"DocComment":" variables, as well as all types that implement [`FnMut`], e.g., (safe)"},{"DocComment":" [function pointers] (since `FnOnce` is a supertrait of [`FnMut`])."},{"DocComment":""},{"DocComment":" Since both [`Fn`] and [`FnMut`] are subtraits of `FnOnce`, any instance of"},{"DocComment":" [`Fn`] or [`FnMut`] can be used where a `FnOnce` is expected."},{"DocComment":""},{"DocComment":" Use `FnOnce` as a bound when you want to accept a parameter of function-like"},{"DocComment":" type and only need to call it once. If you need to call the parameter"},{"DocComment":" repeatedly, use [`FnMut`] as a bound; if you also need it to not mutate"},{"DocComment":" state, use [`Fn`]."},{"DocComment":""},{"DocComment":" See the [chapter on closures in *The Rust Programming Language*][book] for"},{"DocComment":" some more information on this topic."},{"DocComment":""},{"DocComment":" Also of note is the special syntax for `Fn` traits (e.g."},{"DocComment":" `Fn(usize, bool) -> usize`). Those interested in the technical details of"},{"DocComment":" this can refer to [the relevant section in the *Rustonomicon*][nomicon]."},{"DocComment":""},{"DocComment":" [book]: ../../book/ch13-01-closures.html"},{"DocComment":" [function pointers]: fn"},{"DocComment":" [nomicon]: ../../nomicon/hrtb.html"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ## Using a `FnOnce` parameter"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" fn consume_with_relish(func: F)"},{"DocComment":" where F: FnOnce() -> String"},{"DocComment":" {"},{"DocComment":" // `func` consumes its captured variables, so it cannot be run more"},{"DocComment":" // than once."},{"DocComment":" println!(\"Consumed: {}\", func());"},{"DocComment":""},{"DocComment":" println!(\"Delicious!\");"},{"DocComment":""},{"DocComment":" // Attempting to invoke `func()` again will throw a `use of moved"},{"DocComment":" // value` error for `func`."},{"DocComment":" }"},{"DocComment":""},{"DocComment":" let x = String::from(\"x\");"},{"DocComment":" let consume_and_return_x = move || x;"},{"DocComment":" consume_with_relish(consume_and_return_x);"},{"DocComment":""},{"DocComment":" // `consume_and_return_x` can no longer be invoked at this point"},{"DocComment":" ```"},{"Unknown":{"path":"rustc_on_unimplemented","args":"on(Args = \"()\", note =\n\"wrap the `{Self}` in a closure with no arguments: `|| {{ /* code */ }}`\"),\non(Self = \"unsafe fn\", note =\n\"unsafe function cannot be called generically without an unsafe block\", label\n= \"call the function in a closure: `|| unsafe {{ /* code */ }}`\"), message =\n\"expected a `{Trait}` closure, found `{Self}`\", label =\n\"expected an `{Trait}` closure, found `{Self}`\""}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"fn_once"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"Args"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":16,"beg":{"line":242,"col":0},"end":{"line":251,"col":1}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":16,"beg":{"line":242,"col":23},"end":{"line":242,"col":27}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":16,"beg":{"line":242,"col":29},"end":{"line":242,"col":34}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":25,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":3,"span":{"data":{"file_id":16,"beg":{"line":246,"col":4},"end":{"line":246,"col":16}},"generated_from_span":null},"origin":{"TraitItem":0},"trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[5317,{"TraitType":[{"HashConsedValue":[4629,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":1611},{"Deduplicated":2246}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"Output","attr_info":{"attributes":[{"DocComment":" The returned type after the call operator is used."}],"inline":null,"rename":null,"public":false},"default":null,"implied_clauses":[]},"kind":{"TraitType":[4,0]}}],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"call_once","attr_info":{"attributes":[{"DocComment":" Performs the call operation."}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":164},{"Deduplicated":1585}],"output":{"Deduplicated":5317}},"item":{"id":179,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[{"Deduplicated":4629}]}}},"kind":{"TraitMethod":[4,0]}}],"vtable":{"id":{"Adt":42},"generics":{"regions":[],"types":[{"Deduplicated":1589},{"HashConsedValue":[6172,{"TraitType":[{"HashConsedValue":[6171,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}},{"def_id":5,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Ident":["Destruct",0]}],"span":{"data":{"file_id":1,"beg":{"line":1063,"col":0},"end":{"line":1063,"col":38}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" A marker for types that can be dropped."},{"DocComment":""},{"DocComment":" This should be used for `[const]` bounds,"},{"DocComment":" as non-const bounds will always hold for every type."},{"Unknown":{"path":"rustc_on_unimplemented","args":"message = \"can't drop `{Self}`\", append_const_msg"}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"destruct"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[],"consts":[],"types":[],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"drop_in_place","attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":true,"inputs":[{"HashConsedValue":[6173,{"RawPtr":[{"Deduplicated":164},"Mut"]}]}],"output":{"Deduplicated":221}},"item":{"id":180,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}},"kind":{"TraitMethod":[5,0]}}],"vtable":null},{"def_id":6,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["IntoIterator",0]}],"span":{"data":{"file_id":11,"beg":{"line":283,"col":0},"end":{"line":283,"col":28}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Conversion into an [`Iterator`]."},{"DocComment":""},{"DocComment":" By implementing `IntoIterator` for a type, you define how it will be"},{"DocComment":" converted to an iterator. This is common for types which describe a"},{"DocComment":" collection of some kind."},{"DocComment":""},{"DocComment":" One benefit of implementing `IntoIterator` is that your type will [work"},{"DocComment":" with Rust's `for` loop syntax](crate::iter#for-loops-and-intoiterator)."},{"DocComment":""},{"DocComment":" See also: [`FromIterator`]."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" Basic usage:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let v = [1, 2, 3];"},{"DocComment":" let mut iter = v.into_iter();"},{"DocComment":""},{"DocComment":" assert_eq!(Some(1), iter.next());"},{"DocComment":" assert_eq!(Some(2), iter.next());"},{"DocComment":" assert_eq!(Some(3), iter.next());"},{"DocComment":" assert_eq!(None, iter.next());"},{"DocComment":" ```"},{"DocComment":" Implementing `IntoIterator` for your type:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" // A sample collection, that's just a wrapper over Vec"},{"DocComment":" #[derive(Debug)]"},{"DocComment":" struct MyCollection(Vec);"},{"DocComment":""},{"DocComment":" // Let's give it some methods so we can create one and add things"},{"DocComment":" // to it."},{"DocComment":" impl MyCollection {"},{"DocComment":" fn new() -> MyCollection {"},{"DocComment":" MyCollection(Vec::new())"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" fn add(&mut self, elem: i32) {"},{"DocComment":" self.0.push(elem);"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" // and we'll implement IntoIterator"},{"DocComment":" impl IntoIterator for MyCollection {"},{"DocComment":" type Item = i32;"},{"DocComment":" type IntoIter = std::vec::IntoIter;"},{"DocComment":""},{"DocComment":" fn into_iter(self) -> Self::IntoIter {"},{"DocComment":" self.0.into_iter()"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" // Now we can make a new collection..."},{"DocComment":" let mut c = MyCollection::new();"},{"DocComment":""},{"DocComment":" // ... add some stuff to it ..."},{"DocComment":" c.add(0);"},{"DocComment":" c.add(1);"},{"DocComment":" c.add(2);"},{"DocComment":""},{"DocComment":" // ... and then turn it into an Iterator:"},{"DocComment":" for (i, n) in c.into_iter().enumerate() {"},{"DocComment":" assert_eq!(i as i32, n);"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" It is common to use `IntoIterator` as a trait bound. This allows"},{"DocComment":" the input collection type to change, so long as it is still an"},{"DocComment":" iterator. Additional bounds can be specified by restricting on"},{"DocComment":" `Item`:"},{"DocComment":""},{"DocComment":" ```rust"},{"DocComment":" fn collect_as_strings(collection: T) -> Vec"},{"DocComment":" where"},{"DocComment":" T: IntoIterator,"},{"DocComment":" T::Item: std::fmt::Debug,"},{"DocComment":" {"},{"DocComment":" collection"},{"DocComment":" .into_iter()"},{"DocComment":" .map(|item| format!(\"{item:?}\"))"},{"DocComment":" .collect()"},{"DocComment":" }"},{"DocComment":" ```"},{"Unknown":{"path":"rustc_on_unimplemented","args":"on(Self = \"core::ops::range::RangeTo\", label =\n\"if you meant to iterate until a value, add a starting value\", note =\n\"`..end` is a `RangeTo`, which cannot be iterated on; you might have meant to have a \\\n bounded `Range`: `0..end`\"),\non(Self = \"core::ops::range::RangeToInclusive\", label =\n\"if you meant to iterate until a value (including it), add a starting value\",\nnote =\n\"`..=end` is a `RangeToInclusive`, which cannot be iterated on; you might have meant \\\n to have a bounded `RangeInclusive`: `0..=end`\"),\non(Self = \"[]\", label =\n\"`{Self}` is not an iterator; try calling `.into_iter()` or `.iter()`\"),\non(Self = \"&[]\", label =\n\"`{Self}` is not an iterator; try calling `.iter()`\"),\non(Self = \"alloc::vec::Vec\", label =\n\"`{Self}` is not an iterator; try calling `.into_iter()` or `.iter()`\"),\non(Self = \"&str\", label =\n\"`{Self}` is not an iterator; try calling `.chars()` or `.bytes()`\"),\non(Self = \"alloc::string::String\", label =\n\"`{Self}` is not an iterator; try calling `.chars()` or `.bytes()`\"),\non(Self = \"{integral}\", note =\n\"if you want to iterate between `start` until a value `end`, use the exclusive range \\\n syntax `start..end` or the inclusive range syntax `start..=end`\"),\non(Self = \"{float}\", note =\n\"if you want to iterate between `start` until a value `end`, use the exclusive range \\\n syntax `start..end` or the inclusive range syntax `start..=end`\"),\nlabel = \"`{Self}` is not an iterator\", message = \"`{Self}` is not an iterator\""}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"IntoIterator"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[{"regions":[],"skip_binder":{"trait_ref":{"HashConsedValue":[6039,{"kind":{"ParentClause":[{"HashConsedValue":[4663,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":1611}],"const_generics":[],"trait_refs":[]}}}}]},3]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"HashConsedValue":[5663,{"TraitType":[{"HashConsedValue":[5662,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":1619}],"const_generics":[],"trait_refs":[]}}}}]},1]}]}],"const_generics":[],"trait_refs":[]}}}}]},"type_id":0,"ty":{"HashConsedValue":[5552,{"TraitType":[{"Deduplicated":4663},0]}]}}}]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":11,"beg":{"line":283,"col":0},"end":{"line":313,"col":1}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":11,"beg":{"line":287,"col":4},"end":{"line":287,"col":14}},"generated_from_span":null},"origin":{"TraitItem":0},"trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":5552}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":11,"beg":{"line":291,"col":4},"end":{"line":291,"col":47}},"generated_from_span":null},"origin":{"TraitItem":1},"trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[5321,{"TraitType":[{"Deduplicated":4663},1]}]}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":3,"span":{"data":{"file_id":11,"beg":{"line":291,"col":19},"end":{"line":291,"col":46}},"generated_from_span":null},"origin":{"TraitItem":1},"trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":5321}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"Item","attr_info":{"attributes":[{"DocComment":" The type of the elements being iterated over."}],"inline":null,"rename":null,"public":false},"default":null,"implied_clauses":[]},"kind":{"TraitType":[6,0]}},{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"IntoIter","attr_info":{"attributes":[{"DocComment":" Which kind of iterator are we turning this into?"}],"inline":null,"rename":null,"public":false},"default":null,"implied_clauses":[]},"kind":{"TraitType":[6,1]}}],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"into_iter","attr_info":{"attributes":[{"DocComment":" Creates an iterator from a value."},{"DocComment":""},{"DocComment":" See the [module-level documentation] for more."},{"DocComment":""},{"DocComment":" [module-level documentation]: crate::iter"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let v = [1, 2, 3];"},{"DocComment":" let mut iter = v.into_iter();"},{"DocComment":""},{"DocComment":" assert_eq!(Some(1), iter.next());"},{"DocComment":" assert_eq!(Some(2), iter.next());"},{"DocComment":" assert_eq!(Some(3), iter.next());"},{"DocComment":" assert_eq!(None, iter.next());"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":164}],"output":{"Deduplicated":5321}},"item":{"id":183,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[{"Deduplicated":4663}]}}},"kind":{"TraitMethod":[6,0]}}],"vtable":{"id":{"Adt":43},"generics":{"regions":[],"types":[{"HashConsedValue":[6174,{"TraitType":[{"HashConsedValue":[4643,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}]},0]}]},{"HashConsedValue":[6175,{"TraitType":[{"Deduplicated":4643},1]}]}],"const_generics":[],"trait_refs":[]}}},{"def_id":7,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["nonzero",0]},{"Ident":["ZeroablePrimitive",0]}],"span":{"data":{"file_id":19,"beg":{"line":33,"col":0},"end":{"line":33,"col":66}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" A marker trait for primitive types which can be zero."},{"DocComment":""},{"DocComment":" This is an implementation detail for [NonZero]\\ which may disappear or be replaced at any time."},{"DocComment":""},{"DocComment":" # Safety"},{"DocComment":""},{"DocComment":" Types implementing this trait must be primitives that are valid when zeroed."},{"DocComment":""},{"DocComment":" The associated `Self::NonZeroInner` type must have the same size+align as `Self`,"},{"DocComment":" but with a niche and bit validity making it so the following `transmutes` are sound:"},{"DocComment":""},{"DocComment":" - `Self::NonZeroInner` to `Option`"},{"DocComment":" - `Option` to `Self`"},{"DocComment":""},{"DocComment":" (And, consequently, `Self::NonZeroInner` to `Self`.)"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":19,"beg":{"line":33,"col":36},"end":{"line":33,"col":41}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":19,"beg":{"line":33,"col":44},"end":{"line":33,"col":48}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":18,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":19,"beg":{"line":33,"col":51},"end":{"line":33,"col":66}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":26,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":3,"span":{"data":{"file_id":19,"beg":{"line":35,"col":23},"end":{"line":35,"col":28}},"generated_from_span":null},"origin":{"TraitItem":0},"trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[5556,{"TraitType":[{"HashConsedValue":[5555,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":7,"generics":{"regions":[],"types":[{"Deduplicated":1611}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":4,"span":{"data":{"file_id":19,"beg":{"line":35,"col":31},"end":{"line":35,"col":35}},"generated_from_span":null},"origin":{"TraitItem":0},"trait_":{"regions":[],"skip_binder":{"id":18,"generics":{"regions":[],"types":[{"Deduplicated":5556}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"NonZeroInner","attr_info":{"attributes":[{"DocComment":" A type like `Self` but with a niche that includes zero."}],"inline":null,"rename":null,"public":false},"default":null,"implied_clauses":[]},"kind":{"TraitType":[7,0]}}],"methods":[],"vtable":null},{"def_id":8,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["clone",0]},{"Ident":["Clone",0]}],"span":{"data":{"file_id":25,"beg":{"line":194,"col":0},"end":{"line":194,"col":28}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" A common trait that allows explicit creation of a duplicate value."},{"DocComment":""},{"DocComment":" Calling [`clone`] always produces a new value."},{"DocComment":" However, for types that are references to other data (such as smart pointers or references),"},{"DocComment":" the new value may still point to the same underlying data, rather than duplicating it."},{"DocComment":" See [`Clone::clone`] for more details."},{"DocComment":""},{"DocComment":" This distinction is especially important when using `#[derive(Clone)]` on structs containing"},{"DocComment":" smart pointers like `Arc>` - the cloned struct will share mutable state with the"},{"DocComment":" original."},{"DocComment":""},{"DocComment":" Differs from [`Copy`] in that [`Copy`] is implicit and an inexpensive bit-wise copy, while"},{"DocComment":" `Clone` is always explicit and may or may not be expensive. [`Copy`] has no methods, so you"},{"DocComment":" cannot change its behavior, but when implementing `Clone`, the `clone` method you provide"},{"DocComment":" may run arbitrary code."},{"DocComment":""},{"DocComment":" Since `Clone` is a supertrait of [`Copy`], any type that implements `Copy` must also implement"},{"DocComment":" `Clone`."},{"DocComment":""},{"DocComment":" ## Derivable"},{"DocComment":""},{"DocComment":" This trait can be used with `#[derive]` if all fields are `Clone`. The `derive`d"},{"DocComment":" implementation of [`Clone`] calls [`clone`] on each field."},{"DocComment":""},{"DocComment":" [`clone`]: Clone::clone"},{"DocComment":""},{"DocComment":" For a generic struct, `#[derive]` implements `Clone` conditionally by adding bound `Clone` on"},{"DocComment":" generic parameters."},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" // `derive` implements Clone for Reading when T is Clone."},{"DocComment":" #[derive(Clone)]"},{"DocComment":" struct Reading {"},{"DocComment":" frequency: T,"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" ## How can I implement `Clone`?"},{"DocComment":""},{"DocComment":" Types that are [`Copy`] should have a trivial implementation of `Clone`. More formally:"},{"DocComment":" if `T: Copy`, `x: T`, and `y: &T`, then `let x = y.clone();` is equivalent to `let x = *y;`."},{"DocComment":" Manual implementations should be careful to uphold this invariant; however, unsafe code"},{"DocComment":" must not rely on it to ensure memory safety."},{"DocComment":""},{"DocComment":" An example is a generic struct holding a function pointer. In this case, the"},{"DocComment":" implementation of `Clone` cannot be `derive`d, but can be implemented as:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" struct Generate(fn() -> T);"},{"DocComment":""},{"DocComment":" impl Copy for Generate {}"},{"DocComment":""},{"DocComment":" impl Clone for Generate {"},{"DocComment":" fn clone(&self) -> Self {"},{"DocComment":" *self"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" If we `derive`:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #[derive(Copy, Clone)]"},{"DocComment":" struct Generate(fn() -> T);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" the auto-derived implementations will have unnecessary `T: Copy` and `T: Clone` bounds:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # struct Generate(fn() -> T);"},{"DocComment":""},{"DocComment":" // Automatically derived"},{"DocComment":" impl Copy for Generate { }"},{"DocComment":""},{"DocComment":" // Automatically derived"},{"DocComment":" impl Clone for Generate {"},{"DocComment":" fn clone(&self) -> Generate {"},{"DocComment":" Generate(Clone::clone(&self.0))"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" The bounds are unnecessary because clearly the function itself should be"},{"DocComment":" copy- and cloneable even if its return type is not:"},{"DocComment":""},{"DocComment":" ```compile_fail,E0599"},{"DocComment":" #[derive(Copy, Clone)]"},{"DocComment":" struct Generate(fn() -> T);"},{"DocComment":""},{"DocComment":" struct NotCloneable;"},{"DocComment":""},{"DocComment":" fn generate_not_cloneable() -> NotCloneable {"},{"DocComment":" NotCloneable"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" Generate(generate_not_cloneable).clone(); // error: trait bounds were not satisfied"},{"DocComment":" // Note: With the manual implementations the above line will compile."},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" ## `Clone` and `PartialEq`/`Eq`"},{"DocComment":" `Clone` is intended for the duplication of objects. Consequently, when implementing"},{"DocComment":" both `Clone` and [`PartialEq`], the following property is expected to hold:"},{"DocComment":" ```text"},{"DocComment":" x == x -> x.clone() == x"},{"DocComment":" ```"},{"DocComment":" In other words, if an object compares equal to itself,"},{"DocComment":" its clone must also compare equal to the original."},{"DocComment":""},{"DocComment":" For types that also implement [`Eq`] – for which `x == x` always holds –"},{"DocComment":" this implies that `x.clone() == x` must always be true."},{"DocComment":" Standard library collections such as"},{"DocComment":" [`HashMap`], [`HashSet`], [`BTreeMap`], [`BTreeSet`] and [`BinaryHeap`]"},{"DocComment":" rely on their keys respecting this property for correct behavior."},{"DocComment":" Furthermore, these collections require that cloning a key preserves the outcome of the"},{"DocComment":" [`Hash`] and [`Ord`] methods. Thankfully, this follows automatically from `x.clone() == x`"},{"DocComment":" if `Hash` and `Ord` are correctly implemented according to their own requirements."},{"DocComment":""},{"DocComment":" When deriving both `Clone` and [`PartialEq`] using `#[derive(Clone, PartialEq)]`"},{"DocComment":" or when additionally deriving [`Eq`] using `#[derive(Clone, PartialEq, Eq)]`,"},{"DocComment":" then this property is automatically upheld – provided that it is satisfied by"},{"DocComment":" the underlying types."},{"DocComment":""},{"DocComment":" Violating this property is a logic error. The behavior resulting from a logic error is not"},{"DocComment":" specified, but users of the trait must ensure that such logic errors do *not* result in"},{"DocComment":" undefined behavior. This means that `unsafe` code **must not** rely on this property"},{"DocComment":" being satisfied."},{"DocComment":""},{"DocComment":" ## Additional implementors"},{"DocComment":""},{"DocComment":" In addition to the [implementors listed below][impls],"},{"DocComment":" the following types also implement `Clone`:"},{"DocComment":""},{"DocComment":" * Function item types (i.e., the distinct types defined for each function)"},{"DocComment":" * Function pointer types (e.g., `fn() -> i32`)"},{"DocComment":" * Closure types, if they capture no value from the environment"},{"DocComment":" or if all such captured values implement `Clone` themselves."},{"DocComment":" Note that variables captured by shared reference always implement `Clone`"},{"DocComment":" (even if the referent doesn't),"},{"DocComment":" while variables captured by mutable reference never implement `Clone`."},{"DocComment":""},{"DocComment":" [`HashMap`]: ../../std/collections/struct.HashMap.html"},{"DocComment":" [`HashSet`]: ../../std/collections/struct.HashSet.html"},{"DocComment":" [`BTreeMap`]: ../../std/collections/struct.BTreeMap.html"},{"DocComment":" [`BTreeSet`]: ../../std/collections/struct.BTreeSet.html"},{"DocComment":" [`BinaryHeap`]: ../../std/collections/struct.BinaryHeap.html"},{"DocComment":" [impls]: #implementors"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"clone"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":25,"beg":{"line":194,"col":23},"end":{"line":194,"col":28}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[{"params":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"clone","attr_info":{"attributes":[{"DocComment":" Returns a duplicate of the value."},{"DocComment":""},{"DocComment":" Note that what \"duplicate\" means varies by type:"},{"DocComment":" - For most types, this creates a deep, independent copy"},{"DocComment":" - For reference types like `&T`, this creates another reference to the same value"},{"DocComment":" - For smart pointers like [`Arc`] or [`Rc`], this increments the reference count"},{"DocComment":" but still points to the same underlying data"},{"DocComment":""},{"DocComment":" [`Arc`]: ../../std/sync/struct.Arc.html"},{"DocComment":" [`Rc`]: ../../std/rc/struct.Rc.html"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #![allow(noop_method_call)]"},{"DocComment":" let hello = \"Hello\"; // &str implements Clone"},{"DocComment":""},{"DocComment":" assert_eq!(\"Hello\", hello.clone());"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" Example with a reference-counted type:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::sync::{Arc, Mutex};"},{"DocComment":""},{"DocComment":" let data = Arc::new(Mutex::new(vec![1, 2, 3]));"},{"DocComment":" let data_clone = data.clone(); // Creates another Arc pointing to the same Mutex"},{"DocComment":""},{"DocComment":" {"},{"DocComment":" let mut lock = data.lock().unwrap();"},{"DocComment":" lock.push(4);"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" // Changes are visible through the clone because they share the same underlying data"},{"DocComment":" assert_eq!(*data_clone.lock().unwrap(), vec![1, 2, 3, 4]);"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"HashConsedValue":[1854,{"Ref":[{"Var":{"Bound":[0,0]}},{"Deduplicated":164},"Shared"]}]}],"output":{"Deduplicated":164}},"item":{"id":184,"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[{"HashConsedValue":[5667,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":8,"generics":{"regions":[],"types":[{"Deduplicated":1611}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"kind":{"TraitMethod":[8,0]}},null],"vtable":null},{"def_id":9,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["function",0]},{"Ident":["FnMut",0]}],"span":{"data":{"file_id":16,"beg":{"line":163,"col":0},"end":{"line":163,"col":48}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" The version of the call operator that takes a mutable receiver."},{"DocComment":""},{"DocComment":" Instances of `FnMut` can be called repeatedly and may mutate state."},{"DocComment":""},{"DocComment":" `FnMut` is implemented automatically by closures which take mutable"},{"DocComment":" references to captured variables, as well as all types that implement"},{"DocComment":" [`Fn`], e.g., (safe) [function pointers] (since `FnMut` is a supertrait of"},{"DocComment":" [`Fn`]). Additionally, for any type `F` that implements `FnMut`, `&mut F`"},{"DocComment":" implements `FnMut`, too."},{"DocComment":""},{"DocComment":" Since [`FnOnce`] is a supertrait of `FnMut`, any instance of `FnMut` can be"},{"DocComment":" used where a [`FnOnce`] is expected, and since [`Fn`] is a subtrait of"},{"DocComment":" `FnMut`, any instance of [`Fn`] can be used where `FnMut` is expected."},{"DocComment":""},{"DocComment":" Use `FnMut` as a bound when you want to accept a parameter of function-like"},{"DocComment":" type and need to call it repeatedly, while allowing it to mutate state."},{"DocComment":" If you don't want the parameter to mutate state, use [`Fn`] as a"},{"DocComment":" bound; if you don't need to call it repeatedly, use [`FnOnce`]."},{"DocComment":""},{"DocComment":" See the [chapter on closures in *The Rust Programming Language*][book] for"},{"DocComment":" some more information on this topic."},{"DocComment":""},{"DocComment":" Also of note is the special syntax for `Fn` traits (e.g."},{"DocComment":" `Fn(usize, bool) -> usize`). Those interested in the technical details of"},{"DocComment":" this can refer to [the relevant section in the *Rustonomicon*][nomicon]."},{"DocComment":""},{"DocComment":" [book]: ../../book/ch13-01-closures.html"},{"DocComment":" [function pointers]: fn"},{"DocComment":" [nomicon]: ../../nomicon/hrtb.html"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ## Calling a mutably capturing closure"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let mut x = 5;"},{"DocComment":" {"},{"DocComment":" let mut square_x = || x *= x;"},{"DocComment":" square_x();"},{"DocComment":" }"},{"DocComment":" assert_eq!(x, 25);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" ## Using a `FnMut` parameter"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" fn do_twice(mut func: F)"},{"DocComment":" where F: FnMut()"},{"DocComment":" {"},{"DocComment":" func();"},{"DocComment":" func();"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" let mut x: usize = 1;"},{"DocComment":" {"},{"DocComment":" let add_two_to_x = || x += 2;"},{"DocComment":" do_twice(add_two_to_x);"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" assert_eq!(x, 5);"},{"DocComment":" ```"},{"Unknown":{"path":"rustc_on_unimplemented","args":"on(Args = \"()\", note =\n\"wrap the `{Self}` in a closure with no arguments: `|| {{ /* code */ }}`\"),\non(Self = \"unsafe fn\", note =\n\"unsafe function cannot be called generically without an unsafe block\", label\n= \"call the function in a closure: `|| unsafe {{ /* code */ }}`\"), message =\n\"expected a `{Trait}` closure, found `{Self}`\", label =\n\"expected an `{Trait}` closure, found `{Self}`\""}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"fn_mut"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"Args"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":16,"beg":{"line":163,"col":0},"end":{"line":167,"col":1}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":16,"beg":{"line":163,"col":36},"end":{"line":163,"col":48}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":16,"beg":{"line":163,"col":22},"end":{"line":163,"col":26}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":3,"span":{"data":{"file_id":16,"beg":{"line":163,"col":28},"end":{"line":163,"col":33}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":25,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[{"params":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"call_mut","attr_info":{"attributes":[{"DocComment":" Performs the call operation."}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":1729},{"Deduplicated":1585}],"output":{"HashConsedValue":[6177,{"TraitType":[{"HashConsedValue":[6176,{"kind":{"ParentClause":[{"HashConsedValue":[4781,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":9,"generics":{"regions":[],"types":[{"Deduplicated":1611},{"Deduplicated":2246}],"const_generics":[],"trait_refs":[]}}}}]},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":1611},{"Deduplicated":2246}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}},"item":{"id":186,"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[{"Deduplicated":164},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[{"Deduplicated":4781}]}}},"kind":{"TraitMethod":[9,0]}}],"vtable":{"id":{"Adt":45},"generics":{"regions":[],"types":[{"Deduplicated":1589},{"HashConsedValue":[6180,{"TraitType":[{"HashConsedValue":[6179,{"kind":{"ParentClause":[{"HashConsedValue":[6178,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":9,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}]},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}},{"def_id":10,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["FromIterator",0]}],"span":{"data":{"file_id":11,"beg":{"line":134,"col":0},"end":{"line":134,"col":32}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Conversion from an [`Iterator`]."},{"DocComment":""},{"DocComment":" By implementing `FromIterator` for a type, you define how it will be"},{"DocComment":" created from an iterator. This is common for types which describe a"},{"DocComment":" collection of some kind."},{"DocComment":""},{"DocComment":" If you want to create a collection from the contents of an iterator, the"},{"DocComment":" [`Iterator::collect()`] method is preferred. However, when you need to"},{"DocComment":" specify the container type, [`FromIterator::from_iter()`] can be more"},{"DocComment":" readable than using a turbofish (e.g. `::>()`). See the"},{"DocComment":" [`Iterator::collect()`] documentation for more examples of its use."},{"DocComment":""},{"DocComment":" See also: [`IntoIterator`]."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" Basic usage:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let five_fives = std::iter::repeat(5).take(5);"},{"DocComment":""},{"DocComment":" let v = Vec::from_iter(five_fives);"},{"DocComment":""},{"DocComment":" assert_eq!(v, vec![5, 5, 5, 5, 5]);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" Using [`Iterator::collect()`] to implicitly use `FromIterator`:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let five_fives = std::iter::repeat(5).take(5);"},{"DocComment":""},{"DocComment":" let v: Vec = five_fives.collect();"},{"DocComment":""},{"DocComment":" assert_eq!(v, vec![5, 5, 5, 5, 5]);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" Using [`FromIterator::from_iter()`] as a more readable alternative to"},{"DocComment":" [`Iterator::collect()`]:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::collections::VecDeque;"},{"DocComment":" let first = (0..10).collect::>();"},{"DocComment":" let second = VecDeque::from_iter(0..10);"},{"DocComment":""},{"DocComment":" assert_eq!(first, second);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" Implementing `FromIterator` for your type:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" // A sample collection, that's just a wrapper over Vec"},{"DocComment":" #[derive(Debug)]"},{"DocComment":" struct MyCollection(Vec);"},{"DocComment":""},{"DocComment":" // Let's give it some methods so we can create one and add things"},{"DocComment":" // to it."},{"DocComment":" impl MyCollection {"},{"DocComment":" fn new() -> MyCollection {"},{"DocComment":" MyCollection(Vec::new())"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" fn add(&mut self, elem: i32) {"},{"DocComment":" self.0.push(elem);"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" // and we'll implement FromIterator"},{"DocComment":" impl FromIterator for MyCollection {"},{"DocComment":" fn from_iter>(iter: I) -> Self {"},{"DocComment":" let mut c = MyCollection::new();"},{"DocComment":""},{"DocComment":" for i in iter {"},{"DocComment":" c.add(i);"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" c"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" // Now we can make a new iterator..."},{"DocComment":" let iter = (0..5).into_iter();"},{"DocComment":""},{"DocComment":" // ... and make a MyCollection out of it"},{"DocComment":" let c = MyCollection::from_iter(iter);"},{"DocComment":""},{"DocComment":" assert_eq!(c.0, vec![0, 1, 2, 3, 4]);"},{"DocComment":""},{"DocComment":" // collect works too!"},{"DocComment":""},{"DocComment":" let iter = (0..5).into_iter();"},{"DocComment":" let c: MyCollection = iter.collect();"},{"DocComment":""},{"DocComment":" assert_eq!(c.0, vec![0, 1, 2, 3, 4]);"},{"DocComment":" ```"},{"Unknown":{"path":"rustc_on_unimplemented","args":"on(Self = \"&[{A}]\", message =\n\"a slice of type `{Self}` cannot be built since we need to store the elements somewhere\",\nlabel = \"try explicitly collecting into a `Vec<{A}>`\",),\non(all(A = \"{integer}\", any(Self = \"&[{integral}]\",)), message =\n\"a slice of type `{Self}` cannot be built since we need to store the elements somewhere\",\nlabel = \"try explicitly collecting into a `Vec<{A}>`\",),\non(Self = \"[{A}]\", message =\n\"a slice of type `{Self}` cannot be built since `{Self}` has no definite size\",\nlabel = \"try explicitly collecting into a `Vec<{A}>`\",),\non(all(A = \"{integer}\", any(Self = \"[{integral}]\",)), message =\n\"a slice of type `{Self}` cannot be built since `{Self}` has no definite size\",\nlabel = \"try explicitly collecting into a `Vec<{A}>`\",),\non(Self = \"[{A}; _]\", message =\n\"an array of type `{Self}` cannot be built directly from an iterator\", label =\n\"try collecting into a `Vec<{A}>`, then using `.try_into()`\",),\non(all(A = \"{integer}\", any(Self = \"[{integral}; _]\",)), message =\n\"an array of type `{Self}` cannot be built directly from an iterator\", label =\n\"try collecting into a `Vec<{A}>`, then using `.try_into()`\",), message =\n\"a value of type `{Self}` cannot be built from an iterator \\\n over elements of type `{A}`\",\nlabel =\n\"value of type `{Self}` cannot be built from `std::iter::Iterator`\""}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"FromIterator"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"A"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":11,"beg":{"line":134,"col":27},"end":{"line":134,"col":32}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":11,"beg":{"line":134,"col":23},"end":{"line":134,"col":24}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[{"params":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":11,"beg":{"line":152,"col":17},"end":{"line":152,"col":18}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":11,"beg":{"line":152,"col":20},"end":{"line":152,"col":42}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[{"regions":[],"skip_binder":{"trait_ref":{"HashConsedValue":[3889,{"kind":{"Clause":{"Bound":[1,1]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":1611}],"const_generics":[],"trait_refs":[]}}}}]},"type_id":0,"ty":{"Deduplicated":2246}}}]},"skip_binder":{"name":"from_iter","attr_info":{"attributes":[{"DocComment":" Creates a value from an iterator."},{"DocComment":""},{"DocComment":" See the [module-level documentation] for more."},{"DocComment":""},{"DocComment":" [module-level documentation]: crate::iter"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let five_fives = std::iter::repeat(5).take(5);"},{"DocComment":""},{"DocComment":" let v = Vec::from_iter(five_fives);"},{"DocComment":""},{"DocComment":" assert_eq!(v, vec![5, 5, 5, 5, 5]);"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":196}],"output":{"Deduplicated":164}},"item":{"id":187,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585},{"Deduplicated":196}],"const_generics":[],"trait_refs":[{"HashConsedValue":[5673,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":10,"generics":{"regions":[],"types":[{"Deduplicated":1611},{"Deduplicated":2246}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":199},{"HashConsedValue":[3890,{"kind":{"Clause":{"Bound":[0,1]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"kind":{"TraitMethod":[10,0]}}],"vtable":null},{"def_id":11,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["try_trait",0]},{"Ident":["Try",0]}],"span":{"data":{"file_id":42,"beg":{"line":133,"col":0},"end":{"line":133,"col":41}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" The `?` operator and `try {}` blocks."},{"DocComment":""},{"DocComment":" `try_*` methods typically involve a type implementing this trait. For"},{"DocComment":" example, the closures passed to [`Iterator::try_fold`] and"},{"DocComment":" [`Iterator::try_for_each`] must return such a type."},{"DocComment":""},{"DocComment":" `Try` types are typically those containing two or more categories of values,"},{"DocComment":" some subset of which are so commonly handled via early returns that it's"},{"DocComment":" worth providing a terse (but still visible) syntax to make that easy."},{"DocComment":""},{"DocComment":" This is most often seen for error handling with [`Result`] and [`Option`]."},{"DocComment":" The quintessential implementation of this trait is on [`ControlFlow`]."},{"DocComment":""},{"DocComment":" # Using `Try` in Generic Code"},{"DocComment":""},{"DocComment":" `Iterator::try_fold` was stabilized to call back in Rust 1.27, but"},{"DocComment":" this trait is much newer. To illustrate the various associated types and"},{"DocComment":" methods, let's implement our own version."},{"DocComment":""},{"DocComment":" As a reminder, an infallible version of a fold looks something like this:"},{"DocComment":" ```"},{"DocComment":" fn simple_fold("},{"DocComment":" iter: impl Iterator,"},{"DocComment":" mut accum: A,"},{"DocComment":" mut f: impl FnMut(A, T) -> A,"},{"DocComment":" ) -> A {"},{"DocComment":" for x in iter {"},{"DocComment":" accum = f(accum, x);"},{"DocComment":" }"},{"DocComment":" accum"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" So instead of `f` returning just an `A`, we'll need it to return some other"},{"DocComment":" type that produces an `A` in the \"don't short circuit\" path. Conveniently,"},{"DocComment":" that's also the type we need to return from the function."},{"DocComment":""},{"DocComment":" Let's add a new generic parameter `R` for that type, and bound it to the"},{"DocComment":" output type that we want:"},{"DocComment":" ```"},{"DocComment":" # #![feature(try_trait_v2)]"},{"DocComment":" # use std::ops::Try;"},{"DocComment":" fn simple_try_fold_1>("},{"DocComment":" iter: impl Iterator,"},{"DocComment":" mut accum: A,"},{"DocComment":" mut f: impl FnMut(A, T) -> R,"},{"DocComment":" ) -> R {"},{"DocComment":" todo!()"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" If we get through the entire iterator, we need to wrap up the accumulator"},{"DocComment":" into the return type using [`Try::from_output`]:"},{"DocComment":" ```"},{"DocComment":" # #![feature(try_trait_v2)]"},{"DocComment":" # use std::ops::{ControlFlow, Try};"},{"DocComment":" fn simple_try_fold_2>("},{"DocComment":" iter: impl Iterator,"},{"DocComment":" mut accum: A,"},{"DocComment":" mut f: impl FnMut(A, T) -> R,"},{"DocComment":" ) -> R {"},{"DocComment":" for x in iter {"},{"DocComment":" let cf = f(accum, x).branch();"},{"DocComment":" match cf {"},{"DocComment":" ControlFlow::Continue(a) => accum = a,"},{"DocComment":" ControlFlow::Break(_) => todo!(),"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":" R::from_output(accum)"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" We'll also need [`FromResidual::from_residual`] to turn the residual back"},{"DocComment":" into the original type. But because it's a supertrait of `Try`, we don't"},{"DocComment":" need to mention it in the bounds. All types which implement `Try` can be"},{"DocComment":" recreated from their corresponding residual, so we'll just call it:"},{"DocComment":" ```"},{"DocComment":" # #![feature(try_trait_v2)]"},{"DocComment":" # use std::ops::{ControlFlow, Try};"},{"DocComment":" pub fn simple_try_fold_3>("},{"DocComment":" iter: impl Iterator,"},{"DocComment":" mut accum: A,"},{"DocComment":" mut f: impl FnMut(A, T) -> R,"},{"DocComment":" ) -> R {"},{"DocComment":" for x in iter {"},{"DocComment":" let cf = f(accum, x).branch();"},{"DocComment":" match cf {"},{"DocComment":" ControlFlow::Continue(a) => accum = a,"},{"DocComment":" ControlFlow::Break(r) => return R::from_residual(r),"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":" R::from_output(accum)"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" But this \"call `branch`, then `match` on it, and `return` if it was a"},{"DocComment":" `Break`\" is exactly what happens inside the `?` operator. So rather than"},{"DocComment":" do all this manually, we can just use `?` instead:"},{"DocComment":" ```"},{"DocComment":" # #![feature(try_trait_v2)]"},{"DocComment":" # use std::ops::Try;"},{"DocComment":" fn simple_try_fold>("},{"DocComment":" iter: impl Iterator,"},{"DocComment":" mut accum: A,"},{"DocComment":" mut f: impl FnMut(A, T) -> R,"},{"DocComment":" ) -> R {"},{"DocComment":" for x in iter {"},{"DocComment":" accum = f(accum, x)?;"},{"DocComment":" }"},{"DocComment":" R::from_output(accum)"},{"DocComment":" }"},{"DocComment":" ```"},{"Unknown":{"path":"rustc_on_unimplemented","args":"on(all(from_desugaring = \"TryBlock\"), message =\n\"a `try` block must return `Result` or `Option` \\\n (or another type that implements `{This}`)\",\nlabel =\n\"could not wrap the final value of the block as `{Self}` doesn't implement `Try`\",),\non(all(from_desugaring = \"QuestionMark\"), message =\n\"the `?` operator can only be applied to values that implement `{This}`\",\nlabel = \"the `?` operator cannot be applied to type `{Self}`\")"}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"Try"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":42,"beg":{"line":133,"col":0},"end":{"line":220,"col":1}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":42,"beg":{"line":133,"col":21},"end":{"line":133,"col":41}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":24,"generics":{"regions":[],"types":[{"Deduplicated":164},{"HashConsedValue":[4813,{"TraitType":[{"HashConsedValue":[4812,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":1611}],"const_generics":[],"trait_refs":[]}}}}]},1]}]}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":42,"beg":{"line":136,"col":4},"end":{"line":136,"col":16}},"generated_from_span":null},"origin":{"TraitItem":0},"trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[4826,{"TraitType":[{"Deduplicated":4812},0]}]}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":3,"span":{"data":{"file_id":42,"beg":{"line":160,"col":4},"end":{"line":160,"col":18}},"generated_from_span":null},"origin":{"TraitItem":1},"trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":4813}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"Output","attr_info":{"attributes":[{"DocComment":" The type of the value produced by `?` when *not* short-circuiting."}],"inline":null,"rename":null,"public":false},"default":null,"implied_clauses":[]},"kind":{"TraitType":[11,0]}},{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"Residual","attr_info":{"attributes":[{"DocComment":" The type of the value passed to [`FromResidual::from_residual`]"},{"DocComment":" as part of `?` when short-circuiting."},{"DocComment":""},{"DocComment":" This represents the possible values of the `Self` type which are *not*"},{"DocComment":" represented by the `Output` type."},{"DocComment":""},{"DocComment":" # Note to Implementors"},{"DocComment":""},{"DocComment":" The choice of this type is critical to interconversion."},{"DocComment":" Unlike the `Output` type, which will often be a raw generic type,"},{"DocComment":" this type is typically a newtype of some sort to \"color\" the type"},{"DocComment":" so that it's distinguishable from the residuals of other types."},{"DocComment":""},{"DocComment":" This is why `Result::Residual` is not `E`, but `Result`."},{"DocComment":" That way it's distinct from `ControlFlow::Residual`, for example,"},{"DocComment":" and thus `?` on `ControlFlow` cannot be used in a method returning `Result`."},{"DocComment":""},{"DocComment":" If you're making a generic type `Foo` that implements `Try`,"},{"DocComment":" then typically you can use `Foo` as its `Residual`"},{"DocComment":" type: that type will have a \"hole\" in the correct place, and will maintain the"},{"DocComment":" \"foo-ness\" of the residual so other types need to opt-in to interconversion."}],"inline":null,"rename":null,"public":false},"default":null,"implied_clauses":[]},"kind":{"TraitType":[11,1]}}],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"from_output","attr_info":{"attributes":[{"DocComment":" Constructs the type from its `Output` type."},{"DocComment":""},{"DocComment":" This should be implemented consistently with the `branch` method"},{"DocComment":" such that applying the `?` operator will get back the original value:"},{"DocComment":" `Try::from_output(x).branch() --> ControlFlow::Continue(x)`."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #![feature(try_trait_v2)]"},{"DocComment":" use std::ops::Try;"},{"DocComment":""},{"DocComment":" assert_eq!( as Try>::from_output(3), Ok(3));"},{"DocComment":" assert_eq!( as Try>::from_output(4), Some(4));"},{"DocComment":" assert_eq!("},{"DocComment":" as Try>::from_output(5),"},{"DocComment":" std::ops::ControlFlow::Continue(5),"},{"DocComment":" );"},{"DocComment":""},{"DocComment":" # fn make_question_mark_work() -> Option<()> {"},{"DocComment":" assert_eq!(Option::from_output(4)?, 4);"},{"DocComment":" # None }"},{"DocComment":" # make_question_mark_work();"},{"DocComment":""},{"DocComment":" // This is used, for example, on the accumulator in `try_fold`:"},{"DocComment":" let r = std::iter::empty().try_fold(4, |_, ()| -> Option<_> { unreachable!() });"},{"DocComment":" assert_eq!(r, Some(4));"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":4826}],"output":{"Deduplicated":164}},"item":{"id":188,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[{"Deduplicated":4812}]}}},"kind":{"TraitMethod":[11,0]}},{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"branch","attr_info":{"attributes":[{"DocComment":" Used in `?` to decide whether the operator should produce a value"},{"DocComment":" (because this returned [`ControlFlow::Continue`])"},{"DocComment":" or propagate a value back to the caller"},{"DocComment":" (because this returned [`ControlFlow::Break`])."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #![feature(try_trait_v2)]"},{"DocComment":" use std::ops::{ControlFlow, Try};"},{"DocComment":""},{"DocComment":" assert_eq!(Ok::<_, String>(3).branch(), ControlFlow::Continue(3));"},{"DocComment":" assert_eq!(Err::(3).branch(), ControlFlow::Break(Err(3)));"},{"DocComment":""},{"DocComment":" assert_eq!(Some(3).branch(), ControlFlow::Continue(3));"},{"DocComment":" assert_eq!(None::.branch(), ControlFlow::Break(None));"},{"DocComment":""},{"DocComment":" assert_eq!(ControlFlow::::Continue(3).branch(), ControlFlow::Continue(3));"},{"DocComment":" assert_eq!("},{"DocComment":" ControlFlow::<_, String>::Break(3).branch(),"},{"DocComment":" ControlFlow::Break(ControlFlow::Break(3)),"},{"DocComment":" );"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":164}],"output":{"HashConsedValue":[6185,{"Adt":{"id":{"Adt":8},"generics":{"regions":[],"types":[{"Deduplicated":4813},{"Deduplicated":4826}],"const_generics":[],"trait_refs":[{"HashConsedValue":[6182,{"kind":{"ParentClause":[{"HashConsedValue":[6181,{"kind":{"ParentClause":[{"Deduplicated":4812},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":24,"generics":{"regions":[],"types":[{"Deduplicated":1611},{"HashConsedValue":[4820,{"TraitType":[{"HashConsedValue":[4818,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":1619}],"const_generics":[],"trait_refs":[]}}}}]},1]}]}],"const_generics":[],"trait_refs":[]}}}}]},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":4820}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6184,{"kind":{"ParentClause":[{"Deduplicated":4812},2]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[6183,{"TraitType":[{"Deduplicated":4818},0]}]}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},"item":{"id":189,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[{"Deduplicated":4812}]}}},"kind":{"TraitMethod":[11,1]}}],"vtable":null},{"def_id":12,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["try_trait",0]},{"Ident":["Residual",0]}],"span":{"data":{"file_id":42,"beg":{"line":364,"col":0},"end":{"line":364,"col":34}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Allows retrieving the canonical type implementing [`Try`] that has this type"},{"DocComment":" as its residual and allows it to hold an `O` as its output."},{"DocComment":""},{"DocComment":" If you think of the `Try` trait as splitting a type into its [`Try::Output`]"},{"DocComment":" and [`Try::Residual`] components, this allows putting them back together."},{"DocComment":""},{"DocComment":" For example,"},{"DocComment":" `Result: Try>`,"},{"DocComment":" and in the other direction,"},{"DocComment":" ` as Residual>::TryType = Result`."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"O"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[{"regions":[],"skip_binder":{"trait_ref":{"HashConsedValue":[5679,{"kind":{"ParentClause":[{"HashConsedValue":[5557,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":12,"generics":{"regions":[],"types":[{"Deduplicated":1611},{"Deduplicated":2246}],"const_generics":[],"trait_refs":[]}}}}]},3]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":11,"generics":{"regions":[],"types":[{"HashConsedValue":[5559,{"TraitType":[{"HashConsedValue":[4852,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":12,"generics":{"regions":[],"types":[{"Deduplicated":1619},{"HashConsedValue":[4587,{"TypeVar":{"Bound":[3,1]}}]}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}}}]},"type_id":0,"ty":{"Deduplicated":1585}}},{"regions":[],"skip_binder":{"trait_ref":{"Deduplicated":5679},"type_id":1,"ty":{"Deduplicated":164}}}]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":42,"beg":{"line":364,"col":29},"end":{"line":364,"col":34}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":42,"beg":{"line":364,"col":25},"end":{"line":364,"col":26}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":42,"beg":{"line":368,"col":4},"end":{"line":368,"col":59}},"generated_from_span":null},"origin":{"TraitItem":0},"trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[5558,{"TraitType":[{"Deduplicated":5557},0]}]}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":3,"span":{"data":{"file_id":42,"beg":{"line":368,"col":18},"end":{"line":368,"col":58}},"generated_from_span":null},"origin":{"TraitItem":0},"trait_":{"regions":[],"skip_binder":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":5558}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"TryType","attr_info":{"attributes":[{"DocComment":" The \"return\" type of this meta-function."}],"inline":null,"rename":null,"public":false},"default":null,"implied_clauses":[]},"kind":{"TraitType":[12,0]}}],"methods":[],"vtable":null},{"def_id":13,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Ident":["Extend",0]}],"span":{"data":{"file_id":11,"beg":{"line":397,"col":0},"end":{"line":397,"col":19}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Extend a collection with the contents of an iterator."},{"DocComment":""},{"DocComment":" Iterators produce a series of values, and collections can also be thought"},{"DocComment":" of as a series of values. The `Extend` trait bridges this gap, allowing you"},{"DocComment":" to extend a collection by including the contents of that iterator. When"},{"DocComment":" extending a collection with an already existing key, that entry is updated"},{"DocComment":" or, in the case of collections that permit multiple entries with equal"},{"DocComment":" keys, that entry is inserted."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" Basic usage:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" // You can extend a String with some chars:"},{"DocComment":" let mut message = String::from(\"The first three letters are: \");"},{"DocComment":""},{"DocComment":" message.extend(&['a', 'b', 'c']);"},{"DocComment":""},{"DocComment":" assert_eq!(\"abc\", &message[29..32]);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" Implementing `Extend`:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" // A sample collection, that's just a wrapper over Vec"},{"DocComment":" #[derive(Debug)]"},{"DocComment":" struct MyCollection(Vec);"},{"DocComment":""},{"DocComment":" // Let's give it some methods so we can create one and add things"},{"DocComment":" // to it."},{"DocComment":" impl MyCollection {"},{"DocComment":" fn new() -> MyCollection {"},{"DocComment":" MyCollection(Vec::new())"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" fn add(&mut self, elem: i32) {"},{"DocComment":" self.0.push(elem);"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" // since MyCollection has a list of i32s, we implement Extend for i32"},{"DocComment":" impl Extend for MyCollection {"},{"DocComment":""},{"DocComment":" // This is a bit simpler with the concrete type signature: we can call"},{"DocComment":" // extend on anything which can be turned into an Iterator which gives"},{"DocComment":" // us i32s. Because we need i32s to put into MyCollection."},{"DocComment":" fn extend>(&mut self, iter: T) {"},{"DocComment":""},{"DocComment":" // The implementation is very straightforward: loop through the"},{"DocComment":" // iterator, and add() each element to ourselves."},{"DocComment":" for elem in iter {"},{"DocComment":" self.add(elem);"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" let mut c = MyCollection::new();"},{"DocComment":""},{"DocComment":" c.add(5);"},{"DocComment":" c.add(6);"},{"DocComment":" c.add(7);"},{"DocComment":""},{"DocComment":" // let's extend our collection with three more numbers"},{"DocComment":" c.extend(vec![1, 2, 3]);"},{"DocComment":""},{"DocComment":" // we've added these elements onto the end"},{"DocComment":" assert_eq!(\"MyCollection([5, 6, 7, 1, 2, 3])\", format!(\"{c:?}\"));"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"A"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":11,"beg":{"line":397,"col":0},"end":{"line":451,"col":1}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":11,"beg":{"line":397,"col":17},"end":{"line":397,"col":18}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[{"params":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":11,"beg":{"line":416,"col":14},"end":{"line":416,"col":15}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":11,"beg":{"line":416,"col":17},"end":{"line":416,"col":39}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[{"regions":[],"skip_binder":{"trait_ref":{"Deduplicated":3889},"type_id":0,"ty":{"Deduplicated":2246}}}]},"skip_binder":{"name":"extend","attr_info":{"attributes":[{"DocComment":" Extends a collection with the contents of an iterator."},{"DocComment":""},{"DocComment":" As this is the only required method for this trait, the [trait-level] docs"},{"DocComment":" contain more details."},{"DocComment":""},{"DocComment":" [trait-level]: Extend"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" // You can extend a String with some chars:"},{"DocComment":" let mut message = String::from(\"abc\");"},{"DocComment":""},{"DocComment":" message.extend(['d', 'e', 'f'].iter());"},{"DocComment":""},{"DocComment":" assert_eq!(\"abcdef\", &message);"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":1729},{"Deduplicated":196}],"output":{"Deduplicated":221}},"item":{"id":190,"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[{"Deduplicated":164},{"Deduplicated":1585},{"Deduplicated":196}],"const_generics":[],"trait_refs":[{"HashConsedValue":[5680,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":13,"generics":{"regions":[],"types":[{"Deduplicated":1611},{"Deduplicated":2246}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":199},{"Deduplicated":3890}]}}},"kind":{"TraitMethod":[13,0]}},null,null,null],"vtable":null},{"def_id":14,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["default",0]},{"Ident":["Default",0]}],"span":{"data":{"file_id":43,"beg":{"line":107,"col":0},"end":{"line":107,"col":30}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" A trait for giving a type a useful default value."},{"DocComment":""},{"DocComment":" Sometimes, you want to fall back to some kind of default value, and"},{"DocComment":" don't particularly care what it is. This comes up often with `struct`s"},{"DocComment":" that define a set of options:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #[allow(dead_code)]"},{"DocComment":" struct SomeOptions {"},{"DocComment":" foo: i32,"},{"DocComment":" bar: f32,"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" How can we define some default values? You can use `Default`:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #[allow(dead_code)]"},{"DocComment":" #[derive(Default)]"},{"DocComment":" struct SomeOptions {"},{"DocComment":" foo: i32,"},{"DocComment":" bar: f32,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" fn main() {"},{"DocComment":" let options: SomeOptions = Default::default();"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" Now, you get all of the default values. Rust implements `Default` for various primitive types."},{"DocComment":""},{"DocComment":" If you want to override a particular option, but still retain the other defaults:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #[allow(dead_code)]"},{"DocComment":" # #[derive(Default)]"},{"DocComment":" # struct SomeOptions {"},{"DocComment":" # foo: i32,"},{"DocComment":" # bar: f32,"},{"DocComment":" # }"},{"DocComment":" fn main() {"},{"DocComment":" let options = SomeOptions { foo: 42, ..Default::default() };"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" ## Derivable"},{"DocComment":""},{"DocComment":" This trait can be used with `#[derive]` if all of the type's fields implement"},{"DocComment":" `Default`. When `derive`d, it will use the default value for each field's type."},{"DocComment":""},{"DocComment":" ### `enum`s"},{"DocComment":""},{"DocComment":" When using `#[derive(Default)]` on an `enum`, you need to choose which unit variant will be"},{"DocComment":" default. You do this by placing the `#[default]` attribute on the variant."},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #[derive(Default)]"},{"DocComment":" enum Kind {"},{"DocComment":" #[default]"},{"DocComment":" A,"},{"DocComment":" B,"},{"DocComment":" C,"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" You cannot use the `#[default]` attribute on non-unit or non-exhaustive variants."},{"DocComment":""},{"DocComment":" The `#[default]` attribute was stabilized in Rust 1.62.0."},{"DocComment":""},{"DocComment":" ## How can I implement `Default`?"},{"DocComment":""},{"DocComment":" Provide an implementation for the `default()` method that returns the value of"},{"DocComment":" your type that should be the default:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #![allow(dead_code)]"},{"DocComment":" enum Kind {"},{"DocComment":" A,"},{"DocComment":" B,"},{"DocComment":" C,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl Default for Kind {"},{"DocComment":" fn default() -> Self { Kind::A }"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #[allow(dead_code)]"},{"DocComment":" #[derive(Default)]"},{"DocComment":" struct SomeOptions {"},{"DocComment":" foo: i32,"},{"DocComment":" bar: f32,"},{"DocComment":" }"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"Default"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":43,"beg":{"line":107,"col":25},"end":{"line":107,"col":30}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"default","attr_info":{"attributes":[{"DocComment":" Returns the \"default value\" for a type."},{"DocComment":""},{"DocComment":" Default values are often some kind of initial value, identity value, or anything else that"},{"DocComment":" may make sense as a default."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" Using built-in default values:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let i: i8 = Default::default();"},{"DocComment":" let (x, y): (Option, f64) = Default::default();"},{"DocComment":" let (a, b, (c, d)): (i32, u32, (bool, bool)) = Default::default();"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" Making your own:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #[allow(dead_code)]"},{"DocComment":" enum Kind {"},{"DocComment":" A,"},{"DocComment":" B,"},{"DocComment":" C,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl Default for Kind {"},{"DocComment":" fn default() -> Self { Kind::A }"},{"DocComment":" }"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[],"output":{"Deduplicated":164}},"item":{"id":194,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[{"HashConsedValue":[5681,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":14,"generics":{"regions":[],"types":[{"Deduplicated":1611}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"kind":{"TraitMethod":[14,0]}}],"vtable":null},{"def_id":15,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["double_ended",0]},{"Ident":["DoubleEndedIterator",0]}],"span":{"data":{"file_id":44,"beg":{"line":41,"col":0},"end":{"line":41,"col":39}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator able to yield elements from both ends."},{"DocComment":""},{"DocComment":" Something that implements `DoubleEndedIterator` has one extra capability"},{"DocComment":" over something that implements [`Iterator`]: the ability to also take"},{"DocComment":" `Item`s from the back, as well as the front."},{"DocComment":""},{"DocComment":" It is important to note that both back and forth work on the same range,"},{"DocComment":" and do not cross: iteration is over when they meet in the middle."},{"DocComment":""},{"DocComment":" In a similar fashion to the [`Iterator`] protocol, once a"},{"DocComment":" `DoubleEndedIterator` returns [`None`] from a [`next_back()`], calling it"},{"DocComment":" again may or may not ever return [`Some`] again. [`next()`] and"},{"DocComment":" [`next_back()`] are interchangeable for this purpose."},{"DocComment":""},{"DocComment":" [`next_back()`]: DoubleEndedIterator::next_back"},{"DocComment":" [`next()`]: Iterator::next"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" Basic usage:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let numbers = vec![1, 2, 3, 4, 5, 6];"},{"DocComment":""},{"DocComment":" let mut iter = numbers.iter();"},{"DocComment":""},{"DocComment":" assert_eq!(Some(&1), iter.next());"},{"DocComment":" assert_eq!(Some(&6), iter.next_back());"},{"DocComment":" assert_eq!(Some(&5), iter.next_back());"},{"DocComment":" assert_eq!(Some(&2), iter.next());"},{"DocComment":" assert_eq!(Some(&3), iter.next());"},{"DocComment":" assert_eq!(Some(&4), iter.next());"},{"DocComment":" assert_eq!(None, iter.next());"},{"DocComment":" assert_eq!(None, iter.next_back());"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"DoubleEndedIterator"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":44,"beg":{"line":41,"col":0},"end":{"line":380,"col":1}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":44,"beg":{"line":41,"col":31},"end":{"line":41,"col":39}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[{"params":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"next_back","attr_info":{"attributes":[{"DocComment":" Removes and returns an element from the end of the iterator."},{"DocComment":""},{"DocComment":" Returns `None` when there are no more elements."},{"DocComment":""},{"DocComment":" The [trait-level] docs contain more details."},{"DocComment":""},{"DocComment":" [trait-level]: DoubleEndedIterator"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" Basic usage:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let numbers = vec![1, 2, 3, 4, 5, 6];"},{"DocComment":""},{"DocComment":" let mut iter = numbers.iter();"},{"DocComment":""},{"DocComment":" assert_eq!(Some(&1), iter.next());"},{"DocComment":" assert_eq!(Some(&6), iter.next_back());"},{"DocComment":" assert_eq!(Some(&5), iter.next_back());"},{"DocComment":" assert_eq!(Some(&2), iter.next());"},{"DocComment":" assert_eq!(Some(&3), iter.next());"},{"DocComment":" assert_eq!(Some(&4), iter.next());"},{"DocComment":" assert_eq!(None, iter.next());"},{"DocComment":" assert_eq!(None, iter.next_back());"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" # Remarks"},{"DocComment":""},{"DocComment":" The elements yielded by `DoubleEndedIterator`'s methods may differ from"},{"DocComment":" the ones yielded by [`Iterator`]'s methods:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let vec = vec![(1, 'a'), (1, 'b'), (1, 'c'), (2, 'a'), (2, 'b')];"},{"DocComment":" let uniq_by_fst_comp = || {"},{"DocComment":" let mut seen = std::collections::HashSet::new();"},{"DocComment":" vec.iter().copied().filter(move |x| seen.insert(x.0))"},{"DocComment":" };"},{"DocComment":""},{"DocComment":" assert_eq!(uniq_by_fst_comp().last(), Some((2, 'a')));"},{"DocComment":" assert_eq!(uniq_by_fst_comp().next_back(), Some((2, 'b')));"},{"DocComment":""},{"DocComment":" assert_eq!("},{"DocComment":" uniq_by_fst_comp().fold(vec![], |mut v, x| {v.push(x); v}),"},{"DocComment":" vec![(1, 'a'), (2, 'a')]"},{"DocComment":" );"},{"DocComment":" assert_eq!("},{"DocComment":" uniq_by_fst_comp().rfold(vec![], |mut v, x| {v.push(x); v}),"},{"DocComment":" vec![(2, 'b'), (1, 'c')]"},{"DocComment":" );"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":1729}],"output":{"HashConsedValue":[6188,{"Adt":{"id":{"Adt":7},"generics":{"regions":[],"types":[{"HashConsedValue":[6186,{"TraitType":[{"HashConsedValue":[4916,{"kind":{"ParentClause":[{"HashConsedValue":[4915,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":15,"generics":{"regions":[],"types":[{"Deduplicated":1611}],"const_generics":[],"trait_refs":[]}}}}]},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":1611}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[{"HashConsedValue":[6187,{"kind":{"ParentClause":[{"Deduplicated":4916},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"HashConsedValue":[4920,{"TraitType":[{"HashConsedValue":[4919,{"kind":{"ParentClause":[{"HashConsedValue":[4918,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":15,"generics":{"regions":[],"types":[{"Deduplicated":1619}],"const_generics":[],"trait_refs":[]}}}}]},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":1619}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}},"item":{"id":195,"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[{"Deduplicated":4915}]}}},"kind":{"TraitMethod":[15,0]}},null,null,null,null,null],"vtable":{"id":{"Adt":46},"generics":{"regions":[],"types":[{"HashConsedValue":[6191,{"TraitType":[{"HashConsedValue":[6190,{"kind":{"ParentClause":[{"HashConsedValue":[6189,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":15,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}]},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}},{"def_id":16,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["exact_size",0]},{"Ident":["ExactSizeIterator",0]}],"span":{"data":{"file_id":45,"beg":{"line":86,"col":0},"end":{"line":86,"col":37}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" An iterator that knows its exact length."},{"DocComment":""},{"DocComment":" Many [`Iterator`]s don't know how many times they will iterate, but some do."},{"DocComment":" If an iterator knows how many times it can iterate, providing access to"},{"DocComment":" that information can be useful. For example, if you want to iterate"},{"DocComment":" backwards, a good start is to know where the end is."},{"DocComment":""},{"DocComment":" When implementing an `ExactSizeIterator`, you must also implement"},{"DocComment":" [`Iterator`]. When doing so, the implementation of [`Iterator::size_hint`]"},{"DocComment":" *must* return the exact size of the iterator."},{"DocComment":""},{"DocComment":" The [`len`] method has a default implementation, so you usually shouldn't"},{"DocComment":" implement it. However, you may be able to provide a more performant"},{"DocComment":" implementation than the default, so overriding it in this case makes sense."},{"DocComment":""},{"DocComment":" Note that this trait is a safe trait and as such does *not* and *cannot*"},{"DocComment":" guarantee that the returned length is correct. This means that `unsafe`"},{"DocComment":" code **must not** rely on the correctness of [`Iterator::size_hint`]. The"},{"DocComment":" unstable and unsafe [`TrustedLen`](super::marker::TrustedLen) trait gives"},{"DocComment":" this additional guarantee."},{"DocComment":""},{"DocComment":" [`len`]: ExactSizeIterator::len"},{"DocComment":""},{"DocComment":" # When *shouldn't* an adapter be `ExactSizeIterator`?"},{"DocComment":""},{"DocComment":" If an adapter makes an iterator *longer*, then it's usually incorrect for"},{"DocComment":" that adapter to implement `ExactSizeIterator`. The inner exact-sized"},{"DocComment":" iterator might already be `usize::MAX`-long, and thus the length of the"},{"DocComment":" longer adapted iterator would no longer be exactly representable in `usize`."},{"DocComment":""},{"DocComment":" This is why [`Chain`](crate::iter::Chain) isn't `ExactSizeIterator`,"},{"DocComment":" even when `A` and `B` are both `ExactSizeIterator`."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" Basic usage:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" // a finite range knows exactly how many times it will iterate"},{"DocComment":" let five = 0..5;"},{"DocComment":""},{"DocComment":" assert_eq!(5, five.len());"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" In the [module-level docs], we implemented an [`Iterator`], `Counter`."},{"DocComment":" Let's implement `ExactSizeIterator` for it as well:"},{"DocComment":""},{"DocComment":" [module-level docs]: crate::iter"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # struct Counter {"},{"DocComment":" # count: usize,"},{"DocComment":" # }"},{"DocComment":" # impl Counter {"},{"DocComment":" # fn new() -> Counter {"},{"DocComment":" # Counter { count: 0 }"},{"DocComment":" # }"},{"DocComment":" # }"},{"DocComment":" # impl Iterator for Counter {"},{"DocComment":" # type Item = usize;"},{"DocComment":" # fn next(&mut self) -> Option {"},{"DocComment":" # self.count += 1;"},{"DocComment":" # if self.count < 6 {"},{"DocComment":" # Some(self.count)"},{"DocComment":" # } else {"},{"DocComment":" # None"},{"DocComment":" # }"},{"DocComment":" # }"},{"DocComment":" # }"},{"DocComment":" impl ExactSizeIterator for Counter {"},{"DocComment":" // We can easily calculate the remaining number of iterations."},{"DocComment":" fn len(&self) -> usize {"},{"DocComment":" 5 - self.count"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" // And now we can use it!"},{"DocComment":""},{"DocComment":" let mut counter = Counter::new();"},{"DocComment":""},{"DocComment":" assert_eq!(5, counter.len());"},{"DocComment":" let _ = counter.next();"},{"DocComment":" assert_eq!(4, counter.len());"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":45,"beg":{"line":86,"col":0},"end":{"line":151,"col":1}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":45,"beg":{"line":86,"col":29},"end":{"line":86,"col":37}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[null,null],"vtable":{"id":{"Adt":47},"generics":{"regions":[],"types":[{"HashConsedValue":[6194,{"TraitType":[{"HashConsedValue":[6193,{"kind":{"ParentClause":[{"HashConsedValue":[6192,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":16,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}]},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}]},0]}]}],"const_generics":[],"trait_refs":[]}}},{"def_id":17,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["Ord",0]}],"span":{"data":{"file_id":46,"beg":{"line":973,"col":0},"end":{"line":973,"col":73}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Trait for types that form a [total order](https://en.wikipedia.org/wiki/Total_order)."},{"DocComment":""},{"DocComment":" Implementations must be consistent with the [`PartialOrd`] implementation, and ensure `max`,"},{"DocComment":" `min`, and `clamp` are consistent with `cmp`:"},{"DocComment":""},{"DocComment":" - `partial_cmp(a, b) == Some(cmp(a, b))`."},{"DocComment":" - `max(a, b) == max_by(a, b, cmp)` (ensured by the default implementation)."},{"DocComment":" - `min(a, b) == min_by(a, b, cmp)` (ensured by the default implementation)."},{"DocComment":" - For `a.clamp(min, max)`, see the [method docs](#method.clamp) (ensured by the default"},{"DocComment":" implementation)."},{"DocComment":""},{"DocComment":" Violating these requirements is a logic error. The behavior resulting from a logic error is not"},{"DocComment":" specified, but users of the trait must ensure that such logic errors do *not* result in"},{"DocComment":" undefined behavior. This means that `unsafe` code **must not** rely on the correctness of these"},{"DocComment":" methods."},{"DocComment":""},{"DocComment":" ## Corollaries"},{"DocComment":""},{"DocComment":" From the above and the requirements of `PartialOrd`, it follows that for all `a`, `b` and `c`:"},{"DocComment":""},{"DocComment":" - exactly one of `a < b`, `a == b` or `a > b` is true; and"},{"DocComment":" - `<` is transitive: `a < b` and `b < c` implies `a < c`. The same must hold for both `==` and"},{"DocComment":" `>`."},{"DocComment":""},{"DocComment":" Mathematically speaking, the `<` operator defines a strict [weak order]. In cases where `==`"},{"DocComment":" conforms to mathematical equality, it also defines a strict [total order]."},{"DocComment":""},{"DocComment":" [weak order]: https://en.wikipedia.org/wiki/Weak_ordering"},{"DocComment":" [total order]: https://en.wikipedia.org/wiki/Total_order"},{"DocComment":""},{"DocComment":" ## Derivable"},{"DocComment":""},{"DocComment":" This trait can be used with `#[derive]`."},{"DocComment":""},{"DocComment":" When `derive`d on structs, it will produce a"},{"DocComment":" [lexicographic](https://en.wikipedia.org/wiki/Lexicographic_order) ordering based on the"},{"DocComment":" top-to-bottom declaration order of the struct's members."},{"DocComment":""},{"DocComment":" When `derive`d on enums, variants are ordered primarily by their discriminants. Secondarily,"},{"DocComment":" they are ordered by their fields. By default, the discriminant is smallest for variants at the"},{"DocComment":" top, and largest for variants at the bottom. Here's an example:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #[derive(PartialEq, Eq, PartialOrd, Ord)]"},{"DocComment":" enum E {"},{"DocComment":" Top,"},{"DocComment":" Bottom,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" assert!(E::Top < E::Bottom);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" However, manually setting the discriminants can override this default behavior:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #[derive(PartialEq, Eq, PartialOrd, Ord)]"},{"DocComment":" enum E {"},{"DocComment":" Top = 2,"},{"DocComment":" Bottom = 1,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" assert!(E::Bottom < E::Top);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" ## Lexicographical comparison"},{"DocComment":""},{"DocComment":" Lexicographical comparison is an operation with the following properties:"},{"DocComment":" - Two sequences are compared element by element."},{"DocComment":" - The first mismatching element defines which sequence is lexicographically less or greater"},{"DocComment":" than the other."},{"DocComment":" - If one sequence is a prefix of another, the shorter sequence is lexicographically less than"},{"DocComment":" the other."},{"DocComment":" - If two sequences have equivalent elements and are of the same length, then the sequences are"},{"DocComment":" lexicographically equal."},{"DocComment":" - An empty sequence is lexicographically less than any non-empty sequence."},{"DocComment":" - Two empty sequences are lexicographically equal."},{"DocComment":""},{"DocComment":" ## How can I implement `Ord`?"},{"DocComment":""},{"DocComment":" `Ord` requires that the type also be [`PartialOrd`], [`PartialEq`], and [`Eq`]."},{"DocComment":""},{"DocComment":" Because `Ord` implies a stronger ordering relationship than [`PartialOrd`], and both `Ord` and"},{"DocComment":" [`PartialOrd`] must agree, you must choose how to implement `Ord` **first**. You can choose to"},{"DocComment":" derive it, or implement it manually. If you derive it, you should derive all four traits. If you"},{"DocComment":" implement it manually, you should manually implement all four traits, based on the"},{"DocComment":" implementation of `Ord`."},{"DocComment":""},{"DocComment":" Here's an example where you want to define the `Character` comparison by `health` and"},{"DocComment":" `experience` only, disregarding the field `mana`:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::cmp::Ordering;"},{"DocComment":""},{"DocComment":" struct Character {"},{"DocComment":" health: u32,"},{"DocComment":" experience: u32,"},{"DocComment":" mana: f32,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl Ord for Character {"},{"DocComment":" fn cmp(&self, other: &Self) -> Ordering {"},{"DocComment":" self.experience"},{"DocComment":" .cmp(&other.experience)"},{"DocComment":" .then(self.health.cmp(&other.health))"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialOrd for Character {"},{"DocComment":" fn partial_cmp(&self, other: &Self) -> Option {"},{"DocComment":" Some(self.cmp(other))"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialEq for Character {"},{"DocComment":" fn eq(&self, other: &Self) -> bool {"},{"DocComment":" self.health == other.health && self.experience == other.experience"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl Eq for Character {}"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" If all you need is to `slice::sort` a type by a field value, it can be simpler to use"},{"DocComment":" `slice::sort_by_key`."},{"DocComment":""},{"DocComment":" ## Examples of incorrect `Ord` implementations"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::cmp::Ordering;"},{"DocComment":""},{"DocComment":" #[derive(Debug)]"},{"DocComment":" struct Character {"},{"DocComment":" health: f32,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl Ord for Character {"},{"DocComment":" fn cmp(&self, other: &Self) -> std::cmp::Ordering {"},{"DocComment":" if self.health < other.health {"},{"DocComment":" Ordering::Less"},{"DocComment":" } else if self.health > other.health {"},{"DocComment":" Ordering::Greater"},{"DocComment":" } else {"},{"DocComment":" Ordering::Equal"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialOrd for Character {"},{"DocComment":" fn partial_cmp(&self, other: &Self) -> Option {"},{"DocComment":" Some(self.cmp(other))"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialEq for Character {"},{"DocComment":" fn eq(&self, other: &Self) -> bool {"},{"DocComment":" self.health == other.health"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl Eq for Character {}"},{"DocComment":""},{"DocComment":" let a = Character { health: 4.5 };"},{"DocComment":" let b = Character { health: f32::NAN };"},{"DocComment":""},{"DocComment":" // Mistake: floating-point values do not form a total order and using the built-in comparison"},{"DocComment":" // operands to implement `Ord` irregardless of that reality does not change it. Use"},{"DocComment":" // `f32::total_cmp` if you need a total order for floating-point values."},{"DocComment":""},{"DocComment":" // Reflexivity requirement of `Ord` is not given."},{"DocComment":" assert!(a == a);"},{"DocComment":" assert!(b != b);"},{"DocComment":""},{"DocComment":" // Antisymmetry requirement of `Ord` is not given. Only one of a < c and c < a is allowed to be"},{"DocComment":" // true, not both or neither."},{"DocComment":" assert_eq!((a < b) as u8 + (b < a) as u8, 0);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::cmp::Ordering;"},{"DocComment":""},{"DocComment":" #[derive(Debug)]"},{"DocComment":" struct Character {"},{"DocComment":" health: u32,"},{"DocComment":" experience: u32,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialOrd for Character {"},{"DocComment":" fn partial_cmp(&self, other: &Self) -> Option {"},{"DocComment":" Some(self.cmp(other))"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl Ord for Character {"},{"DocComment":" fn cmp(&self, other: &Self) -> std::cmp::Ordering {"},{"DocComment":" if self.health < 50 {"},{"DocComment":" self.health.cmp(&other.health)"},{"DocComment":" } else {"},{"DocComment":" self.experience.cmp(&other.experience)"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" // For performance reasons implementing `PartialEq` this way is not the idiomatic way, but it"},{"DocComment":" // ensures consistent behavior between `PartialEq`, `PartialOrd` and `Ord` in this example."},{"DocComment":" impl PartialEq for Character {"},{"DocComment":" fn eq(&self, other: &Self) -> bool {"},{"DocComment":" self.cmp(other) == Ordering::Equal"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl Eq for Character {}"},{"DocComment":""},{"DocComment":" let a = Character {"},{"DocComment":" health: 3,"},{"DocComment":" experience: 5,"},{"DocComment":" };"},{"DocComment":" let b = Character {"},{"DocComment":" health: 10,"},{"DocComment":" experience: 77,"},{"DocComment":" };"},{"DocComment":" let c = Character {"},{"DocComment":" health: 143,"},{"DocComment":" experience: 2,"},{"DocComment":" };"},{"DocComment":""},{"DocComment":" // Mistake: The implementation of `Ord` compares different fields depending on the value of"},{"DocComment":" // `self.health`, the resulting order is not total."},{"DocComment":""},{"DocComment":" // Transitivity requirement of `Ord` is not given. If a is smaller than b and b is smaller than"},{"DocComment":" // c, by transitive property a must also be smaller than c."},{"DocComment":" assert!(a < b && b < c && c < a);"},{"DocComment":""},{"DocComment":" // Antisymmetry requirement of `Ord` is not given. Only one of a < c and c < a is allowed to be"},{"DocComment":" // true, not both or neither."},{"DocComment":" assert_eq!((a < c) as u8 + (c < a) as u8, 2);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" The documentation of [`PartialOrd`] contains further examples, for example it's wrong for"},{"DocComment":" [`PartialOrd`] and [`PartialEq`] to disagree."},{"DocComment":""},{"DocComment":" [`cmp`]: Ord::cmp"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"Ord"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":46,"beg":{"line":973,"col":21},"end":{"line":973,"col":31}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":27,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":46,"beg":{"line":973,"col":34},"end":{"line":973,"col":58}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":21,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[{"params":{"regions":[{"index":0,"name":null,"mutability":"Unknown"},{"index":1,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"cmp","attr_info":{"attributes":[{"DocComment":" This method returns an [`Ordering`] between `self` and `other`."},{"DocComment":""},{"DocComment":" By convention, `self.cmp(&other)` returns the ordering matching the expression"},{"DocComment":" `self other` if true."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::cmp::Ordering;"},{"DocComment":""},{"DocComment":" assert_eq!(5.cmp(&10), Ordering::Less);"},{"DocComment":" assert_eq!(10.cmp(&5), Ordering::Greater);"},{"DocComment":" assert_eq!(5.cmp(&5), Ordering::Equal);"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":1854},{"HashConsedValue":[6195,{"Ref":[{"Var":{"Bound":[0,1]}},{"Deduplicated":164},"Shared"]}]}],"output":{"Deduplicated":3603}},"item":{"id":203,"generics":{"regions":[{"Var":{"Bound":[0,0]}},{"Var":{"Bound":[0,1]}}],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[{"HashConsedValue":[5692,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":17,"generics":{"regions":[],"types":[{"Deduplicated":1611}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"kind":{"TraitMethod":[17,0]}},null,null,null],"vtable":null},{"def_id":18,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Ident":["Copy",0]}],"span":{"data":{"file_id":1,"beg":{"line":457,"col":0},"end":{"line":457,"col":21}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Types whose values can be duplicated simply by copying bits."},{"DocComment":""},{"DocComment":" By default, variable bindings have 'move semantics.' In other"},{"DocComment":" words:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #[derive(Debug)]"},{"DocComment":" struct Foo;"},{"DocComment":""},{"DocComment":" let x = Foo;"},{"DocComment":""},{"DocComment":" let y = x;"},{"DocComment":""},{"DocComment":" // `x` has moved into `y`, and so cannot be used"},{"DocComment":""},{"DocComment":" // println!(\"{x:?}\"); // error: use of moved value"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" However, if a type implements `Copy`, it instead has 'copy semantics':"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" // We can derive a `Copy` implementation. `Clone` is also required, as it's"},{"DocComment":" // a supertrait of `Copy`."},{"DocComment":" #[derive(Debug, Copy, Clone)]"},{"DocComment":" struct Foo;"},{"DocComment":""},{"DocComment":" let x = Foo;"},{"DocComment":""},{"DocComment":" let y = x;"},{"DocComment":""},{"DocComment":" // `y` is a copy of `x`"},{"DocComment":""},{"DocComment":" println!(\"{x:?}\"); // A-OK!"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" It's important to note that in these two examples, the only difference is whether you"},{"DocComment":" are allowed to access `x` after the assignment. Under the hood, both a copy and a move"},{"DocComment":" can result in bits being copied in memory, although this is sometimes optimized away."},{"DocComment":""},{"DocComment":" ## How can I implement `Copy`?"},{"DocComment":""},{"DocComment":" There are two ways to implement `Copy` on your type. The simplest is to use `derive`:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #[derive(Copy, Clone)]"},{"DocComment":" struct MyStruct;"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" You can also implement `Copy` and `Clone` manually:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" struct MyStruct;"},{"DocComment":""},{"DocComment":" impl Copy for MyStruct { }"},{"DocComment":""},{"DocComment":" impl Clone for MyStruct {"},{"DocComment":" fn clone(&self) -> MyStruct {"},{"DocComment":" *self"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" There is a small difference between the two. The `derive` strategy will also place a `Copy`"},{"DocComment":" bound on type parameters:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #[derive(Clone)]"},{"DocComment":" struct MyStruct(T);"},{"DocComment":""},{"DocComment":" impl Copy for MyStruct { }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" This isn't always desired. For example, shared references (`&T`) can be copied regardless of"},{"DocComment":" whether `T` is `Copy`. Likewise, a generic struct containing markers such as [`PhantomData`]"},{"DocComment":" could potentially be duplicated with a bit-wise copy."},{"DocComment":""},{"DocComment":" ## What's the difference between `Copy` and `Clone`?"},{"DocComment":""},{"DocComment":" Copies happen implicitly, for example as part of an assignment `y = x`. The behavior of"},{"DocComment":" `Copy` is not overloadable; it is always a simple bit-wise copy."},{"DocComment":""},{"DocComment":" Cloning is an explicit action, `x.clone()`. The implementation of [`Clone`] can"},{"DocComment":" provide any type-specific behavior necessary to duplicate values safely. For example,"},{"DocComment":" the implementation of [`Clone`] for [`String`] needs to copy the pointed-to string"},{"DocComment":" buffer in the heap. A simple bitwise copy of [`String`] values would merely copy the"},{"DocComment":" pointer, leading to a double free down the line. For this reason, [`String`] is [`Clone`]"},{"DocComment":" but not `Copy`."},{"DocComment":""},{"DocComment":" [`Clone`] is a supertrait of `Copy`, so everything which is `Copy` must also implement"},{"DocComment":" [`Clone`]. If a type is `Copy` then its [`Clone`] implementation only needs to return `*self`"},{"DocComment":" (see the example above)."},{"DocComment":""},{"DocComment":" ## When can my type be `Copy`?"},{"DocComment":""},{"DocComment":" A type can implement `Copy` if all of its components implement `Copy`. For example, this"},{"DocComment":" struct can be `Copy`:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #[allow(dead_code)]"},{"DocComment":" #[derive(Copy, Clone)]"},{"DocComment":" struct Point {"},{"DocComment":" x: i32,"},{"DocComment":" y: i32,"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" A struct can be `Copy`, and [`i32`] is `Copy`, therefore `Point` is eligible to be `Copy`."},{"DocComment":" By contrast, consider"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #![allow(dead_code)]"},{"DocComment":" # struct Point;"},{"DocComment":" struct PointList {"},{"DocComment":" points: Vec,"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" The struct `PointList` cannot implement `Copy`, because [`Vec`] is not `Copy`. If we"},{"DocComment":" attempt to derive a `Copy` implementation, we'll get an error:"},{"DocComment":""},{"DocComment":" ```text"},{"DocComment":" the trait `Copy` cannot be implemented for this type; field `points` does not implement `Copy`"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" Shared references (`&T`) are also `Copy`, so a type can be `Copy`, even when it holds"},{"DocComment":" shared references of types `T` that are *not* `Copy`. Consider the following struct,"},{"DocComment":" which can implement `Copy`, because it only holds a *shared reference* to our non-`Copy`"},{"DocComment":" type `PointList` from above:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" # #![allow(dead_code)]"},{"DocComment":" # struct PointList;"},{"DocComment":" #[derive(Copy, Clone)]"},{"DocComment":" struct PointListWrapper<'a> {"},{"DocComment":" point_list_ref: &'a PointList,"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" ## When *can't* my type be `Copy`?"},{"DocComment":""},{"DocComment":" Some types can't be copied safely. For example, copying `&mut T` would create an aliased"},{"DocComment":" mutable reference. Copying [`String`] would duplicate responsibility for managing the"},{"DocComment":" [`String`]'s buffer, leading to a double free."},{"DocComment":""},{"DocComment":" Generalizing the latter case, any type implementing [`Drop`] can't be `Copy`, because it's"},{"DocComment":" managing some resource besides its own [`size_of::`] bytes."},{"DocComment":""},{"DocComment":" If you try to implement `Copy` on a struct or enum containing non-`Copy` data, you will get"},{"DocComment":" the error [E0204]."},{"DocComment":""},{"DocComment":" [E0204]: ../../error_codes/E0204.html"},{"DocComment":""},{"DocComment":" ## When *should* my type be `Copy`?"},{"DocComment":""},{"DocComment":" Generally speaking, if your type _can_ implement `Copy`, it should. Keep in mind, though,"},{"DocComment":" that implementing `Copy` is part of the public API of your type. If the type might become"},{"DocComment":" non-`Copy` in the future, it could be prudent to omit the `Copy` implementation now, to"},{"DocComment":" avoid a breaking API change."},{"DocComment":""},{"DocComment":" ## Additional implementors"},{"DocComment":""},{"DocComment":" In addition to the [implementors listed below][impls],"},{"DocComment":" the following types also implement `Copy`:"},{"DocComment":""},{"DocComment":" * Function item types (i.e., the distinct types defined for each function)"},{"DocComment":" * Function pointer types (e.g., `fn() -> i32`)"},{"DocComment":" * Closure types, if they capture no value from the environment"},{"DocComment":" or if all such captured values implement `Copy` themselves."},{"DocComment":" Note that variables captured by shared reference always implement `Copy`"},{"DocComment":" (even if the referent doesn't),"},{"DocComment":" while variables captured by mutable reference never implement `Copy`."},{"DocComment":""},{"DocComment":" [`Vec`]: ../../std/vec/struct.Vec.html"},{"DocComment":" [`String`]: ../../std/string/struct.String.html"},{"DocComment":" [`size_of::`]: size_of"},{"DocComment":" [impls]: #implementors"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"copy"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":1,"beg":{"line":457,"col":0},"end":{"line":459,"col":1}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":1,"beg":{"line":457,"col":16},"end":{"line":457,"col":21}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":8,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[],"vtable":null},{"def_id":19,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["accum",0]},{"Ident":["Sum",0]}],"span":{"data":{"file_id":52,"beg":{"line":17,"col":0},"end":{"line":17,"col":30}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Trait to represent types that can be created by summing up an iterator."},{"DocComment":""},{"DocComment":" This trait is used to implement [`Iterator::sum()`]. Types which implement"},{"DocComment":" this trait can be generated by using the [`sum()`] method on an iterator."},{"DocComment":" Like [`FromIterator`], this trait should rarely be called directly."},{"DocComment":""},{"DocComment":" [`sum()`]: Iterator::sum"},{"DocComment":" [`FromIterator`]: iter::FromIterator"},{"Unknown":{"path":"diagnostic::on_unimplemented","args":"message =\n\"a value of type `{Self}` cannot be made by summing an iterator over elements of type `{A}`\",\nlabel =\n\"value of type `{Self}` cannot be made by summing a `std::iter::Iterator`\""}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"A"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":52,"beg":{"line":17,"col":25},"end":{"line":17,"col":30}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":52,"beg":{"line":17,"col":14},"end":{"line":17,"col":22}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[{"params":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":52,"beg":{"line":21,"col":11},"end":{"line":21,"col":12}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":52,"beg":{"line":21,"col":14},"end":{"line":21,"col":32}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[{"regions":[],"skip_binder":{"trait_ref":{"Deduplicated":4567},"type_id":0,"ty":{"Deduplicated":2246}}}]},"skip_binder":{"name":"sum","attr_info":{"attributes":[{"DocComment":" Takes an iterator and generates `Self` from the elements by \"summing up\""},{"DocComment":" the items."}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":196}],"output":{"Deduplicated":164}},"item":{"id":207,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585},{"Deduplicated":196}],"const_generics":[],"trait_refs":[{"HashConsedValue":[5693,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":19,"generics":{"regions":[],"types":[{"Deduplicated":1611},{"Deduplicated":2246}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":199},{"Deduplicated":4562}]}}},"kind":{"TraitMethod":[19,0]}}],"vtable":null},{"def_id":20,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["accum",0]},{"Ident":["Product",0]}],"span":{"data":{"file_id":52,"beg":{"line":38,"col":0},"end":{"line":38,"col":34}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Trait to represent types that can be created by multiplying elements of an"},{"DocComment":" iterator."},{"DocComment":""},{"DocComment":" This trait is used to implement [`Iterator::product()`]. Types which implement"},{"DocComment":" this trait can be generated by using the [`product()`] method on an iterator."},{"DocComment":" Like [`FromIterator`], this trait should rarely be called directly."},{"DocComment":""},{"DocComment":" [`product()`]: Iterator::product"},{"DocComment":" [`FromIterator`]: iter::FromIterator"},{"Unknown":{"path":"diagnostic::on_unimplemented","args":"message =\n\"a value of type `{Self}` cannot be made by multiplying all elements of type `{A}` from an iterator\",\nlabel =\n\"value of type `{Self}` cannot be made by multiplying all elements from a `std::iter::Iterator`\""}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"A"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":52,"beg":{"line":38,"col":29},"end":{"line":38,"col":34}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":52,"beg":{"line":38,"col":18},"end":{"line":38,"col":26}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[{"params":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":52,"beg":{"line":42,"col":15},"end":{"line":42,"col":16}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":52,"beg":{"line":42,"col":18},"end":{"line":42,"col":36}},"generated_from_span":null},"origin":"WhereClauseOnFn","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[{"regions":[],"skip_binder":{"trait_ref":{"Deduplicated":4567},"type_id":0,"ty":{"Deduplicated":2246}}}]},"skip_binder":{"name":"product","attr_info":{"attributes":[{"DocComment":" Takes an iterator and generates `Self` from the elements by multiplying"},{"DocComment":" the items."}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":196}],"output":{"Deduplicated":164}},"item":{"id":208,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585},{"Deduplicated":196}],"const_generics":[],"trait_refs":[{"HashConsedValue":[5694,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":20,"generics":{"regions":[],"types":[{"Deduplicated":1611},{"Deduplicated":2246}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":199},{"Deduplicated":4562}]}}},"kind":{"TraitMethod":[20,0]}}],"vtable":null},{"def_id":21,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialOrd",0]}],"span":{"data":{"file_id":46,"beg":{"line":1358,"col":0},"end":{"line":1359,"col":41}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Trait for types that form a [partial order](https://en.wikipedia.org/wiki/Partial_order)."},{"DocComment":""},{"DocComment":" The `lt`, `le`, `gt`, and `ge` methods of this trait can be called using the `<`, `<=`, `>`, and"},{"DocComment":" `>=` operators, respectively."},{"DocComment":""},{"DocComment":" This trait should **only** contain the comparison logic for a type **if one plans on only"},{"DocComment":" implementing `PartialOrd` but not [`Ord`]**. Otherwise the comparison logic should be in [`Ord`]"},{"DocComment":" and this trait implemented with `Some(self.cmp(other))`."},{"DocComment":""},{"DocComment":" The methods of this trait must be consistent with each other and with those of [`PartialEq`]."},{"DocComment":" The following conditions must hold:"},{"DocComment":""},{"DocComment":" 1. `a == b` if and only if `partial_cmp(a, b) == Some(Equal)`."},{"DocComment":" 2. `a < b` if and only if `partial_cmp(a, b) == Some(Less)`"},{"DocComment":" 3. `a > b` if and only if `partial_cmp(a, b) == Some(Greater)`"},{"DocComment":" 4. `a <= b` if and only if `a < b || a == b`"},{"DocComment":" 5. `a >= b` if and only if `a > b || a == b`"},{"DocComment":" 6. `a != b` if and only if `!(a == b)`."},{"DocComment":""},{"DocComment":" Conditions 2–5 above are ensured by the default implementation. Condition 6 is already ensured"},{"DocComment":" by [`PartialEq`]."},{"DocComment":""},{"DocComment":" If [`Ord`] is also implemented for `Self` and `Rhs`, it must also be consistent with"},{"DocComment":" `partial_cmp` (see the documentation of that trait for the exact requirements). It's easy to"},{"DocComment":" accidentally make them disagree by deriving some of the traits and manually implementing others."},{"DocComment":""},{"DocComment":" The comparison relations must satisfy the following conditions (for all `a`, `b`, `c` of type"},{"DocComment":" `A`, `B`, `C`):"},{"DocComment":""},{"DocComment":" - **Transitivity**: if `A: PartialOrd` and `B: PartialOrd` and `A: PartialOrd`, then `a"},{"DocComment":" < b` and `b < c` implies `a < c`. The same must hold for both `==` and `>`. This must also"},{"DocComment":" work for longer chains, such as when `A: PartialOrd`, `B: PartialOrd`, `C:"},{"DocComment":" PartialOrd`, and `A: PartialOrd` all exist."},{"DocComment":" - **Duality**: if `A: PartialOrd` and `B: PartialOrd`, then `a < b` if and only if `b >"},{"DocComment":" a`."},{"DocComment":""},{"DocComment":" Note that the `B: PartialOrd` (dual) and `A: PartialOrd` (transitive) impls are not forced"},{"DocComment":" to exist, but these requirements apply whenever they do exist."},{"DocComment":""},{"DocComment":" Violating these requirements is a logic error. The behavior resulting from a logic error is not"},{"DocComment":" specified, but users of the trait must ensure that such logic errors do *not* result in"},{"DocComment":" undefined behavior. This means that `unsafe` code **must not** rely on the correctness of these"},{"DocComment":" methods."},{"DocComment":""},{"DocComment":" ## Cross-crate considerations"},{"DocComment":""},{"DocComment":" Upholding the requirements stated above can become tricky when one crate implements `PartialOrd`"},{"DocComment":" for a type of another crate (i.e., to allow comparing one of its own types with a type from the"},{"DocComment":" standard library). The recommendation is to never implement this trait for a foreign type. In"},{"DocComment":" other words, such a crate should do `impl PartialOrd for LocalType`, but it should"},{"DocComment":" *not* do `impl PartialOrd for ForeignType`."},{"DocComment":""},{"DocComment":" This avoids the problem of transitive chains that criss-cross crate boundaries: for all local"},{"DocComment":" types `T`, you may assume that no other crate will add `impl`s that allow comparing `T < U`. In"},{"DocComment":" other words, if other crates add `impl`s that allow building longer transitive chains `U1 < ..."},{"DocComment":" < T < V1 < ...`, then all the types that appear to the right of `T` must be types that the crate"},{"DocComment":" defining `T` already knows about. This rules out transitive chains where downstream crates can"},{"DocComment":" add new `impl`s that \"stitch together\" comparisons of foreign types in ways that violate"},{"DocComment":" transitivity."},{"DocComment":""},{"DocComment":" Not having such foreign `impl`s also avoids forward compatibility issues where one crate adding"},{"DocComment":" more `PartialOrd` implementations can cause build failures in downstream crates."},{"DocComment":""},{"DocComment":" ## Corollaries"},{"DocComment":""},{"DocComment":" The following corollaries follow from the above requirements:"},{"DocComment":""},{"DocComment":" - irreflexivity of `<` and `>`: `!(a < a)`, `!(a > a)`"},{"DocComment":" - transitivity of `>`: if `a > b` and `b > c` then `a > c`"},{"DocComment":" - duality of `partial_cmp`: `partial_cmp(a, b) == partial_cmp(b, a).map(Ordering::reverse)`"},{"DocComment":""},{"DocComment":" ## Strict and non-strict partial orders"},{"DocComment":""},{"DocComment":" The `<` and `>` operators behave according to a *strict* partial order. However, `<=` and `>=`"},{"DocComment":" do **not** behave according to a *non-strict* partial order. That is because mathematically, a"},{"DocComment":" non-strict partial order would require reflexivity, i.e. `a <= a` would need to be true for"},{"DocComment":" every `a`. This isn't always the case for types that implement `PartialOrd`, for example:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let a = f64::NAN;"},{"DocComment":" assert_eq!(a <= a, false);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" ## Derivable"},{"DocComment":""},{"DocComment":" This trait can be used with `#[derive]`."},{"DocComment":""},{"DocComment":" When `derive`d on structs, it will produce a"},{"DocComment":" [lexicographic](https://en.wikipedia.org/wiki/Lexicographic_order) ordering based on the"},{"DocComment":" top-to-bottom declaration order of the struct's members."},{"DocComment":""},{"DocComment":" When `derive`d on enums, variants are primarily ordered by their discriminants. Secondarily,"},{"DocComment":" they are ordered by their fields. By default, the discriminant is smallest for variants at the"},{"DocComment":" top, and largest for variants at the bottom. Here's an example:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #[derive(PartialEq, PartialOrd)]"},{"DocComment":" enum E {"},{"DocComment":" Top,"},{"DocComment":" Bottom,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" assert!(E::Top < E::Bottom);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" However, manually setting the discriminants can override this default behavior:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #[derive(PartialEq, PartialOrd)]"},{"DocComment":" enum E {"},{"DocComment":" Top = 2,"},{"DocComment":" Bottom = 1,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" assert!(E::Bottom < E::Top);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" ## How can I implement `PartialOrd`?"},{"DocComment":""},{"DocComment":" `PartialOrd` only requires implementation of the [`partial_cmp`] method, with the others"},{"DocComment":" generated from default implementations."},{"DocComment":""},{"DocComment":" However it remains possible to implement the others separately for types which do not have a"},{"DocComment":" total order. For example, for floating point numbers, `NaN < 0 == false` and `NaN >= 0 == false`"},{"DocComment":" (cf. IEEE 754-2008 section 5.11)."},{"DocComment":""},{"DocComment":" `PartialOrd` requires your type to be [`PartialEq`]."},{"DocComment":""},{"DocComment":" If your type is [`Ord`], you can implement [`partial_cmp`] by using [`cmp`]:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::cmp::Ordering;"},{"DocComment":""},{"DocComment":" struct Person {"},{"DocComment":" id: u32,"},{"DocComment":" name: String,"},{"DocComment":" height: u32,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialOrd for Person {"},{"DocComment":" fn partial_cmp(&self, other: &Self) -> Option {"},{"DocComment":" Some(self.cmp(other))"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl Ord for Person {"},{"DocComment":" fn cmp(&self, other: &Self) -> Ordering {"},{"DocComment":" self.height.cmp(&other.height)"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialEq for Person {"},{"DocComment":" fn eq(&self, other: &Self) -> bool {"},{"DocComment":" self.height == other.height"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl Eq for Person {}"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" You may also find it useful to use [`partial_cmp`] on your type's fields. Here is an example of"},{"DocComment":" `Person` types who have a floating-point `height` field that is the only field to be used for"},{"DocComment":" sorting:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::cmp::Ordering;"},{"DocComment":""},{"DocComment":" struct Person {"},{"DocComment":" id: u32,"},{"DocComment":" name: String,"},{"DocComment":" height: f64,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialOrd for Person {"},{"DocComment":" fn partial_cmp(&self, other: &Self) -> Option {"},{"DocComment":" self.height.partial_cmp(&other.height)"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialEq for Person {"},{"DocComment":" fn eq(&self, other: &Self) -> bool {"},{"DocComment":" self.height == other.height"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" ## Examples of incorrect `PartialOrd` implementations"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::cmp::Ordering;"},{"DocComment":""},{"DocComment":" #[derive(PartialEq, Debug)]"},{"DocComment":" struct Character {"},{"DocComment":" health: u32,"},{"DocComment":" experience: u32,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialOrd for Character {"},{"DocComment":" fn partial_cmp(&self, other: &Self) -> Option {"},{"DocComment":" Some(self.health.cmp(&other.health))"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" let a = Character {"},{"DocComment":" health: 10,"},{"DocComment":" experience: 5,"},{"DocComment":" };"},{"DocComment":" let b = Character {"},{"DocComment":" health: 10,"},{"DocComment":" experience: 77,"},{"DocComment":" };"},{"DocComment":""},{"DocComment":" // Mistake: `PartialEq` and `PartialOrd` disagree with each other."},{"DocComment":""},{"DocComment":" assert_eq!(a.partial_cmp(&b).unwrap(), Ordering::Equal); // a == b according to `PartialOrd`."},{"DocComment":" assert_ne!(a, b); // a != b according to `PartialEq`."},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let x: u32 = 0;"},{"DocComment":" let y: u32 = 1;"},{"DocComment":""},{"DocComment":" assert_eq!(x < y, true);"},{"DocComment":" assert_eq!(x.lt(&y), true);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" [`partial_cmp`]: PartialOrd::partial_cmp"},{"DocComment":" [`cmp`]: Ord::cmp"},{"Unknown":{"path":"rustc_on_unimplemented","args":"message = \"can't compare `{Self}` with `{Rhs}`\", label =\n\"no implementation for `{Self} < {Rhs}` and `{Self} > {Rhs}`\",\nappend_const_msg"}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"partial_ord"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"Rhs"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":46,"beg":{"line":1359,"col":4},"end":{"line":1359,"col":26}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":22,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[{"params":{"regions":[{"index":0,"name":null,"mutability":"Unknown"},{"index":1,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"partial_cmp","attr_info":{"attributes":[{"DocComment":" This method returns an ordering between `self` and `other` values if one exists."},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" use std::cmp::Ordering;"},{"DocComment":""},{"DocComment":" let result = 1.0.partial_cmp(&2.0);"},{"DocComment":" assert_eq!(result, Some(Ordering::Less));"},{"DocComment":""},{"DocComment":" let result = 1.0.partial_cmp(&1.0);"},{"DocComment":" assert_eq!(result, Some(Ordering::Equal));"},{"DocComment":""},{"DocComment":" let result = 2.0.partial_cmp(&1.0);"},{"DocComment":" assert_eq!(result, Some(Ordering::Greater));"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" When comparison is impossible:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let result = f64::NAN.partial_cmp(&1.0);"},{"DocComment":" assert_eq!(result, None);"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":1854},{"HashConsedValue":[5140,{"Ref":[{"Var":{"Bound":[0,1]}},{"Deduplicated":1585},"Shared"]}]}],"output":{"Deduplicated":3972}},"item":{"id":209,"generics":{"regions":[{"Var":{"Bound":[0,0]}},{"Var":{"Bound":[0,1]}}],"types":[{"Deduplicated":164},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[{"HashConsedValue":[5695,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":21,"generics":{"regions":[],"types":[{"Deduplicated":1611},{"Deduplicated":2246}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"kind":{"TraitMethod":[21,0]}},null,null,null,null,null,null,null,null],"vtable":{"id":{"Adt":48},"generics":{"regions":[],"types":[{"Deduplicated":1589}],"const_generics":[],"trait_refs":[]}}},{"def_id":22,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["PartialEq",0]}],"span":{"data":{"file_id":46,"beg":{"line":251,"col":0},"end":{"line":251,"col":65}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Trait for comparisons using the equality operator."},{"DocComment":""},{"DocComment":" Implementing this trait for types provides the `==` and `!=` operators for"},{"DocComment":" those types."},{"DocComment":""},{"DocComment":" `x.eq(y)` can also be written `x == y`, and `x.ne(y)` can be written `x != y`."},{"DocComment":" We use the easier-to-read infix notation in the remainder of this documentation."},{"DocComment":""},{"DocComment":" This trait allows for comparisons using the equality operator, for types"},{"DocComment":" that do not have a full equivalence relation. For example, in floating point"},{"DocComment":" numbers `NaN != NaN`, so floating point types implement `PartialEq` but not"},{"DocComment":" [`trait@Eq`]. Formally speaking, when `Rhs == Self`, this trait corresponds"},{"DocComment":" to a [partial equivalence relation]."},{"DocComment":""},{"DocComment":" [partial equivalence relation]: https://en.wikipedia.org/wiki/Partial_equivalence_relation"},{"DocComment":""},{"DocComment":" Implementations must ensure that `eq` and `ne` are consistent with each other:"},{"DocComment":""},{"DocComment":" - `a != b` if and only if `!(a == b)`."},{"DocComment":""},{"DocComment":" The default implementation of `ne` provides this consistency and is almost"},{"DocComment":" always sufficient. It should not be overridden without very good reason."},{"DocComment":""},{"DocComment":" If [`PartialOrd`] or [`Ord`] are also implemented for `Self` and `Rhs`, their methods must also"},{"DocComment":" be consistent with `PartialEq` (see the documentation of those traits for the exact"},{"DocComment":" requirements). It's easy to accidentally make them disagree by deriving some of the traits and"},{"DocComment":" manually implementing others."},{"DocComment":""},{"DocComment":" The equality relation `==` must satisfy the following conditions"},{"DocComment":" (for all `a`, `b`, `c` of type `A`, `B`, `C`):"},{"DocComment":""},{"DocComment":" - **Symmetry**: if `A: PartialEq` and `B: PartialEq`, then **`a == b`"},{"DocComment":" implies `b == a`**; and"},{"DocComment":""},{"DocComment":" - **Transitivity**: if `A: PartialEq` and `B: PartialEq` and `A:"},{"DocComment":" PartialEq`, then **`a == b` and `b == c` implies `a == c`**."},{"DocComment":" This must also work for longer chains, such as when `A: PartialEq`, `B: PartialEq`,"},{"DocComment":" `C: PartialEq`, and `A: PartialEq` all exist."},{"DocComment":""},{"DocComment":" Note that the `B: PartialEq` (symmetric) and `A: PartialEq`"},{"DocComment":" (transitive) impls are not forced to exist, but these requirements apply"},{"DocComment":" whenever they do exist."},{"DocComment":""},{"DocComment":" Violating these requirements is a logic error. The behavior resulting from a logic error is not"},{"DocComment":" specified, but users of the trait must ensure that such logic errors do *not* result in"},{"DocComment":" undefined behavior. This means that `unsafe` code **must not** rely on the correctness of these"},{"DocComment":" methods."},{"DocComment":""},{"DocComment":" ## Cross-crate considerations"},{"DocComment":""},{"DocComment":" Upholding the requirements stated above can become tricky when one crate implements `PartialEq`"},{"DocComment":" for a type of another crate (i.e., to allow comparing one of its own types with a type from the"},{"DocComment":" standard library). The recommendation is to never implement this trait for a foreign type. In"},{"DocComment":" other words, such a crate should do `impl PartialEq for LocalType`, but it should"},{"DocComment":" *not* do `impl PartialEq for ForeignType`."},{"DocComment":""},{"DocComment":" This avoids the problem of transitive chains that criss-cross crate boundaries: for all local"},{"DocComment":" types `T`, you may assume that no other crate will add `impl`s that allow comparing `T == U`. In"},{"DocComment":" other words, if other crates add `impl`s that allow building longer transitive chains `U1 == ..."},{"DocComment":" == T == V1 == ...`, then all the types that appear to the right of `T` must be types that the"},{"DocComment":" crate defining `T` already knows about. This rules out transitive chains where downstream crates"},{"DocComment":" can add new `impl`s that \"stitch together\" comparisons of foreign types in ways that violate"},{"DocComment":" transitivity."},{"DocComment":""},{"DocComment":" Not having such foreign `impl`s also avoids forward compatibility issues where one crate adding"},{"DocComment":" more `PartialEq` implementations can cause build failures in downstream crates."},{"DocComment":""},{"DocComment":" ## Derivable"},{"DocComment":""},{"DocComment":" This trait can be used with `#[derive]`. When `derive`d on structs, two"},{"DocComment":" instances are equal if all fields are equal, and not equal if any fields"},{"DocComment":" are not equal. When `derive`d on enums, two instances are equal if they"},{"DocComment":" are the same variant and all fields are equal."},{"DocComment":""},{"DocComment":" ## How can I implement `PartialEq`?"},{"DocComment":""},{"DocComment":" An example implementation for a domain in which two books are considered"},{"DocComment":" the same book if their ISBN matches, even if the formats differ:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" enum BookFormat {"},{"DocComment":" Paperback,"},{"DocComment":" Hardback,"},{"DocComment":" Ebook,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" struct Book {"},{"DocComment":" isbn: i32,"},{"DocComment":" format: BookFormat,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialEq for Book {"},{"DocComment":" fn eq(&self, other: &Self) -> bool {"},{"DocComment":" self.isbn == other.isbn"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" let b1 = Book { isbn: 3, format: BookFormat::Paperback };"},{"DocComment":" let b2 = Book { isbn: 3, format: BookFormat::Ebook };"},{"DocComment":" let b3 = Book { isbn: 10, format: BookFormat::Paperback };"},{"DocComment":""},{"DocComment":" assert!(b1 == b2);"},{"DocComment":" assert!(b1 != b3);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" ## How can I compare two different types?"},{"DocComment":""},{"DocComment":" The type you can compare with is controlled by `PartialEq`'s type parameter."},{"DocComment":" For example, let's tweak our previous code a bit:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" // The derive implements == comparisons"},{"DocComment":" #[derive(PartialEq)]"},{"DocComment":" enum BookFormat {"},{"DocComment":" Paperback,"},{"DocComment":" Hardback,"},{"DocComment":" Ebook,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" struct Book {"},{"DocComment":" isbn: i32,"},{"DocComment":" format: BookFormat,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" // Implement == comparisons"},{"DocComment":" impl PartialEq for Book {"},{"DocComment":" fn eq(&self, other: &BookFormat) -> bool {"},{"DocComment":" self.format == *other"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" // Implement == comparisons"},{"DocComment":" impl PartialEq for BookFormat {"},{"DocComment":" fn eq(&self, other: &Book) -> bool {"},{"DocComment":" *self == other.format"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" let b1 = Book { isbn: 3, format: BookFormat::Paperback };"},{"DocComment":""},{"DocComment":" assert!(b1 == BookFormat::Paperback);"},{"DocComment":" assert!(BookFormat::Ebook != b1);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" By changing `impl PartialEq for Book` to `impl PartialEq for Book`,"},{"DocComment":" we allow `BookFormat`s to be compared with `Book`s."},{"DocComment":""},{"DocComment":" A comparison like the one above, which ignores some fields of the struct,"},{"DocComment":" can be dangerous. It can easily lead to an unintended violation of the"},{"DocComment":" requirements for a partial equivalence relation. For example, if we kept"},{"DocComment":" the above implementation of `PartialEq` for `BookFormat` and added an"},{"DocComment":" implementation of `PartialEq` for `Book` (either via a `#[derive]` or"},{"DocComment":" via the manual implementation from the first example) then the result would"},{"DocComment":" violate transitivity:"},{"DocComment":""},{"DocComment":" ```should_panic"},{"DocComment":" #[derive(PartialEq)]"},{"DocComment":" enum BookFormat {"},{"DocComment":" Paperback,"},{"DocComment":" Hardback,"},{"DocComment":" Ebook,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" #[derive(PartialEq)]"},{"DocComment":" struct Book {"},{"DocComment":" isbn: i32,"},{"DocComment":" format: BookFormat,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialEq for Book {"},{"DocComment":" fn eq(&self, other: &BookFormat) -> bool {"},{"DocComment":" self.format == *other"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialEq for BookFormat {"},{"DocComment":" fn eq(&self, other: &Book) -> bool {"},{"DocComment":" *self == other.format"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" fn main() {"},{"DocComment":" let b1 = Book { isbn: 1, format: BookFormat::Paperback };"},{"DocComment":" let b2 = Book { isbn: 2, format: BookFormat::Paperback };"},{"DocComment":""},{"DocComment":" assert!(b1 == BookFormat::Paperback);"},{"DocComment":" assert!(BookFormat::Paperback == b2);"},{"DocComment":""},{"DocComment":" // The following should hold by transitivity but doesn't."},{"DocComment":" assert!(b1 == b2); // <-- PANICS"},{"DocComment":" }"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" let x: u32 = 0;"},{"DocComment":" let y: u32 = 1;"},{"DocComment":""},{"DocComment":" assert_eq!(x == y, false);"},{"DocComment":" assert_eq!(x.eq(&y), false);"},{"DocComment":" ```"},{"DocComment":""},{"DocComment":" [`eq`]: PartialEq::eq"},{"DocComment":" [`ne`]: PartialEq::ne"},{"Unknown":{"path":"rustc_on_unimplemented","args":"message = \"can't compare `{Self}` with `{Rhs}`\", label =\n\"no implementation for `{Self} == {Rhs}`\", append_const_msg"}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"eq"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"Rhs"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[],"consts":[],"types":[],"methods":[{"params":{"regions":[{"index":0,"name":null,"mutability":"Unknown"},{"index":1,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"eq","attr_info":{"attributes":[{"DocComment":" Tests for `self` and `other` values to be equal, and is used by `==`."}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":1854},{"Deduplicated":5140}],"output":{"Deduplicated":211}},"item":{"id":218,"generics":{"regions":[{"Var":{"Bound":[0,0]}},{"Var":{"Bound":[0,1]}}],"types":[{"Deduplicated":164},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[{"HashConsedValue":[6196,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":22,"generics":{"regions":[],"types":[{"Deduplicated":1611},{"Deduplicated":2246}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"kind":{"TraitMethod":[22,0]}},null],"vtable":{"id":{"Adt":49},"generics":{"regions":[],"types":[{"Deduplicated":1589}],"const_generics":[],"trait_refs":[]}}},{"def_id":23,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["adapters",0]},{"Ident":["zip",0]},{"Ident":["TrustedRandomAccessNoCoerce",0]}],"span":{"data":{"file_id":24,"beg":{"line":585,"col":0},"end":{"line":585,"col":51}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Like [`TrustedRandomAccess`] but without any of the requirements / guarantees around"},{"DocComment":" coercions to supertypes after `__iterator_get_unchecked` (they aren’t allowed here!), and"},{"DocComment":" without the requirement that subtypes / supertypes implement `TrustedRandomAccessNoCoerce`."},{"DocComment":""},{"DocComment":" This trait was created in PR #85874 to fix soundness issue #85873 without performance regressions."},{"DocComment":" It is subject to change as we might want to build a more generally useful (for performance"},{"DocComment":" optimizations) and more sophisticated trait or trait hierarchy that replaces or extends"},{"DocComment":" [`TrustedRandomAccess`] and `TrustedRandomAccessNoCoerce`."}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":24,"beg":{"line":585,"col":46},"end":{"line":585,"col":51}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"consts":[{"name":"MAY_HAVE_SIDE_EFFECT","attr_info":{"attributes":[{"DocComment":" `true` if getting an iterator element may have side effects."},{"DocComment":" Remember to take inner iterators into account."}],"inline":null,"rename":null,"public":true},"ty":{"Deduplicated":211},"default":null}],"types":[],"methods":[null],"vtable":null},{"def_id":24,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["ops",0]},{"Ident":["try_trait",0]},{"Ident":["FromResidual",0]}],"span":{"data":{"file_id":42,"beg":{"line":310,"col":0},"end":{"line":310,"col":57}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Used to specify which residuals can be converted into which [`crate::ops::Try`] types."},{"DocComment":""},{"DocComment":" Every `Try` type needs to be recreatable from its own associated"},{"DocComment":" `Residual` type, but can also have additional `FromResidual` implementations"},{"DocComment":" to support interconversion with other `Try` types."},{"Unknown":{"path":"rustc_on_unimplemented","args":"on(all(from_desugaring = \"QuestionMark\", Self = \"core::result::Result\",\nR = \"core::option::Option\",), message =\n\"the `?` operator can only be used on `Result`s, not `Option`s, \\\n in {ItemContext} that returns `Result`\",\nlabel = \"use `.ok_or(...)?` to provide an error compatible with `{Self}`\",\nparent_label = \"this function returns a `Result`\"),\non(all(from_desugaring = \"QuestionMark\", Self =\n\"core::result::Result\",), message =\n\"the `?` operator can only be used on `Result`s \\\n in {ItemContext} that returns `Result`\",\nlabel = \"this `?` produces `{R}`, which is incompatible with `{Self}`\",\nparent_label = \"this function returns a `Result`\"),\non(all(from_desugaring = \"QuestionMark\", Self = \"core::option::Option\", R =\n\"core::result::Result\",), message =\n\"the `?` operator can only be used on `Option`s, not `Result`s, \\\n in {ItemContext} that returns `Option`\",\nlabel = \"use `.ok()?` if you want to discard the `{R}` error information\",\nparent_label = \"this function returns an `Option`\"),\non(all(from_desugaring = \"QuestionMark\", Self = \"core::option::Option\",),\nmessage =\n\"the `?` operator can only be used on `Option`s \\\n in {ItemContext} that returns `Option`\",\nlabel = \"this `?` produces `{R}`, which is incompatible with `{Self}`\",\nparent_label = \"this function returns an `Option`\"),\non(all(from_desugaring = \"QuestionMark\", Self =\n\"core::ops::control_flow::ControlFlow\", R =\n\"core::ops::control_flow::ControlFlow\",), message =\n\"the `?` operator in {ItemContext} that returns `ControlFlow` \\\n can only be used on other `ControlFlow`s (with the same Break type)\",\nlabel = \"this `?` produces `{R}`, which is incompatible with `{Self}`\",\nparent_label = \"this function returns a `ControlFlow`\", note =\n\"unlike `Result`, there's no `From`-conversion performed for `ControlFlow`\"),\non(all(from_desugaring = \"QuestionMark\", Self =\n\"core::ops::control_flow::ControlFlow\",), message =\n\"the `?` operator can only be used on `ControlFlow`s \\\n in {ItemContext} that returns `ControlFlow`\",\nlabel = \"this `?` produces `{R}`, which is incompatible with `{Self}`\",\nparent_label = \"this function returns a `ControlFlow`\",),\non(all(from_desugaring = \"QuestionMark\"), message =\n\"the `?` operator can only be used in {ItemContext} \\\n that returns `Result` or `Option` \\\n (or another type that implements `{This}`)\",\nlabel = \"cannot use the `?` operator in {ItemContext} that returns `{Self}`\",\nparent_label =\n\"this function should return `Result` or `Option` to accept `?`\"),"}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"FromResidual"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"},{"index":1,"name":"R"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":42,"beg":{"line":310,"col":0},"end":{"line":334,"col":1}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":42,"beg":{"line":310,"col":29},"end":{"line":310,"col":56}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"name":"from_residual","attr_info":{"attributes":[{"DocComment":" Constructs the type from a compatible `Residual` type."},{"DocComment":""},{"DocComment":" This should be implemented consistently with the `branch` method such"},{"DocComment":" that applying the `?` operator will get back an equivalent residual:"},{"DocComment":" `FromResidual::from_residual(r).branch() --> ControlFlow::Break(r)`."},{"DocComment":" (The residual is not mandated to be *identical* when interconversion is involved.)"},{"DocComment":""},{"DocComment":" # Examples"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" #![feature(try_trait_v2)]"},{"DocComment":" use std::ops::{ControlFlow, FromResidual};"},{"DocComment":""},{"DocComment":" assert_eq!(Result::::from_residual(Err(3_u8)), Err(3));"},{"DocComment":" assert_eq!(Option::::from_residual(None), None);"},{"DocComment":" assert_eq!("},{"DocComment":" ControlFlow::<_, String>::from_residual(ControlFlow::Break(5)),"},{"DocComment":" ControlFlow::Break(5),"},{"DocComment":" );"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"signature":{"is_unsafe":false,"inputs":[{"Deduplicated":1585}],"output":{"Deduplicated":164}},"item":{"id":221,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[{"HashConsedValue":[5697,{"kind":"SelfId","trait_decl_ref":{"regions":[],"skip_binder":{"id":24,"generics":{"regions":[],"types":[{"Deduplicated":1611},{"Deduplicated":2246}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"kind":{"TraitMethod":[24,0]}}],"vtable":null},{"def_id":25,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Ident":["Tuple",0]}],"span":{"data":{"file_id":1,"beg":{"line":1074,"col":0},"end":{"line":1074,"col":15}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" A marker for tuple types."},{"DocComment":""},{"DocComment":" The implementation of this trait is built-in and cannot be implemented"},{"DocComment":" for any user type."},{"Unknown":{"path":"diagnostic::on_unimplemented","args":"message = \"`{Self}` is not a tuple\""}}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"tuple_trait"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":1,"beg":{"line":1074,"col":0},"end":{"line":1074,"col":18}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[],"vtable":null},{"def_id":26,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["nonzero",0]},{"Ident":["private",0]},{"Ident":["Sealed",0]}],"span":{"data":{"file_id":19,"beg":{"line":46,"col":12},"end":{"line":46,"col":28}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":null},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":19,"beg":{"line":46,"col":12},"end":{"line":46,"col":31}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[],"vtable":{"id":{"Adt":50},"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}},{"def_id":27,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["cmp",0]},{"Ident":["Eq",0]}],"span":{"data":{"file_id":46,"beg":{"line":338,"col":0},"end":{"line":338,"col":58}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[{"DocComment":" Trait for comparisons corresponding to [equivalence relations]("},{"DocComment":" https://en.wikipedia.org/wiki/Equivalence_relation)."},{"DocComment":""},{"DocComment":" The primary difference to [`PartialEq`] is the additional requirement for reflexivity. A type"},{"DocComment":" that implements [`PartialEq`] guarantees that for all `a`, `b` and `c`:"},{"DocComment":""},{"DocComment":" - symmetric: `a == b` implies `b == a` and `a != b` implies `!(a == b)`"},{"DocComment":" - transitive: `a == b` and `b == c` implies `a == c`"},{"DocComment":""},{"DocComment":" `Eq`, which builds on top of [`PartialEq`] also implies:"},{"DocComment":""},{"DocComment":" - reflexive: `a == a`"},{"DocComment":""},{"DocComment":" This property cannot be checked by the compiler, and therefore `Eq` is a trait without methods."},{"DocComment":""},{"DocComment":" Violating this property is a logic error. The behavior resulting from a logic error is not"},{"DocComment":" specified, but users of the trait must ensure that such logic errors do *not* result in"},{"DocComment":" undefined behavior. This means that `unsafe` code **must not** rely on the correctness of these"},{"DocComment":" methods."},{"DocComment":""},{"DocComment":" Floating point types such as [`f32`] and [`f64`] implement only [`PartialEq`] but *not* `Eq`"},{"DocComment":" because `NaN` != `NaN`."},{"DocComment":""},{"DocComment":" ## Derivable"},{"DocComment":""},{"DocComment":" This trait can be used with `#[derive]`. When `derive`d, because `Eq` has no extra methods, it"},{"DocComment":" is only informing the compiler that this is an equivalence relation rather than a partial"},{"DocComment":" equivalence relation. Note that the `derive` strategy requires all fields are `Eq`, which isn't"},{"DocComment":" always desired."},{"DocComment":""},{"DocComment":" ## How can I implement `Eq`?"},{"DocComment":""},{"DocComment":" If you cannot use the `derive` strategy, specify that your type implements `Eq`, which has no"},{"DocComment":" extra methods:"},{"DocComment":""},{"DocComment":" ```"},{"DocComment":" enum BookFormat {"},{"DocComment":" Paperback,"},{"DocComment":" Hardback,"},{"DocComment":" Ebook,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" struct Book {"},{"DocComment":" isbn: i32,"},{"DocComment":" format: BookFormat,"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl PartialEq for Book {"},{"DocComment":" fn eq(&self, other: &Self) -> bool {"},{"DocComment":" self.isbn == other.isbn"},{"DocComment":" }"},{"DocComment":" }"},{"DocComment":""},{"DocComment":" impl Eq for Book {}"},{"DocComment":" ```"}],"inline":null,"rename":null,"public":true},"is_local":false,"opacity":"Foreign","lang_item":"Eq"},"generics":{"regions":[],"types":[{"index":0,"name":"Self"}],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_clauses":[{"clause_id":0,"span":{"data":{"file_id":46,"beg":{"line":338,"col":20},"end":{"line":338,"col":43}},"generated_from_span":null},"origin":"WhereClauseOnTrait","trait_":{"regions":[],"skip_binder":{"id":22,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"consts":[],"types":[],"methods":[null],"vtable":null}],"trait_impls":[{"def_id":0,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":0}}],"span":{"data":{"file_id":4,"beg":{"line":21,"col":0},"end":{"line":21,"col":36}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":1602}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[{"index":0,"name":"'a","mutability":"Unknown"}],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":4,"beg":{"line":21,"col":9},"end":{"line":21,"col":10}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"HashConsedValue":[6204,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[6203,{"Ref":["Erased",{"HashConsedValue":[1603,{"Slice":{"Deduplicated":164}}]},"Shared"]}]}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[5561,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"Deduplicated":5517}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":5516}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6205,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[5361,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[5360,{"Adt":{"id":{"Adt":6},"generics":{"regions":["Erased"],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[{"HashConsedValue":[1617,{"kind":{"Clause":{"Bound":[1,0]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1611}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":5360}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6206,{"kind":{"TraitImpl":{"id":1,"generics":{"regions":["Erased"],"types":[{"Deduplicated":196}],"const_generics":[],"trait_refs":[{"Deduplicated":199}]}}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":5360}],"const_generics":[],"trait_refs":[]}}}}]}],"consts":[],"types":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"value":{"Deduplicated":1610},"implied_trait_refs":[]},"kind":{"TraitType":[6,0]}},{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"value":{"HashConsedValue":[1618,{"Adt":{"id":{"Adt":6},"generics":{"regions":[{"Var":{"Bound":[1,0]}}],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[{"Deduplicated":1617}]}}}]},"implied_trait_refs":[]},"kind":{"TraitType":[6,1]}}],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":12,"generics":{"regions":[{"Var":{"Bound":[1,0]}}],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[{"Deduplicated":1617}]}},"kind":{"TraitMethod":[6,0]}}],"vtable":{"id":0,"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[{"Deduplicated":196}],"const_generics":[],"trait_refs":[{"Deduplicated":199}]}}},{"def_id":1,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["slice",0]},{"Ident":["iter",0]},{"Impl":{"Trait":1}}],"span":{"data":{"file_id":7,"beg":{"line":153,"col":8},"end":{"line":153,"col":45}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":1625}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[{"index":0,"name":"'a","mutability":"Unknown"}],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":7,"beg":{"line":153,"col":17},"end":{"line":153,"col":18}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"Deduplicated":5361},{"Deduplicated":5561}],"consts":[],"types":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"value":{"Deduplicated":1610},"implied_trait_refs":[]},"kind":{"TraitType":[2,0]}}],"methods":[{"params":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":13,"generics":{"regions":[{"Var":{"Bound":[1,0]}},{"Var":{"Bound":[0,0]}}],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[{"Deduplicated":1617}]}},"kind":{"TraitMethod":[2,0]}},null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"vtable":{"id":1,"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[{"Deduplicated":196}],"const_generics":[],"trait_refs":[{"Deduplicated":199}]}}},{"def_id":2,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["iter",0]},{"Ident":["traits",0]},{"Ident":["collect",0]},{"Impl":{"Trait":2}}],"span":{"data":{"file_id":11,"beg":{"line":317,"col":0},"end":{"line":317,"col":50}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":6,"generics":{"regions":[],"types":[{"Deduplicated":196}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[],"types":[{"index":0,"name":"I"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":11,"beg":{"line":317,"col":5},"end":{"line":317,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":11,"beg":{"line":317,"col":8},"end":{"line":317,"col":24}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"HashConsedValue":[6213,{"kind":{"ParentClause":[{"Deduplicated":199},0]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6214,{"kind":{"ParentClause":[{"Deduplicated":4562},1]},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":4568}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":199},{"Deduplicated":4562}],"consts":[],"types":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"value":{"Deduplicated":4568},"implied_trait_refs":[]},"kind":{"TraitType":[6,0]}},{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"value":{"Deduplicated":164},"implied_trait_refs":[]},"kind":{"TraitType":[6,1]}}],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":15,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[{"Deduplicated":1617},{"Deduplicated":4567}]}},"kind":{"TraitMethod":[6,0]}}],"vtable":{"id":2,"generics":{"regions":[],"types":[{"Deduplicated":196}],"const_generics":[],"trait_refs":[{"Deduplicated":199},{"Deduplicated":4562}]}}},{"def_id":3,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["result",0]},{"Impl":{"Trait":3}}],"span":{"data":{"file_id":3,"beg":{"line":2162,"col":0},"end":{"line":2162,"col":42}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":4574}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[],"types":[{"index":0,"name":"T"},{"index":1,"name":"E"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":3,"beg":{"line":2162,"col":5},"end":{"line":2162,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":3,"beg":{"line":2162,"col":8},"end":{"line":2162,"col":9}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"HashConsedValue":[6217,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[4575,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[{"Deduplicated":1617},{"Deduplicated":2664}]}}}]}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6219,{"kind":{"TraitImpl":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":196},{"Deduplicated":1589},{"Deduplicated":1589}],"const_generics":[],"trait_refs":[{"Deduplicated":199},{"Deduplicated":2275},{"Deduplicated":2275},{"HashConsedValue":[6218,{"kind":{"TraitImpl":{"id":5,"generics":{"regions":[],"types":[{"Deduplicated":1589}],"const_generics":[],"trait_refs":[{"Deduplicated":2275}]}}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":3,"generics":{"regions":[],"types":[{"Deduplicated":1585},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}]}]}}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":24,"generics":{"regions":[],"types":[{"Deduplicated":4575},{"Deduplicated":4580}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":199},{"Deduplicated":4596}],"consts":[],"types":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"value":{"Deduplicated":164},"implied_trait_refs":[]},"kind":{"TraitType":[11,0]}},{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"value":{"Deduplicated":4580},"implied_trait_refs":[]},"kind":{"TraitType":[11,1]}}],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":175,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[{"Deduplicated":1617},{"Deduplicated":2664}]}},"kind":{"TraitMethod":[11,0]}},{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":16,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[{"Deduplicated":1617},{"Deduplicated":2664}]}},"kind":{"TraitMethod":[11,1]}}],"vtable":null},{"def_id":4,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["result",0]},{"Impl":{"Trait":4}}],"span":{"data":{"file_id":3,"beg":{"line":2182,"col":0},"end":{"line":2183,"col":20}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":24,"generics":{"regions":[],"types":[{"Deduplicated":4598},{"Deduplicated":4594}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[],"types":[{"index":0,"name":"T"},{"index":1,"name":"E"},{"index":2,"name":"F"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":3,"beg":{"line":2182,"col":5},"end":{"line":2182,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":1,"span":{"data":{"file_id":3,"beg":{"line":2182,"col":8},"end":{"line":2182,"col":9}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":2,"span":{"data":{"file_id":3,"beg":{"line":2182,"col":11},"end":{"line":2182,"col":12}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":2626}],"const_generics":[],"trait_refs":[]}}}},{"clause_id":3,"span":{"data":{"file_id":3,"beg":{"line":2182,"col":14},"end":{"line":2182,"col":29}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":3,"generics":{"regions":[],"types":[{"Deduplicated":2626},{"Deduplicated":1585}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"HashConsedValue":[6220,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[4601,{"Adt":{"id":{"Adt":5},"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":2626}],"const_generics":[],"trait_refs":[{"Deduplicated":1617},{"HashConsedValue":[4600,{"kind":{"Clause":{"Bound":[1,2]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":2630}],"const_generics":[],"trait_refs":[]}}}}]}]}}}]}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":4596}],"consts":[],"types":[],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":17,"generics":{"regions":[],"types":[{"Deduplicated":164},{"Deduplicated":1585},{"Deduplicated":2626}],"const_generics":[],"trait_refs":[{"Deduplicated":1617},{"Deduplicated":2664},{"Deduplicated":4600},{"HashConsedValue":[6221,{"kind":{"Clause":{"Bound":[1,3]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":3,"generics":{"regions":[],"types":[{"Deduplicated":2630},{"Deduplicated":2246}],"const_generics":[],"trait_refs":[]}}}}]}]}},"kind":{"TraitMethod":[24,0]}}],"vtable":null},{"def_id":5,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["convert",0]},{"Impl":{"Trait":5}}],"span":{"data":{"file_id":10,"beg":{"line":785,"col":0},"end":{"line":785,"col":27}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":3,"generics":{"regions":[],"types":[{"Deduplicated":196},{"Deduplicated":196}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":10,"beg":{"line":785,"col":5},"end":{"line":785,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"Deduplicated":199},{"Deduplicated":199}],"consts":[],"types":[],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":177,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[{"Deduplicated":1617}]}},"kind":{"TraitMethod":[3,0]}}],"vtable":null},{"def_id":6,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_closure",0]},{"Impl":{"Trait":6}}],"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"source_text":"|| x + 1","attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":true,"opacity":"Transparent","lang_item":null},"impl_trait":{"id":4,"generics":{"regions":[],"types":[{"Deduplicated":4614},{"Deduplicated":221}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"HashConsedValue":[6225,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[5709,{"Adt":{"id":{"Adt":10},"generics":{"regions":[{"Var":{"Bound":[1,0]}}],"types":[],"const_generics":[],"trait_refs":[]}}}]}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[1972,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[1971,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":221}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":221}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6226,{"kind":{"BuiltinOrAuto":{"builtin_data":"Tuple","parent_trait_refs":[{"Deduplicated":1971}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":25,"generics":{"regions":[],"types":[{"Deduplicated":221}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":332}],"consts":[],"types":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"value":{"Deduplicated":207},"implied_trait_refs":[]},"kind":{"TraitType":[4,0]}}],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":178,"generics":{"regions":[{"Var":{"Bound":[1,0]}}],"types":[],"const_generics":[],"trait_refs":[]}},"kind":{"TraitMethod":[4,0]}}],"vtable":null},{"def_id":7,"item_meta":{"name":[{"Ident":["charon_corpus",0]},{"Ident":["bool_then_closure",0]},{"Ident":["closure",0]},{"Impl":{"Trait":7}}],"span":{"data":{"file_id":0,"beg":{"line":106,"col":11},"end":{"line":106,"col":19}},"generated_from_span":null},"source_text":"|| x + 1","attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":true,"opacity":"Transparent","lang_item":null},"impl_trait":{"id":5,"generics":{"regions":[],"types":[{"Deduplicated":4614}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[],"consts":[],"types":[],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":181,"generics":{"regions":[{"Var":{"Bound":[1,0]}}],"types":[],"const_generics":[],"trait_refs":[]}},"kind":{"TraitMethod":[5,0]}}],"vtable":null},{"def_id":8,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["option",0]},{"Impl":{"Trait":8}}],"span":{"data":{"file_id":6,"beg":{"line":2755,"col":0},"end":{"line":2755,"col":36}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":11,"generics":{"regions":[],"types":[{"Deduplicated":3361}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":6,"beg":{"line":2755,"col":5},"end":{"line":2755,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"HashConsedValue":[4634,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"HashConsedValue":[2395,{"Adt":{"id":{"Adt":7},"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[{"Deduplicated":1617}]}}}]}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6229,{"kind":{"TraitImpl":{"id":9,"generics":{"regions":[],"types":[{"Deduplicated":196}],"const_generics":[],"trait_refs":[{"Deduplicated":199}]}}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":24,"generics":{"regions":[],"types":[{"Deduplicated":2395},{"Deduplicated":1533}],"const_generics":[],"trait_refs":[]}}}}]},{"Deduplicated":199},{"Deduplicated":1535}],"consts":[],"types":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"value":{"Deduplicated":164},"implied_trait_refs":[]},"kind":{"TraitType":[11,0]}},{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"value":{"Deduplicated":1533},"implied_trait_refs":[]},"kind":{"TraitType":[11,1]}}],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":182,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[{"Deduplicated":1617}]}},"kind":{"TraitMethod":[11,0]}},{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":20,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[{"Deduplicated":1617}]}},"kind":{"TraitMethod":[11,1]}}],"vtable":null},{"def_id":9,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["option",0]},{"Impl":{"Trait":9}}],"span":{"data":{"file_id":6,"beg":{"line":2777,"col":0},"end":{"line":2777,"col":74}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":24,"generics":{"regions":[],"types":[{"Deduplicated":3361},{"Deduplicated":1533}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[],"types":[{"index":0,"name":"T"}],"const_generics":[],"trait_clauses":[{"clause_id":0,"span":{"data":{"file_id":6,"beg":{"line":2777,"col":5},"end":{"line":2777,"col":6}},"generated_from_span":null},"origin":"WhereClauseOnImpl","trait_":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[]}}}}],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"Deduplicated":4634},{"Deduplicated":1535}],"consts":[],"types":[],"methods":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":21,"generics":{"regions":[],"types":[{"Deduplicated":164}],"const_generics":[],"trait_refs":[{"Deduplicated":1617}]}},"kind":{"TraitMethod":[24,0]}}],"vtable":null},{"def_id":10,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["nonzero",0]},{"Impl":{"Trait":10}}],"span":{"data":{"file_id":19,"beg":{"line":62,"col":12},"end":{"line":62,"col":56}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":7,"generics":{"regions":[],"types":[{"Deduplicated":614}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"HashConsedValue":[1856,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[1855,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":614}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":614}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6231,{"kind":{"TraitImpl":{"id":11,"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":18,"generics":{"regions":[],"types":[{"Deduplicated":614}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6232,{"kind":{"TraitImpl":{"id":12,"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":26,"generics":{"regions":[],"types":[{"Deduplicated":614}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[4742,{"kind":{"BuiltinOrAuto":{"builtin_data":"Sized","parent_trait_refs":[{"HashConsedValue":[4741,{"kind":{"BuiltinOrAuto":{"builtin_data":"MetaSized","parent_trait_refs":[],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":1,"generics":{"regions":[],"types":[{"Deduplicated":4740}],"const_generics":[],"trait_refs":[]}}}}]}],"types":[]}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":0,"generics":{"regions":[],"types":[{"Deduplicated":4740}],"const_generics":[],"trait_refs":[]}}}}]},{"HashConsedValue":[6233,{"kind":{"TraitImpl":{"id":13,"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":18,"generics":{"regions":[],"types":[{"Deduplicated":4740}],"const_generics":[],"trait_refs":[]}}}}]}],"consts":[],"types":[{"params":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"value":{"Deduplicated":4740},"implied_trait_refs":[]},"kind":{"TraitType":[7,0]}}],"methods":[],"vtable":null},{"def_id":11,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["marker",0]},{"Impl":{"Trait":11}}],"span":{"data":{"file_id":1,"beg":{"line":60,"col":25},"end":{"line":60,"col":62}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":18,"generics":{"regions":[],"types":[{"Deduplicated":614}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"Deduplicated":1855},{"HashConsedValue":[6234,{"kind":{"TraitImpl":{"id":14,"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":8,"generics":{"regions":[],"types":[{"Deduplicated":614}],"const_generics":[],"trait_refs":[]}}}}]}],"consts":[],"types":[],"methods":[],"vtable":null},{"def_id":12,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["nonzero",0]},{"Impl":{"Trait":12}}],"span":{"data":{"file_id":19,"beg":{"line":55,"col":12},"end":{"line":55,"col":47}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":26,"generics":{"regions":[],"types":[{"Deduplicated":614}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"Deduplicated":1855}],"consts":[],"types":[],"methods":[],"vtable":{"id":3,"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}},{"def_id":13,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["niche_types",0]},{"Impl":{"Trait":13}}],"span":{"data":{"file_id":53,"beg":{"line":17,"col":24},"end":{"line":17,"col":28}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":18,"generics":{"regions":[],"types":[{"Deduplicated":4740}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"Deduplicated":4741},{"HashConsedValue":[6235,{"kind":{"TraitImpl":{"id":15,"generics":{"regions":[],"types":[],"const_generics":[],"trait_refs":[]}}},"trait_decl_ref":{"regions":[],"skip_binder":{"id":8,"generics":{"regions":[],"types":[{"Deduplicated":4740}],"const_generics":[],"trait_refs":[]}}}}]}],"consts":[],"types":[],"methods":[],"vtable":null},{"def_id":14,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["clone",0]},{"Ident":["impls",0]},{"Impl":{"Trait":14}}],"span":{"data":{"file_id":25,"beg":{"line":612,"col":16},"end":{"line":612,"col":39}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":8,"generics":{"regions":[],"types":[{"Deduplicated":614}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"Deduplicated":1856}],"consts":[],"types":[],"methods":[{"params":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":223,"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[],"const_generics":[],"trait_refs":[]}},"kind":{"TraitMethod":[8,0]}},null],"vtable":null},{"def_id":15,"item_meta":{"name":[{"Ident":["core",0]},{"Ident":["num",0]},{"Ident":["niche_types",0]},{"Impl":{"Trait":15}}],"span":{"data":{"file_id":53,"beg":{"line":17,"col":17},"end":{"line":17,"col":22}},"generated_from_span":null},"source_text":null,"attr_info":{"attributes":[],"inline":null,"rename":null,"public":false},"is_local":false,"opacity":"Foreign","lang_item":null},"impl_trait":{"id":8,"generics":{"regions":[],"types":[{"Deduplicated":4740}],"const_generics":[],"trait_refs":[]}},"generics":{"regions":[],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"implied_trait_refs":[{"Deduplicated":4742}],"consts":[],"types":[],"methods":[{"params":{"regions":[{"index":0,"name":null,"mutability":"Unknown"}],"types":[],"const_generics":[],"trait_clauses":[],"regions_outlive":[],"types_outlive":[],"trait_type_constraints":[]},"skip_binder":{"id":225,"generics":{"regions":[{"Var":{"Bound":[0,0]}}],"types":[],"const_generics":[],"trait_refs":[]}},"kind":{"TraitMethod":[8,0]}},null],"vtable":null}],"ordered_decls":[{"TraitDecl":{"NonRec":1}},{"TraitDecl":{"NonRec":0}},{"Type":{"NonRec":7}},{"TraitDecl":{"NonRec":5}},{"Fun":{"NonRec":19}},{"TraitDecl":{"NonRec":25}},{"TraitDecl":{"NonRec":4}},{"Fun":{"NonRec":18}},{"TraitDecl":{"NonRec":3}},{"Fun":{"NonRec":176}},{"Fun":{"NonRec":177}},{"TraitImpl":{"NonRec":5}},{"Type":{"NonRec":9}},{"TraitDecl":{"NonRec":2}},{"Fun":{"NonRec":15}},{"Fun":{"NonRec":29}},{"Fun":{"NonRec":180}},{"Type":{"NonRec":8}},{"Fun":{"NonRec":179}},{"Fun":{"NonRec":20}},{"Fun":{"NonRec":21}},{"Type":{"NonRec":5}},{"Fun":{"NonRec":16}},{"Fun":{"NonRec":17}},{"Type":{"NonRec":6}},{"Fun":{"NonRec":13}},{"TraitImpl":{"NonRec":1}},{"Fun":{"NonRec":12}},{"Fun":{"NonRec":14}},{"Type":{"NonRec":0}},{"Fun":{"NonRec":0}},{"Fun":{"NonRec":1}},{"Type":{"NonRec":1}},{"Fun":{"NonRec":2}},{"Type":{"NonRec":2}},{"Fun":{"NonRec":3}},{"Fun":{"NonRec":4}},{"Fun":{"NonRec":5}},{"Type":{"NonRec":10}},{"Fun":{"NonRec":181}},{"TraitImpl":{"NonRec":7}},{"Fun":{"NonRec":178}},{"TraitImpl":{"NonRec":6}},{"Fun":{"NonRec":6}},{"Fun":{"NonRec":7}},{"Fun":{"NonRec":8}},{"Fun":{"NonRec":9}},{"Type":{"NonRec":3}},{"Type":{"NonRec":4}},{"Fun":{"NonRec":10}},{"Fun":{"NonRec":11}}]},"has_errors":false} \ No newline at end of file diff --git a/majit/charon-corpus/src/lib.rs b/majit/charon-corpus/src/lib.rs index 9cf2851a8b1..41bae65e249 100644 --- a/majit/charon-corpus/src/lib.rs +++ b/majit/charon-corpus/src/lib.rs @@ -9,8 +9,7 @@ pub type PyResult = Result; -// --- 1. Straight-line --------------------------------------------------- - +// 1. Straight-line #[inline(never)] pub fn straight_line_add(a: i64, b: i64, c: i64) -> i64 { let s = a + b; @@ -18,8 +17,7 @@ pub fn straight_line_add(a: i64, b: i64, c: i64) -> i64 { t + c } -// --- 2. Branch + loop --------------------------------------------------- - +// 2. Branch and loop #[inline(never)] pub fn branch_loop_sum(slice: &[i64], threshold: i64) -> i64 { let mut acc: i64 = 0; @@ -33,8 +31,7 @@ pub fn branch_loop_sum(slice: &[i64], threshold: i64) -> i64 { acc } -// --- 3. Strategy dispatch (dict-strategy stand-in) ---------------------- - +// 3. Strategy dispatch (dict-strategy stand-in) pub enum Strategy { Empty, IntKeyed { len: usize }, @@ -50,8 +47,7 @@ pub fn strategy_len(s: &Strategy) -> usize { } } -// --- 4. Desugar mix: ?, match, iterator -------------------------------- - +// 4. Desugaring mix: `?`, `match`, and iteration pub enum Token { Add(i64), Sub(i64), @@ -81,7 +77,7 @@ pub fn desugar_mix(input: &[i64]) -> PyResult { Ok(acc) } -// --- 5. Tuple round-trip: construct a tuple, read .0/.1 in same fn ------ +// 5. Tuple round-trip: construct a tuple and read both fields // // Exercises `Rvalue::Aggregate` for a *non-Adt* (tuple) value paired // with `Field` projection reads of that same local. The lowering must @@ -94,8 +90,7 @@ pub fn tuple_roundtrip(a: i64, b: i64) -> i64 { pair.0 * pair.1 } -// --- 6. Closures -------------------------------------------------------- -// +// 6. Closures // `bool_then_closure` is the exact `core::bool::::then` census shape: // an opaque combinator taking a `FnOnce` closure that captures a value from // the enclosing scope. Charon extracts the closure's `call_once` body as a @@ -114,8 +109,7 @@ pub fn bool_then_some(c: bool, x: i64) -> Option { c.then_some(x + 1) } -// --- 7. Option question mark ------------------------------------------- -// +// 7. Option question mark // Exercises `Try::branch` on `Option`: `Some(v)` continues with `v`, while // `None` returns `None` normally from the enclosing Option-returning function. @@ -129,3 +123,35 @@ pub fn option_question_mark(keep: bool, value: i64, addend: i64) -> Option let v = option_source(keep, value)?; Some(v + addend) } + +// A host-registered callback table. + +/// The callback a host installs at run time. A bare `fn` pointer, so the set +/// of addresses that can reach a call through it is not recoverable from this +/// artifact — the shape used by host-settable callback hooks. +pub type HostCallback = fn(i64) -> i64; + +pub struct HostRegistry { + pub slot: HostCallback, + pub maybe_slot: Option, +} + +/// Call through the registered callback. `front::mir` lowers this to +/// `OpKind::IndirectCall { graphs: None }` — `indirect_call` with an +/// unknown PBC family, which `guess_call_kind` answers `residual` for +/// (`call.py:105`/`137`, `jtransform.py:410-412`). The `__dyn_call` +/// placeholder it used to reach is an unregistered synthetic path with no +/// continuation. +#[inline(never)] +pub fn host_registry_dispatch(reg: &HostRegistry, x: i64) -> i64 { + (reg.slot)(x) +} + +/// The one-hop `Option` spelling of the same shape. +#[inline(never)] +pub fn host_registry_dispatch_optional(reg: &HostRegistry, x: i64) -> i64 { + match reg.maybe_slot { + Some(f) => f(x), + None => 0, + } +} diff --git a/majit/examples/braininterp/src/jit_interp.rs b/majit/examples/braininterp/src/jit_interp.rs index 76fb3aa3d73..6789c5a84e3 100644 --- a/majit/examples/braininterp/src/jit_interp.rs +++ b/majit/examples/braininterp/src/jit_interp.rs @@ -1,15 +1,46 @@ -/// JIT-enabled Brainfuck interpreter using `#[jit_interp]` + `state_fields`. +/// JIT-enabled Brainfuck interpreter using `#[jit_interp]` and virtualizable +/// state fields. /// -/// Greens: [pc, program] -/// Reds: [pointer, tape] (tracked via state_fields) +/// The program counter and bytecode are green inputs. The pointer and tape are +/// red state, with tape cells represented as symbolic values while tracing. +/// A backward `]` branch identifies the loop header. +pub type Bytecode = [u8]; + +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; + +/// Hot loops majit compiled. /// -/// The tape is a state array: during tracing, tape cells are tracked as -/// symbolic OpRefs, eliminating memory loads/stores in compiled code. +/// Until this counter existed, this crate had **no** observable for the JIT +/// tier at all — no compile count, no `ops_after`, no degraded-arm read. Its +/// two tests compare JIT output against the interpreter, which an interpreter +/// answering alone satisfies perfectly. That is not hypothetical here: this +/// example ran its whole suite green while `Total ops recorded` went 9610 -> 0 +/// under an experiment that declared `greens` (see the header). +pub static COMPILES: AtomicUsize = AtomicUsize::new(0); + +/// Ops in the last compiled loop body after optimization. /// -/// Back-edge detection: `]` that jumps backward to matching `[` is the -/// loop back-edge. When it becomes hot, tracing starts at the `[` header. +/// `COMPILES > 0` is necessary but NOT sufficient: an entirely empty dispatch +/// still compiles a trace — one whose whole optimized body is `Finish()`, i.e. +/// `ops_after == 1`. A compile counter counts TRACES, not WORK. +/// +/// On THIS crate it does not separate a compiled loop from a compiled +/// nothing either, and it never could: see [`LAST_OPS_BEFORE`]. +pub static LAST_OPS_AFTER: AtomicUsize = AtomicUsize::new(0); -pub type Bytecode = [u8]; +/// Shape of the last compiled loop body — see [`majit_metainterp::LoopBodyShape`]. +/// +/// Held as two flags rather than the struct itself so the recording stays +/// lock-free on the compile path; the probe rebuilds the struct inside the same +/// lock window it reads the counters in, because this is as process-global as +/// they are. +pub static LAST_HAS_JUMP: AtomicBool = AtomicBool::new(false); +pub static LAST_ALWAYS_FAILS: AtomicBool = AtomicBool::new(false); + +/// Operations recorded before optimization for the most recently compiled +/// loop. Tests compare this with [`LAST_OPS_AFTER`] to distinguish a useful +/// loop body from a segmented trace-limit terminator. +pub static LAST_OPS_BEFORE: AtomicUsize = AtomicUsize::new(0); #[expect( dead_code, @@ -38,6 +69,7 @@ struct BfState { #[majit_macros::jit_interp( state = BfState, env = Bytecode, + greens = [pc, program], state_fields = { pointer: int, tape: [int; virt], @@ -46,6 +78,14 @@ struct BfState { fn mainloop(program: &Bytecode, threshold: u32) -> String { let mut driver: majit_metainterp::JitDriver = majit_metainterp::JitDriver::new(threshold); + driver.set_on_compile_loop(|_green_key, ops_before, ops_after, opcodes| { + COMPILES.fetch_add(1, Ordering::Relaxed); + LAST_OPS_BEFORE.store(ops_before, Ordering::Relaxed); + LAST_OPS_AFTER.store(ops_after, Ordering::Relaxed); + let shape = majit_metainterp::LoopBodyShape::of(opcodes); + LAST_HAS_JUMP.store(shape.has_jump, Ordering::Relaxed); + LAST_ALWAYS_FAILS.store(shape.has_always_fails, Ordering::Relaxed); + }); let mut pc: usize = 0; let _stacksize: i32 = 0; let mut state = BfState { @@ -244,22 +284,175 @@ impl JitBrainInterp { #[cfg(test)] mod tests { use super::*; + use crate::interp; + use majit_metainterp::{RefusalKind, refusal_kind}; + + static PROBE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + + /// Run `prog` and return only its output. + /// + /// Not an alias for `JitBrainInterp::new().run()`: the lock is the point. + /// Neither this nor [`compile_probe`] may call the other — the mutex is + /// plain, and re-entering it on one thread deadlocks. + fn run_jit(prog: &[u8]) -> String { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + JitBrainInterp::new().run(prog) + } + + /// Run `prog` with the tier counters reset, returning + /// `(output, compiles, ops_before, ops_after)`. + fn compile_probe( + prog: &[u8], + ) -> (String, usize, usize, usize, majit_metainterp::LoopBodyShape) { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + COMPILES.store(0, Ordering::Relaxed); + LAST_OPS_BEFORE.store(0, Ordering::Relaxed); + LAST_OPS_AFTER.store(0, Ordering::Relaxed); + LAST_HAS_JUMP.store(false, Ordering::Relaxed); + LAST_ALWAYS_FAILS.store(false, Ordering::Relaxed); + let got = JitBrainInterp::new().run(prog); + ( + got, + COMPILES.load(Ordering::Relaxed), + LAST_OPS_BEFORE.load(Ordering::Relaxed), + LAST_OPS_AFTER.load(Ordering::Relaxed), + majit_metainterp::LoopBodyShape { + has_jump: LAST_HAS_JUMP.load(Ordering::Relaxed), + has_always_fails: LAST_ALWAYS_FAILS.load(Ordering::Relaxed), + }, + ) + } + + fn multiply_gate_bf(a: u8, b: u8) -> Vec { + let mut prog = Vec::new(); + for _ in 0..a { + prog.push(b'+'); + } + prog.push(b'['); + prog.push(b'>'); + for _ in 0..b { + prog.push(b'+'); + } + prog.extend_from_slice(b"<-]"); + prog.extend_from_slice(b">."); + prog + } + + #[test] + fn jit_tier_aborts_in_the_loop_arm_stub() { + const A: u8 = 7; + const B: u8 = 3; + let prog = multiply_gate_bf(A, B); + let (got, compiles, ops_before, ops_after, _shape) = compile_probe(&prog); + + // The interpreter must still answer correctly whatever the tier does. + assert_eq!( + got.chars().count(), + 1, + "multiply({A}*{B}) printed {got:?}; the gate expects exactly one cell" + ); + let cell = got.chars().next().unwrap() as u32; + assert_eq!( + cell, + u32::from(A) * u32::from(B), + "multiply({A}*{B}) left {cell} in cell 1, so the loop ran {} passes \ + rather than {A}", + cell / u32::from(B) + ); + + let bf_arms: Vec<_> = majit_metainterp::degraded_dispatch_arms() + .into_iter() + .filter(|a| a.interp == "BfState") + .collect(); + let degraded: Vec<&str> = bf_arms.iter().map(|a| a.arm).collect(); + assert_eq!( + degraded, + vec!["b'['", "b']'"], + "degraded dispatch arms moved — every trace reaching one aborts" + ); + + // The CAUSE, which the name set above cannot see: an arm can keep + // degrading for an entirely different reason. See tl's `jit_tier_is_alive` + // for the measurement that motivated this — three A/B arms whose name + // sets and pass counts were identical while the mechanism changed. + let causes: Vec<(&str, RefusalKind)> = bf_arms + .iter() + .map(|a| (a.arm, refusal_kind(a.reason))) + .collect(); + assert_eq!( + causes, + [ + ("b'['", RefusalKind::GreenWriteback), + ("b']'", RefusalKind::UnlowerableStmt) + ], + "an arm still degrades, but a different mechanism is refusing it now" + ); + let bracket = bf_arms + .iter() + .find(|a| a.arm == "b'['") + .expect("b'[' is in the set asserted immediately above"); + let close = bf_arms + .iter() + .find(|a| a.arm == "b']'") + .expect("b']' is in the set asserted immediately above"); + assert!( + close.reason.contains("find_matching_open(program, pc)"), + "b']' refusal no longer names the unsupported matching-bracket call: {}", + close.reason + ); + assert!( + close.reason.contains("pc = target"), + "b']' refusal no longer preserves the trailing green writeback: {}", + close.reason + ); + assert!( + bracket.reason.contains("while need > 0"), + "b'[' refusal no longer names the unsupported scan loop: {}", + bracket.reason + ); + + // THE ASSERTION THAT DISCRIMINATES, and the one to replace rather than + // re-record. `b']'` is the loop arm, so every trace that reaches the + // loop aborts in its stub and nothing is ever compiled. + // + // IF THIS FAILS, the loop arm lowers and the tier has come alive. Do + // NOT re-record it: replace this whole gate with a liveness gate + // asserting `shape.closes_a_loop()`, which is the property a segmented + // or aborted trace can never satisfy. + assert_eq!( + compiles, 0, + "multiply({A}*{B}) compiled {compiles} loops. This crate's loop arm \ + `b']'` is an abort stub, so no trace should survive to be compiled. \ + A non-zero count means either the arm lowers now — replace this gate \ + with `assert!(shape.closes_a_loop())` — or another test entered the \ + JIT inside this probe's window without taking PROBE_LOCK" + ); + + // No assertion on `ops_after` or on the fold ratio, deliberately. With + // nothing compiled they are both 0, and an assertion over a value that + // is structurally 0 is the same species of oracle-that-cannot-fail this + // gate was rebuilt to stop making. `ops_before` is reported, not pinned, + // for the same reason. + println!( + "[tier-aborting] multiply({A}*{B}) = {cell} from the interpreter \ + alone, {compiles} loops compiled, {ops_before} ops recorded, \ + {ops_after} after opt, degraded {degraded:?}" + ); + } #[test] fn jit_matches_interp_hot_loops() { let code = b"+++++[->+<]>++++++++[<++++++++>-]<."; let expected = interp::interpret(code); - let mut jit = JitBrainInterp::new(); - let got = jit.run(code); + let got = run_jit(code); assert_eq!(got, "h"); assert_eq!(got, expected); } #[test] fn jit_no_loop() { - let mut jit = JitBrainInterp::new(); - let output = jit.run(b"+++"); + let output = run_jit(b"+++"); assert_eq!(output.len(), 0); } } diff --git a/majit/examples/braininterp/src/main.rs b/majit/examples/braininterp/src/main.rs index a07f8af2986..e9bb82343b7 100644 --- a/majit/examples/braininterp/src/main.rs +++ b/majit/examples/braininterp/src/main.rs @@ -58,7 +58,37 @@ fn multiply_bf(a: u8, b: u8) -> Vec { prog } +/// Checks the interpreted trip count for multiplication programs of different +/// parity. This gate validates arithmetic output; JIT liveness is covered by +/// the tests in `jit_interp`. +fn trip_count_gate() { + for a in [113u8, 114] { + let b = 2u8; + let mut prog = multiply_bf(a, b); + prog.extend_from_slice(b">."); + let mut jit = jit_interp::JitBrainInterp::new(); + let out = jit.run(&prog); + assert_eq!( + out.chars().count(), + 1, + "multiply({a}*{b}) printed {out:?}; the gate expects exactly one cell" + ); + let got = out.chars().next().unwrap() as u32; + let expected = u32::from(a) * u32::from(b); + assert_eq!( + got, + expected, + "multiply({a}*{b}) left {got} in cell 1, so the loop ran {} passes \ + rather than {a}", + got / u32::from(b) + ); + println!("[trip-count] multiply({a}*{b}) = {got} — exactly {a} passes"); + } +} + fn main() { + trip_count_gate(); + // Benchmark 1: countdown (hot tight loop) let n = 100_000; let countdown = countdown_bf(n); diff --git a/majit/examples/calc/src/main.rs b/majit/examples/calc/src/main.rs index 485fab9e3d6..4f216ee241f 100644 --- a/majit/examples/calc/src/main.rs +++ b/majit/examples/calc/src/main.rs @@ -1,10 +1,10 @@ -/// Toy calculator interpreter — the first test target for the majit JIT. +/// Toy calculator interpreter. This example currently runs without a JIT tier. /// /// Supports: -/// - Integer arithmetic (+, -, *, /, %) -/// - Variables (a-z, 26 registers) -/// - Comparison operators (<, <=, ==, !=, >, >=) -/// - While loops via conditional/unconditional jumps +/// - Integer arithmetic (`+`, `-`, `*`, `/`, `%`) +/// - Variables (`a`-`z`, 26 registers) +/// - Comparison operators (`<`, `<=`, `==`, `!=`, `>`, `>=`) +/// - While loops via conditional and unconditional jumps /// - A simple bytecode format pub mod bytecode; pub mod interp; diff --git a/majit/examples/cel/src/colscalar.rs b/majit/examples/cel/src/colscalar.rs index 5fc1d7d486a..d200a5d9215 100644 --- a/majit/examples/cel/src/colscalar.rs +++ b/majit/examples/cel/src/colscalar.rs @@ -37,7 +37,7 @@ struct VmState { fn mainloop(program: &Code, num_regs: usize, col_base: i64, threshold: u32) -> i64 { let mut driver: majit_metainterp::JitDriver = majit_metainterp::JitDriver::new(threshold); - driver.set_on_compile_loop(|_g, _a, _b| { + driver.set_on_compile_loop(|_g, _a, _b, _opcodes| { COMPILES.fetch_add(1, Ordering::Relaxed); }); driver.set_on_trace_abort(|_g, _p| { diff --git a/majit/examples/cel/src/column.rs b/majit/examples/cel/src/column.rs index 2bf27556451..0b59400d91b 100644 --- a/majit/examples/cel/src/column.rs +++ b/majit/examples/cel/src/column.rs @@ -1,28 +1,9 @@ -//! cell-majit de-risk — COLUMNAR read kill-test (issue #357). ZERO CEL text. +//! Column-read benchmark for the MAJIT CEL prototype. //! -//! The `probe`/`policy` machines proved majit beats a clean interpreter on batches -//! whose per-row inputs are SYNTHESIZED in-trace by an LCG. The open question -//! blocking a REAL cel batch evaluator: can a compiled majit trace read an -//! actual external i64 data column at a RED (data-dependent) row index and -//! still CLOSE THE LOOP / compile? -//! -//! ANSWER (this example): YES — via the `raw_load_i` intrinsic the wasmi majit -//! kernel already uses, provided the buffer BASE address is a loop-invariant -//! held in the virtualizable register file (`regs`), NOT a scalar `int` state -//! field. A scalar-field base forces the virtualizable frame to a real Ref and -//! trips `VirtualStatesCantMatch` (slot-0 Ref vs Int) at loop close; a register -//! base is a plain loop-invariant int input the optimizer hoists — exactly how -//! PyPy carries a loop-invariant `rffi` pointer. `majit_raw_load_i64(base, i*8)` -//! at the red row counter then reads `col[i]` and the loop compiles. -//! -//! Two programs over REAL i64 data columns (LCG-filled Vecs, read identically -//! by every tier so the 3-way equality gate is a genuine columnar-read -//! faithfulness check, not a synthesis): -//! * SUM — `acc += col[i]` (one column) -//! * POLICY — `acc += (col_a[i] >= col_b[i])` (two columns + compare) -//! Three measurements each: (a) JIT-on, (b) clean hand interp (honest -//! baseline, also reads raw memory), (c) JIT-off. Meaningful ratio = (b)/(a). -//! RELEASE ONLY (i64 wrap; 3-way equality gate). +//! The programs load real external `i64` columns at data-dependent row +//! indices through `raw_load_i`, then compare JIT, clean-interpreter, and +//! JIT-disabled execution. The fixtures cover a one-column sum and a +//! two-column comparison policy. use crate::common::*; use std::hint::black_box; @@ -52,7 +33,7 @@ struct VmState { fn mainloop(program: &Code, num_regs: usize, threshold: u32) -> i64 { let mut driver: majit_metainterp::JitDriver = majit_metainterp::JitDriver::new(threshold); - driver.set_on_compile_loop(|_green_key, _ops_before, _ops_after| { + driver.set_on_compile_loop(|_green_key, _ops_before, _ops_after, _opcodes| { COMPILES.fetch_add(1, Ordering::Relaxed); }); driver.set_on_trace_abort(|_green_key, _permanent| { diff --git a/majit/examples/cel/src/float.rs b/majit/examples/cel/src/float.rs index 1a2359b7039..d25132e3a0d 100644 --- a/majit/examples/cel/src/float.rs +++ b/majit/examples/cel/src/float.rs @@ -63,7 +63,7 @@ struct VmState { fn mainloop(program: &Code, base_a: i64, base_b: i64, n: i64, threshold: u32) -> f64 { let mut driver: majit_metainterp::JitDriver = majit_metainterp::JitDriver::new(threshold); - driver.set_on_compile_loop(|_green_key, _ops_before, _ops_after| { + driver.set_on_compile_loop(|_green_key, _ops_before, _ops_after, _opcodes| { COMPILES.fetch_add(1, Ordering::Relaxed); }); driver.set_on_trace_abort(|_green_key, _permanent| { @@ -192,11 +192,11 @@ fn clean_interp(program: &Code, base_a: i64, base_b: i64, n: i64) -> f64 { } } -// ── Float comparison policy machine ────────────────────────────────────── // Mirrors the real cel flagship shape: transient float column loads, a float // comparison MATERIALIZED to an int bool, and an int count — no float bank. // A separate `#[jit_interp]` machine lives in its own module (the macro emits // module-level items that would collide with the value machine above). +// Float comparison policy machine mod count { use super::*; @@ -227,7 +227,7 @@ mod count { fn mainloop_count(program: &Code, base_a: i64, base_b: i64, n: i64, threshold: u32) -> i64 { let mut driver: majit_metainterp::JitDriver = majit_metainterp::JitDriver::new(threshold); - driver.set_on_compile_loop(|_green_key, _ob, _oa| { + driver.set_on_compile_loop(|_green_key, _ob, _oa, _opcodes| { COMPILES.fetch_add(1, Ordering::Relaxed); }); driver.set_on_trace_abort(|_green_key, _permanent| { @@ -363,12 +363,12 @@ mod count { } } // mod count -// ── Two-bank general register machine (the cel VM shape for S3) ─────────── // The linchpin de-risk for a general float S3: a single state carrying BOTH // an int virt-array bank (addressing, bool results, count) AND a float // virt-array bank (column values), with a float compare crossing banks // (float operands -> int bool). If this compiles and traces bit-exact, cel's // VM can gain a float bank the general lowering can target. +// Two-bank general register machine mod twobank { use super::*; @@ -414,7 +414,7 @@ mod twobank { fn mainloop_twobank(program: &Code, threshold: u32) -> i64 { let mut driver: majit_metainterp::JitDriver = majit_metainterp::JitDriver::new(threshold); - driver.set_on_compile_loop(|_g, _a, _b| { + driver.set_on_compile_loop(|_g, _a, _b, _opcodes| { COMPILES.fetch_add(1, Ordering::Relaxed); }); driver.set_on_trace_abort(|_g, _p| { diff --git a/majit/examples/cel/src/policy.rs b/majit/examples/cel/src/policy.rs index fadb6e1597e..537ed0eded5 100644 --- a/majit/examples/cel/src/policy.rs +++ b/majit/examples/cel/src/policy.rs @@ -1,34 +1,9 @@ -//! cell-majit de-risk — CEL-subset policy prototype (issue #357). ZERO CEL text. +//! Slot-resolved policy benchmark for the MAJIT CEL prototype. //! -//! The kill-test (`probe`) proved majit beats a clean interpreter ~10x on a -//! straight-line ARITHMETIC batch. This prototype takes the next step: a -//! realistic POLICY PREDICATE with comparisons + boolean AND over slot-resolved -//! context fields, probing whether slot-resolution defeats the "member_access -//! wall" (cometkim measured member_access at only 1.1x because context hashmap -//! lookups dominate and the JIT cannot remove them). -//! -//! Modelled policy: `account.balance >= txn.amount && !account.frozen`. -//! SLOT-RESOLVED context = the fields are pre-resolved to int register slots -//! (no hashmap): the AST's `account.balance` compiles to a direct slot read. -//! Each row's slots are synthesized by an LCG (distinct, data-dependent inputs; -//! a counter-derived value would let the optimizer strength-reduce the loop). -//! The predicate is a branchless AND of two comparisons (`t0*t1`, both in -//! {0,1}); `acc` counts passing rows (a real "count rows matching policy"). -//! -//! Two data regimes probe the guard behavior meta-tracing has but a tree-walk -//! does not: -//! * SKEWED — comparisons against extreme constants (`bal >= i64::MIN+1`, -//! `draw >= i64::MAX`) so the predicate is ~always true yet the -//! comparisons are GENUINE, data-dependent, and NOT constant- -//! foldable. Guard-stable best case = realistic skewed policy -//! data (a policy usually returns the same verdict). -//! * UNBIASED — bal, amt, frozen independent full-range LCG draws, so each -//! comparison is ~50/50. Guards fail constantly: the -//! trace-tree/bridge stress case. -//! -//! Three measurements per regime, identical bytecode: -//! (a) majit JIT-on, (b) clean hand interp (honest baseline), (c) JIT-off. -//! Meaningful ratio = (b)/(a). RELEASE ONLY (i64 wrap; 3-way equality gate). +//! It models `account.balance >= txn.amount && !account.frozen` with fields +//! pre-resolved to register slots. Skewed and unbiased data sets exercise both +//! guard-stable and frequent-guard-failure behavior across JIT, clean +//! interpreter, and JIT-disabled execution. use crate::common::*; use std::hint::black_box; @@ -63,7 +38,7 @@ struct VmState { fn mainloop(program: &Code, num_regs: usize, threshold: u32) -> i64 { let mut driver: majit_metainterp::JitDriver = majit_metainterp::JitDriver::new(threshold); - driver.set_on_compile_loop(|_green_key, _ops_before, _ops_after| { + driver.set_on_compile_loop(|_green_key, _ops_before, _ops_after, _opcodes| { COMPILES.fetch_add(1, Ordering::Relaxed); }); let mut pc: usize = 0; diff --git a/majit/examples/cel/src/probe.rs b/majit/examples/cel/src/probe.rs index 251e64fa8fb..7c1d90fc525 100644 --- a/majit/examples/cel/src/probe.rs +++ b/majit/examples/cel/src/probe.rs @@ -1,29 +1,8 @@ -//! cell-majit de-risk kill-test (issue #357). Contains ZERO CEL code. +//! Arithmetic-loop benchmark for the MAJIT CEL prototype. //! -//! Question: does a compiled majit trace of a straight-line arithmetic body, -//! evaluated over a stream of distinct inputs, beat a CLEAN interpreter of the -//! same bytecode by the >=3x bar cometkim's cel-jit hit on simple_arithmetic -//! (8.3x)? If majit cannot clear 3x here — on its structural best case, a tight -//! arithmetic loop — applying it to cel-rust is dead before any CEL work. -//! -//! FAITHFULNESS: the "input" per row must be data-dependent, not derived from -//! the loop counter. A counter-driven polynomial lets the optimizer -//! strength-reduce / vectorize the whole loop into near-nothing (an early -//! version measured a bogus 406x this way). So each row's input `x` is advanced -//! by an LCG (`x = x*A + C`), an inherently serial recurrence the compiler -//! cannot fold or vectorize — modelling a real stream of distinct CEL inputs. -//! The body then computes `f(x) = ((x*3)+7)*(x-2)` and accumulates it. -//! -//! Three measurements, all on the identical bytecode: -//! (a) majit JIT-on — the compiled trace (small threshold). -//! (b) clean interp — a plain `match` loop, NO majit macro/instrumentation. -//! The honest baseline: a good non-JIT implementation. -//! (c) majit JIT-off — the same instrumented mainloop, compilation disabled -//! (`u32::MAX` threshold). Shows jit_merge_point cost. -//! The meaningful ratio is (b)/(a): clean interpreter vs compiled trace. -//! -//! RELEASE ONLY: `acc`/`x` wrap i64 and all three paths must wrap identically -//! (`+`/`*` in release), so the equality gate needs overflow checks off. +//! A serial LCG supplies data-dependent inputs so the loop cannot collapse to +//! a counter-derived closed form. The same bytecode runs through JIT, clean +//! interpreter, and JIT-disabled paths for result and timing comparisons. use crate::common::*; use std::hint::black_box; @@ -52,7 +31,7 @@ struct VmState { fn mainloop(program: &Code, num_regs: usize, threshold: u32) -> i64 { let mut driver: majit_metainterp::JitDriver = majit_metainterp::JitDriver::new(threshold); - driver.set_on_compile_loop(|_green_key, _ops_before, _ops_after| { + driver.set_on_compile_loop(|_green_key, _ops_before, _ops_after, _opcodes| { COMPILES.fetch_add(1, Ordering::Relaxed); }); let mut pc: usize = 0; diff --git a/majit/examples/dualtape/src/jit_interp.rs b/majit/examples/dualtape/src/jit_interp.rs index 472743c6ccd..58944d3a63c 100644 --- a/majit/examples/dualtape/src/jit_interp.rs +++ b/majit/examples/dualtape/src/jit_interp.rs @@ -1,17 +1,34 @@ -/// JIT-enabled two-tape interpreter using `#[jit_interp]` + two `[int; virt]` -/// state arrays. +/// JIT-enabled two-tape interpreter using `#[jit_interp]` and two +/// virtualizable integer arrays. /// -/// Greens: [pc, program] -/// Reds: [pa, a, pb, b] (two virtualizable tapes, tracked via state_fields) -/// -/// Both tapes are virtualized: during tracing their cells are tracked as -/// symbolic OpRefs and carried through the loop header as `virtualizable_boxes` -/// element shadows. The loop-close splices every tape's `_ptr`/`_len` -/// header first, then the concatenated element block, matching the trace-entry -/// Label for any number of arrays. - +/// The program counter and bytecode are green inputs. Each tape contributes +/// its pointer, length, and symbolic elements to the loop state. pub type Bytecode = [u8]; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; + +/// Hot loops majit compiled, and the optimized op count of the last one. +/// +/// `jit_tier_liveness_gate` reads both, and neither is a liveness verdict. +/// `COMPILES > 0` is necessary and nowhere near sufficient: an empty dispatch +/// still compiles a trace, just one whose whole body is `Finish()`. +/// +/// AND THE OP COUNT DOES NOT RESCUE IT, which this doc asserted until the +/// shape gate refuted it. The count is one integer over at least three states, +/// and it collides where it matters: `1` is an empty dispatch, `5` is a +/// segmented runaway, and a healthy body is some other number entirely — so +/// reading a count tells you a body's SIZE and never its SHAPE. That is why +/// `jit_tier_shape_gate` exists and why these two booleans do the grading. +pub static COMPILES: AtomicUsize = AtomicUsize::new(0); +pub static LAST_OPS_AFTER: AtomicUsize = AtomicUsize::new(0); +/// Shape of the last compiled body, for `jit_tier_shape_gate`. +/// +/// The op count cannot tell an empty dispatch from a segmented runaway (see +/// that gate). These two can: they are `LoopBodyShape`'s fields, recorded off +/// the same hook. +pub static LAST_HAS_JUMP: AtomicBool = AtomicBool::new(false); +pub static LAST_ALWAYS_FAILS: AtomicBool = AtomicBool::new(false); + const TAPE_SIZE: usize = 8; const DEFAULT_THRESHOLD: u32 = 3; @@ -22,9 +39,13 @@ struct DualState { b: Vec, } +/// Uses split dispatch to exercise two virtualizable tapes and scalar tape +/// pointers through lowering and bridge setup. #[majit_macros::jit_interp( state = DualState, env = Bytecode, + split_dispatch = true, + greens = [pc, program], state_fields = { pa: int, a: [int; virt], @@ -35,6 +56,13 @@ struct DualState { fn mainloop(program: &Bytecode, threshold: u32) -> i64 { let mut driver: majit_metainterp::JitDriver = majit_metainterp::JitDriver::new(threshold); + driver.set_on_compile_loop(|_green_key, _ops_before, ops_after, opcodes| { + COMPILES.fetch_add(1, Ordering::Relaxed); + LAST_OPS_AFTER.store(ops_after, Ordering::Relaxed); + let shape = majit_metainterp::LoopBodyShape::of(opcodes); + LAST_HAS_JUMP.store(shape.has_jump, Ordering::Relaxed); + LAST_ALWAYS_FAILS.store(shape.has_always_fails, Ordering::Relaxed); + }); let mut pc: usize = 0; let mut state = DualState { pa: 0, @@ -167,11 +195,25 @@ impl Default for JitDualInterp { mod tests { use super::*; use crate::interp; + use majit_metainterp::{RefusalKind, refusal_kind}; + + /// `COMPILES` / `LAST_OPS_AFTER` are process-global, so under the default + /// parallel libtest runner any concurrent `run` lands inside + /// `jit_tier_liveness_gate`'s store/run/load window and the gate reads + /// another test's compile. The lock therefore covers *every* test that can + /// compile, not just the gate. `run_locked` and the gate are the only ways + /// a test may enter `JitDualInterp::run`; neither may call the other, since + /// this is a plain mutex and re-entering it on one thread deadlocks. + static PROBE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + + fn run_locked(code: &[u8]) -> i64 { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + JitDualInterp::new().run(code) + } fn check(code: &[u8]) { let expected = interp::interpret(code); - let mut jit = JitDualInterp::new(); - let got = jit.run(code); + let got = run_locked(code); assert_eq!( got, expected, "JIT result {got} != interp {expected} for {code:?}" @@ -202,4 +244,196 @@ mod tests { fn jit_matches_interp_no_loop() { check(b"+++>+<*}*"); } + + #[test] + fn jit_matches_interp_zero_cell_bracket() { + let mut program = vec![b'+'; 1001]; + program.extend_from_slice(b"[->[*]<]"); + // Absolute pin BESIDE the differential one. `check()` alone compares two + // readers that would agree if both were wrong; 0 is the answer the + // program has, derived from the drain: `a` empties and nothing is ever + // banked into `b`. + assert_eq!( + interp::interpret(&program), + 0, + "fixture changed: the zero-cell scan no longer drains to 0, so the \ + differential below no longer covers the branch this test exists for" + ); + check(&program); + } + + /// A program whose answer IS the number of loop passes. + /// + /// `('+' * n) + "[-*}*{]"` charges `a[0]` to `n`; each pass spends one unit + /// of `a[0]` and banks one unit into each of `b[0]` and `b[1]`. `a` drains + /// to zero, so `mainloop`'s trailing `sum(a) + sum(b)` is `2 * passes` — an + /// exact count, not a modular one: the tape cells are `i64` and nothing + /// here folds them. + /// + /// The two banked cells are what make the count readable. A one-for-one + /// body (`[-*]`) answers `n` after `n` passes AND `n` after zero passes, + /// because the drain and the deposit cancel — the assertion would hold on a + /// loop that never ran. Depositing twice per pass separates them: `n` + /// passes answer `2n`, zero passes answer `n`, `n + 1` passes answer + /// `2n + 1`, `n - 1` passes answer `2n - 1`. + /// + /// `check()` above cannot settle this: it compares the JIT against + /// `interp::interpret`, and both run the same program, so a duplicated + /// iteration of the *compiled* loop is invisible to it. Only an ABSOLUTE + /// count catches that. + /// + /// Two trip counts of different parity, because a peeled first iteration + /// plus an even/odd body count is exactly the shape an off-by-one hides in. + #[test] + fn trip_count_gate() { + for n in [1001i64, 1002] { + let mut program = vec![b'+'; n as usize]; + program.extend_from_slice(b"[-*}*{]"); + let got = run_locked(&program); + assert_eq!( + got, + 2 * n, + "sum after ('+' * {n}) + \"[-*}}*{{]\" is {got}, so the dispatch loop \ + banked {got} units rather than {} — the pass count is off, which is \ + what a merge-point exit that re-runs or drops a pass the walk \ + already executed looks like", + 2 * n + ); + println!("[trip-count] ('+' * {n}) + \"[-*}}*{{]\" = {got} — exactly {n} passes"); + } + } + + #[test] + #[ignore = "enable after `jit_tier_shape_gate` proves the compiled body closes a loop"] + fn jit_tier_liveness_gate() { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + COMPILES.store(0, Ordering::Relaxed); + LAST_OPS_AFTER.store(0, Ordering::Relaxed); + + let mut program = vec![b'+'; 1001]; + program.extend_from_slice(b"[-*}*{]"); + let got = JitDualInterp::new().run(&program); + assert_eq!(got, 2002, "fixture changed; the liveness reading is moot"); + + let compiles = COMPILES.load(Ordering::Relaxed); + let ops_after = LAST_OPS_AFTER.load(Ordering::Relaxed); + println!("[tier] COMPILES={compiles} LAST_OPS_AFTER={ops_after}"); + + assert!( + compiles > 0, + "nothing compiled at all, so LAST_OPS_AFTER says nothing either" + ); + } + + /// WHICH dispatch arms lowered to an abort stub, and WHY. + /// + /// Split out of `jit_tier_liveness_gate` when that gate's `compiles > 0` + /// was suspended as non-discriminating. This is the half that still does + /// work, and it is the half that fired when the arm set moved. It is also + /// independent of the suspension's cause: `record_degraded_dispatch_arm` + /// runs when the dispatch JitCode is INSTALLED, not when a trace walks into + /// a stub, so these readings survive a tier that compiles nothing at all. + #[test] + fn jit_tier_degraded_arm_gate() { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + + let mut program = vec![b'+'; 1001]; + program.extend_from_slice(b"[-*}*{]"); + let got = JitDualInterp::new().run(&program); + assert_eq!(got, 2002, "fixture changed; the arm reading is moot"); + + // Equality over a named set, never `is_empty()`. Both arms are known + // abort stubs, so an emptiness assertion would be permanently red and + // discriminate nothing. Pinning the set catches a third degraded arm + // and also catches either of these two arms becoming lowerable. + // + // `b'['` JOINED THIS SET AS A REPAIR, NOT AS A REGRESSION. Before the + // three jit-state probes descended into `while`/`loop`, the scan in its + // body was scored inert and silently dropped, so the arm "lowered" with + // its zero-cell branch deleted. Refusing it is the honest outcome. Do + // NOT "fix" a future failure here by trimming the set back to + // `["b']'"]` — that spelling asserts the arm lowers, which is the state + // the deletion produced. + // + // If this fails because the set is now EMPTY, the lowering gaps may be + // FIXED. Do not re-record the vector: delete this pin and gate on the + // real body instead. + let mut degraded: Vec<_> = majit_metainterp::degraded_dispatch_arms() + .into_iter() + .filter(|a| a.interp == "DualState") + .collect(); + degraded.sort_unstable_by_key(|a| a.arm); + let degraded_arms: Vec<&str> = degraded.iter().map(|a| a.arm).collect(); + println!("[tier] degraded_arms={degraded_arms:?}"); + assert_eq!( + degraded_arms, + ["b'['", "b']'"], + "the degraded-arm set moved; every trace reaching an abort stub aborts" + ); + + let causes: Vec<(&str, RefusalKind)> = degraded + .iter() + .map(|a| (a.arm, refusal_kind(a.reason))) + .collect(); + assert_eq!( + causes, + [ + ("b'['", RefusalKind::GreenWriteback), + ("b']'", RefusalKind::UnlowerableStmt) + ], + "an arm still degrades, but a different mechanism is refusing it. \ + `RefusalKind::Unclassified` means majit grew a refusal family the \ + classifier does not know — add it in `majit-metainterp`, do not \ + re-record this pin" + ); + + let close = degraded + .iter() + .find(|a| a.arm == "b']'") + .expect("b']' is pinned in the set above"); + assert!( + close.reason.contains("find_matching_open(program, pc)"), + "b']' refusal no longer names the unsupported matching-bracket call: {}", + close.reason + ); + assert!( + close.reason.contains("pc = target"), + "b']' refusal no longer preserves the trailing green writeback: {}", + close.reason + ); + } + + #[test] + #[ignore = "enable when the back-edge arm lowers and trace inputs use separate namespaces"] + fn jit_tier_shape_gate() { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + COMPILES.store(0, Ordering::Relaxed); + LAST_OPS_AFTER.store(0, Ordering::Relaxed); + LAST_HAS_JUMP.store(false, Ordering::Relaxed); + LAST_ALWAYS_FAILS.store(false, Ordering::Relaxed); + + let mut program = vec![b'+'; 1001]; + program.extend_from_slice(b"[-*}*{]"); + let got = JitDualInterp::new().run(&program); + assert_eq!(got, 2002, "fixture changed; the liveness reading is moot"); + + let compiles = COMPILES.load(Ordering::Relaxed); + let ops_after = LAST_OPS_AFTER.load(Ordering::Relaxed); + let shape = majit_metainterp::LoopBodyShape { + has_jump: LAST_HAS_JUMP.load(Ordering::Relaxed), + has_always_fails: LAST_ALWAYS_FAILS.load(Ordering::Relaxed), + }; + // Both booleans in the failure output, not only the rendered reason. + // A single human-readable string is a lossy encoding of a compound + // state, and the loss is always the discrimination — which is the exact + // defect this gate replaces. + assert!( + shape.closes_a_loop(), + "compiled body does not close a loop: has_jump={} has_always_fails={} \ + ({}). COMPILES={compiles}, ops_after={ops_after}", + shape.has_jump, + shape.has_always_fails, + shape.why_not().unwrap_or("closes a loop"), + ); + } } diff --git a/majit/examples/i64env/src/main.rs b/majit/examples/i64env/src/main.rs index ff027e4af29..6316f703127 100644 --- a/majit/examples/i64env/src/main.rs +++ b/majit/examples/i64env/src/main.rs @@ -11,7 +11,7 @@ //! `[int]` array element is not restored on a CloseLoop guard deopt. (See the //! macro's loop-carried-plain-array diagnostic.) -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; /// The env: an i64-word bytecode stream. The whole point of this example is /// that the element is 8 bytes wide, not 1. @@ -27,8 +27,27 @@ const OP_RETURN: i64 = 3; // [RETURN, reg] /// Hot loops majit compiled — evidence the JIT tier traced + compiled. pub static COMPILES: AtomicUsize = AtomicUsize::new(0); +/// Ops in the last compiled loop body after optimization. `COMPILES` counts +/// TRACES, not WORK: an entirely empty dispatch still compiles one, whose whole +/// optimized body is `Finish()` — `ops_after == 1`. Pinning this value is what +/// separates a real body from that degenerate one. +pub static LAST_OPS_AFTER: AtomicUsize = AtomicUsize::new(0); + +/// Shape of the last compiled loop body — see [`majit_metainterp::LoopBodyShape`]. +/// +/// Held as two flags rather than the struct itself so the recording stays +/// lock-free on the compile path; the probe rebuilds the struct inside the same +/// lock window it reads the counters in, because this is as process-global as +/// they are. +pub static LAST_HAS_JUMP: AtomicBool = AtomicBool::new(false); +pub static LAST_ALWAYS_FAILS: AtomicBool = AtomicBool::new(false); + struct VmState { regs: Vec, + /// What `OP_RETURN` hands back. The `; state` merge point leaves the + /// dispatch loop through `break` before the in-arm `return` can run, so the + /// result has to arrive in `state` and be returned by the post-loop tail. + ret: i64, } #[majit_macros::jit_interp( @@ -37,18 +56,24 @@ struct VmState { greens = [pc, program], state_fields = { regs: [int; virt], + ret: int, }, )] fn mainloop(program: &Code, num_regs: usize, threshold: u32) -> i64 { let mut driver: majit_metainterp::JitDriver = majit_metainterp::JitDriver::new(threshold); - driver.set_on_compile_loop(|_green_key, _ops_before, _ops_after| { + driver.set_on_compile_loop(|_green_key, _ops_before, ops_after, opcodes| { COMPILES.fetch_add(1, Ordering::Relaxed); + LAST_OPS_AFTER.store(ops_after, Ordering::Relaxed); + let shape = majit_metainterp::LoopBodyShape::of(opcodes); + LAST_HAS_JUMP.store(shape.has_jump, Ordering::Relaxed); + LAST_ALWAYS_FAILS.store(shape.has_always_fails, Ordering::Relaxed); }); let mut pc: usize = 0; let _stacksize: i32 = 0; let mut state = VmState { regs: vec![0; num_regs], + ret: 0, }; { @@ -59,7 +84,10 @@ fn mainloop(program: &Code, num_regs: usize, threshold: u32) -> i64 { } loop { - jit_merge_point!(); + jit_merge_point!(driver, program, pc; state); + if pc == 0 { + can_enter_jit!(driver, pc, &mut state, program, || {}); + } let opcode = program[pc]; match opcode { OP_LOAD => { @@ -90,12 +118,13 @@ fn mainloop(program: &Code, num_regs: usize, threshold: u32) -> i64 { } OP_RETURN => { let r = program[pc + 1] as usize; - return state.regs[r]; + state.ret = state.regs[r]; + return state.ret; } - _ => break, + _ => panic!("fell off end of code"), } } - panic!("fell off end of code"); + state.ret } /// `r0` counts up by `step` while `n` > `r0`, exiting at `r0 >= n`. `step` and @@ -134,21 +163,309 @@ fn main() { println!("count to 1000 (step 1) = {result}"); } +/// Same computation as `count_program`, but the loop header is at **pc 0** — +/// the portal entry pc. `r0` is never reset, so the two `OP_LOAD`s re-running +/// each iteration are idempotent and the result is still `n`. +/// +/// pc 0: LOAD 1 -> r1 +/// pc 3: LOAD n -> r2 +/// pc 6: ADD r0 = r0 + r1 +/// pc 10: JIA if r2 > r0 goto 0 <- back edge to pc 0 +/// pc 14: RETURN r0 +/// Straight-line, no jump at all: control never returns to pc 0. +/// +/// pc 0: LOAD 111 -> r1 +/// pc 3: LOAD 222 -> r2 +/// pc 6: ADD r0 = r1 + r2 +/// pc 10: RETURN r0 (= 333) +#[cfg(test)] +fn straight_line_program() -> Vec { + vec![ + OP_LOAD, 111, 1, // r1 = 111 + OP_LOAD, 222, 2, // r2 = 222 + OP_ADD, 1, 2, 0, // r0 = r1 + r2 + OP_RETURN, 0, + ] +} + +/// The traced program's terminal is its FIRST instruction: the walk is armed +/// at pc 0 and the very next thing it executes is the interpreter's `return`. +/// Isolates the portal-return termination path from everything else. +#[cfg(test)] +fn return_immediately() -> Vec { + vec![OP_RETURN, 0] +} + +// One instruction per source line, operands beside their opcode. rustfmt would +// put each element on its own line, which detaches every `// r1 = 1` comment +// from the row it annotates. +#[rustfmt::skip] +#[cfg(test)] +fn loop_header_at_zero(n: i64) -> Vec { + vec![ + OP_LOAD, 1, 1, // r1 = 1 + OP_LOAD, n, 2, // r2 = n + OP_ADD, 0, 1, 0, // r0 = r0 + r1 + OP_JUMP_IF_ABOVE, 2, 0, 0, // if r2 > r0 goto 0 + OP_RETURN, 0, + ] +} + +/// Byte-identical to `loop_header_at_zero` except the back edge targets **pc 3** +/// instead of pc 0. The discriminant for "is pc 0 special, or just early?". +#[rustfmt::skip] +#[cfg(test)] +fn loop_header_at_three(n: i64) -> Vec { + vec![ + OP_LOAD, 1, 1, // r1 = 1 + OP_LOAD, n, 2, // r2 = n <- header + OP_ADD, 0, 1, 0, // r0 = r0 + r1 + OP_JUMP_IF_ABOVE, 2, 0, 3, // if r2 > r0 goto 3 + OP_RETURN, 0, + ] +} + #[cfg(test)] mod tests { use super::*; + static PROBE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + + /// Run `program` with the counter reset, returning `(result, compiles)` + /// observed under [`PROBE_LOCK`]. + fn probe(label: &str, program: &Code, num_regs: usize, threshold: u32) -> (i64, usize) { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + COMPILES.store(0, Ordering::Relaxed); + let r = run(program, num_regs, threshold); + let compiles = COMPILES.load(Ordering::Relaxed); + eprintln!("[probe] {label} result={r} COMPILES={compiles}"); + (r, compiles) + } + + /// How many unroll-free fallback compiles `jit_tier_is_alive` currently + /// sees — see the block at its assertion. `MC_DIAG` slot 73. + const EXPECT_UNPEELED: u64 = 1; + + /// Like [`probe`], but also reports `LAST_OPS_AFTER`, read *inside* the + /// lock. + /// + /// Reading it after the guard drops would be a race for exactly the reason + /// [`PROBE_LOCK`] exists: `LAST_OPS_AFTER` is process-global, so another + /// test's compile can land between `run` returning and the load, and the + /// gate would then pin a body it never ran. Both counters are reset inside + /// the lock too, so a zero here means *this* run compiled nothing rather + /// than inheriting a previous run's value. + /// + /// Must not be called from [`probe`] or [`run_locked`], nor they from it: + /// [`PROBE_LOCK`] is a plain mutex and re-entering it on one thread + /// deadlocks. + fn probe_with_ops( + label: &str, + program: &Code, + num_regs: usize, + threshold: u32, + ) -> (i64, usize, usize, u64, majit_metainterp::LoopBodyShape) { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + COMPILES.store(0, Ordering::Relaxed); + LAST_OPS_AFTER.store(0, Ordering::Relaxed); + LAST_HAS_JUMP.store(false, Ordering::Relaxed); + LAST_ALWAYS_FAILS.store(false, Ordering::Relaxed); + // Slot 72 is process-global and CUMULATIVE, so it is read as a delta + // across this run and inside `PROBE_LOCK` — the same window the other + // counters are reset in. An absolute read would carry every other + // test's compiles, which is the defect `PROBE_LOCK` exists to prevent. + let unpeeled_before = majit_metainterp::mc_diag(73); + let r = run(program, num_regs, threshold); + let unpeeled = majit_metainterp::mc_diag(73) - unpeeled_before; + let compiles = COMPILES.load(Ordering::Relaxed); + let ops_after = LAST_OPS_AFTER.load(Ordering::Relaxed); + eprintln!( + "[probe] {label} result={r} COMPILES={compiles} OPS_AFTER={ops_after} \ + UNPEELED={unpeeled}" + ); + ( + r, + compiles, + ops_after, + unpeeled, + majit_metainterp::LoopBodyShape { + has_jump: LAST_HAS_JUMP.load(Ordering::Relaxed), + has_always_fails: LAST_ALWAYS_FAILS.load(Ordering::Relaxed), + }, + ) + } + + /// For tests that assert only on the result: they still compile, so they + /// must not run inside a probe's window. See [`PROBE_LOCK`]. + fn run_locked(program: &Code, num_regs: usize, threshold: u32) -> i64 { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + run(program, num_regs, threshold) + } + + /// Loop header at pc 0 (the portal entry pc). Same arithmetic as the + /// pc-3 and pc-9 variants; only the back-edge target differs. + #[test] + fn header_at_zero() { + let (r, _) = probe("header_at_zero", &loop_header_at_zero(1000), 3, 3); + assert_eq!(r, 1000); + } + + /// Loop header at pc 3. + #[test] + fn header_at_three() { + let (r, _) = probe("header_at_three", &loop_header_at_three(1000), 3, 3); + assert_eq!(r, 1000); + } + + /// Portal-entry door, straight-line program: the walk is armed at pc 0 + /// (threshold 1 trips on the single entry-door hit) and control never + /// returns to pc 0. + #[test] + fn entry_door_straight_line() { + let (r, compiles) = probe("entry_door_straight_line", &straight_line_program(), 3, 1); + assert_eq!(r, 333); + assert!( + compiles >= 1, + "the straight-line entry walk must reach its return terminator and mint a procedure" + ); + } + + /// Same entry door, but the program loops back to pc 0 — control does + /// return to the armed pc. Isolates "never returns" from "pc 0". + #[test] + fn entry_door_loops_back_to_zero() { + let (r, compiles) = probe( + "entry_door_loops_back_to_zero", + &loop_header_at_zero(1000), + 3, + 1, + ); + assert_eq!(r, 1000); + // The paired control for the test above: same door, same armed pc, + // but control does return there, so this one closed a loop even + // before in-arm returns lowered. It must keep doing so. + assert!( + compiles >= 1, + "the looping entry walk must still close its loop and compile" + ); + } + + /// Walk armed at pc 0; the first instruction executed is `OP_RETURN`. + #[test] + fn entry_door_return_immediately() { + let (r, _) = probe("entry_door_return_immediately", &return_immediately(), 3, 1); + assert_eq!(r, 0); + } + + #[test] + fn jit_tier_is_alive() { + let (got, compiles, ops_after, unpeeled, shape) = + probe_with_ops("jit_tier_is_alive", &count_program(1000, 1), 3, 3); + assert_eq!( + unpeeled, EXPECT_UNPEELED, + "expected {EXPECT_UNPEELED} unroll-free fallback compile(s) out of \ + {compiles}, saw {unpeeled}" + ); + // The body actually closes a loop — see `LoopBodyShape`. A compile + // count and an op count together still accept a body that bails out on + // its first pass; this is the term that does not. Sound HERE because + // this fixture loops: on a straight-line subject a `Jump`-less body is + // the right answer, not a defect. + assert!( + shape.closes_a_loop(), + "compiled {ops_after} ops but the body {} ({shape:?})", + shape.why_not().unwrap_or("closes a loop") + ); + assert_eq!( + got, 1000, + "count_program(1000, 1) counts r0 up by 1 per pass while r2 > r0, so \ + the answer IS the trip count" + ); + + let degraded: Vec<&str> = majit_metainterp::degraded_dispatch_arms() + .iter() + .filter(|a| a.interp == "VmState") + .map(|a| a.arm) + .collect(); + assert_eq!( + degraded, + Vec::<&str>::new(), + "dispatch arms degraded to abort stubs: {degraded:?} — an equality \ + rather than is_empty() so this also catches a name disappearing, \ + which is what emptying the dispatch looks like" + ); + + assert!( + compiles >= 1, + "compiled {compiles} loops — the JIT tier is inert and the \ + interpreter is answering alone, which every other assertion in this \ + file would still pass" + ); + + assert_eq!( + ops_after, 4, + "compiled body is {ops_after} ops, not the pinned 4 — 1 is a bare \ + `Finish()`, i.e. a dispatch that lowered nothing at all, and 9 is \ + the peeled shape the legacy bare merge point produced" + ); + println!( + "[tier-alive] count_program(1000, 1) = {got}, compiled {compiles} loop(s) of {ops_after} ops, 0 degraded arms" + ); + } + + #[test] + fn straight_line_program_folds_to_one_finish() { + let (got, compiles, ops_after, _unpeeled, shape) = + probe_with_ops("straight_line_body", &straight_line_program(), 3, 1); + // The counterpart to `jit_tier_is_alive`'s shape assertion, and the + // reason that one has to be conditional: same crate, same probe, + // opposite shape. This subject has no loop, so its body correctly + // carries no back edge — `closes_a_loop()` is FALSE here and that is + // health. The pair is what shows the predicate discriminating rather + // than merely passing everywhere. + assert_eq!( + shape, + majit_metainterp::LoopBodyShape::default(), + "a straight-line body should carry neither a back edge nor an \ + always-failing guard" + ); + // Load-bearing: this is what separates `Finish(333)` from a `Finish(0)` + // that lowered nothing. See the doc comment. + assert_eq!(got, 333, "straight_line_program must answer 111 + 222"); + assert!( + compiles >= 1, + "the straight-line walk must mint a procedure" + ); + assert_eq!( + ops_after, 1, + "straight-line body is {ops_after} ops, not the pinned 1 — 2 was \ + the pre-fix shape, where the add could not fold because the \ + walk headed past its arming pc" + ); + println!("[straight-line] 333 from a {ops_after}-op body with no back edge"); + } + + /// Loop header at pc 9 — the pre-existing shape, as a control. + #[test] + fn header_at_nine() { + let (r, _) = probe("header_at_nine", &count_program(1000, 1), 3, 3); + assert_eq!(r, 1000); + } + /// The env element is 8 bytes wide and the immediate `n = 1000` does not /// fit a byte. A byte-stride descr would read the wrong word and never /// reach 1000. #[test] fn i64_env_reads_wide_immediates() { - COMPILES.store(0, Ordering::Relaxed); - let program = count_program(1000, 1); - let result = run(&program, 3, 3); + let (result, compiles) = probe( + "i64_env_reads_wide_immediates", + &count_program(1000, 1), + 3, + 3, + ); assert_eq!(result, 1000, "i64-env loop must compute 1000"); assert!( - COMPILES.load(Ordering::Relaxed) >= 1, + compiles >= 1, "majit should have compiled the hot loop at least once" ); } @@ -158,7 +475,7 @@ mod tests { #[test] fn i64_env_varies_n() { for n in [300_i64, 500, 1000, 4096, 100_000] { - let r = run(&count_program(n, 1), 3, 3); + let r = run_locked(&count_program(n, 1), 3, 3); assert_eq!(r, n, "count to {n} mismatch"); } } @@ -167,7 +484,7 @@ mod tests { /// too: counting by 7 up to a multiple of 7 lands exactly on `n`. #[test] fn i64_env_wide_step() { - let r = run(&count_program(7 * 300, 7), 3, 3); + let r = run_locked(&count_program(7 * 300, 7), 3, 3); assert_eq!(r, 7 * 300); } } diff --git a/majit/examples/spcount/src/main.rs b/majit/examples/spcount/src/main.rs index cf65ea0b8b7..c16a9d396d2 100644 --- a/majit/examples/spcount/src/main.rs +++ b/majit/examples/spcount/src/main.rs @@ -1,45 +1,14 @@ //! Single-pass whole-circuit-close regression example. //! -//! Every other `majit/examples` interpreter uses the plain -//! `jit_merge_point!()` marker. This one uses the `; state` selector form -//! (`jit_merge_point!(driver, program, pc; state)`) — the single-pass -//! whole-circuit-close path. When the walk closes a loop, the merge-point hook -//! writes the walk-final scalar state field(s) back into native `state`, -//! re-derives storage-backed caches via `recover` (a no-op here — no -//! storage-derived caches), then direct-enters the compiled loop rather than -//! replaying the walked body. That path is the sole/default trace-close path -//! after issue #344 Phase B, yet inside this repository only aheui — a separate -//! git repo, outside CI — exercised it. This crate closes that coverage gap. -//! -//! The interpreter is a minimal stack machine, structurally the tl example's -//! virtualizable-stack shape (`state_fields = { stackpos: int, stack: [int; -//! virt] }`): a scalar `stackpos` the loop mutates plus a loop-carried virt -//! array. That is the shape whose single-pass close is known to converge (a -//! scalar-only interpreter with a residual adjacent to a tight back-edge -//! collapses its per-opcode merge points and never reaches the loop header). -//! -//! ## Reds regime -//! -//! The loop-carried reds are recovered through the state-field side channel: -//! `stackpos` is written back by `writeback_scalar_state_fields_from_sym` and -//! the virt `stack` lives on the heap the compiled code mutates in place. This -//! is the EMPTY-reds case the driver publishes at CloseLoop -//! (`single_pass_outcome = Some((pc, Vec::new()))` in `jitdriver.rs`, where the -//! empty reds vector is documented as INTENTIONALLY empty). The macro's -//! non-empty-reds transfer branch (`if !__sp_reds.is_empty()` — a -//! `restore_values` into native state) therefore stays UNEXERCISED: the driver -//! hard-codes empty reds at the close, so no example crate can drive that -//! branch without changing the driver/macro. A genuinely register-resident, -//! non-storage-recoverable loop-carried red is not expressible through this -//! macro surface — every loop-carried value must land in a declared state -//! field, which the state-field write-back (not `__sp_reds`) transfers. Adding -//! coverage for the non-empty-reds branch is a driver/macro change, out of -//! scope for an example crate. +//! This stack machine uses the `jit_merge_point!(...; state)` form. On loop +//! close, the walk-final scalar state is written back and the compiled loop is +//! entered directly. The `TOUCH` residual lets tests detect accidental +//! execution by both the trace walk and the native interpreter. /// Bytecode stream. Byte-wide opcodes/operands, same shape as the tl env. pub type Bytecode = [u8]; -// ── Opcodes ── +// Opcodes const PUSH: u8 = 2; // [PUSH, imm]: push a signed-byte immediate const POP: u8 = 3; // pop top const SWAP: u8 = 4; // swap the top two @@ -51,7 +20,7 @@ const RETURN: u8 = 21; // return top const PUSHARG: u8 = 22; // push the input argument const TOUCH: u8 = 30; // residual: side-effecting, result-neutral stack touch -// ── Countable side-effecting residual ── +// Countable side-effecting residual /// Number of `touch` invocations, observed by the tests. A walk-vs-native /// double-execution of the residual during single-pass tracing would inflate @@ -66,6 +35,25 @@ static TOUCH_CALLS: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU3 /// loudly instead of passing vacuously. Mirrors tl's `SPIKE_COMPILES`. static SPCOUNT_COMPILES: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0); +/// Optimized op count of the most recently compiled loop body. +/// +/// A compile *count* says a trace closed; it does not say the trace did any +/// work. An entirely empty dispatch still compiles one trace whose whole +/// optimized body is `Finish()` — `ops_after == 1` — and that degenerate body +/// satisfies every inequality a real loop satisfies. `SPCOUNT_COMPILES` alone +/// therefore cannot tell a live tier from a hollow one, which is why the third +/// callback parameter is captured here instead of discarded. +static SPCOUNT_LAST_OPS_AFTER: core::sync::atomic::AtomicUsize = + core::sync::atomic::AtomicUsize::new(0); + +/// Loop-shape flags recorded with [`SPCOUNT_LAST_OPS_AFTER`]. They distinguish +/// a body that reaches its back edge from an empty or always-failing body +/// without relying only on a measured operation count. +static SPCOUNT_LAST_HAS_JUMP: core::sync::atomic::AtomicBool = + core::sync::atomic::AtomicBool::new(false); +static SPCOUNT_LAST_ALWAYS_FAILS: core::sync::atomic::AtomicBool = + core::sync::atomic::AtomicBool::new(false); + /// Side-effecting residual, `@dont_look_inside` — the JIT does not trace into /// it; it emits a residual CALL. `#[dont_look_inside]` is non-elidable and may /// raise, so the optimizer keeps the call. It is result-neutral (its only @@ -108,8 +96,19 @@ pub fn mainloop(program: &Bytecode, inputarg: i64, threshold: u32) -> i64 { majit_metainterp::JitDriver::new(threshold); // Count compiled loops so the residual-count test can assert the // single-pass close actually ran. Mirrors tl's SPIKE_COMPILES hook. - driver.set_on_compile_loop(|_gk, _b, _a| { + // + // The third parameter is the optimized op count of the closed body. It is + // captured rather than discarded because the count alone cannot separate a + // real compiled loop from a bare `Finish()` — see SPCOUNT_LAST_OPS_AFTER. + driver.set_on_compile_loop(|_gk, _before, after, opcodes| { SPCOUNT_COMPILES.fetch_add(1, core::sync::atomic::Ordering::Relaxed); + SPCOUNT_LAST_OPS_AFTER.store(after, core::sync::atomic::Ordering::Relaxed); + let shape = majit_metainterp::LoopBodyShape::of(opcodes); + SPCOUNT_LAST_HAS_JUMP.store(shape.has_jump, core::sync::atomic::Ordering::Relaxed); + SPCOUNT_LAST_ALWAYS_FAILS.store( + shape.has_always_fails, + core::sync::atomic::Ordering::Relaxed, + ); }); let mut pc: usize = 0; let stacksize: i32 = 0; @@ -338,6 +337,75 @@ fn touch_loop_program() -> Vec { ] } +/// Two loops in ONE program, at two different headers, so one run holds two +/// distinct green keys. `greens = [pc, program]` keys a merge point by the +/// header it was reached at, so the inner header @11 and the outer header @3 +/// are different keys with the same `program` — which is the shape no other +/// example crate has. spcount already compiled two keys, but they came from two +/// different *programs* in two different tests; nothing could ever be already +/// compiled while another loop was being traced. +/// +/// The inner loop runs `K` times per outer iteration, so at `threshold = 3` it +/// reaches its trip count first and compiles while the outer loop is still +/// cold. When the outer back edge later arms and its walk reaches the inner +/// header, `has_compiled_targets_fn` is true there — the `already_compiled_here` +/// branch (`dispatch.rs:5798`) publishes `close_jump_into_key`, which is the +/// cross-loop close. +/// +/// The inner loop is a pure spin (it only decrements its own counter), so the +/// result is the same `N*(N+1)/2` the sum fixture computes and can be checked +/// against [`interp`] rather than against a hand-computed constant. +/// +/// 0: PUSH 0 [acc] +/// 2: PUSHARG [acc, i] +/// 3: PICK 0 OUTER header [acc, i, i] +/// 5: BR_COND +2 -> outer body @9 [acc, i] +/// 7: POP [acc] +/// 8: RETURN +/// 9: PUSH K outer body: j = K [acc, i, j] +/// 11: PICK 0 INNER header [acc, i, j, j] +/// 13: BR_COND +5 -> inner body @20 [acc, i, j] +/// 15: POP inner exit: drop j==0 [acc, i] +/// 16: PUSH 1 [acc, i, 1] +/// 18: BR_COND +7 -> outer tail @27 [acc, i] +/// 20: PUSH 1 SUB inner body: j -= 1 [acc, i, j-1] +/// 23: PUSH 1 +/// 25: BR_COND -16 -> INNER header @11 +/// 27: SWAP outer tail [i, acc] +/// 28: PICK 1 [i, acc, i] +/// 30: ADD [i, acc+i] +/// 31: SWAP [acc+i, i] +/// 32: PUSH 1 SUB i -= 1 [acc, i-1] +/// 35: PUSH 1 +/// 37: BR_COND -36 -> OUTER header @3 +#[cfg(test)] +fn nested_loop_program(k: i8) -> Vec { + vec![ + PUSH, 0, // 0 + PUSHARG, // 2 + PICK, 0, // 3 outer header + BR_COND, 2, // 5 offset @6 -> 6 + 2 + 1 = 9 + POP, // 7 + RETURN, // 8 + PUSH, k as u8, // 9 j = K + PICK, 0, // 11 inner header + BR_COND, 5, // 13 offset @14 -> 14 + 5 + 1 = 20 + POP, // 15 inner exit + PUSH, 1, // 16 + BR_COND, 7, // 18 offset @19 -> 19 + 7 + 1 = 27 + PUSH, 1, SUB, // 20 inner body + PUSH, 1, // 23 + BR_COND, 240, // 25 offset @26 -> 26 + (-16) + 1 = 11 + SWAP, // 27 outer tail + PICK, 1, // 28 + ADD, // 30 + SWAP, // 31 + PUSH, 1, SUB, // 32 + PUSH, 1, // 35 + BR_COND, 220, // 37 offset @38 -> 38 + (-36) + 1 = 3 + ] +} + fn main() { let n: i64 = std::env::args() .nth(1) @@ -352,6 +420,58 @@ fn main() { mod tests { use super::*; use core::sync::atomic::Ordering; + use majit_metainterp::{RefusalKind, refusal_kind}; + + /// Serializes the tier probe against every other test that runs the JIT. + /// + /// `SPCOUNT_COMPILES` and `SPCOUNT_LAST_OPS_AFTER` are process-wide, and + /// `libtest` runs these tests on parallel threads. Both counters must be + /// zeroed and read inside one window or a concurrent test's compile lands + /// between the reset and the load. `SPCOUNT_LAST_OPS_AFTER` needs this even + /// more than the count does: it is last-writer-wins, so without the lock it + /// can report another fixture's body size, which is a *plausible* number and + /// therefore will not look wrong. + /// + /// The lock only works if EVERY test that enters the JIT takes it, not + /// just the probe — a one-sided lock serializes nothing. This was not + /// hypothetical: with `jit_output_matches_interp` still calling `mainloop` + /// directly, the probe read `2 compile(s)` for a fixture that compiles + /// exactly one, and could have pinned that test's 13-op body instead of this + /// one's 17. Hence [`run_jit`]. Neither helper may call the other — a plain + /// mutex re-entered on one thread deadlocks. + static PROBE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + + /// For tests that assert only on the result. They still compile, so they + /// must not run inside the probe's window. See [`PROBE_LOCK`]. + fn run_jit(program: &[u8], inputarg: i64) -> i64 { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + mainloop(program, inputarg, 3) + } + + fn compile_probe( + program: &[u8], + inputarg: i64, + ) -> (i64, u32, u32, usize, majit_metainterp::LoopBodyShape) { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + TOUCH_CALLS.store(0, Ordering::Relaxed); + SPCOUNT_COMPILES.store(0, Ordering::Relaxed); + SPCOUNT_LAST_OPS_AFTER.store(0, Ordering::Relaxed); + // Reset to the values `closes_a_loop()` rejects, so a hook that never + // fires fails the shape assertion instead of passing it untouched. + SPCOUNT_LAST_HAS_JUMP.store(false, Ordering::Relaxed); + SPCOUNT_LAST_ALWAYS_FAILS.store(false, Ordering::Relaxed); + let got = mainloop(program, inputarg, 3); + ( + got, + TOUCH_CALLS.load(Ordering::Relaxed), + SPCOUNT_COMPILES.load(Ordering::Relaxed), + SPCOUNT_LAST_OPS_AFTER.load(Ordering::Relaxed), + majit_metainterp::LoopBodyShape { + has_jump: SPCOUNT_LAST_HAS_JUMP.load(Ordering::Relaxed), + has_always_fails: SPCOUNT_LAST_ALWAYS_FAILS.load(Ordering::Relaxed), + }, + ) + } /// The plain interpreter and the single-pass JIT mainloop must compute the /// identical result across a range of inputs (each exercises the CloseLoop @@ -361,53 +481,86 @@ mod tests { let program = sum_program(); for n in [1_i64, 2, 3, 5, 10, 20, 50, 100, 200] { let expected = interp(&program, n); - let got = mainloop(&program, n, 3); + let got = run_jit(&program, n); assert_eq!(got, n * (n + 1) / 2, "sum({n}) closed form"); assert_eq!(got, expected, "JIT diverged from interp for n={n}"); } } - /// THE regression this crate exists to catch. The residual `touch` fires - /// exactly once per loop iteration, and the loop runs a known number of - /// times. Under single-pass tracing (the walk is the sole executor) the - /// residual must run exactly the interpreter's count — a walk-vs-native - /// double-execution during the trace-then-close would inflate it. - /// - /// The `SPCOUNT_COMPILES >= 1` canary below asserts the single-pass close - /// actually ran, so the residual count is not measured on a merely- - /// interpreted loop. This is the load-bearing guard: before the virt-array - /// write-back fix, the compiled loop resumed from the trace-start virt array - /// (the walk-final loop counter was never transferred into native `state`) - /// and re-executed the peeled iteration, firing `touch` N+1 times. The - /// write-back (`writeback_virt_array_state_fields`) makes native `state` - /// hold the walk-final (post-peel) counter before re-entry, so the compiled - /// loop advances instead of re-running the traced iteration — exactly N - /// firings. A residual whose argument is a virt-array base pointer instead - /// degrades to a `BC_ABORT` stub (no `#[jit_interp]` lowering) and never - /// compiles; using the scalar `stackpos` keeps the loop compilable so this - /// canary exercises the real single-pass close. #[test] fn jit_residual_not_double_executed() { let program = touch_loop_program(); let n: i64 = 50; let expected = interp(&program, n); - TOUCH_CALLS.store(0, Ordering::Relaxed); - SPCOUNT_COMPILES.store(0, Ordering::Relaxed); - let got = mainloop(&program, n, 3); - let jit_touches = TOUCH_CALLS.load(Ordering::Relaxed); + let (got, jit_touches, compiles, ops_after, shape) = compile_probe(&program, n); // The residual-count canary is only meaningful if a trace actually // compiled and closed via the `; state` single-pass path. Without this // guard the loop could merely interpret (no tracing, or an abort before // the close), run `touch` exactly n× anyway, and leave the count green - // vacuously. A `>= 1` lower bound is robust under parallel tests: only - // this test resets the counter, and other tests can only raise it. + // vacuously. assert!( - SPCOUNT_COMPILES.load(Ordering::Relaxed) >= 1, + compiles >= 1, "single-pass trace never compiled — canary would pass vacuously" ); + assert!( + shape.closes_a_loop(), + "compiled {compiles} trace(s) but the body {} ({shape:?}) — the \ + canary would count n residual calls the interpreter made, not the \ + JIT", + shape.why_not().unwrap_or("closes a loop") + ); + + assert_eq!( + ops_after, 17, + "compiled loop body is {ops_after} ops across {compiles} compile(s), \ + not the pinned 17 — a value of 1 means the body is a bare \ + `Finish()`, i.e. a dispatch that lowered nothing at all" + ); + + // Equality over a NAMED set, never `is_empty()`: PUSHARG is a known + // abort stub here (`state.stack[state.stackpos as usize] = inputarg;` — + // the lowerer cannot express the store of a loop-external input), so an + // emptiness check would be red on day one and the natural response would + // be to weaken it. Pinning the set instead means a SECOND arm degrading + // is a failure rather than a silent addition, and PUSHARG lowering again + // is also a failure — the prompt to re-measure `ops_after` above. + let mut sp_arms: Vec<_> = majit_metainterp::degraded_dispatch_arms() + .into_iter() + .filter(|a| a.interp == "StackState") + .collect(); + sp_arms.sort_unstable_by_key(|a| a.arm); + let degraded: Vec<&str> = sp_arms.iter().map(|a| a.arm).collect(); + assert_eq!( + degraded, + ["PUSHARG"], + "the degraded-arm set moved; every trace reaching an abort stub aborts" + ); + + // The CAUSE, which the name set above cannot see: the comment on the + // name pin says PUSHARG degrades because the lowerer cannot express the + // store of a loop-external input. That was prose; this asserts it. + let causes: Vec<(&str, RefusalKind)> = sp_arms + .iter() + .map(|a| (a.arm, refusal_kind(a.reason))) + .collect(); + assert_eq!( + causes, + [("PUSHARG", RefusalKind::UnlowerableStmt)], + "PUSHARG still degrades but a different mechanism is refusing it. \ + `RefusalKind::Unclassified` means majit grew a refusal family the \ + classifier does not know — add it in `majit-metainterp`, do not \ + re-record this pin" + ); + assert!( + sp_arms[0].reason.contains("inputarg"), + "PUSHARG's refusal no longer names the loop-external input it \ + stores: {}", + sp_arms[0].reason + ); + assert_eq!(got, expected, "JIT result diverged from interp"); // One TOUCH per iteration; N iterations before the counter hits 0. let expected_touches = n as u32; @@ -417,12 +570,56 @@ mod tests { {expected_touches} iterations — a walk-vs-native double-execution \ during single-pass tracing would inflate this count" ); + println!( + "[tier-alive] touch_loop({n}) = {got}, compiled {compiles} loop(s) of \ + {ops_after} ops, {jit_touches} residual calls, degraded {degraded:?}" + ); } /// Smoke test: a program with no back-edge never enters the JIT. #[test] fn jit_no_loop() { let program = vec![PUSH, 42, RETURN]; - assert_eq!(mainloop(&program, 0, 3), 42); + assert_eq!(run_jit(&program, 0), 42); + } + + /// The nested program must compute what the plain interpreter computes. + /// + /// This is the precondition for reading anything else off + /// [`nested_loop_program`]: a bytecode with two loops is easy to get subtly + /// wrong (every `BR_COND` offset is relative to its own operand byte), and + /// a wrong program that happens to compile two loops would look exactly + /// like a right one to the tier assertions. + #[test] + fn nested_jit_output_matches_interp() { + let program = nested_loop_program(3); + for n in [1_i64, 2, 3, 5, 10, 20] { + let expected = interp(&program, n); + assert_eq!( + run_jit(&program, n), + expected, + "nested_loop_program({n}) diverged from the plain interpreter", + ); + assert_eq!( + expected, + n * (n + 1) / 2, + "the inner loop must be a pure spin, leaving the outer sum unchanged", + ); + } + } + + #[test] + #[ignore = "end-state gate: the outer loop does not trace once the inner loop is compiled"] + fn nested_loops_compile_two_keys_in_one_run() { + let program = nested_loop_program(3); + let (got, _touches, compiles, _ops_after, _shape) = compile_probe(&program, 8); + assert_eq!(got, 36, "sum(8) = 36"); + assert!( + compiles >= 2, + "both the inner header @11 and the outer header @3 must compile in \ + one run — got {compiles} compile(s); with only one, no loop is ever \ + already-compiled while another is traced and the cross-loop close \ + is unreachable by construction", + ); } } diff --git a/majit/examples/tiny2/src/jit_interp.rs b/majit/examples/tiny2/src/jit_interp.rs index 02d2553a46b..473a8d3cc59 100644 --- a/majit/examples/tiny2/src/jit_interp.rs +++ b/majit/examples/tiny2/src/jit_interp.rs @@ -78,6 +78,9 @@ struct Tiny2State { pub type Bytecode = [u8]; +pub static COMPILES: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); +pub static LAST_OPS_AFTER: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); + #[expect( dead_code, reason = "the jit_interp macro resolves bytecode reads through this trait surface" @@ -97,6 +100,7 @@ impl BytecodeExt for [u8] { #[majit_macros::jit_interp( state = Tiny2State, env = Bytecode, + greens = [pc, program], state_fields = { stackpos: int, stack: [int; virt], @@ -106,6 +110,10 @@ impl BytecodeExt for [u8] { fn mainloop(program: &Bytecode, num_args: usize, args_out: &mut [i64], threshold: u32) -> i64 { let mut driver: majit_metainterp::JitDriver = majit_metainterp::JitDriver::new(threshold); + driver.set_on_compile_loop(|_green_key, _ops_before, ops_after, _opcodes| { + COMPILES.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + LAST_OPS_AFTER.store(ops_after, std::sync::atomic::Ordering::Relaxed); + }); let mut pc: usize = 0; let stacksize: i32 = 0; let mut state = Tiny2State { @@ -123,6 +131,12 @@ fn mainloop(program: &Bytecode, num_args: usize, args_out: &mut [i64], threshold while pc < program.len() { // RPython: tinyjitdriver.jit_merge_point(...) + // + // Still the bare observer/replay form. The single-executor + // `jit_merge_point!(driver, program, pc; state)` conversion does not + // hold for this interpreter yet, and `trip_count_gate` below is the + // permanent assertion that says so — see its second doc paragraph for + // the two wrong answers the conversion produces. jit_merge_point!(); let opcode = program[pc]; pc += 1; @@ -312,7 +326,134 @@ fn parse_int(s: &str, start: usize) -> i64 { #[cfg(test)] mod tests { use super::*; + use crate::interp; + use majit_metainterp::{RefusalKind, refusal_kind}; + + /// Serializes every JIT entry in this module, so the `COMPILES` window in + /// [`jit_tier_is_inert_pending_arm_lowering`] cannot be written by another + /// test running concurrently. The counters are process-wide, and libtest + /// runs these tests in parallel by default. + /// + /// Plain mutex: the helpers below and the gate must never call one + /// another, or a single thread takes it twice and deadlocks. The gate holds + /// the guard itself and calls `mainloop` directly for exactly that reason. + static PROBE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + + /// [`mainloop`] under [`PROBE_LOCK`]. + fn mainloop_locked( + program: &Bytecode, + num_args: usize, + args_out: &mut [i64], + threshold: u32, + ) -> i64 { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + mainloop(program, num_args, args_out, threshold) + } + + /// [`JitTiny2Interp::run`] under [`PROBE_LOCK`]. + /// + /// Not a convenience alias for `jit.run(..)` — the lock is the whole + /// point. A test that enters the JIT without it never READS the counters, + /// but it does MOVE them, which is what makes it the hazard. + fn run_locked(jit: &mut JitTiny2Interp, prog: &[&str], args: &mut Vec) -> i64 { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + jit.run(prog, args) + } + + #[test] + fn jit_tier_is_inert_pending_arm_lowering() { + use std::sync::atomic::Ordering; + + const N: i64 = 1001; + let src = format!("0 {N} {{ #1 1 ADD ->#1 #2 1 SUB ->#2 #2 }} #1"); + let words: Vec<&str> = src.split_whitespace().collect(); + let mut args_out = [i64::MIN; 2]; + // Bare `mainloop`, not `mainloop_locked`: the guard is taken here so the + // store/run/load is a single critical section. Going through the helper + // would take the plain mutex twice on one thread and deadlock. + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + COMPILES.store(0, Ordering::Relaxed); + let got = mainloop(&compile(&words), 0, &mut args_out, 3); + let compiles = COMPILES.load(Ordering::Relaxed); + assert_eq!(got, N, "the interpreter's own trip count moved"); + + // Read after the run: nothing installs the dispatch JitCode until the + // interpreter is entered, so a list gathered before it is empty for the + // wrong reason. + let t2_arms: Vec<_> = majit_metainterp::degraded_dispatch_arms() + .into_iter() + .filter(|a| a.interp == "Tiny2State") + .collect(); + let mut degraded: Vec<&str> = t2_arms.iter().map(|a| a.arm).collect(); + degraded.sort_unstable(); + + // The CAUSE, which the name set cannot see: an arm can keep degrading + // for an entirely different reason. See tl's `jit_tier_is_alive` for the + // measurement that motivated this — three A/B arms whose name sets and + // pass counts were identical while the mechanism changed underneath. + let causes: Vec<(&str, RefusalKind)> = t2_arms + .iter() + .map(|a| (a.arm, refusal_kind(a.reason))) + .collect(); + assert_eq!( + causes, + [("OP_PUSH_INT", RefusalKind::UnlowerableStmt)], + "a degraded arm's cause moved while its name did not — a different \ + mechanism is refusing it now" + ); + // The offending statement, not just the mechanism. Substring, not the + // whole reason: the macro renders the snippet with its own spacing. + assert!( + t2_arms[0].reason.contains("from_le_bytes"), + "OP_PUSH_INT's refusal no longer names the `from_le_bytes` operand \ + read: {}", + t2_arms[0].reason + ); + + assert_eq!( + degraded, + ["OP_PUSH_INT"], + "the degraded-arm set moved. A MISSING name means that arm lowers \ + again; once the set is EMPTY the loop body holds no stub, the back \ + edge can close, and this crate should get a real jit_tier_is_alive \ + gate instead of this test" + ); + assert_eq!( + compiles, 0, + "count_to({N}) compiled {compiles} loops, but OP_PUSH_INT is an \ + abort stub inside the loop body, so every trace aborts and nothing \ + closes. A non-zero count means the tier came alive: replace this \ + test with a real liveness gate pinning a measured ops_after" + ); + println!( + "[tier-inert] count_to({N}) = {got} from the interpreter alone, {compiles} loops compiled, degraded {degraded:?}" + ); + } + + #[test] + fn trip_count_gate() { + for n in [1001i64, 1002] { + let src = format!("0 {n} {{ #1 1 ADD ->#1 #2 1 SUB ->#2 #2 }} #1"); + let words: Vec<&str> = src.split_whitespace().collect(); + let mut args_out = [i64::MIN; 2]; + let got = mainloop_locked(&compile(&words), 0, &mut args_out, 3); + assert_eq!( + got, n, + "count_to({n}) = {got}, so the loop ran {got} passes rather than \ + {n} — an off-by-one trip count is the signature of a terminal \ + arm whose exit the trace dropped, leaving the lowered arm to \ + fall through to the dispatch back-edge" + ); + assert_eq!( + args_out, + [n, 0], + "the walk-final stack written back through args_out should hold \ + the accumulator at {n} and the counter drained to 0" + ); + println!("[trip-count] count_to({n}) = {got} — exactly {n} passes"); + } + } #[test] fn jit_fibonacci_single() { @@ -321,7 +462,7 @@ mod tests { .collect(); let mut jit = JitTiny2Interp::new(); let mut args = vec![1i64, 1, 11]; - let result = jit.run(&prog, &mut args); + let result = run_locked(&mut jit, &prog, &mut args); assert_eq!(result, 89); } @@ -343,14 +484,14 @@ mod tests { // JIT let mut jit = JitTiny2Interp::new(); let mut jit_args = vec![1i64, 1, n]; - let jit_result = jit.run(&prog, &mut jit_args); + let jit_result = run_locked(&mut jit, &prog, &mut jit_args); assert_eq!(jit_result.to_string(), expected, "fib({n}) mismatch"); } } #[test] - fn jit_factorial() { + fn interp_factorial() { let prog: Vec<&str> = "1 { #1 MUL #1 1 SUB ->#1 #1 }".split_whitespace().collect(); let mut interp_args = vec![interp::Box::Int(5)]; @@ -363,7 +504,7 @@ mod tests { let prog: Vec<&str> = "{ #1 #1 1 SUB ->#1 #1 }".split_whitespace().collect(); let mut jit = JitTiny2Interp::new(); let mut args = vec![5i64]; - jit.run(&prog, &mut args); + run_locked(&mut jit, &prog, &mut args); // After loop, arg1 should be 0 assert_eq!(args[0], 0); } diff --git a/majit/examples/tiny3/src/jit_interp.rs b/majit/examples/tiny3/src/jit_interp.rs index 38d8b7ef841..f22ce3fab1c 100644 --- a/majit/examples/tiny3/src/jit_interp.rs +++ b/majit/examples/tiny3/src/jit_interp.rs @@ -81,6 +81,9 @@ struct Tiny3State { pub type Bytecode = [u8]; +pub static COMPILES: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); +pub static LAST_OPS_AFTER: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); + #[expect( dead_code, reason = "the jit_interp macro resolves bytecode reads through this trait surface" @@ -100,6 +103,7 @@ impl BytecodeExt for [u8] { #[majit_macros::jit_interp( state = Tiny3State, env = Bytecode, + greens = [pc, program], state_fields = { stackpos: int, stack: [int; virt], @@ -109,6 +113,10 @@ impl BytecodeExt for [u8] { fn mainloop(program: &Bytecode, num_args: usize, threshold: u32) -> i64 { let mut driver: majit_metainterp::JitDriver = majit_metainterp::JitDriver::new(threshold); + driver.set_on_compile_loop(|_green_key, _ops_before, ops_after, _opcodes| { + COMPILES.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + LAST_OPS_AFTER.store(ops_after, std::sync::atomic::Ordering::Relaxed); + }); let mut pc: usize = 0; let stacksize: i32 = 0; let mut state = Tiny3State { @@ -125,6 +133,10 @@ fn mainloop(program: &Bytecode, num_args: usize, threshold: u32) -> i64 { } while pc < program.len() { + // Still the bare observer/replay form. The single-executor + // `jit_merge_point!(driver, program, pc; state)` conversion does not + // hold for this interpreter yet, and `trip_count_gate` below is the + // permanent assertion that says so — see its second doc paragraph. jit_merge_point!(); let opcode = program[pc]; pc += 1; @@ -345,7 +357,129 @@ fn parse_int(s: &str, start: usize) -> i64 { #[cfg(test)] mod tests { use super::*; + use crate::interp; + use majit_metainterp::{RefusalKind, refusal_kind}; + + /// Serializes every JIT entry in this module, so the `COMPILES` window in + /// [`jit_tier_is_inert_pending_arm_lowering`] cannot be written by another + /// test running concurrently. The counters are process-wide, and libtest + /// runs these tests in parallel by default. + /// + /// Plain mutex: the helpers below and the gate must never call one + /// another, or a single thread takes it twice and deadlocks. The gate holds + /// the guard itself and calls `mainloop` directly for exactly that reason. + static PROBE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + + /// [`mainloop`] under [`PROBE_LOCK`]. + fn mainloop_locked(program: &Bytecode, num_args: usize, threshold: u32) -> i64 { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + mainloop(program, num_args, threshold) + } + + /// [`JitTiny3Interp::run`] under [`PROBE_LOCK`]. + /// + /// Not a convenience alias for `jit.run(..)` — the lock is the whole + /// point. A test that enters the JIT without it never READS the counters, + /// but it does MOVE them, which is what makes it the hazard. + fn run_locked(jit: &mut JitTiny3Interp, prog: &[&str], args: &mut Vec) -> i64 { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + jit.run(prog, args) + } + + #[test] + fn jit_tier_is_inert_pending_arm_lowering() { + use std::sync::atomic::Ordering; + + const N: i64 = 1001; + let src = format!("0 {N} {{ #1 1 ADD ->#1 #2 1 SUB ->#2 #2 }} #1"); + let words: Vec<&str> = src.split_whitespace().collect(); + // Bare `mainloop`, not `mainloop_locked`: the guard is taken here so the + // store/run/load is a single critical section. Going through the helper + // would take the plain mutex twice on one thread and deadlock. + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + COMPILES.store(0, Ordering::Relaxed); + let got = mainloop(&compile(&words), 0, 3); + let compiles = COMPILES.load(Ordering::Relaxed); + assert_eq!(got, N, "the interpreter's own trip count moved"); + + // Read after the run: nothing installs the dispatch JitCode until the + // interpreter is entered, so a list gathered before it is empty for the + // wrong reason. + let t3_arms: Vec<_> = majit_metainterp::degraded_dispatch_arms() + .into_iter() + .filter(|a| a.interp == "Tiny3State") + .collect(); + let mut degraded: Vec<&str> = t3_arms.iter().map(|a| a.arm).collect(); + degraded.sort_unstable(); + + // The CAUSE, which the name set cannot see: an arm can keep degrading + // for an entirely different reason. See tl's `jit_tier_is_alive` for the + // measurement that motivated this — three A/B arms whose name sets and + // pass counts were identical while the mechanism changed underneath. + let mut causes: Vec<(&str, RefusalKind)> = t3_arms + .iter() + .map(|a| (a.arm, refusal_kind(a.reason))) + .collect(); + causes.sort_unstable(); + assert_eq!( + causes, + [ + ("OP_PUSH_FLOAT", RefusalKind::UnlowerableStmt), + ("OP_PUSH_INT", RefusalKind::UnlowerableStmt), + ], + "a degraded arm's cause moved while its name did not — a different \ + mechanism is refusing it now" + ); + // Both arms are refused for the same statement, the widening read of the + // operand bytes. Substring, not the whole reason: the macro renders the + // snippet with its own token spacing. + for a in &t3_arms { + assert!( + a.reason.contains("from_le_bytes"), + "{}'s refusal no longer names the `from_le_bytes` operand read: {}", + a.arm, + a.reason + ); + } + + assert_eq!( + degraded, + ["OP_PUSH_FLOAT", "OP_PUSH_INT"], + "the degraded-arm set moved. A MISSING name means that arm lowers \ + again; once the set is EMPTY the loop body holds no stub, the back \ + edge can close, and this crate should get a real jit_tier_is_alive \ + gate instead of this test" + ); + assert_eq!( + compiles, 0, + "count_to({N}) compiled {compiles} loops, but OP_PUSH_INT and \ + OP_PUSH_FLOAT are abort stubs inside the loop body, so every trace \ + aborts and nothing closes. A \ + non-zero count means the tier came alive: replace this test with a \ + real liveness gate pinning a measured ops_after" + ); + println!( + "[tier-inert] count_to({N}) = {got} from the interpreter alone, {compiles} loops compiled, degraded {degraded:?}" + ); + } + + #[test] + fn trip_count_gate() { + for n in [1001i64, 1002] { + let src = format!("0 {n} {{ #1 1 ADD ->#1 #2 1 SUB ->#2 #2 }} #1"); + let words: Vec<&str> = src.split_whitespace().collect(); + let got = mainloop_locked(&compile(&words), 0, 3); + assert_eq!( + got, n, + "count_to({n}) = {got}, so the loop ran {got} passes rather than \ + {n} — an off-by-one trip count is the signature of a terminal \ + arm whose exit the trace dropped, leaving the lowered arm to \ + fall through to the dispatch back-edge" + ); + println!("[trip-count] count_to({n}) = {got} — exactly {n} passes"); + } + } #[test] fn jit_fibonacci_single() { @@ -354,7 +488,7 @@ mod tests { .collect(); let mut jit = JitTiny3Interp::new(); let mut args = vec![1i64, 1, 11]; - let result = jit.run(&prog, &mut args); + let result = run_locked(&mut jit, &prog, &mut args); assert_eq!(result, 89); } @@ -374,7 +508,7 @@ mod tests { let mut jit = JitTiny3Interp::new(); let mut jit_args = vec![1i64, 1, n]; - let jit_result = jit.run(&prog, &mut jit_args); + let jit_result = run_locked(&mut jit, &prog, &mut jit_args); assert_eq!(jit_result.to_string(), expected, "fib({n}) mismatch"); } @@ -385,7 +519,7 @@ mod tests { let prog: Vec<&str> = "{ #1 #1 1 SUB ->#1 #1 }".split_whitespace().collect(); let mut jit = JitTiny3Interp::new(); let mut args = vec![5i64]; - jit.run(&prog, &mut args); + run_locked(&mut jit, &prog, &mut args); assert_eq!(args[0], 0); } diff --git a/majit/examples/tinyframe/src/jit_interp.rs b/majit/examples/tinyframe/src/jit_interp.rs index 786fb929d13..2dda67c0205 100644 --- a/majit/examples/tinyframe/src/jit_interp.rs +++ b/majit/examples/tinyframe/src/jit_interp.rs @@ -3,9 +3,32 @@ /// Greens: [pc, bytecode] /// Reds: [regs] (tracked via state_fields) use crate::interp::{ADD, JUMP_IF_ABOVE, LOAD, RETURN}; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; pub type Bytecode = [u8]; +/// Hot loops majit compiled. The only positive evidence the JIT tier is alive: +/// a green suite, agreement with the plain interpreter and an exact trip count +/// are all satisfied by an interpreter answering alone. +pub static COMPILES: AtomicUsize = AtomicUsize::new(0); + +/// Ops in the last compiled loop body after optimization. +/// +/// `COMPILES > 0` is necessary but NOT sufficient: an entirely empty dispatch +/// still compiles a trace — one whose whole optimized body is `Finish()`, i.e. +/// `ops_after == 1`. A compile counter counts TRACES, not WORK. This is the +/// term that separates a compiled loop from a compiled nothing. +pub static LAST_OPS_AFTER: AtomicUsize = AtomicUsize::new(0); + +/// Shape of the last compiled loop body — see [`majit_metainterp::LoopBodyShape`]. +/// +/// Held as two flags rather than the struct itself so the recording stays +/// lock-free on the compile path; the probe rebuilds the struct inside the same +/// lock window it reads the counters in, because this is as process-global as +/// they are. +pub static LAST_HAS_JUMP: AtomicBool = AtomicBool::new(false); +pub static LAST_ALWAYS_FAILS: AtomicBool = AtomicBool::new(false); + #[expect( dead_code, reason = "the jit_interp macro resolves bytecode reads through this trait surface" @@ -21,23 +44,25 @@ impl BytecodeExt for [u8] { struct TinyFrameState { regs: Vec, + /// What `RETURN` hands back. + /// + /// The `; state` merge point leaves the loop through `break` before it + /// assigns the walk's resume pc, so after the loop `pc` still names the + /// position the walk started from and `program[pc + 1]` — the operand + /// saying which register holds the result — cannot be read there. The + /// result has to arrive in `state`. + ret: i64, } const DEFAULT_THRESHOLD: u32 = 3; -// `greens = [pc, program]` lets the operand reads (`program[pc + N]`) -// constant-fold so the loop traces and compiles. `regs` is `[int; virt]` -// (virtualizable), not plain `[int]`: a loop-carried plain `[int]` element is -// kept in a trace register and is *not* restored to the array on a CloseLoop -// guard deopt, so the post-loop value reads back as the pre-loop one. A virt -// array writes through to the heap-backing Vec, which the deopt path reads -// directly — the same mechanism braininterp relies on. #[majit_macros::jit_interp( state = TinyFrameState, env = Bytecode, greens = [pc, program], state_fields = { regs: [int; virt], + ret: int, }, )] fn mainloop( @@ -48,10 +73,18 @@ fn mainloop( ) -> i64 { let mut driver: majit_metainterp::JitDriver = majit_metainterp::JitDriver::new(threshold); + driver.set_on_compile_loop(|_green_key, _ops_before, ops_after, opcodes| { + COMPILES.fetch_add(1, Ordering::Relaxed); + LAST_OPS_AFTER.store(ops_after, Ordering::Relaxed); + let shape = majit_metainterp::LoopBodyShape::of(opcodes); + LAST_HAS_JUMP.store(shape.has_jump, Ordering::Relaxed); + LAST_ALWAYS_FAILS.store(shape.has_always_fails, Ordering::Relaxed); + }); let mut pc: usize = 0; let _stacksize: i32 = 0; let mut state = TinyFrameState { regs: vec![0; num_regs], + ret: 0, }; for &(r, v) in init_regs { state.regs[r] = v; @@ -66,7 +99,25 @@ fn mainloop( } loop { - jit_merge_point!(); + // `; state` selects the single-executor close: the walk's final state is + // transferred into `state` here and the native loop resumes at the close + // pc, instead of discarding the walk outcome and re-running the circuit + // the walk already executed. + // With `TINYFRAME_NATIVE_PROBE=1`, every pass of the NATIVE loop prints + // its own pc and the state it is about to hand to the merge point. This + // sits BEFORE the merge point on purpose: the merge point is where a + // walk takes over and leaves the loop, so a probe after it never prints + // the pass the walk consumed — the one pass whose state is the question. + if std::env::var_os("TINYFRAME_NATIVE_PROBE").is_some() { + eprintln!( + "[native-mp] pc={pc} regs={:?} ret={}", + state.regs, state.ret + ); + } + jit_merge_point!(driver, program, pc; state); + if pc == 0 { + can_enter_jit!(driver, pc, &mut state, program, || {}); + } let opcode = program[pc]; match opcode { @@ -99,16 +150,29 @@ fn mainloop( } pc += 4; } + // Stores into `ret` and then leaves through an in-arm `return`, + // never `{ store; break }`: `classify.rs` `is_break_expr` requires + // the arm body to be exactly `break`, so a composite body classifies + // `Lowerable` and its tail `break` reaches `lower_stmt_fallback`, + // which guards an enclosed `return` but not an enclosed `break` — + // the statement is inert and is silently dropped, leaving the + // lowered arm to fall through to the dispatch back-edge. RETURN => { let r = program[pc + 1] as usize; - return state.regs[r]; - } - _ => { - break; + state.ret = state.regs[r]; + return state.ret; } + // Was `_ => { break; }` with the panic below the loop. The loop now + // has a second way out — the merge point's own `break` on a walk + // that reached a terminal return — so falling out of it no longer + // identifies a bad opcode, and the panic moves into the arm that + // actually saw one. + _ => panic!("fell off end of code"), } } - panic!("fell off end of code"); + // Reached only when the merge point broke out on a walk that already ran the + // terminal opcode, so the result is whatever that opcode parked in `ret`. + state.ret } // -- Public wrapper matching the old API -- @@ -141,6 +205,159 @@ mod tests { use super::*; use crate::interp; + /// [`COMPILES`] is process-global, so under the default parallel libtest + /// runner a concurrent `run` lands inside [`compile_probe`]'s + /// store/run/load window and the probe reads someone else's compile. The + /// lock therefore covers *every* call that can compile, not just the + /// probe's own — [`run_jit`] and [`compile_probe`] are the only two ways a + /// test may enter the JIT, and neither may call the other (a plain mutex + /// re-entered on one thread deadlocks). + static PROBE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + + /// For tests that assert only on the result. They still compile, so they + /// must not run inside the probe's window. See [`PROBE_LOCK`]. + fn run_jit(code: &interp::Code, init_regs: &[(usize, i64)]) -> i64 { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + JitTinyFrameInterp::new().run(code, init_regs) + } + + /// How many unroll-free fallback compiles the tier gate currently sees — + /// see the block at its assertion. `MC_DIAG` slot 73. + const EXPECT_UNPEELED: u64 = 1; + + /// Run with both counters reset, returning `(result, compiles, ops_after)`. + /// + /// [`LAST_OPS_AFTER`] is read here rather than at the call site, and reset + /// here rather than nowhere. Both counters are process-global, so both need + /// the same treatment [`PROBE_LOCK`] exists to give [`COMPILES`]: a load + /// taken after the guard drops can observe a concurrent test's compile, and + /// a counter that is never stored to zero retains whatever the last compile + /// anywhere in the process left behind. Unreset, a zero from this probe is + /// indistinguishable from an inherited value. + fn compile_probe( + code: &interp::Code, + init_regs: &[(usize, i64)], + ) -> (i64, usize, usize, u64, majit_metainterp::LoopBodyShape) { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + COMPILES.store(0, Ordering::Relaxed); + LAST_OPS_AFTER.store(0, Ordering::Relaxed); + LAST_HAS_JUMP.store(false, Ordering::Relaxed); + LAST_ALWAYS_FAILS.store(false, Ordering::Relaxed); + // Slot 72 is process-global and CUMULATIVE, so it is read as a delta + // across this run and inside `PROBE_LOCK`, alongside the counters this + // probe resets. An absolute read would carry every other test's + // compiles — the inherited-value defect this probe already guards. + let unpeeled_before = majit_metainterp::mc_diag(73); + let got = JitTinyFrameInterp::new().run(code, init_regs); + let unpeeled = majit_metainterp::mc_diag(73) - unpeeled_before; + ( + got, + COMPILES.load(Ordering::Relaxed), + LAST_OPS_AFTER.load(Ordering::Relaxed), + unpeeled, + majit_metainterp::LoopBodyShape { + has_jump: LAST_HAS_JUMP.load(Ordering::Relaxed), + has_always_fails: LAST_ALWAYS_FAILS.load(Ordering::Relaxed), + }, + ) + } + + /// A real loop body was compiled — the one property no assertion on a + /// *result* can establish. + /// + /// This certifies that the JIT tier produced a non-empty compiled body. It + /// is silent on whether that body's resume data is well-formed: a trace can + /// carry a malformed promote snapshot and still be counted here. + /// + /// All three parts are needed and none implies another: + /// + /// 1. `COMPILES` 0 → non-zero. A green suite, agreement with + /// `interp::Frame::interpret` and even an exact absolute trip count are + /// all satisfied by the interpreter answering alone. + /// 2. `ops_after` pinned by equality. `compiles >= 1` is necessary but NOT + /// sufficient: an entirely empty dispatch still compiles a trace — one + /// whose whole optimized body is `Finish()`, i.e. `ops_after == 1`. A + /// compile counter counts TRACES, not WORK, and every inequality a real + /// loop satisfies that degenerate body satisfies too. + /// 3. `degraded_dispatch_arms()` empty. An arm whose body did not lower is + /// an abort stub, so any trace reaching it aborts. The list names the + /// arm, which an abort count cannot: `trace action at pc=N -> Abort` + /// reports the trace-START pc, not the arm that caused it. + /// + /// The registry is process-wide, so it is filtered to this machine's + /// `state = TinyFrameState`, and read *after* a run because nothing + /// installs the dispatch JitCode until the interpreter is entered. + /// + /// The subject is the `jit_matches_interp` program seeded through + /// `init_regs`, so the result assertion is itself an absolute trip count: + /// r0 counts up by 1 per pass while r2 > r0, so `run(.., [(2, N)])` returns + /// the number of passes. One compiled artifact carries both properties. + #[test] + fn jit_tier_is_alive() { + const N: i64 = 1001; + let code = interp::compile( + " + main: + LOAD 1 => r1 + LOAD 0 => r0 + @l1 + ADD r0 r1 => r0 + JUMP_IF_ABOVE r2 r0 @l1 + RETURN r0 + ", + ); + let (got, compiles, ops_after, unpeeled, shape) = compile_probe(&code, &[(2, N)]); + assert_eq!( + unpeeled, EXPECT_UNPEELED, + "expected {EXPECT_UNPEELED} unroll-free fallback compile(s) out of \ + {compiles}, saw {unpeeled}" + ); + // The body actually closes a loop — see `LoopBodyShape`. A compile + // count and an op count together still accept a body that bails out on + // its first pass; this is the term that does not. Sound HERE because + // this fixture loops: on a straight-line subject a `Jump`-less body is + // the right answer, not a defect. + assert!( + shape.closes_a_loop(), + "compiled {ops_after} ops but the body {} ({shape:?})", + shape.why_not().unwrap_or("closes a loop") + ); + assert_eq!( + got, N, + "count_to({N}) = {got}, so the loop the tier assertions below \ + describe ran {got} passes rather than {N}" + ); + + let degraded: Vec<&str> = majit_metainterp::degraded_dispatch_arms() + .iter() + .filter(|a| a.interp == "TinyFrameState") + .map(|a| a.arm) + .collect(); + assert!( + degraded.is_empty(), + "dispatch arms degraded to abort stubs: {degraded:?} — every trace \ + reaching one aborts" + ); + + // Zero-vs-nonzero is the property; a later change that legitimately + // mints more than one artifact is not this regression. + assert!( + compiles >= 1, + "count_to({N}) compiled {compiles} loops — the JIT tier is inert and \ + the interpreter is answering alone, which every other assertion in \ + this file would still pass" + ); + assert_eq!( + ops_after, 4, + "compiled loop body is {ops_after} ops, not the pinned 4 — a value \ + of 1 means the body is a bare `Finish()`, i.e. a dispatch that \ + lowered nothing at all" + ); + println!( + "[tier-alive] count_to({N}) = {got}, compiled {compiles} loop(s) of {ops_after} ops, 0 degraded arms" + ); + } + #[test] fn jit_loop_count_to_40() { let code = interp::compile( @@ -155,8 +372,7 @@ mod tests { RETURN r0 ", ); - let mut jit = JitTinyFrameInterp::new(); - let result = jit.run(&code, &[]); + let result = run_jit(&code, &[]); assert_eq!(result, 40); } @@ -174,11 +390,70 @@ mod tests { RETURN r1 ", ); - let mut jit = JitTinyFrameInterp::new(); - let result = jit.run(&code, &[(0, 40)]); + let result = run_jit(&code, &[(0, 40)]); assert_eq!(result, 40); } + /// The failing fixture's program: straight-line, no back edge anywhere. + /// `r1` is written at pc 0 and read by the `ADD` at pc 6. + /// + /// pc 0 LOAD 111 => r1 + /// pc 3 LOAD 222 => r2 + /// pc 6 ADD r1 r2 => r0 + /// pc 10 RETURN r0 + fn straight_line_program() -> interp::Code { + interp::compile( + " + main: + LOAD 111 => r1 + LOAD 222 => r2 + ADD r1 r2 => r0 + RETURN r0 + ", + ) + } + + #[test] + fn jit_trace_reads_input_written_after_the_arming_pc() { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + let code = straight_line_program(); + // Threshold 1: the entry door arms on its single hit at pc 0. + let got = mainloop(&code.code, code.regno, &[], 1); + assert_eq!( + got, 333, + "expected 111 + 222; a result of 222 means the walk read r1 as 0 — \ + its value at the arming pc 0 — instead of the 111 native stored \ + there before the walk headed at pc 3" + ); + } + + fn pre_loop_read_program() -> interp::Code { + interp::compile( + " + main: + LOAD 111 => r3 + LOAD 1 => r1 + LOAD 0 => r0 + @l1 + ADD r0 r1 => r0 + JUMP_IF_ABOVE r2 r0 @l1 + ADD r0 r3 => r0 + RETURN r0 + ", + ) + } + + #[test] + fn jit_finish_trace_reads_pre_loop_input() { + let code = pre_loop_read_program(); + let got = run_jit(&code, &[(2, 4)]); + assert_eq!( + got, 115, + "expected 4 + 111 from the walk; a result of 4 would mean a \ + Finish-terminated trace read r3 as 0 even with no arming-pc skew" + ); + } + #[test] fn jit_matches_interp() { let code = interp::compile( @@ -200,8 +475,7 @@ mod tests { let interp_result = frame.interpret(&code).as_int(); // JIT - let mut jit = JitTinyFrameInterp::new(); - let jit_result = jit.run(&code, &[(2, n)]); + let jit_result = run_jit(&code, &[(2, n)]); assert_eq!(jit_result, interp_result, "count_to({n}) mismatch"); } diff --git a/majit/examples/tinyframe/src/main.rs b/majit/examples/tinyframe/src/main.rs index dfc23d2f772..23e33d5842d 100644 --- a/majit/examples/tinyframe/src/main.rs +++ b/majit/examples/tinyframe/src/main.rs @@ -7,6 +7,31 @@ pub mod jit_interp; use std::time::Instant; +/// Absolute trip-count gate on the JIT path. +/// +/// `count_to` adds 1 to `r1` once per pass and stops when `r1` reaches `r0`, +/// so the returned value names the number of passes exactly: `n` passes answer +/// `n`, and one extra pass answers `n+1`. Agreement with `interp::Frame` alone +/// would not settle this — both run the same program, and a duplicated +/// iteration of the *compiled* loop is invisible to any check that does not +/// assert an absolute count. +/// +/// Two lengths of different parity, because a peeled first iteration plus an +/// even/odd body count is exactly the shape an off-by-one hides in. +fn trip_count_gate(code: &interp::Code) { + for n in [1001i64, 1002] { + let mut jit = jit_interp::JitTinyFrameInterp::new(); + let got = jit.run(code, &[(0, n)]); + assert_eq!( + got, n, + "count_to({n}) = {got}, so the loop ran {got} passes rather than {n} \ + — an off-by-one trip count is the signature of a terminal arm whose \ + exit the trace dropped" + ); + println!("[trip-count] count_to({n}) = {got} — exactly {n} passes"); + } +} + fn main() { let n: i64 = std::env::args() .nth(1) @@ -26,6 +51,8 @@ fn main() { ", ); + trip_count_gate(&code); + // Correctness check { let mut frame = interp::Frame::new(&code); diff --git a/majit/examples/tl/src/jit_interp.rs b/majit/examples/tl/src/jit_interp.rs index 38e4835008b..cc34f55aa7f 100644 --- a/majit/examples/tl/src/jit_interp.rs +++ b/majit/examples/tl/src/jit_interp.rs @@ -8,8 +8,28 @@ /// Reds: [inputarg, stackpos, stack] (inputarg is a function parameter — red by nature) use majit_metainterp::jit::promote; -// Throwaway compile counter to measure portal-call compile-through. -pub static SPIKE_COMPILES: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0); +/// Hot loops majit compiled. The only positive evidence the JIT tier is alive: +/// a green suite, agreement with `interp::interpret` and an exact absolute trip +/// count are all satisfied by an interpreter answering alone. +/// +/// Also the control/probe counter for `spike_portal_call_compile_through`. +pub static COMPILES: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0); + +/// Optimized operation count for the most recently compiled loop. A compile +/// count alone cannot distinguish a real loop body from an empty `Finish`. +pub static LAST_OPS_AFTER: core::sync::atomic::AtomicUsize = + core::sync::atomic::AtomicUsize::new(0); + +/// Shape of the last compiled loop body — see [`majit_metainterp::LoopBodyShape`]. +/// +/// Held as two flags rather than the struct itself so the recording stays +/// lock-free on the compile path; the probe rebuilds the struct inside the same +/// lock window it reads the counters in, because this is as process-global as +/// they are. +pub static LAST_HAS_JUMP: core::sync::atomic::AtomicBool = + core::sync::atomic::AtomicBool::new(false); +pub static LAST_ALWAYS_FAILS: core::sync::atomic::AtomicBool = + core::sync::atomic::AtomicBool::new(false); /// Stack rotation — @dont_look_inside in RPython (tl.py:43). /// @@ -28,6 +48,10 @@ pub static SPIKE_COMPILES: core::sync::atomic::AtomicU32 = core::sync::atomic::A #[cfg(test)] pub static ROLL_CALLS: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0); +/// Rotates the live stack through a residual call. The state-machine +/// virtualizable has no force token, so lowering this raw-pointer mutation as +/// an ordinary call would leave symbolic array cells stale; the dispatch arm +/// must remain degraded until array effects can be synchronized. #[majit_macros::jit_may_force] extern "C" fn storage_roll(stack_ptr: usize, stackpos: i64, r: i64) { #[cfg(test)] @@ -127,9 +151,16 @@ const PUSHARG: u8 = 22; pub fn mainloop(program: &Bytecode, inputarg: i64, threshold: u32) -> i64 { let mut driver: majit_metainterp::JitDriver = majit_metainterp::JitDriver::new(threshold); - // Count compiled loops. - driver.set_on_compile_loop(|_gk, _b, _a| { - SPIKE_COMPILES.fetch_add(1, core::sync::atomic::Ordering::Relaxed); + // Count compiled loops, and record the size of the last compiled body. + driver.set_on_compile_loop(|_green_key, _ops_before, ops_after, opcodes| { + COMPILES.fetch_add(1, core::sync::atomic::Ordering::Relaxed); + LAST_OPS_AFTER.store(ops_after, core::sync::atomic::Ordering::Relaxed); + let shape = majit_metainterp::LoopBodyShape::of(opcodes); + LAST_HAS_JUMP.store(shape.has_jump, core::sync::atomic::Ordering::Relaxed); + LAST_ALWAYS_FAILS.store( + shape.has_always_fails, + core::sync::atomic::Ordering::Relaxed, + ); }); let mut pc: usize = 0; let stacksize: i32 = 0; @@ -147,6 +178,31 @@ pub fn mainloop(program: &Bytecode, inputarg: i64, threshold: u32) -> i64 { } while pc < program.len() { + // Still the legacy bare form, which discards the walk outcome and + // re-runs the circuit the walk already executed. + // + // `jit_merge_point!(driver, program, pc; state)` carries the tests whose + // loops hold no `ROLL` — `sum_bytecode` compiles a loop and resumes at + // its header — but not `roll_loop_bytecode`. `ROLL`'s arm lowers to an + // abort stub, because `storage_roll` is handed + // `state.stack.as_mut_ptr()` and the macro has no spelling for the base + // pointer of a `[int; virt]` state-field array. The abort then lands + // after the shared `pc += 1` below and before the arm's own operand + // advance at `pc += 1` inside `ROLL`, so the resume position names + // `ROLL`'s operand byte rather than an opcode boundary: + // `PYRE_PORTAL_RCA=1` reports `resume_pc=10 compiled_key=None` where + // `ROLL, 2` occupies pc 9–10, against `resume_pc=3` with a real + // `compiled_key` on the `ROLL`-free control. + // + // Two gaps have to close, not one. The arm has to lower (the macro + // spelling above), AND the abort path needs a source-opcode boundary to + // resume at: both of its exits report the same mid-opcode pc today. + // `run_pending_abort_blackhole` takes it from the merge point the + // blackhole chain reaches, and declining before the chain runs falls + // back to `walk_final_pc`, which the `TraceAction::Abort` arm sets from + // i0 — advanced by dispatch before the arm ran. No per-source-opcode + // entry pc is retained during the walk, so neither exit can name the + // boundary. jit_merge_point!(); // tl.py:88 stack.stackpos = promote(stack.stackpos) state.stackpos = promote(state.stackpos); @@ -304,6 +360,14 @@ pub fn mainloop(program: &Bytecode, inputarg: i64, threshold: u32) -> i64 { state.stackpos += 1; } // tl.py:180-181 + // + // A bare `break` body, never `{ …; break }`: `classify.rs` + // `is_break_expr` requires the arm body to be exactly `break`, so a + // composite body classifies `Lowerable` and its tail `break` reaches + // `lower_stmt_fallback`, which guards an enclosed `return` but not an + // enclosed `break` — the statement is judged inert and silently + // dropped, leaving the lowered arm to fall through to the dispatch + // back-edge and run one extra iteration. RETURN => break, // tl.py:183-184 PUSHARG => { @@ -314,6 +378,13 @@ pub fn mainloop(program: &Bytecode, inputarg: i64, threshold: u32) -> i64 { } } + // Reads `state` and never `pc`, which is what the single-executor merge + // point will need when it lands: its `break` precedes the `pc = __sp_pc` + // handoff, so `pc` there still names the position the walk started from. + // `stackpos` is a scalar state field and `stack` a virtualizable array + // field, so the walk-final values arrive through + // `writeback_scalar_state_fields` / `writeback_virt_array_state_fields` — + // no `ret` field is needed here. state.stackpos -= 1; state.stack[state.stackpos as usize] } @@ -344,6 +415,7 @@ impl JitTlInterp { mod tests { use super::*; use crate::interp; + use core::sync::atomic::Ordering; /// sum(N) = 1 + 2 + ... + N fn sum_bytecode() -> Vec { @@ -364,18 +436,174 @@ mod tests { ] } + /// [`COMPILES`] is process-global, so under the default parallel libtest + /// runner a concurrent `run` lands inside [`compile_probe`]'s + /// store/run/load window and the probe reads someone else's compile. The + /// lock therefore covers *every* call that can compile, not just the + /// probe's own — [`run_jit`] and [`compile_probe`] are the only two ways a + /// test may enter the JIT, and neither may call the other (a plain mutex + /// re-entered on one thread deadlocks). + static PROBE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + + /// For tests that assert only on the result. They still compile, so they + /// must not run inside the probe's window. See [`PROBE_LOCK`]. + fn run_jit(bc: &[u8], inputarg: i64) -> i64 { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + JitTlInterp::new().run(bc, inputarg) + } + + /// Run with both counters reset, returning `(result, compiles, ops_after)`. + /// + /// [`LAST_OPS_AFTER`] is read here rather than at the call site, and reset + /// here rather than nowhere. Both counters are process-global, so both need + /// the same treatment [`PROBE_LOCK`] exists to give [`COMPILES`]: a load + /// taken after the guard drops can observe a concurrent test's compile, and + /// a counter that is never stored to zero retains whatever the last compile + /// anywhere in the process left behind. Unreset, a zero from this probe is + /// indistinguishable from an inherited value. + fn compile_probe( + bc: &[u8], + inputarg: i64, + ) -> (i64, usize, usize, majit_metainterp::LoopBodyShape) { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + COMPILES.store(0, Ordering::Relaxed); + LAST_OPS_AFTER.store(0, Ordering::Relaxed); + LAST_HAS_JUMP.store(false, Ordering::Relaxed); + LAST_ALWAYS_FAILS.store(false, Ordering::Relaxed); + let got = JitTlInterp::new().run(bc, inputarg); + ( + got, + COMPILES.load(Ordering::Relaxed), + LAST_OPS_AFTER.load(Ordering::Relaxed), + majit_metainterp::LoopBodyShape { + has_jump: LAST_HAS_JUMP.load(Ordering::Relaxed), + has_always_fails: LAST_ALWAYS_FAILS.load(Ordering::Relaxed), + }, + ) + } + + /// Run with [`ROLL_CALLS`] reset, returning `(result, roll_calls)`. + /// + /// [`ROLL_CALLS`] is process-global for the same reason [`COMPILES`] is, and + /// any concurrently running test whose program issues `ROLL` adds to it. It + /// therefore needs [`PROBE_LOCK`] over its own store/run/load window too. + fn run_jit_counting_rolls(bc: &[u8], inputarg: i64) -> (i64, u32) { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + ROLL_CALLS.store(0, Ordering::Relaxed); + let got = JitTlInterp::new().run(bc, inputarg); + (got, ROLL_CALLS.load(Ordering::Relaxed)) + } + + use majit_metainterp::{RefusalKind, refusal_kind}; + + #[test] + fn jit_tier_is_alive() { + // 500 * 501 / 2. `sum` is a weaker trip-count oracle than + // `trip_count_bytecode` (its terminal pass adds a counter of 0, so a + // duplicated final iteration leaves the sum unchanged), but it is a + // loop the tier actually compiles, and tier liveness is what this test + // is for. + let (got, compiles, ops_after, shape) = compile_probe(&sum_bytecode(), 500); + // The body actually closes a loop — see `LoopBodyShape`. A compile + // count and an op count together still accept a body that bails out on + // its first pass; this is the term that does not. Sound HERE because + // this fixture loops: on a straight-line subject a `Jump`-less body is + // the right answer, not a defect. + assert!( + shape.closes_a_loop(), + "compiled {ops_after} ops but the body {} ({shape:?})", + shape.why_not().unwrap_or("closes a loop") + ); + assert_eq!(got, 125_250, "sum(500) must still answer 125250"); + + let tl_arms: Vec<_> = majit_metainterp::degraded_dispatch_arms() + .into_iter() + .filter(|a| a.interp == "TlState") + .collect(); + + let mut degraded: Vec<&str> = tl_arms.iter().map(|a| a.arm).collect(); + degraded.sort_unstable(); + assert_eq!( + degraded, + ["PUSHARG", "ROLL"], + "the degraded-arm set moved. A NEW name means an arm silently \ + stopped lowering and every trace reaching it now aborts; a MISSING \ + name means that arm lowers again, so a loop that could not trace \ + before may now — re-check which subjects compile" + ); + + let mut causes: Vec<(&str, RefusalKind)> = tl_arms + .iter() + .map(|a| (a.arm, refusal_kind(a.reason))) + .collect(); + causes.sort_unstable(); + assert_eq!( + causes, + [ + ("PUSHARG", RefusalKind::UnlowerableStmt), + ("ROLL", RefusalKind::GreenWriteback) + ], + "a degraded arm's CAUSE moved while its name did not. That is the \ + signal the set above structurally cannot carry: the arm still \ + degrades, so part 3 is unchanged, but a different mechanism is \ + refusing it now — which is what a routing or lowering change looks \ + like when it half-works" + ); + + // Same mechanism, different offending statement, is a third way to move + // while parts 3 and 4 both hold. Substrings, not whole reasons: the + // macro's stringification spacing (`state.stack [ ... ]`) is an artifact + // of token rendering and is not a property worth pinning. + let reason_of = |arm: &str| -> &'static str { + tl_arms + .iter() + .find(|a| a.arm == arm) + .expect("part 3 already pinned this arm as present") + .reason + }; + assert!( + reason_of("ROLL").contains("pc += 1"), + "ROLL's refusal no longer names `pc += 1` as the offending \ + statement — the green-writeback guard is now stopping somewhere \ + else in the arm: {}", + reason_of("ROLL") + ); + assert!( + reason_of("PUSHARG").contains("state.stack"), + "PUSHARG's refusal no longer names the `state.stack` write as the \ + unlowerable statement: {}", + reason_of("PUSHARG") + ); + + // Zero-vs-nonzero is the property; a later change that legitimately + // mints more than one artifact is not this regression. + assert!( + compiles >= 1, + "sum(500) compiled {compiles} loops — the JIT tier is inert and the \ + interpreter is answering alone, which every other assertion in \ + this file would still pass" + ); + assert_eq!( + ops_after, 12, + "compiled loop body is {ops_after} ops, not the pinned 12 — a value \ + of 1 means the body is a bare `Finish()`, i.e. a dispatch that \ + lowered nothing at all" + ); + println!( + "[tier-alive] sum(500) = {got}, compiled {compiles} loop(s) of {ops_after} ops, degraded {degraded:?}" + ); + } + #[test] fn jit_sum_5() { let bc = sum_bytecode(); - let mut jit = JitTlInterp::new(); - assert_eq!(jit.run(&bc, 5), 15); + assert_eq!(run_jit(&bc, 5), 15); } #[test] fn jit_sum_100() { let bc = sum_bytecode(); - let mut jit = JitTlInterp::new(); - assert_eq!(jit.run(&bc, 100), 5050); + assert_eq!(run_jit(&bc, 100), 5050); } #[test] @@ -383,8 +611,7 @@ mod tests { let bc = sum_bytecode(); for a in [1, 2, 5, 10, 50, 100, 200] { let expected = interp::interpret(&bc, a); - let mut jit = JitTlInterp::new(); - let got = jit.run(&bc, a); + let got = run_jit(&bc, a); assert_eq!(got, expected, "mismatch for a={a}"); } } @@ -430,31 +657,91 @@ mod tests { #[test] fn jit_residual_not_double_executed() { let bc = roll_loop_bytecode(); - let n: i64 = 20; - // First confirm the program is well-formed and the JIT result matches - // the interpreter (the two ROLLs cancel, so acc == 0). - let expected = interp::interpret(&bc, n); - ROLL_CALLS.store(0, core::sync::atomic::Ordering::Relaxed); - let mut jit = JitTlInterp::new(); - let got = jit.run(&bc, n); - let jit_rolls = ROLL_CALLS.load(core::sync::atomic::Ordering::Relaxed); - assert_eq!(got, expected, "JIT result diverged from interp"); - - // Two ROLLs per iteration; N iterations before the counter hits 0. - let expected_rolls = (n as u32) * 2; - assert_eq!( - jit_rolls, expected_rolls, - "residual storage_roll executed {jit_rolls}× but the program has \ - exactly {expected_rolls} ROLLs — a walk-vs-native double-execution \ - would inflate this count" - ); + // Two trip counts of different parity: an off-by-one that only shows on + // one parity cannot hide behind the other. + for n in [20i64, 21] { + // First confirm the program is well-formed and the JIT result matches + // the interpreter (the two ROLLs cancel, so acc == 0). + let expected = interp::interpret(&bc, n); + let (got, jit_rolls) = run_jit_counting_rolls(&bc, n); + assert_eq!(got, expected, "JIT result diverged from interp at n={n}"); + + // Two ROLLs per iteration; N iterations before the counter hits 0. + let expected_rolls = (n as u32) * 2; + assert_eq!( + jit_rolls, expected_rolls, + "residual storage_roll executed {jit_rolls}× at n={n} but the \ + program has exactly {expected_rolls} ROLLs — a walk-vs-native \ + double-execution would inflate this count" + ); + } + } + + fn trip_count_bytecode() -> Vec { + vec![ + PUSH, 0, // 0: [0] + // loop @ 2: + PUSH, 1, // 2: [acc, 1] + ADD, // 4: [acc+1] + PICK, 0, // 5: [acc, acc] + PUSHARG, // 7: [acc, acc, n] + LT, // 8: [acc, acc target 2, a back edge + RETURN, // 11: [acc] + ] + } + + /// The same loop seeded at `n` instead of 0, so it leaves one pass above + /// `n` and answers `n + 1`. Without this the gate below would be asserting + /// on a value no reachable program can overshoot, and could not fail. + fn trip_count_overshoot_bytecode() -> Vec { + vec![ + PUSHARG, // 0: [acc=n] + // loop @ 1: + PUSH, 1, // 1: [acc, 1] + ADD, // 3: [acc+1] + PICK, 0, // 4: [acc, acc] + PUSHARG, // 6: [acc, acc, n] + LT, // 7: [acc, acc target 1, a back edge + RETURN, // 10: [acc] + ] + } + + #[test] + fn jit_trip_count_gate() { + let bc = trip_count_bytecode(); + for n in [1001i64, 1002] { + let got = run_jit(&bc, n); + assert_eq!( + got, n, + "the accumulator gains 1 per pass, so the loop ran {got} passes \ + rather than {n}" + ); + assert_eq!( + interp::interpret(&bc, n), + n, + "interpreter disagrees with the expected pass count at n={n}" + ); + } + + // Non-vacuity, in the same test: the overshoot the gate asserts against + // is representable by this program shape and is actually produced. + let over = trip_count_overshoot_bytecode(); + for n in [1001i64, 1002] { + assert_eq!( + run_jit(&over, n), + n + 1, + "the seeded variant must answer n+1, otherwise the gate above \ + asserts on a value nothing can move" + ); + } } #[test] fn jit_no_loop() { let prog = vec![PUSH, 42, RETURN]; - let mut jit = JitTlInterp::new(); - assert_eq!(jit.run(&prog, 0), 42); + assert_eq!(run_jit(&prog, 0), 42); } /// A hot loop whose body issues a recursive `CALL` to a constant-returning @@ -486,27 +773,95 @@ mod tests { ] } - /// Measure whether a portal call inside a hot loop compiles - /// through. Control = pure loop (no call). Probe = leaf call in loop body. - #[test] - fn spike_portal_call_compile_through() { - use core::sync::atomic::Ordering; + /// [`call_loop_bytecode`]'s twin with the recursive `CALL` replaced by the + /// inert `PUSH 3` that returns the same value the subroutine would. + /// + /// The two differ in **exactly two bytes** — `CALL, 10` at offsets 11-12 + /// becomes `PUSH, 3` — so the program length, every jump offset, the loop + /// header, the stack shape at each point and the final result are all + /// identical. The subroutine bytes at offset 23 stay in place and become + /// unreachable, which is what keeps the offsets aligned. + /// + /// That makes this the arm that turns "call_loop compiles nothing" from an + /// observation into evidence: it holds the loop *shape* fixed and varies + /// only the opcode under suspicion. + fn call_loop_inert_twin_bytecode() -> Vec { + let mut bc = call_loop_bytecode(); + // Offsets 11-12: `CALL, 10` -> `PUSH, 3`. + assert_eq!( + (bc[11], bc[12]), + (CALL, 10), + "twin patches the wrong offset: call_loop_bytecode has been edited", + ); + bc[11] = PUSH; + bc[12] = 3; + bc + } - SPIKE_COMPILES.store(0, Ordering::Relaxed); - let bc = sum_bytecode(); - let mut jit = JitTlInterp::new(); - let got = jit.run(&bc, 500); - let control = SPIKE_COMPILES.load(Ordering::Relaxed); - eprintln!("[SPIKE] control sum(500)={got} compiles={control}"); + /// A recursive portal call in a hot loop body blocks tracing, and it is the + /// `CALL` itself that does it — not the loop shape and not a degraded arm. + /// + /// Three arms, because a two-arm reading of `sum` against `call_loop` proves + /// nothing: those are different programs with different loops, so a + /// difference in compile count has many available explanations. The twin is + /// the arm whose outcome is *known* to differ by one opcode. + /// + /// | arm | program | compiles | + /// |---|---|---| + /// | control | `sum(500)` — a loop known to trace | 1 | + /// | twin | `call_loop` with `CALL` -> `PUSH 3` | 1 | + /// | probe | `call_loop` | **0** | + /// + /// All three compute their expected value, so the interpreter is answering + /// correctly throughout and the difference is purely in the JIT tier. + /// + /// The twin's `compiles >= 1` is the load-bearing assertion. Without it, + /// the probe's zero is equally explained by "this loop shape cannot trace", + /// and that is exactly the confusion `jit_trip_count_gate` fell into — there + /// a `PUSHARG` *inside* the loop was the cause, and the loop looked innocent. + /// + /// `recursive_portal_call!` appears in **no other example crate**, so this + /// is the only place in the corpus where the portal-call path is exercised + /// at all. Its being untraceable is therefore invisible everywhere else. + /// + /// The probe's `0` is pinned deliberately. It records a gap, not a desired + /// property: when the portal call starts compiling this assertion fails, + /// which is the intended signal to come back and re-measure rather than a + /// regression. Do not relax it to `>= 0`, which would assert nothing. + #[test] + fn jit_portal_call_in_loop_body_blocks_tracing() { + let (control_got, control, ..) = compile_probe(&sum_bytecode(), 500); + assert_eq!(control_got, 125250, "control still sums 1..=500"); + assert!( + control >= 1, + "control loop compiled nothing — the JIT tier is dead and this \ + experiment cannot discriminate anything (compiles={control})", + ); - SPIKE_COMPILES.store(0, Ordering::Relaxed); - let bc = call_loop_bytecode(); - let mut jit = JitTlInterp::new(); - let got = jit.run(&bc, 500); - let probe = SPIKE_COMPILES.load(Ordering::Relaxed); - eprintln!("[SPIKE] probe call_loop(500)={got} compiles={probe}"); + let twin_bc = call_loop_inert_twin_bytecode(); + let (twin_got, twin, ..) = compile_probe(&twin_bc, 500); + assert_eq!( + twin_got, + interp::interpret(&twin_bc, 500), + "twin agrees with the interpreter", + ); + assert_eq!(twin_got, 1500, "twin computes the same 3*N as call_loop"); + assert!( + twin >= 1, + "the loop SHAPE compiles when the only change is CALL -> PUSH 3; \ + if this fails the probe's zero is not attributable to the portal \ + call (compiles={twin})", + ); - assert_eq!(got, 1500, "leaf-call loop still computes 3*N"); + let probe_bc = call_loop_bytecode(); + let (probe_got, probe, ..) = compile_probe(&probe_bc, 500); + assert_eq!(probe_got, 1500, "leaf-call loop still computes 3*N"); + assert_eq!( + probe, 0, + "a recursive portal call in the loop body is expected to block \ + tracing today; if this now compiles, the portal-call path has \ + started working — re-measure and update this gate", + ); } #[test] @@ -516,20 +871,11 @@ mod tests { assert_eq!(interp::interpret(&bc, 4), 12); for a in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 50, 100] { let expected = interp::interpret(&bc, a); - let mut jit = JitTlInterp::new(); - let got = jit.run(&bc, a); + let got = run_jit(&bc, a); assert_eq!(got, expected, "recursive-call mismatch for a={a}"); } } - /// #184 recursive CALL_ASSEMBLER portal entry: the macro-generated - /// `JitCodeSym::recursive_fresh_entry_reds` yields fresh-frame reds in - /// `extract_live` order — stackpos zeroed, then a fresh vable identity Ref - /// distinct from the caller's. The stack is re-allocated at the caller's - /// captured capacity (read from the sym's `stack_len_value` cache), but - /// that capacity is NOT a red: a virtualizable is named once, and its array - /// lengths are read off the live object (`virtualizable.py:150-153`). It is - /// checked here on the returned owner instead. #[test] fn recursive_fresh_entry_reds_layout() { use majit_metainterp::{JitCodeSym as _, JitState as _}; @@ -569,11 +915,6 @@ mod tests { assert_eq!(fresh.stackpos, 0, "fresh frame starts empty"); } - /// #184 S3f-1: the host alloc/free targets the recursive dispatcher records - /// as residual `CallR`/`CallN` for the compiled caller loop. Exercises the - /// macro-generated `extern "C"` pair directly through the `JitCodeSym` seam: - /// `alloc(cap)` returns a fresh `Box::into_raw`-ed `TlState` (stackpos 0, - /// `stack` of length `cap`, all zero), `free` drops it without crashing. #[test] fn recursive_fresh_alloc_free_roundtrip() { use majit_metainterp::{JitCodeSym as _, JitState as _}; @@ -623,8 +964,7 @@ mod tests { let bc = sum_bytecode(); for a in [1, 2, 3, 4, 5, 10, 20, 50, 100, 500, 1000] { let expected = interp::interpret(&bc, a); - let mut jit = JitTlInterp::new(); - let got = jit.run(&bc, a); + let got = run_jit(&bc, a); assert_eq!(got, expected, "mismatch for a={a}"); } } @@ -632,10 +972,9 @@ mod tests { #[test] fn jit_bridge_exercise() { let bc = sum_bytecode(); - let mut jit = JitTlInterp::new(); for a in [3, 5, 10, 20, 50, 100] { let expected = interp::interpret(&bc, a); - let got = jit.run(&bc, a); + let got = run_jit(&bc, a); assert_eq!(got, expected, "mismatch for a={a}"); } } @@ -685,8 +1024,7 @@ mod tests { // (`> 50`) path guard-fails for the lower half of every run. for a in [3, 5, 49, 50, 51, 60, 100, 200] { let expected = interp::interpret(&bc, a); - let mut jit = JitTlInterp::new(); - let got = jit.run(&bc, a); + let got = run_jit(&bc, a); assert_eq!(got, expected, "mismatch for a={a}"); } } diff --git a/majit/examples/tla/src/jit_interp.rs b/majit/examples/tla/src/jit_interp.rs index f5aaf7024d3..966a347826b 100644 --- a/majit/examples/tla/src/jit_interp.rs +++ b/majit/examples/tla/src/jit_interp.rs @@ -5,9 +5,40 @@ /// /// Greens: [pc, bytecode] /// Reds: [stackpos, stack] (tracked via state_fields) +/// +/// `greens = [pc, program]` is load-bearing, not documentation: with the list +/// left empty the `CONST_INT` and `JUMP_IF` arms — the two that read an operand +/// out of `program` — do not lower and are emitted as abort stubs, so every +/// trace of the countdown loop aborts and nothing is ever compiled. Declaring +/// the greens is also what gives the merge point a green pc to report, which the +/// `; state` close needs to name a resume position. +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; pub type Bytecode = [u8]; +/// Hot loops majit compiled. The only positive evidence the JIT tier is alive: +/// a green suite, byte-identical output and an exact trip count are all +/// satisfied by an interpreter answering alone. Before `greens = [pc, program]` +/// was declared this example ran its whole suite green at `Traces compiled: 0`. +pub static COMPILES: AtomicUsize = AtomicUsize::new(0); + +/// Ops in the last compiled loop body after optimization. +/// +/// `COMPILES > 0` is necessary but NOT sufficient: an entirely empty dispatch +/// still compiles a trace — one whose whole optimized body is `Finish()`, i.e. +/// `ops_after == 1`. A compile counter counts TRACES, not WORK. This is the +/// term that separates a compiled loop from a compiled nothing. +pub static LAST_OPS_AFTER: AtomicUsize = AtomicUsize::new(0); + +/// Shape of the last compiled loop body — see [`majit_metainterp::LoopBodyShape`]. +/// +/// Held as two flags rather than the struct itself so the recording stays +/// lock-free on the compile path; the probe rebuilds the struct inside the same +/// lock window it reads the counters in, because this is as process-global as +/// they are. +pub static LAST_HAS_JUMP: AtomicBool = AtomicBool::new(false); +pub static LAST_ALWAYS_FAILS: AtomicBool = AtomicBool::new(false); + #[expect( dead_code, reason = "the jit_interp macro resolves bytecode reads through this trait surface" @@ -46,6 +77,7 @@ const NEWSTR: u8 = 7; #[majit_macros::jit_interp( state = TlaState, env = Bytecode, + greens = [pc, program], state_fields = { stackpos: int, stack: [int; virt], @@ -55,6 +87,13 @@ const NEWSTR: u8 = 7; pub fn mainloop(program: &Bytecode, initial_value: i64, threshold: u32) -> i64 { let mut driver: majit_metainterp::JitDriver = majit_metainterp::JitDriver::new(threshold); + driver.set_on_compile_loop(|_green_key, _ops_before, ops_after, opcodes| { + COMPILES.fetch_add(1, Ordering::Relaxed); + LAST_OPS_AFTER.store(ops_after, Ordering::Relaxed); + let shape = majit_metainterp::LoopBodyShape::of(opcodes); + LAST_HAS_JUMP.store(shape.has_jump, Ordering::Relaxed); + LAST_ALWAYS_FAILS.store(shape.has_always_fails, Ordering::Relaxed); + }); let mut pc: usize = 0; let stacksize: i32 = 0; let mut state = TlaState { @@ -75,7 +114,11 @@ pub fn mainloop(program: &Bytecode, initial_value: i64, threshold: u32) -> i64 { } while pc < program.len() { - jit_merge_point!(); + // `; state` selects the single-executor close: the walk's final state is + // transferred into `state` here and the native loop resumes at the close + // pc, instead of discarding the walk outcome and re-running the circuit + // the walk already executed. + jit_merge_point!(driver, program, pc; state); let opcode = program[pc]; pc += 1; @@ -119,17 +162,20 @@ pub fn mainloop(program: &Bytecode, initial_value: i64, threshold: u32) -> i64 { continue; } } - NEWSTR => { - // String operations cause trace abort — RPython would - // guard-fail on non-int type (W_StringObject vs W_IntObject). - pc += 1; - break; - } + NEWSTR => break, RETURN => break, _ => {} } } + // Also reached when the merge point broke out on a walk that already ran the + // terminal opcode. That `break` precedes the merge point's own `pc` handoff, + // so `pc` here still names the position the walk started from and nothing + // below may read it; the result is rebuilt from `state` alone. `stackpos` is + // a scalar state field and `stack` a virtualizable array field, so + // `writeback_scalar_state_fields` / `writeback_virt_array_state_fields` have + // already pushed the walk-final values into native `state` by here — no + // separate `ret` field is needed. state.stackpos -= 1; state.stack[state.stackpos as usize] } @@ -174,20 +220,59 @@ mod tests { vec![DUP, CONST_INT, 1, SUB, DUP, JUMP_IF, 1, POP, RETURN] } + /// [`COMPILES`] is process-global, so under the default parallel libtest + /// runner a concurrent `run` lands inside [`compile_probe`]'s + /// store/run/load window and the probe reads someone else's compile. The + /// lock therefore covers *every* call that can compile, not just the + /// probe's own — [`run_jit`] and [`compile_probe`] are the only two ways a + /// test may enter the JIT, and neither may call the other (a plain mutex + /// re-entered on one thread deadlocks). + static PROBE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + + /// For tests that assert only on the result. They still compile, so they + /// must not run inside the probe's window. See [`PROBE_LOCK`]. + fn run_jit(bc: &[u8], arg: i64) -> i64 { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + let mut jit = JitTlaInterp::new(); + jit.run(bc, interp::WObject::Int(arg)).int_value() + } + + /// Run with both counters reset, returning `(result, compiles, ops_after)`. + /// + /// [`LAST_OPS_AFTER`] is read here rather than at the call site, and reset + /// here rather than nowhere. Both counters are process-global, so both need + /// the same treatment [`PROBE_LOCK`] exists to give [`COMPILES`]: a load + /// taken after the guard drops can observe a concurrent test's compile, and + /// a counter that is never stored to zero retains whatever the last compile + /// anywhere in the process left behind. Unreset, a zero from this probe is + /// indistinguishable from an inherited value. + fn compile_probe(bc: &[u8], arg: i64) -> (i64, usize, usize, majit_metainterp::LoopBodyShape) { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + COMPILES.store(0, Ordering::Relaxed); + LAST_OPS_AFTER.store(0, Ordering::Relaxed); + LAST_HAS_JUMP.store(false, Ordering::Relaxed); + LAST_ALWAYS_FAILS.store(false, Ordering::Relaxed); + let mut jit = JitTlaInterp::new(); + let got = jit.run(bc, interp::WObject::Int(arg)).int_value(); + ( + got, + COMPILES.load(Ordering::Relaxed), + LAST_OPS_AFTER.load(Ordering::Relaxed), + majit_metainterp::LoopBodyShape { + has_jump: LAST_HAS_JUMP.load(Ordering::Relaxed), + has_always_fails: LAST_ALWAYS_FAILS.load(Ordering::Relaxed), + }, + ) + } + #[test] fn jit_countdown_5() { - let bc = countdown_bytecode(); - let mut jit = JitTlaInterp::new(); - let result = jit.run(&bc, interp::WObject::Int(5)); - assert_eq!(result.int_value(), 5); + assert_eq!(run_jit(&countdown_bytecode(), 5), 5); } #[test] fn jit_countdown_30() { - let bc = countdown_bytecode(); - let mut jit = JitTlaInterp::new(); - let result = jit.run(&bc, interp::WObject::Int(30)); - assert_eq!(result.int_value(), 30); + assert_eq!(run_jit(&countdown_bytecode(), 30), 30); } #[test] @@ -195,17 +280,183 @@ mod tests { let bc = countdown_bytecode(); for n in [1, 2, 5, 10, 20, 30, 40] { let expected = interp::run(&bc, interp::WObject::Int(n)); - let mut jit = JitTlaInterp::new(); - let got = jit.run(&bc, interp::WObject::Int(n)); - assert_eq!(got.int_value(), expected.int_value(), "mismatch for n={n}"); + let got = run_jit(&bc, n); + assert_eq!(got, expected.int_value(), "mismatch for n={n}"); + } + } + + /// The JIT tier is alive — the one property no assertion on a *result* can + /// establish. + /// + /// Both halves are needed and neither implies the other: + /// + /// 1. `Traces compiled` 0 → non-zero. A green suite, byte-identical output + /// and even an exact absolute trip count are all satisfied by the + /// interpreter answering alone; this example ran its entire suite green + /// at `Traces compiled: 0` before `greens = [pc, program]` was declared. + /// 2. `degraded_dispatch_arms()` empty. An arm whose body did not lower is + /// an abort stub, so any trace reaching it aborts. The list is populated + /// at dispatch-JitCode install time and names the arm, which an abort + /// count cannot: `trace action at pc=N -> Abort` reports the trace-START + /// pc, not the arm that caused it. + /// + /// The list is a process-wide registry, so it is filtered to this machine's + /// `state = TlaState`. It is read *after* a run, because nothing installs + /// the dispatch JitCode until the interpreter is entered. + /// + /// The subject is `count_to`, not `countdown`, so that the *result* assertion + /// is itself an absolute trip count: `count_to(n)` returns the number of + /// passes, while `countdown(n)` returns its own input whatever the trip count + /// (`jit_trip_count_gate` states why). One compiled artifact then carries + /// both properties — a body that ran the wrong number of times fails here on + /// the result, not only in a separate test against a separately compiled loop. + #[test] + fn jit_tier_is_alive() { + const N: i64 = 1001; + let (got, compiles, ops_after, shape) = compile_probe(&count_to_bytecode(N), 0); + // The body actually closes a loop — see `LoopBodyShape`. A compile + // count and an op count together still accept a body that bails out on + // its first pass; this is the term that does not. Sound HERE because + // this fixture loops: on a straight-line subject a `Jump`-less body is + // the right answer, not a defect. + assert!( + shape.closes_a_loop(), + "compiled {ops_after} ops but the body {} ({shape:?})", + shape.why_not().unwrap_or("closes a loop") + ); + assert_eq!( + got, N, + "count_to({N}) = {got}, so the loop the tier assertions below \ + describe ran {got} passes rather than {N}" + ); + + let degraded: Vec<&str> = majit_metainterp::degraded_dispatch_arms() + .iter() + .filter(|a| a.interp == "TlaState") + .map(|a| a.arm) + .collect(); + // `greens = [pc, program]` means a degraded arm aborts only traces that + // reach that arm, rather than disabling the whole dispatch loop. This + // assertion pins the stronger property that every exercised arm lowers. + assert!( + degraded.is_empty(), + "dispatch arms degraded to abort stubs: {degraded:?} — every trace \ + that reaches one aborts. With `greens = [pc, program]` declared, \ + that costs the traces reaching those arms, not the dispatch loop \ + as a whole; the arms this crate exercises are still expected to \ + lower, so a non-empty set is a regression and not a trade-off" + ); + + // Zero-vs-nonzero is the property; a later change that legitimately + // mints more than one artifact is not this regression. + assert!( + compiles >= 1, + "count_to({N}) compiled {compiles} loops — the JIT tier is inert and \ + the interpreter is answering alone, which every other assertion in \ + this file would still pass" + ); + assert_eq!( + ops_after, 24, + "compiled loop body is {ops_after} ops, not the pinned 24 — a value \ + of 1 means the body is a bare `Finish()`, i.e. a dispatch that \ + lowered nothing at all" + ); + println!( + "[tier-alive] count_to({N}) = {got}, compiled {compiles} loop(s) of {ops_after} ops, 0 degraded arms" + ); + } + + /// Leave `top - value` on the stack. `CONST_INT`'s operand is one byte, so + /// anything above 255 is subtracted in 255-sized bites. + fn sub_const(code: &mut Vec, mut value: i64) { + assert!(value > 0); + while value > 0 { + let bite = value.min(255); + code.push(CONST_INT); + code.push(bite as u8); + code.push(SUB); + value -= bite; + } + } + + /// Count up to `n`, one increment per pass, and return the counter. + /// + /// The exit test has TWO roots (`n` and `n + 1`) rather than one. TLA has no + /// comparison opcode and no way to reach below the top of the stack, so the + /// counter has to double as the loop condition — and a single-root test + /// (`while v != n`) pins the answer to `n` no matter how many passes ran, + /// which is exactly the insensitivity this gate exists to remove. Accepting + /// `n + 1` as well makes one extra pass return `n + 1` instead of diverging. + fn count_to_bytecode(n: i64) -> Vec { + let mut code = Vec::new(); + // loop: (pc 0) — v += 1 + code.push(CONST_INT); + code.push(1); + code.push(ADD); + // if v != n goto check2 + code.push(DUP); + sub_const(&mut code, n); + code.push(JUMP_IF); + let check2_operand = code.len(); + code.push(0); + code.push(RETURN); + // check2: if v != n + 1 goto loop + let check2 = code.len(); + code[check2_operand] = u8::try_from(check2).expect("jump target must fit a byte"); + code.push(DUP); + sub_const(&mut code, n + 1); + code.push(JUMP_IF); + code.push(0); + code.push(RETURN); + code + } + + /// Absolute trip-count gate on the JIT path. + /// + /// `count_to` adds 1 to the counter once per pass and leaves the loop as soon + /// as it reaches `n` (or `n + 1`), so the returned value names the number of + /// passes exactly: `n` passes answer `n`, and one extra pass answers `n + 1`. + /// + /// Agreement with `interp::run` would not settle this — the two run the same + /// program — and neither would a byte-identical before/after output + /// comparison: a duplicated iteration of the *compiled* loop is invisible to + /// any check that does not assert an absolute count. `jit_countdown_30` is + /// precisely such a check: it returns its own input whatever the trip count. + /// + /// Two lengths of different parity, because a peeled first iteration plus an + /// even/odd body count is exactly the shape an off-by-one hides in. + #[test] + fn jit_trip_count_gate() { + for n in [1001i64, 1002] { + let bc = count_to_bytecode(n); + let got = run_jit(&bc, 0); + assert_eq!( + got, n, + "count_to({n}) = {got}, so the loop ran {got} passes rather than \ + {n} — an off-by-one trip count is the signature of a terminal \ + arm whose exit the trace dropped" + ); + println!("[trip-count] count_to({n}) = {got} — exactly {n} passes"); + + // Non-vacuity: seeding the counter at `n` leaves exactly one pass to + // run, which overshoots onto the second root the same way a + // duplicated compiled iteration would. It answers `n + 1`, so the + // assertion above is one the program can fail rather than one pinned + // to `n` by its own exit condition. + let overshot = run_jit(&bc, n); + assert_eq!( + overshot, + n + 1, + "the second root is unreachable, so the gate above cannot \ + distinguish {n} passes from {} passes", + n + 1 + ); } } #[test] fn jit_no_loop() { let prog = vec![RETURN]; - let mut jit = JitTlaInterp::new(); - let result = jit.run(&prog, interp::WObject::Int(42)); - assert_eq!(result.int_value(), 42); + assert_eq!(run_jit(&prog, 42), 42); } } diff --git a/majit/examples/tlc/src/jit_interp.rs b/majit/examples/tlc/src/jit_interp.rs index a75841545b2..bf8669ca219 100644 --- a/majit/examples/tlc/src/jit_interp.rs +++ b/majit/examples/tlc/src/jit_interp.rs @@ -5,18 +5,50 @@ /// `state_fields = { stackpos: int, stack: [int; virt] }` to mirror the /// virtualizable-stack shape used by tl.py/tla.py for the integer-only trace. /// -/// Greens: [pc] +/// Greens: [pc, program] /// Reds: [stackpos, stack] /// +/// `greens = [pc, program]` is load-bearing, not documentation. With the list +/// left empty, every arm that reads an operand out of `program` — PUSH, PICK, +/// PUT, BR, BR_COND, PUSHARG — failed to lower and was emitted as an abort stub, +/// so every trace aborted and `Traces compiled` stayed 0 for the whole suite: +/// the JIT compiled nothing and the tests were green only because the legacy +/// merge point discards the walk and lets the native loop answer. Declaring the +/// greens leaves ROLL and PUSHARG as the only degraded arms. +/// /// Only integer-stack opcodes are traced. Object opcodes (NIL, CONS, CAR, CDR, /// NEW, GETATTR, SETATTR, SEND) cause guard failure in RPython and are absent /// from this function, matching that behavior. use crate::interp::{self, ConstantPool}; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; // ── State ── pub type Bytecode = [u8]; +/// Hot loops majit compiled. The only positive evidence the JIT tier is alive: +/// a green suite, byte-identical output and an exact absolute trip count are all +/// satisfied by the interpreter answering alone. This example ran 22/22 green at +/// `Traces compiled: 0` for the whole suite before `greens` was declared. +pub static COMPILES: AtomicUsize = AtomicUsize::new(0); + +/// Ops in the last compiled loop body after optimization. +/// +/// `COMPILES > 0` is necessary but NOT sufficient: an entirely empty dispatch +/// still compiles a trace — one whose whole optimized body is `Finish()`, i.e. +/// `ops_after == 1`. A compile counter counts TRACES, not WORK. This is the +/// term that separates a compiled loop from a compiled nothing. +pub static LAST_OPS_AFTER: AtomicUsize = AtomicUsize::new(0); + +/// Shape of the last compiled loop body — see [`majit_metainterp::LoopBodyShape`]. +/// +/// Held as two flags rather than the struct itself so the recording stays +/// lock-free on the compile path; the probe rebuilds the struct inside the same +/// lock window it reads the counters in, because this is as process-global as +/// they are. +pub static LAST_HAS_JUMP: AtomicBool = AtomicBool::new(false); +pub static LAST_ALWAYS_FAILS: AtomicBool = AtomicBool::new(false); + #[expect( dead_code, reason = "the jit_interp macro resolves bytecode reads through this trait surface" @@ -36,28 +68,11 @@ struct TlcState { stack: Vec, } -/// Stack rotation — residual CALL in the trace. -/// -/// RPython parity: tlc.py:284 inlines `stack.insert(i, stack.pop())` and -/// `stack.append(stack.pop(i))`. Under the rpython translator those list -/// operations lower to `ll_list_insert` / `ll_list_pop_at` residual helpers -/// (rpython/rtyper/lltypesystem/rlist.py), so the RPython trace ends up with -/// two residual CALLs at this opcode. pyre's `#[jit_interp]` macro does not -/// model state-field array ops with `state.stack.insert(i, ...)` / `.pop(i)`, -/// so we hoist the shuffle into one `#[dont_look_inside]` residual helper; -/// this is a packaging adaptation — the trace-level shape (single residual -/// CALL on ROLL) is strictly simpler than RPython's two-residual sequence, -/// and reverting to inline would require a state-field-array `insert`/`pop_at` -/// lowering that the macro does not yet provide. +/// Rotates the live stack through a residual call. /// -/// The hoisted residual is MAY-FORCE. Upstream reaches that row through -/// `call.py:287-289 getcalldescr`, which consults `virtualizable_analyzer` -/// before `_canraise`; there it does not fire, because tlc.py's `Frame` holds a -/// plain list. This port models the stack as `stack: [int; virt]` (see the -/// module header), so the array IS virtualizable here and a residual writing it -/// through a raw base pointer takes that row. `#[dont_look_inside]` would -/// instead assert `EF_CAN_RAISE` with an empty write set, which the mutation -/// contradicts. +/// This mirrors the RPython TLC list operations. Because it mutates the +/// virtualizable array through a raw pointer, the call is may-force and the +/// dispatch arm remains degraded until the trace can reload those effects. #[majit_macros::jit_may_force] extern "C" fn tlc_roll(stack_ptr: usize, stackpos: i64, r: i64) { let stack = unsafe { std::slice::from_raw_parts_mut(stack_ptr as *mut i64, stackpos as usize) }; @@ -117,6 +132,7 @@ const DEFAULT_THRESHOLD: u32 = 3; #[majit_macros::jit_interp( state = TlcState, env = Bytecode, + greens = [pc, program], auto_calls = true, state_fields = { stackpos: int, @@ -127,6 +143,13 @@ const DEFAULT_THRESHOLD: u32 = 3; pub fn mainloop(program: &Bytecode, inputarg: i64, threshold: u32) -> i64 { let mut driver: majit_metainterp::JitDriver = majit_metainterp::JitDriver::new(threshold); + driver.set_on_compile_loop(|_green_key, _ops_before, ops_after, opcodes| { + COMPILES.fetch_add(1, Ordering::Relaxed); + LAST_OPS_AFTER.store(ops_after, Ordering::Relaxed); + let shape = majit_metainterp::LoopBodyShape::of(opcodes); + LAST_HAS_JUMP.store(shape.has_jump, Ordering::Relaxed); + LAST_ALWAYS_FAILS.store(shape.has_always_fails, Ordering::Relaxed); + }); let mut pc: usize = 0; let stacksize: i32 = 0; // tlc.py:223 `self.stack = []` is a plain dynamic Python list (no @@ -153,7 +176,26 @@ pub fn mainloop(program: &Bytecode, inputarg: i64, threshold: u32) -> i64 { } while pc < program.len() { - jit_merge_point!(); + // The `; state` single-pass form. Reaching it took two fixes, because + // this file's `ROLL` and `PUSHARG` arms are abort stubs (see + // `jit_tier_is_alive`) and its post-loop expression stores: + // + // 1. A degraded-stub abort resumed at `opcode_pc + 1` — the shared + // prologue advance below, not the instruction width — so the + // aborting opcode applied nothing and was then skipped. Fixed by + // resuming at the opcode's own boundary; the regression test is + // `jit_interp_degraded_stub_abort_resume.rs`. + // 2. The walk ran this function's trailing expression, whose `stackpos` + // store the write-back then pushed into native `state` for the + // post-loop code to apply a second time — `fibo(7)` answered 8, the + // entry one below the right one, instead of 13. Fixed by keeping a + // *storing* trailing expression out of the walk; the regression test + // is `jit_interp_halt_arm_post_loop_expression.rs`. + // + // Lowering the `ROLL` arm is still open (it needs a macro spelling for + // the base pointer of a `[int; virt]` state-field array), but it is no + // longer what blocks this merge point. + jit_merge_point!(driver, program, pc; state); let opcode = program[pc]; pc += 1; @@ -274,11 +316,23 @@ pub fn mainloop(program: &Bytecode, inputarg: i64, threshold: u32) -> i64 { pc = target; continue; } + // Bare `break` bodies, never `{ …; break }`: `classify.rs` + // `is_break_expr` requires the arm body to be exactly `break`, so a + // composite body classifies `Lowerable` and its tail `break` reaches + // `lower_stmt_fallback`, which guards an enclosed `return` but not an + // enclosed `break` — the statement is inert and is silently dropped, + // leaving the lowered arm to fall through to the dispatch back-edge. RETURN => break, _ => break, } } + // Reads `state` and never `pc`, which is what the single-executor merge + // point will need when it lands: its `break` precedes the `pc` handoff, so + // `pc` there still names the position the walk started from. `stackpos` is a + // scalar state field and `stack` a virtualizable array field, so the + // walk-final values arrive through `writeback_scalar_state_fields` / + // `writeback_virt_array_state_fields` — no `ret` field is needed here. if state.stackpos == 0 { 0 } else { @@ -317,6 +371,7 @@ impl JitTlcInterp { mod tests { use super::*; use crate::interp; + use majit_metainterp::{RefusalKind, refusal_kind}; /// Fibonacci using ROLL -- pure integer loop, good JIT candidate. fn fibo_bytecode(pool: &mut ConstantPool) -> Vec { @@ -326,12 +381,142 @@ mod tests { ) } + /// [`COMPILES`] is process-global, so under the default parallel libtest + /// runner a concurrent run lands inside [`compile_probe`]'s store/run/load + /// window and the probe reads someone else's compile. The lock therefore + /// covers *every* call that can compile — [`run_jit`] and [`compile_probe`] + /// are the only two ways a test may enter the JIT, and neither may call the + /// other (a plain mutex re-entered on one thread deadlocks). + static PROBE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + + /// For tests that assert only on the result. They still compile, so they + /// must not run inside the probe's window. See [`PROBE_LOCK`]. + fn run_jit(bc: &[u8], arg: i64, pool: &ConstantPool) -> i64 { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + let mut jit = JitTlcInterp::new(); + jit.run(bc, arg, pool) + } + + /// Run with both counters reset, returning `(result, compiles, ops_after)`. + /// + /// [`LAST_OPS_AFTER`] is read here rather than at the call site, and reset + /// here rather than nowhere. Both counters are process-global, so both need + /// the same treatment [`PROBE_LOCK`] exists to give [`COMPILES`]: a load + /// taken after the guard drops can observe a concurrent test's compile, and + /// a counter that is never stored to zero retains whatever the last compile + /// anywhere in the process left behind. Unreset, a zero from this probe is + /// indistinguishable from an inherited value. + fn compile_probe( + bc: &[u8], + arg: i64, + pool: &ConstantPool, + ) -> (i64, usize, usize, majit_metainterp::LoopBodyShape) { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + COMPILES.store(0, Ordering::Relaxed); + LAST_OPS_AFTER.store(0, Ordering::Relaxed); + LAST_HAS_JUMP.store(false, Ordering::Relaxed); + LAST_ALWAYS_FAILS.store(false, Ordering::Relaxed); + let mut jit = JitTlcInterp::new(); + let got = jit.run(bc, arg, pool); + ( + got, + COMPILES.load(Ordering::Relaxed), + LAST_OPS_AFTER.load(Ordering::Relaxed), + majit_metainterp::LoopBodyShape { + has_jump: LAST_HAS_JUMP.load(Ordering::Relaxed), + has_always_fails: LAST_ALWAYS_FAILS.load(Ordering::Relaxed), + }, + ) + } + #[test] fn jit_fibo_7() { let mut pool = ConstantPool::new(); let bc = fibo_bytecode(&mut pool); - let mut jit = JitTlcInterp::new(); - assert_eq!(jit.run(&bc, 7, &pool), 13); + assert_eq!(run_jit(&bc, 7, &pool), 13); + } + + #[test] + fn jit_tier_is_alive() { + let mut pool = ConstantPool::new(); + let bc = countdown_bytecode(&mut pool); + let (got, compiles, ops_after, shape) = compile_probe(&bc, 100, &pool); + // The body actually closes a loop — see `LoopBodyShape`. A compile + // count and an op count together still accept a body that bails out on + // its first pass; this is the term that does not. Sound HERE because + // this fixture loops: on a straight-line subject a `Jump`-less body is + // the right answer, not a defect. + assert!( + shape.closes_a_loop(), + "compiled {ops_after} ops but the body {} ({shape:?})", + shape.why_not().unwrap_or("closes a loop") + ); + assert_eq!(got, 0, "countdown(100) must still answer 0"); + + let mut tlc_arms: Vec<_> = majit_metainterp::degraded_dispatch_arms() + .into_iter() + .filter(|a| a.interp == "TlcState") + .collect(); + tlc_arms.sort_unstable_by_key(|a| a.arm); + let degraded: Vec<&str> = tlc_arms.iter().map(|a| a.arm).collect(); + assert_eq!( + degraded, + ["PUSHARG", "ROLL"], + "the degraded-arm set moved. A NEW name means an arm silently \ + stopped lowering and every trace reaching it now aborts; a MISSING \ + name means that arm lowers again, so the abort that blocks the \ + `; state` conversion may be gone — re-check the merge point" + ); + + let causes: Vec<(&str, RefusalKind)> = tlc_arms + .iter() + .map(|a| (a.arm, refusal_kind(a.reason))) + .collect(); + for a in &tlc_arms { + eprintln!( + "[cause] {} {:?} — {}", + a.arm, + refusal_kind(a.reason), + a.reason + ); + } + assert_eq!( + causes, + [ + ("PUSHARG", RefusalKind::UnlowerableStmt), + ("ROLL", RefusalKind::GreenWriteback), + ], + "an arm still degrades but a different mechanism is refusing it. \ + `RefusalKind::Unclassified` on either side means majit grew a \ + refusal family the classifier does not know — add it in \ + `majit-metainterp`, do not re-record this pin" + ); + + assert!( + tlc_arms[0].reason.contains("inputarg"), + "PUSHARG's refusal no longer names the loop-external input it \ + stores: {}", + tlc_arms[0].reason + ); + + // Zero-vs-nonzero is the property; a later change that legitimately + // mints more than one artifact is not this regression. + assert!( + compiles >= 1, + "countdown(100) compiled {compiles} loops — the JIT tier is inert and \ + the interpreter is answering alone, which every other assertion in \ + this file would still pass" + ); + assert_eq!( + ops_after, 10, + "compiled loop body is {ops_after} ops, not the pinned 10 — a value \ + of 1 means the body is a bare `Finish()`, i.e. a dispatch that \ + lowered nothing at all" + ); + println!( + "[tier-alive] countdown(100) = {got}, compiled {compiles} loop(s) of \ + {ops_after} ops, degraded = {degraded:?}" + ); } #[test] @@ -340,30 +525,132 @@ mod tests { let bc = fibo_bytecode(&mut pool); for n in [1, 2, 3, 5, 7, 10, 15] { let expected = interp::interp(&bc, 0, n, &pool); - let mut jit = JitTlcInterp::new(); - let got = jit.run(&bc, n, &pool); + let got = run_jit(&bc, n, &pool); assert_eq!(got, expected, "fibo mismatch for n={n}"); } } /// Simple integer countdown loop (no object ops). + fn countdown_bytecode(pool: &mut ConstantPool) -> Vec { + interp::compile( + " + PUSHARG # [n] + loop: + PUSH 1 + SUB # [n-1] + PICK 0 # [n-1, n-1] + BR_COND loop # [n-1] if n-1 != 0 + RETURN + ", + pool, + ) + } + #[test] fn jit_countdown() { + let mut pool = ConstantPool::new(); + let bc = countdown_bytecode(&mut pool); + assert_eq!(run_jit(&bc, 100, &pool), 0); + } + + #[test] + fn jit_operand_less_degraded_arm_runs_every_pass() { + for n in [100i64, 101] { + let mut pool = ConstantPool::new(); + let bc = interp::compile( + &format!( + " + PUSH 0 # [acc] + loop: + PUSHARG # [acc, 1] + ADD # [acc+1] + PICK 0 # [acc, acc] + PUSH {n} # [acc, acc, n] + LT # [acc, acc < n] + BR_COND loop # [acc] + RETURN + " + ), + &mut pool, + ); + let got = run_jit(&bc, 1, &pool); + assert_eq!( + got, n, + "count-by-PUSHARG returned {got}, not {n} — a degraded arm's \ + abort dropped the opcode, so a pass ran without its increment" + ); + } + } + + /// Absolute trip-count gate on the JIT path. + /// + /// The accumulator gains 1 once per pass and the loop runs while it is still + /// below `n`, so the returned value names the number of passes exactly: `n` + /// passes answer `n`, and one extra pass answers `n + 1`. `LT` (rather than + /// `NE`) is what makes the overshoot observable instead of divergent. + /// + /// Agreement with `interp::interp` would not settle this — the two run the + /// same program — and neither would a byte-identical before/after output + /// comparison: a duplicated iteration of the *compiled* loop is invisible to + /// any check that does not assert an absolute count. + /// + /// Two lengths of different parity, because a peeled first iteration plus an + /// even/odd body count is exactly the shape an off-by-one hides in. + #[test] + fn jit_trip_count_gate() { let mut pool = ConstantPool::new(); let bc = interp::compile( " PUSHARG # [n] + PUSH 0 # [n, acc] + loop: + PUSH 1 # [n, acc, 1] + ADD # [n, acc+1] + PICK 0 # [n, acc, acc] + PICK 2 # [n, acc, acc, n] + LT # [n, acc, acc < n] + BR_COND loop # [n, acc] + RETURN + ", + &mut pool, + ); + for n in [1001i64, 1002] { + let got = run_jit(&bc, n, &pool); + assert_eq!( + got, n, + "count_to({n}) = {got}, so the loop ran {got} passes rather than \ + {n} — an off-by-one trip count is the signature of a terminal \ + arm whose exit the trace dropped" + ); + println!("[trip-count] count_to({n}) = {got} — exactly {n} passes"); + } + + // Non-vacuity: the same loop with the accumulator seeded at `n` instead + // of 0 runs one pass, overshoots to `n + 1`, and `LT` lets it out — + // exactly what a duplicated compiled iteration would do at the end. So + // the assertion above is one the program can fail, not one pinned to `n` + // by its own exit condition. + let overshoot = interp::compile( + " + PUSHARG # [n] + PUSHARG # [n, acc=n] loop: PUSH 1 - SUB # [n-1] - PICK 0 # [n-1, n-1] - BR_COND loop # [n-1] if n-1 != 0 + ADD # [n, acc+1] + PICK 0 + PICK 2 + LT # [n, acc, acc < n] + BR_COND loop RETURN ", &mut pool, ); - let mut jit = JitTlcInterp::new(); - assert_eq!(jit.run(&bc, 100, &pool), 0); + let got = run_jit(&overshoot, 1001, &pool); + assert_eq!( + got, 1002, + "the loop cannot leave above `n`, so the gate above cannot \ + distinguish 1001 passes from 1002" + ); } #[test] @@ -390,9 +677,7 @@ mod tests { ", &mut pool, ); - let mut jit = JitTlcInterp::new(); - assert_eq!(jit.run(&bc, 10, &pool), 55); - let mut jit2 = JitTlcInterp::new(); - assert_eq!(jit2.run(&bc, 100, &pool), 5050); + assert_eq!(run_jit(&bc, 10, &pool), 55); + assert_eq!(run_jit(&bc, 100, &pool), 5050); } } diff --git a/majit/examples/tlr/src/interp.rs b/majit/examples/tlr/src/interp.rs index e0cb11275e1..0f886e22e5e 100644 --- a/majit/examples/tlr/src/interp.rs +++ b/majit/examples/tlr/src/interp.rs @@ -10,6 +10,10 @@ const ADD_R_TO_A: u8 = 5; const RETURN_A: u8 = 6; const ALLOCATE: u8 = 7; const NEG_A: u8 = 8; +/// Two-byte little-endian immediate, the shift-or form. Exists so a wide +/// immediate read from the green bytecode can be exercised inside a loop that +/// actually closes a trace; see `jit_interp`'s `jit_wide_immediate_folds`. +const SET_A_WIDE: u8 = 9; pub fn interpret(bytecode: &[u8], a: i64) -> i64 { let mut regs: Vec = Vec::new(); @@ -51,6 +55,9 @@ pub fn interpret(bytecode: &[u8], a: i64) -> i64 { regs = vec![0; n]; } else if opcode == NEG_A { a = -a; + } else if opcode == SET_A_WIDE { + a = (bytecode[pc] as i64) | ((bytecode[pc + 1] as i64) << 8); + pc += 2; } } } diff --git a/majit/examples/tlr/src/jit_interp.rs b/majit/examples/tlr/src/jit_interp.rs index 1bc4a19603b..8835e3d77c1 100644 --- a/majit/examples/tlr/src/jit_interp.rs +++ b/majit/examples/tlr/src/jit_interp.rs @@ -4,9 +4,32 @@ /// /// Greens: [pc, bytecode] /// Reds: [a, regs] (tracked via state_fields) +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; pub type Bytecode = [u8]; +/// Hot loops majit compiled. The only positive evidence the JIT tier is alive: +/// a green suite, agreement with `interp::interpret` and an exact result are +/// all satisfied by an interpreter answering alone. +pub static COMPILES: AtomicUsize = AtomicUsize::new(0); + +/// Ops in the last compiled loop body after optimization. +/// +/// `COMPILES > 0` is necessary but NOT sufficient: an entirely empty dispatch +/// still compiles a trace — one whose whole optimized body is `Finish()`, i.e. +/// `ops_after == 1`. A compile counter counts TRACES, not WORK. This is the +/// term that separates a compiled loop from a compiled nothing. +pub static LAST_OPS_AFTER: AtomicUsize = AtomicUsize::new(0); + +/// Shape of the last compiled loop body — see [`majit_metainterp::LoopBodyShape`]. +/// +/// Held as two flags rather than the struct itself so the recording stays +/// lock-free on the compile path; the probe rebuilds the struct inside the same +/// lock window it reads the counters in, because this is as process-global as +/// they are. +pub static LAST_HAS_JUMP: AtomicBool = AtomicBool::new(false); +pub static LAST_ALWAYS_FAILS: AtomicBool = AtomicBool::new(false); + #[expect( dead_code, reason = "the jit_interp macro resolves bytecode reads through this trait surface" @@ -33,6 +56,17 @@ const ADD_R_TO_A: u8 = 5; const RETURN_A: u8 = 6; const ALLOCATE: u8 = 7; const NEG_A: u8 = 8; +/// Two-byte little-endian immediate, the shift-or form +/// `(program[pc] | program[pc + 1] << 8)`. +/// +/// It exists to make that form observable. The corpus's other shift-or sites +/// are `tiny2/src/jit_interp.rs:198` and `tiny3:209`, and both sit inside +/// `OP_LOOP_END`, which is a degraded abort stub for an unrelated reason +/// (`break`/`continue`), in crates that compile **zero** traces. So the form +/// has never been asked to lower anywhere, and "it already works" was neither +/// established nor refutable. `SET_A_WIDE` puts it in an arm inside a loop that +/// does close a trace, in a crate that compiles. +const SET_A_WIDE: u8 = 9; const DEFAULT_THRESHOLD: u32 = 3; @@ -56,6 +90,13 @@ const DEFAULT_THRESHOLD: u32 = 3; fn mainloop(program: &Bytecode, initial_a: i64, threshold: u32) -> i64 { let mut driver: majit_metainterp::JitDriver = majit_metainterp::JitDriver::new(threshold); + driver.set_on_compile_loop(|_green_key, _ops_before, ops_after, opcodes| { + COMPILES.fetch_add(1, Ordering::Relaxed); + LAST_OPS_AFTER.store(ops_after, Ordering::Relaxed); + let shape = majit_metainterp::LoopBodyShape::of(opcodes); + LAST_HAS_JUMP.store(shape.has_jump, Ordering::Relaxed); + LAST_ALWAYS_FAILS.store(shape.has_always_fails, Ordering::Relaxed); + }); let mut pc: usize = 0; let _stacksize: i32 = 0; let mut state = TlrState { @@ -73,7 +114,11 @@ fn mainloop(program: &Bytecode, initial_a: i64, threshold: u32) -> i64 { // while True: — RPython tlr.py:22 loop { - jit_merge_point!(); + // `; state` selects the single-executor close: the walk's final state is + // transferred into `state` here and the native loop resumes at the close + // pc, instead of discarding the walk outcome and re-running the circuit + // the walk already executed. + jit_merge_point!(driver, program, pc; state); let opcode = program[pc]; pc += 1; @@ -120,9 +165,18 @@ fn mainloop(program: &Bytecode, initial_a: i64, threshold: u32) -> i64 { NEG_A => { state.a = 0 - state.a; } + SET_A_WIDE => { + state.a = (program[pc] as i64) | ((program[pc + 1] as i64) << 8); + pc += 2; + } _ => {} } } + // Reached only when the merge point broke out on a walk that already ran + // `RETURN_A`, so the result is whatever that opcode left in the accumulator. + // `a` is a plain scalar state field, so `writeback_scalar_state_fields` + // has already pushed the walk-final value into native `state` by here. + state.a } // ── Public wrapper matching the old API ── @@ -153,6 +207,7 @@ impl JitTlrInterp { mod tests { use super::*; use crate::interp; + use majit_metainterp::{RefusalKind, refusal_kind}; fn square_bytecode() -> Vec { vec![ @@ -162,18 +217,326 @@ mod tests { ] } + /// The wide immediate this fixture decodes: 258 = `0x0102`, chosen so that + /// BOTH operand bytes are load-bearing. A value under 256 would decode + /// correctly even if the high byte were dropped entirely. + const WIDE: i64 = 258; + /// The narrow control's immediate. Any value < 256; only its width matters. + const NARROW: i64 = 200; + + fn imm_loop_bytecode(wide: bool) -> Vec { + let mut prog = vec![ + ALLOCATE, 2, // regs = [counter, acc] + MOV_A_R, 0, // regs[0] = initial_a + SET_A, 0, // + MOV_A_R, 1, // regs[1] = 0 + ]; + let loop_target = prog.len() as u8; // 8 + if wide { + prog.extend_from_slice(&[SET_A_WIDE, (WIDE & 0xff) as u8, (WIDE >> 8) as u8]); + } else { + prog.extend_from_slice(&[SET_A, NARROW as u8]); + } + // acc += a; then counter -= 1 via `SET_A 1; NEG_A; ADD_R_TO_A 0` + // (tlr has no subtract); loop back while the counter is non-zero; + // finally return the accumulator. + prog.extend_from_slice(&[ + ADD_R_TO_A, + 1, + MOV_A_R, + 1, + SET_A, + 1, + NEG_A, + ADD_R_TO_A, + 0, + MOV_A_R, + 0, + JUMP_IF_A, + loop_target, + MOV_R_A, + 1, + RETURN_A, + ]); + prog + } + + #[test] + fn jit_wide_immediate_folds() { + const PASSES: i64 = 20; + + let narrow_bc = imm_loop_bytecode(false); + let (narrow_got, narrow_compiles, narrow_ops, ..) = compile_probe(&narrow_bc, PASSES); + assert_eq!( + narrow_got, + NARROW * PASSES, + "narrow control computed {narrow_got}, not {NARROW}*{PASSES}" + ); + assert_eq!(narrow_got, interp::interpret(&narrow_bc, PASSES)); + assert!( + narrow_compiles >= 1, + "the narrow control compiled nothing, so the wide arm's result below \ + is not attributable to the immediate width (compiles={narrow_compiles})" + ); + + let wide_bc = imm_loop_bytecode(true); + let (wide_got, wide_compiles, wide_ops, ..) = compile_probe(&wide_bc, PASSES); + // Decoding, not just compiling: a dropped high byte yields 2*PASSES + // instead of 258*PASSES, and a dropped statement yields 0. + assert_eq!( + wide_got, + WIDE * PASSES, + "wide immediate decoded to {} per pass, not {WIDE} — a value of {} \ + means the high byte was dropped", + wide_got / PASSES, + WIDE & 0xff + ); + assert_eq!(wide_got, interp::interpret(&wide_bc, PASSES)); + assert!( + wide_compiles >= 1, + "the wide-immediate loop compiled nothing while the narrow control \ + compiled {narrow_compiles} — the shift-or form blocks tracing" + ); + // THE RESULT: 11 ops, EQUAL to the narrow control's 11. + // + // The two-byte read folded to a constant exactly as the one-byte read + // did. Had the wide immediate survived as a residual read of the green + // bytecode, this body would carry the extra loads and the shift/or and + // exceed the control. It does not — so the shift-or wide-immediate form + // already folds, and it is NOT a lowering gap. + // + // The equality with `narrow_ops` is asserted rather than the literal + // alone, because the pair is the finding: a future change that inflates + // both equally would keep an absolute pin green while destroying the + // property this test exists to state. + assert_eq!( + wide_ops, narrow_ops, + "wide immediate no longer folds to the same body as the narrow \ + control ({wide_ops} vs {narrow_ops}) — it has become a residual read" + ); + assert_eq!( + wide_ops, 11, + "wide-immediate loop body is {wide_ops} ops, not the pinned 11" + ); + println!( + "[wide-imm] narrow={narrow_got} ({narrow_compiles} loop(s), {narrow_ops} ops), \ + wide={wide_got} ({wide_compiles} loop(s), {wide_ops} ops)" + ); + } + + fn realloc_loop_bytecode(realloc: bool) -> Vec { + let head = if realloc { + [ALLOCATE, 2] + } else { + [ADD_R_TO_A, 1] + }; + vec![ + ALLOCATE, 2, // prologue: regs is non-empty before the loop + // loop target = 2 + head[0], head[1], // the varied instruction + MOV_A_R, 0, // regs[0] = a + SET_A, 1, // a = 1 + NEG_A, // a = -1 + ADD_R_TO_A, 0, // a = regs[0] - 1 + JUMP_IF_A, 2, // back edge while a != 0 + RETURN_A, + ] + } + + #[test] + fn jit_realloc_in_traced_loop() { + const PASSES: i64 = 20; + + let control_bc = realloc_loop_bytecode(false); + let (control_got, control_compiles, control_ops, ..) = compile_probe(&control_bc, PASSES); + assert_eq!(control_got, interp::interpret(&control_bc, PASSES)); + assert!( + control_compiles >= 1, + "the control loop compiled nothing, so the probe's zero below is \ + not attributable to ALLOCATE (compiles={control_compiles})" + ); + + let bc = realloc_loop_bytecode(true); + // Correctness first and unconditionally: this is the assertion that + // catches a stale-mirror miscompile, and it must not be guarded by a + // compile count that a miscompiling build would still satisfy. + let expected = interp::interpret(&bc, PASSES); + let (got, compiles, ops_after, ..) = compile_probe(&bc, PASSES); + assert_eq!( + got, expected, + "realloc-in-loop disagrees with the interpreter ({got} vs \ + {expected}) — a virt-array mirror cached across the reallocation \ + would produce exactly this" + ); + assert_eq!( + got, control_got, + "the two arms must compute the same value; if they do not, the \ + control is not a control" + ); + + assert_eq!( + compiles, 0, + "the reallocating loop compiled {compiles} trace(s) of {ops_after} \ + ops — ALLOCATE has started lowering, so re-check the result \ + assertion above against a stale virt-array mirror before pinning \ + a new number" + ); + println!( + "[realloc] countdown({PASSES}) = {got}; control compiled \ + {control_compiles} loop(s) of {control_ops} ops, realloc arm compiled {compiles}" + ); + } + + /// [`COMPILES`] is process-global, so under the default parallel libtest + /// runner a concurrent `run` lands inside [`compile_probe`]'s + /// store/run/load window and the probe reads someone else's compile. The + /// lock therefore covers *every* call that can compile, not just the + /// probe's own — [`run_jit`] and [`compile_probe`] are the only two ways a + /// test may enter the JIT, and neither may call the other (a plain mutex + /// re-entered on one thread deadlocks). + static PROBE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + + /// For tests that assert only on the result. They still compile, so they + /// must not run inside the probe's window. See [`PROBE_LOCK`]. + fn run_jit(bc: &[u8], initial_a: i64) -> i64 { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + JitTlrInterp::new().run(bc, initial_a) + } + + /// Run with both counters reset, returning `(result, compiles, ops_after)`. + /// + /// [`LAST_OPS_AFTER`] is read here rather than at the call site, and reset + /// here rather than nowhere. Both counters are process-global, so both need + /// the same treatment [`PROBE_LOCK`] exists to give [`COMPILES`]: a load + /// taken after the guard drops can observe a concurrent test's compile, and + /// a counter that is never stored to zero retains whatever the last compile + /// anywhere in the process left behind. Unreset, a zero from this probe is + /// indistinguishable from an inherited value. + fn compile_probe( + bc: &[u8], + initial_a: i64, + ) -> (i64, usize, usize, majit_metainterp::LoopBodyShape) { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + COMPILES.store(0, Ordering::Relaxed); + LAST_OPS_AFTER.store(0, Ordering::Relaxed); + LAST_HAS_JUMP.store(false, Ordering::Relaxed); + LAST_ALWAYS_FAILS.store(false, Ordering::Relaxed); + let got = JitTlrInterp::new().run(bc, initial_a); + ( + got, + COMPILES.load(Ordering::Relaxed), + LAST_OPS_AFTER.load(Ordering::Relaxed), + majit_metainterp::LoopBodyShape { + has_jump: LAST_HAS_JUMP.load(Ordering::Relaxed), + has_always_fails: LAST_ALWAYS_FAILS.load(Ordering::Relaxed), + }, + ) + } + + /// A real loop body was compiled, and exactly one known arm is degraded. + /// + /// This certifies that the JIT tier produced a non-empty compiled body. It + /// is silent on whether that body's resume data is well-formed: a trace can + /// carry a malformed promote snapshot and still be counted here. + /// + /// All three parts are needed and none implies another: + /// + /// 1. `COMPILES` 0 → non-zero. A green suite, agreement with + /// `interp::interpret` and an exact result are all satisfied by the + /// interpreter answering alone. + /// 2. `ops_after` pinned by equality. `compiles >= 1` is necessary but NOT + /// sufficient: an entirely empty dispatch still compiles a trace — one + /// whose whole optimized body is `Finish()`, i.e. `ops_after == 1`. A + /// compile counter counts TRACES, not WORK, and every inequality a real + /// loop satisfies that degenerate body satisfies too. + /// 3. The degraded-arm set, pinned as an **equality over a named set** + /// rather than an emptiness check. `ALLOCATE` does not lower today, so + /// asserting the list is empty would just fail; asserting it equals + /// exactly this one catches a *new* arm silently degrading, and fails + /// loudly on the day `ALLOCATE` starts lowering. The suite is green with + /// `ALLOCATE` degraded only because `square_bytecode` issues it once at + /// pc 0, outside the hot loop — a program whose traced loop reached it + /// would abort every trace. + /// + /// The registry is process-wide, so it is filtered to `state = TlrState`, + /// and read *after* a run because nothing installs the dispatch JitCode + /// until the interpreter is entered. + #[test] + fn jit_tier_is_alive() { + let (got, compiles, ops_after, shape) = compile_probe(&square_bytecode(), 100); + // The body actually closes a loop — see `LoopBodyShape`. A compile + // count and an op count together still accept a body that bails out on + // its first pass; this is the term that does not. Sound HERE because + // this fixture loops: on a straight-line subject a `Jump`-less body is + // the right answer, not a defect. + assert!( + shape.closes_a_loop(), + "compiled {ops_after} ops but the body {} ({shape:?})", + shape.why_not().unwrap_or("closes a loop") + ); + assert_eq!(got, 10_000, "square(100) must still answer 10000"); + + let tlr_arms: Vec<_> = majit_metainterp::degraded_dispatch_arms() + .into_iter() + .filter(|a| a.interp == "TlrState") + .collect(); + let degraded: Vec<&str> = tlr_arms.iter().map(|a| a.arm).collect(); + assert_eq!( + degraded, + ["ALLOCATE"], + "the degraded-arm set moved. A NEW name means an arm silently \ + stopped lowering and every trace reaching it now aborts; a MISSING \ + name means that arm lowers again" + ); + + let causes: Vec<(&str, RefusalKind)> = tlr_arms + .iter() + .map(|a| (a.arm, refusal_kind(a.reason))) + .collect(); + assert_eq!( + causes, + [("ALLOCATE", RefusalKind::GreenWriteback)], + "ALLOCATE still degrades but a different mechanism is refusing it. \ + `RefusalKind::Unclassified` means majit grew a refusal family the \ + classifier does not know — add it in `majit-metainterp`, do not \ + re-record this pin" + ); + assert!( + tlr_arms[0].reason.contains("pc += 1"), + "ALLOCATE's refusal no longer names the green write that stops \ + lowering before the reallocation: {}", + tlr_arms[0].reason + ); + + // Zero-vs-nonzero is the property; a later change that legitimately + // mints more than one artifact is not this regression. + assert!( + compiles >= 1, + "square(100) compiled {compiles} loops — the JIT tier is inert and \ + the interpreter is answering alone, which every other assertion in \ + this file would still pass" + ); + assert_eq!( + ops_after, 11, + "compiled loop body is {ops_after} ops, not the pinned 11 — a value \ + of 1 means the body is a bare `Finish()`, i.e. a dispatch that \ + lowered nothing at all" + ); + println!( + "[tier-alive] square(100) = {got}, compiled {compiles} loop(s) of {ops_after} ops, degraded {degraded:?}" + ); + } + #[test] fn jit_square_5() { let bc = square_bytecode(); - let mut jit = JitTlrInterp::new(); - assert_eq!(jit.run(&bc, 5), 25); + assert_eq!(run_jit(&bc, 5), 25); } #[test] fn jit_square_100() { let bc = square_bytecode(); - let mut jit = JitTlrInterp::new(); - assert_eq!(jit.run(&bc, 100), 10_000); + assert_eq!(run_jit(&bc, 100), 10_000); } #[test] @@ -181,8 +544,7 @@ mod tests { let bc = square_bytecode(); for a in [1, 2, 5, 10, 50, 100, 200] { let expected = interp::interpret(&bc, a); - let mut jit = JitTlrInterp::new(); - let got = jit.run(&bc, a); + let got = run_jit(&bc, a); assert_eq!(got, expected, "mismatch for a={a}"); } } @@ -190,8 +552,7 @@ mod tests { #[test] fn jit_no_loop() { let prog = vec![SET_A, 42, RETURN_A]; - let mut jit = JitTlrInterp::new(); - assert_eq!(jit.run(&prog, 0), 42); + assert_eq!(run_jit(&prog, 0), 42); } /// Exercises the JIT with many input sizes: small values stay interpreted, @@ -203,8 +564,7 @@ mod tests { let bc = square_bytecode(); for a in [1, 2, 3, 4, 5, 10, 20, 50, 100, 500, 1000] { let expected = interp::interpret(&bc, a); - let mut jit = JitTlrInterp::new(); - let got = jit.run(&bc, a); + let got = run_jit(&bc, a); assert_eq!(got, expected, "mismatch for a={a}"); } } diff --git a/majit/examples/tlr/src/main.rs b/majit/examples/tlr/src/main.rs index eb0e40df036..3a6a53b5c63 100644 --- a/majit/examples/tlr/src/main.rs +++ b/majit/examples/tlr/src/main.rs @@ -31,6 +31,34 @@ fn square_bytecode() -> Vec { ] } +/// Absolute trip-count gate on the JIT path. +/// +/// SQUARE adds `a` to the accumulator once per pass and decrements the counter +/// once per pass, so the returned value names the number of passes exactly: +/// `a` passes answer `a*a`, and one extra pass answers `a*a + a`. Agreement +/// with `interp::interpret` alone would not settle this — the two run the same +/// program, and a duplicated iteration of the *compiled* loop is invisible to +/// any check that does not assert an absolute count. +/// +/// Two lengths of different parity, because a peeled first iteration plus an +/// even/odd body count is exactly the shape an off-by-one hides in. +fn trip_count_gate() { + let bytecode = square_bytecode(); + for a in [1001i64, 1002] { + let mut jit = jit_interp::JitTlrInterp::new(); + let got = jit.run(&bytecode, a); + assert_eq!( + got, + a * a, + "square({a}) = {got}, so the loop ran {} passes rather than {a} — an \ + off-by-one trip count is the signature of a terminal arm whose exit \ + the trace dropped", + got / a + ); + println!("[trip-count] square({a}) = {got} — exactly {a} passes"); + } +} + fn main() { let a: i64 = std::env::args() .nth(1) @@ -45,6 +73,8 @@ fn main() { assert_eq!(result, 25, "5*5 should be 25"); } + trip_count_gate(); + // Benchmark: interpreter println!("--- square({a}) [interpreter] ---"); { diff --git a/majit/majit-backend-cranelift/Cargo.toml b/majit/majit-backend-cranelift/Cargo.toml index 1a4dff7ec73..a682836ecb5 100644 --- a/majit/majit-backend-cranelift/Cargo.toml +++ b/majit/majit-backend-cranelift/Cargo.toml @@ -19,21 +19,7 @@ cranelift-module = { workspace = true } cranelift-native = { workspace = true } target-lexicon = { workspace = true } -[features] -# This crate's integration tests depend on `majit-metainterp::recorder::Trace`, -# and `majit-metainterp` requires one backend feature to compile (pyjitpl/mod.rs -# selects `BackendImpl` and `compile_error!`s with none). Selecting the -# metainterp backend through this crate's own features keeps both -# `cargo test --all --features dynasm` and `--features cranelift` coherent, and -# lets the per-package form `cargo test -p majit-backend-cranelift --features -# cranelift` (or `--features dynasm`) compile the dev-dependency. The two -# features are mutually exclusive at the metainterp level (cranelift wins if -# both are set); pick one per test invocation. -dynasm = ["majit-metainterp/dynasm"] -cranelift = ["majit-metainterp/cranelift"] - [dev-dependencies] majit-gc = { workspace = true, features = ["gc_box"] } majit-ir = { workspace = true, features = ["test-support"] } -majit-metainterp = { workspace = true } smallvec = { workspace = true } diff --git a/majit/majit-backend-cranelift/src/compiler.rs b/majit/majit-backend-cranelift/src/compiler.rs index 3e4ec0bedbc..9c1bbc9ffae 100644 --- a/majit/majit-backend-cranelift/src/compiler.rs +++ b/majit/majit-backend-cranelift/src/compiler.rs @@ -4,6 +4,7 @@ /// executes them as ordinary function pointers. use indexmap::{IndexMap, IndexSet}; use majit_ir::IndexMapExt; +use std::borrow::Cow; use std::cell::{Cell, RefCell, UnsafeCell}; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::sync::{Arc, OnceLock}; @@ -90,7 +91,7 @@ fn majit_verify_enabled() -> bool { use crate::guard::{BridgeData, JitFrameDeadFrame, drop_bridge_payload}; -// ── compile.py:665-674 done_with_this_frame singletons ────────────── +// `compile.py:665-674` `done_with_this_frame` singletons // // Per-result-type `DoneWithThisFrame*` singletons. // These are now `DescrRef`s holding the metainterp's class-distinct @@ -272,8 +273,7 @@ fn match_metainterp_finish_descr( None } -// ── JitFrame layout constants (jitframe.py:61-83) ─────────────────── -// +// JitFrame layout constants (`jitframe.py:61-83`) // The canonical layout lives in `majit_backend::jitframe`; re-export the // byte offsets here so uses inside this file stay terse. use majit_backend::jitframe::{ @@ -321,6 +321,7 @@ fn jitframe_gc_type_id_is_explicit() -> bool { JITFRAME_GC_TYPE_ID_IS_EXPLICIT.load(std::sync::atomic::Ordering::Acquire) } +// JitFrame layout registration for the GC rewriter /// Ensure the JITFRAME GC type is registered, and that /// `JITFRAME_GC_TYPE_ID` reflects the id the GC assigned to it. /// @@ -1150,9 +1151,7 @@ const BUILTIN_UNICODE_TOKEN_BASE_SIZE: usize = 2 * std::mem::size_of::(); #[cfg(test)] const BUILTIN_STRING_CHARS_OFFSET: usize = BUILTIN_STR_TOKEN_BASE_SIZE - 1; -// --------------------------------------------------------------------------- // Helpers (free functions to avoid borrow conflicts) -// --------------------------------------------------------------------------- thread_local! { /// regalloc.py:140-181 RegisterManager.{reg_bindings, longevity} parity: @@ -2100,7 +2099,7 @@ pub fn register_call_assembler_blackhole( /// compile.py:701-717 handle_fail callback for call_assembler guard failures. /// (raw_values_ptr, num_values, descr_addr) -> bridge_compiled. /// -/// `pyjitpl.py:2890 handle_guard_failure(self, resumedescr, deadframe)` +/// `pyjitpl.py:2914 handle_guard_failure(self, resumedescr, deadframe)` /// receives the descr directly; the C-ABI delivers the same shape via /// `descr_addr` (recovered to `Arc` by the receiver) /// instead of a surrogate `(green_key, trace_id, fail_index)` triple. @@ -2293,8 +2292,6 @@ pub fn take_pending_frame_restore() -> Option { PENDING_FRAME_RESTORE.with(|c| c.take()) } -// ── JitFrame layout registration for the GC rewriter ──────────────── - /// JitFrame field descriptors supplied by the interpreter crate so the /// GC rewriter's `handle_call_assembler` pass (rewrite.py:665-695) can /// emit the correct GC_LOAD / GC_STORE sequence for callee jitframes. @@ -3172,7 +3169,8 @@ fn grab_exc_value_from_jf_ptr(jf_ptr: usize) -> i64 { fn execute_registered_loop_target(target: &RegisteredLoopTarget, inputs: &[i64]) -> DeadFrame { let mut cur_code_ptr = target.code_ptr; - let mut cur_fail_descrs = target.fail_descrs.clone(); + // Borrowed, not cloned — see `execute_with_inputs_at_dispatch_key`. + let mut cur_fail_descrs: Cow<'_, [DescrRef]> = Cow::Borrowed(&target.fail_descrs); let mut cur_num_ref_roots = target.num_ref_roots; let mut cur_max_output_slots = target.max_output_slots; let mut current_inputs = inputs.to_vec(); @@ -3254,7 +3252,7 @@ fn execute_registered_loop_target(target: &RegisteredLoopTarget, inputs: &[i64]) .map(host_reentry_dispatch_key) .unwrap_or(0); cur_code_ptr = target_entry.code_ptr; - cur_fail_descrs = target_entry.fail_descrs; + cur_fail_descrs = Cow::Owned(target_entry.fail_descrs.into_vec()); cur_num_ref_roots = target_entry.num_ref_roots; cur_max_output_slots = target_entry.max_output_slots; continue; @@ -6974,9 +6972,7 @@ fn emit_guard_exit( builder.ins().return_(&[jf_ptr]); } -// --------------------------------------------------------------------------- // Compiled loop data -// --------------------------------------------------------------------------- struct CompiledLoop { trace_id: u64, @@ -8013,9 +8009,7 @@ fn resolve_fail_arg_types( .collect()) } -// --------------------------------------------------------------------------- // CraneliftBackend -// --------------------------------------------------------------------------- pub struct CraneliftBackend { /// `rpython/jit/backend/model.py:28-29 self.tracker = @@ -8611,7 +8605,13 @@ impl CraneliftBackend { slice_x2_probe::arm_reporter(); // Current trace state (equivalent to LLFrame.lltrace) let mut cur_code_ptr = compiled.code_ptr; - let mut cur_fail_descrs: Box<[DescrRef]> = compiled.fail_descrs.clone(); + // Borrowed, not cloned: the dispatch loop only READS this table, and + // the one writer is the external-JUMP re-entry below, which brings its + // own owned table. Cloning up front allocated once per entry into + // compiled code and bumped one `Arc` refcount per descr — measured at + // 64 B / 4 descrs (straight-line trace) and 96 B / 6 (loop) on cel's + // `price + qty * 2`. + let mut cur_fail_descrs: Cow<'_, [DescrRef]> = Cow::Borrowed(&compiled.fail_descrs); let mut cur_num_ref_roots = compiled.num_ref_roots; let mut cur_max_output_slots = compiled.max_output_slots; let mut cur_inputs = inputs.to_vec(); @@ -8691,7 +8691,7 @@ impl CraneliftBackend { .map(host_reentry_dispatch_key) .unwrap_or(0); cur_code_ptr = target_entry.code_ptr; - cur_fail_descrs = target_entry.fail_descrs; + cur_fail_descrs = Cow::Owned(target_entry.fail_descrs.into_vec()); cur_num_ref_roots = target_entry.num_ref_roots; cur_max_output_slots = target_entry.max_output_slots; cur_inputs = outputs; @@ -15860,9 +15860,7 @@ fn collect_terminal_exit_layouts( Ok(layouts) } -// --------------------------------------------------------------------------- // Backend trait implementation -// --------------------------------------------------------------------------- impl majit_backend::Backend for CraneliftBackend { fn backend_name(&self) -> &'static str { @@ -16481,7 +16479,8 @@ impl majit_backend::Backend for CraneliftBackend { // mirrors that dispatch loop with one additional raw-output // termination per `execute_token_ints_raw`'s contract. let mut cur_code_ptr = compiled.code_ptr; - let mut cur_fail_descrs: Box<[DescrRef]> = compiled.fail_descrs.clone(); + // Borrowed, not cloned — see `execute_with_inputs_at_dispatch_key`. + let mut cur_fail_descrs: Cow<'_, [DescrRef]> = Cow::Borrowed(&compiled.fail_descrs); let mut cur_num_ref_roots = compiled.num_ref_roots; let mut cur_max_output_slots = compiled.max_output_slots; let mut cur_inputs = args.to_vec(); @@ -16603,7 +16602,7 @@ impl majit_backend::Backend for CraneliftBackend { .map(host_reentry_dispatch_key) .unwrap_or(0); cur_code_ptr = target_entry.code_ptr; - cur_fail_descrs = target_entry.fail_descrs; + cur_fail_descrs = Cow::Owned(target_entry.fail_descrs.into_vec()); cur_num_ref_roots = target_entry.num_ref_roots; cur_max_output_slots = target_entry.max_output_slots; cur_inputs = outputs; @@ -17551,7 +17550,6 @@ impl majit_backend::Backend for CraneliftBackend { // the same dispatch. // Tests -// --------------------------------------------------------------------------- #[cfg(test)] mod tests { @@ -23650,7 +23648,7 @@ mod tests { #[test] #[ignore = "sets the process-global PYRE_CL_NO_CLOSING_JUMP env var to force the \ host-loop external-JUMP path (in-code closing_jump disabled); run serially: \ - `PYRE_CL_NO_CLOSING_JUMP=1 cargo test -p majit-backend-cranelift --features dynasm \ + `PYRE_CL_NO_CLOSING_JUMP=1 cargo test -p majit-backend-cranelift \ test_host_loop_external_jump_to_middle_label -- --ignored --test-threads=1`"] fn test_host_loop_external_jump_to_middle_label_uses_label_selector() { // Same scenario as the in-code variant above, but with closing_jump @@ -23767,7 +23765,7 @@ mod tests { #[test] #[ignore = "sets the process-global PYRE_CL_NO_CLOSING_JUMP env var to force the \ host-loop external-JUMP path (in-code closing_jump disabled); run serially: \ - `PYRE_CL_NO_CLOSING_JUMP=1 cargo test -p majit-backend-cranelift --features dynasm \ + `PYRE_CL_NO_CLOSING_JUMP=1 cargo test -p majit-backend-cranelift \ test_host_loop_external_jump_to_first_label -- --ignored --test-threads=1`"] fn test_host_loop_external_jump_to_first_label_uses_label_selector() { // The first LABEL (`label_block_id` 0) is not special: `closing_jump` diff --git a/majit/majit-backend-cranelift/tests/integration.rs b/majit/majit-backend-cranelift/tests/integration.rs index 08b820b6504..1060d78edde 100644 --- a/majit/majit-backend-cranelift/tests/integration.rs +++ b/majit/majit-backend-cranelift/tests/integration.rs @@ -8,18 +8,14 @@ use majit_backend::{ Backend, ExitFrameLayout, ExitRecoveryLayout, ExitValueSourceLayout, JitCellToken, }; use majit_backend_cranelift::{CraneliftBackend, force_token_to_dead_frame, jit_exc_raise}; +use majit_ir::test_support::{RecordedTrace, Trace}; use majit_ir::{ ArrayDescr, Descr, DescrRef, FieldDescr, GcRef, InputArg, Op, OpCode, OpRef, Type, Value, }; -use majit_metainterp::history::TreeLoop; -use majit_metainterp::recorder::Trace; - -/// Materialize a `Vec` view of a `TreeLoop`'s inputargs for backend -/// APIs that take `&[InputArg]`. `TreeLoop::inputargs` stores `InputArgRc` -/// so optimizer/short-preamble/resume metadata observe a single shared -/// identity; backend boundaries that don't traffic in `_forwarded` accept -/// the deref-and-clone projection. -fn inputargs_view(t: &TreeLoop) -> Vec { + +/// Materialize owned input arguments for backend APIs that do not traffic in +/// the recorder's shared input identities. +fn inputargs_view(t: &RecordedTrace) -> Vec { t.inputargs .iter() .map(|rc| (**rc).fresh_value_copy()) diff --git a/majit/majit-backend-dynasm/src/lib.rs b/majit/majit-backend-dynasm/src/lib.rs index ea8b5a28c42..eba0ac71d48 100644 --- a/majit/majit-backend-dynasm/src/lib.rs +++ b/majit/majit-backend-dynasm/src/lib.rs @@ -242,7 +242,7 @@ pub type BlackholeFn = fn(usize, *const i64, usize, *const i64, usize, i64) -> O /// `cpu.get_latest_descr` parity) and derives the bridge source /// identity (`jct.green_key` / `descr.trace_id()` / /// `descr.fail_index_per_trace()`) from that Arc, mirroring -/// `pyjitpl.py:2890 handle_guard_failure(self, resumedescr, +/// `pyjitpl.py:2914 handle_guard_failure(self, resumedescr, /// deadframe)`. No surrogate triple crosses the C-ABI. pub type BridgeFn = fn(*const i64, usize, usize) -> bool; diff --git a/majit/majit-backend-dynasm/src/x86/assembler.rs b/majit/majit-backend-dynasm/src/x86/assembler.rs index f53271328ae..fe59949e3c9 100644 --- a/majit/majit-backend-dynasm/src/x86/assembler.rs +++ b/majit/majit-backend-dynasm/src/x86/assembler.rs @@ -1131,9 +1131,7 @@ impl<'a> Assembler386<'a> { .propagate_exception_descr as i64 } - // ---------------------------------------------------------------- // Helper methods - // ---------------------------------------------------------------- /// Frame-pointer-relative byte offset for a given slot index. /// Slots are absolute jf_frame indices, including the fixed @@ -1645,9 +1643,7 @@ impl<'a> Assembler386<'a> { ); } - // ---------------------------------------------------------------- // assembler.py:543 _call_header — function prologue - // ---------------------------------------------------------------- fn setup_input_state(&mut self, inputargs: &[InputArg]) { // opref_to_slot stores ABSOLUTE jitframe slot indices so that @@ -2141,9 +2137,7 @@ impl<'a> Assembler386<'a> { self.emit_abi_call_reg_with_extra_pushes(reg, 0); } - // ---------------------------------------------------------------- // assembler.py:2153 _call_footer — function epilogue - // ---------------------------------------------------------------- /// Emit the function epilogue: return jf_ptr in RAX/X0. /// Thin wrapper around the free-fn `emit_call_footer_raw` so the @@ -2562,9 +2556,7 @@ impl<'a> Assembler386<'a> { ))) } - // ---------------------------------------------------------------- // assembler.py:501 assemble_loop - // ---------------------------------------------------------------- /// assembler.py:501 assemble_loop: compile a loop trace. /// @@ -5452,9 +5444,7 @@ impl<'a> Assembler386<'a> { } } - // ---------------------------------------------------------------- // assembler.py:652 write_pending_failure_recoveries - // ---------------------------------------------------------------- /// assembler.py:982 generate_quick_failure. /// @@ -5605,9 +5595,7 @@ impl<'a> Assembler386<'a> { } } - // ---------------------------------------------------------------- // assembler.py:965-987 patch_jump_for_descr - // ---------------------------------------------------------------- /// assembler.py:965 patch_jump_for_descr: redirect a guard to a /// bridge by overwriting the recovery stub with a JMP to bridge. @@ -5678,9 +5666,7 @@ impl<'a> Assembler386<'a> { }); } - // ---------------------------------------------------------------- // genop_* — integer arithmetic - // ---------------------------------------------------------------- /// INT_ADD: result = arg0 + arg1 fn genop_int_add(&mut self, op: &Op) { @@ -5801,9 +5787,7 @@ impl<'a> Assembler386<'a> { self.store_rax_to_result(op.pos.get()); } - // ---------------------------------------------------------------- // genop_* — overflow arithmetic (assembler.py:1413-1425) - // ---------------------------------------------------------------- /// assembler.py:1856 genop_int_add_ovf — delegates to genop_int_add, /// then sets guard_success_cc = 'NO'. On x86, ADD always sets OF. @@ -5824,9 +5808,7 @@ impl<'a> Assembler386<'a> { self.guard_success_cc = Some(CC_NO); } - // ---------------------------------------------------------------- // genop_* — comparisons - // ---------------------------------------------------------------- /// INT_LT/LE/GT/GE/EQ/NE/UINT_*: CMP arg0, arg1 then store CC. /// If the next op is a guard, guard_success_cc is set and consumed. @@ -5902,9 +5884,7 @@ impl<'a> Assembler386<'a> { } } - // ---------------------------------------------------------------- // genop_* — guards - // ---------------------------------------------------------------- /// llsupport/gc.py:563 GcLLDescr_framework /// .get_typeid_from_classptr_if_gcremovetypeptr(classptr) @@ -5973,9 +5953,11 @@ impl<'a> Assembler386<'a> { } let descr_arc = op.getdescr(); if let Some(fd) = descr_arc.as_ref().and_then(|d| d.as_fail_descr()) { - // Step A (43c64ee0bb) installs op.descr = ResumeGuardDescr - // with post-numbering fail_arg_types via + // Step A installs op.descr = ResumeGuardDescr with + // post-numbering fail_arg_types via // store_final_boxes_in_guard (optimizeopt/mod.rs:3393-3404). + // The hash once cited for Step A resolves nowhere in this + // repository, so that symbol is the reference. // Prefer the descr for guards too; fall through to // op.fail_arg_types only for sharing-path guards // (optimizeopt/mod.rs:3068-3088) where op.descr=None. @@ -6167,9 +6149,7 @@ impl<'a> Assembler386<'a> { ); } - // ---------------------------------------------------------------- // genop_* — control flow - // ---------------------------------------------------------------- /// LABEL: define the back-edge target for JUMP. /// @@ -6464,9 +6444,7 @@ impl<'a> Assembler386<'a> { .push(majit_ir::FailDescrCell::wrap(descr.clone())); } - // ---------------------------------------------------------------- // genop_* — type conversions - // ---------------------------------------------------------------- /// SAME_AS: result = arg0 (copy value) /// SAME_AS: result = arg0 (identity). @@ -6481,9 +6459,7 @@ impl<'a> Assembler386<'a> { } } - // ---------------------------------------------------------------- // Float helpers - // ---------------------------------------------------------------- /// Load a float value from `opref` into XMM0 (x64) / D0 (aarch64). /// Float values are stored as bit-cast i64 in frame slots. @@ -6535,11 +6511,9 @@ impl<'a> Assembler386<'a> { ); } - // ---------------------------------------------------------------- // genop_* — float arithmetic // x86/assembler.py:1648 genop_float_add etc. // aarch64/assembler.py float equivalents - // ---------------------------------------------------------------- /// FLOAT_ADD: result = arg0 + arg1 fn genop_float_add(&mut self, op: &Op) { @@ -6625,10 +6599,8 @@ impl<'a> Assembler386<'a> { self.store_rax_to_result(op.pos.get()); } - // ---------------------------------------------------------------- // genop_* — memory operations // x86/assembler.py:1747 genop_getfield_gc etc. - // ---------------------------------------------------------------- /// Extract the byte offset from an op's FieldDescr. /// Returns 0 if no field descriptor is present. @@ -7119,10 +7091,8 @@ impl<'a> Assembler386<'a> { self.store_rax_to_result(op.pos.get()); } - // ---------------------------------------------------------------- // genop_* — calls // x86/assembler.py:2230 _genop_call - // ---------------------------------------------------------------- fn argloc_imm(arglocs: &[Loc], index: usize) -> i64 { match arglocs.get(index) { @@ -7614,11 +7584,9 @@ impl<'a> Assembler386<'a> { } } - // ---------------------------------------------------------------- // genop_* — allocation // x86/assembler.py:2338 genop_new etc. // These require GC runtime support. Emit trap for now. - // ---------------------------------------------------------------- /// rewrite.py:936-942 `handle_write_barrier_setarrayitem` value gate. /// @@ -8188,9 +8156,7 @@ impl<'a> Assembler386<'a> { self.genop_alloc_varsize(op, base_size, item_size); } - // ---------------------------------------------------------------- // genop_* — misc - // ---------------------------------------------------------------- /// FORCE_TOKEN: return the jitframe pointer itself. /// x86/assembler.py genop_force_token: mov resloc, ebp @@ -8271,9 +8237,7 @@ impl<'a> Assembler386<'a> { self.store_rax_to_result(op.pos.get()); } - // ================================================================ // assembler.py:1817 genop_save_exc_class / genop_save_exception - // ================================================================ /// assembler.py:1817-1818 genop_save_exc_class: /// `MOV resloc, [pos_exception]`. The regalloc always assigns the @@ -8337,9 +8301,7 @@ impl<'a> Assembler386<'a> { store_loc_to(self, crate::jit_exc_type_addr() as i64, &arglocs[0]); } - // ================================================================ // genop_* — extended integer arithmetic - // ================================================================ /// INT_FLOORDIV: result = arg0 / arg1 (signed) fn genop_int_floordiv(&mut self, op: &Op) { @@ -8393,9 +8355,7 @@ impl<'a> Assembler386<'a> { self.store_rax_to_result(op.pos.get()); } - // ================================================================ // genop_* — extended float operations - // ================================================================ /// FLOAT_ABS: result = |arg0| fn genop_float_abs(&mut self, op: &Op) { @@ -8470,9 +8430,7 @@ impl<'a> Assembler386<'a> { self.store_d0_to_result(op.pos.get()); } - // ================================================================ // genop_* — GC memory operations - // ================================================================ /// Emit a sized load from [rax]/[x0]. Positive size = zero-extend, /// negative = sign-extend. @@ -8616,9 +8574,7 @@ impl<'a> Assembler386<'a> { self.emit_store_to_rax_sized(size); } - // ================================================================ // genop_* — interior field operations - // ================================================================ /// GETINTERIORFIELD_GC_I/R/F: load field from array-of-structs element. fn genop_getinteriorfield(&mut self, op: &Op) { @@ -8690,9 +8646,7 @@ impl<'a> Assembler386<'a> { self.emit_store_to_rax_sized(field_size); } - // ================================================================ // genop_* — call variants - // ================================================================ /// COND_CALL_N: if arg(0) != 0, call function at arg(1). /// @@ -8749,9 +8703,7 @@ impl<'a> Assembler386<'a> { } } - // ================================================================ // genop_* — string/array operations - // ================================================================ /// STRSETITEM / UNICODESETITEM: string[index] = value. /// Address = base + (basesize - extra_null) + index * itemsize, per @@ -9021,9 +8973,7 @@ impl<'a> Assembler386<'a> { self.emit_abi_call_rax(); } - // ================================================================ // genop_* — address computation - // ================================================================ /// LOAD_EFFECTIVE_ADDRESS: result = base + (index << shift) + baseofs. /// resoperation.py:1052-1054 — `[v_gcptr, v_index, c_baseofs, c_shift]`. diff --git a/majit/majit-backend/src/lib.rs b/majit/majit-backend/src/lib.rs index 53002cd054d..501476d69d2 100644 --- a/majit/majit-backend/src/lib.rs +++ b/majit/majit-backend/src/lib.rs @@ -2321,7 +2321,7 @@ pub trait Backend: Send { /// the CA bridge entry as a method on the descr — `compile.py:706-732 /// _trace_and_compile_from_bridge(self, deadframe, ...)`, where `self` /// IS the descr — so the metainterp receives the descr object directly - /// (`pyjitpl.py:2890 handle_guard_failure(self, resumedescr, ...)`) + /// (`pyjitpl.py:2914 handle_guard_failure(self, resumedescr, ...)`) /// without any addr→object lookup. Pyre's bridge crosses native code /// through function pointers (`majit-backend-dynasm/src/lib.rs` /// `BlackholeFn = fn(usize, *const i64, usize, *const i64, usize) -> diff --git a/majit/majit-charon-reader/tests/corpus.rs b/majit/majit-charon-reader/tests/corpus.rs index c54710b35c9..05058c5f256 100644 --- a/majit/majit-charon-reader/tests/corpus.rs +++ b/majit/majit-charon-reader/tests/corpus.rs @@ -25,7 +25,11 @@ fn loads_fixture_corpus() { // closure body and its transparent `::call_once` inherent method) // + `option_source` and `option_question_mark` (the Option `?` fixture) // + `bool_then_some` (the eager `then_some` sibling, no closure). - assert_eq!(local_count, 12, "12 local fns expected"); + // + // + 2 for the host-registered callback table: `host_registry_dispatch` + // and `host_registry_dispatch_optional`. `HostCallback` is a type alias, + // not an item, so it contributes no body. + assert_eq!(local_count, 14, "14 local fns expected"); } #[test] diff --git a/majit/majit-ir/src/descr.rs b/majit/majit-ir/src/descr.rs index 6abe8459417..309d2c58213 100644 --- a/majit/majit-ir/src/descr.rs +++ b/majit/majit-ir/src/descr.rs @@ -690,6 +690,108 @@ static FIELD_INDEX_REDERIVED: std::sync::atomic::AtomicUsize = static FIELD_INDEX_UNRESOLVED: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); +/// One `FIELD_INDEX_UNRESOLVED` event, named, keyed so identical mints fold +/// into one row with a count. +/// +/// `BTreeMap`, not `HashMap`: the whole point of the table is to be printed, +/// and a hash-ordered print makes two runs of the same program produce +/// different bytes for the same finding. +static FIELD_UNRESOLVED_NAMES: std::sync::Mutex< + std::collections::BTreeMap, +> = std::sync::Mutex::new(std::collections::BTreeMap::new()); + +/// Whether to build [`FIELD_UNRESOLVED_NAMES`] at all, read once. +/// +/// The COUNT (`field_pos_unresolved`) is ungated and always accurate; only the +/// names cost anything, so they sit behind a knob like +/// `PYRE_SIZE_SHELL_OWNERS`. Read through this one function so the variable has +/// exactly one spelling in-tree — the collector lives here and the printer +/// lives in `pyre-jit-trace`, and two independently-checked names would let the +/// printer report "nothing unresolved" from a run that never recorded. +/// +/// Unreachable on wasm32 for the ordinary reason: a +/// `wasm32-unknown-unknown` guest's `std::env` is permanently empty, so the +/// knob cannot be set there and this returns false however the host is +/// configured. +/// +/// The names are out of reach there, but the COUNT is not. The guest's export +/// list (`pyre-wasm/src/lib.rs`) carries all eight `pyre_jit_field_pos_*` +/// counters, `field_pos_unresolved` among them, and `pyre-wasm-runner` asks for +/// all eight — so on wasm this hazard is measurable as a number, and only the +/// per-mint naming is unavailable. Read the count; do not infer it from the +/// names being absent. +/// +/// Do not read a missing counter as a zero, and do not assume the runner +/// would object to one: it refuses a counter it ASKS for and cannot resolve, +/// which structurally cannot catch a counter nobody added to its list. A guard +/// against wrong entries is not a guard against missing ones — which is why the +/// four census dispositions had to be exported deliberately rather than waited +/// for. +pub fn field_position_unresolved_naming_enabled() -> bool { + field_position_unresolved_limit().is_some() +} + +/// How many rows the knob asks for: `MAJIT_FIELD_POS_UNRESOLVED=`, or the +/// whole table for `1` / any non-numeric value. `None` when unset. +/// +/// A cap is a parameter, not a constant. `size_shell_owner_sample`'s sibling +/// diagnostic hardcodes 24 and prints it against a count of 155 — so the rows +/// it shows are the alphabetically-first sixth of a `BTreeSet`, which is the +/// one thing a reader must not compute a proportion from. Whoever asks a census +/// for names is asking a question about the whole population; let them say how +/// much of it they want. +fn field_position_unresolved_limit() -> Option { + static LIMIT: std::sync::OnceLock> = std::sync::OnceLock::new(); + *LIMIT.get_or_init(|| { + let raw = std::env::var("MAJIT_FIELD_POS_UNRESOLVED").ok()?; + Some(match raw.trim().parse::() { + Ok(0) | Ok(1) | Err(_) => usize::MAX, + Ok(n) => n, + }) + }) +} + +/// A field mint whose name the parent's positional list does not contain. +/// +/// Everything needed to decide WHERE the fix belongs is in the key, because +/// the layer is not derivable from the count: +/// * `label` — the descr's display name, `Owner.field` or the `T.` +/// stand-in. Says which producer minted it. +/// * `lookup_key` — the string actually searched for (`field_key`), which is +/// not always the tail of `label`; a producer that spells the key one way +/// and the display name another is a different defect from one that ranks +/// the field wrongly. +/// * `caller_index` / `parent_len` — the un-arbitrated `index_in_parent` the +/// caller supplied, against the length of the list it would index. The pair +/// says whether the number is merely wrong or out of range. `None` is a +/// producer that never claimed a slot at all, which is a different finding +/// from one that claimed slot 0 — and while this field was a plain `usize` +/// the two were the same row, so the table could be read but not split. +/// * `parent_keys` — what the parent DOES list. This is the discriminator +/// between "the field is missing from its own parent" and "the parent is a +/// different struct that shares an identity key", and neither the count nor +/// the label can distinguish them. +#[derive(PartialEq, Eq, PartialOrd, Ord)] +struct UnresolvedFieldMint { + label: String, + lookup_key: String, + caller_index: Option, + parent_len: usize, + parent_keys: String, +} + +/// `FIELD_INDEX_UNRESOLVED` split by whether the producer had claimed a slot. +/// +/// Ungated, unlike the row table above: the rows are only recorded when +/// `MAJIT_FIELD_POS_UNRESOLVED` was set before the first mint, so a late-armed +/// gate yields a short table, and a split derived from a short table is a split +/// of the wrong denominator. These two are bumped on every unresolved mint and +/// sum to `FIELD_INDEX_UNRESOLVED` exactly. +static FIELD_INDEX_UNRESOLVED_CLAIMED: std::sync::atomic::AtomicUsize = + std::sync::atomic::AtomicUsize::new(0); +static FIELD_INDEX_UNRESOLVED_PLACEHOLDER: std::sync::atomic::AtomicUsize = + std::sync::atomic::AtomicUsize::new(0); + /// Counters behind [`GcCache::spec_position_census`] — the same question the /// four above ask, put to the producer instead of to the reader. static FIELD_SPEC_CHECKED: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); @@ -702,6 +804,56 @@ static FIELD_ATTACHED_CHECKED: std::sync::atomic::AtomicUsize = static FIELD_ATTACHED_MISPLACED: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); +/// Counters behind [`GcCache::mint_index_census`]. +static FIELD_MINT_INDEX_CLAIMED: std::sync::atomic::AtomicUsize = + std::sync::atomic::AtomicUsize::new(0); +static FIELD_MINT_INDEX_PLACEHOLDER: std::sync::atomic::AtomicUsize = + std::sync::atomic::AtomicUsize::new(0); + +/// Whether a field mint's `index_in_parent` was WRITTEN by a lookup arm or is +/// still the initialiser the mint opened with. +/// +/// No other counter in this module records where that value came from. The ones +/// that look at it at all compare its VALUE — `census_attached_index` against +/// the attached parent's slot, `census_spec_positions` against the submitted +/// list's own order, `derive_index_in_parent` against a name lookup — and the +/// rest report a disposition reached before any comparison happens. An +/// unwritten initialiser is the literal `0`, so a mint that never claimed a slot +/// and a mint that claims slot 0 are the same bytes in the same table to every +/// one of them, and no reader can separate them at any level of care. +/// +/// This is the missing bit, taken at the only place that still holds it. +/// +/// UNGUARDED BY DESIGN, and that is the point. The censuses that ask about a +/// mint's `index_in_parent` are conditional on a usable parent — +/// `census_attached_index`'s caller needs `Some(parent_spec)` and a resolvable +/// slot, and `derive_index_in_parent` diverts to `FIELD_PARENT_ABSENT` / +/// `FIELD_PARENT_EMPTY` before it reaches the comparison. In exactly the case +/// where the placeholder survives unrepaired FOR WANT OF A PARENT, both of them +/// are silent. Counting here, before any parent is consulted, is what reaches +/// that hole. Do not move this call inside a parent check. +/// +/// What does NOT bump it, so the number is read against the right +/// population: only `codewriter::assembler::fielddescrof` calls this, so +/// `claimed + placeholder` is the count of `fielddescrof` mints and NOT of +/// field descrs in the program. +/// +/// The vable-field and vable-array synthesizers in `blackhole.rs` also emit +/// `index_in_parent: 0` — but as a literal, beside `parent: None`, with no +/// lookup in front of it that could have written something else. There is no +/// provenance question to ask of a constant: those mints never had a parent +/// list to claim a slot in, so counting them would add rows to `placeholder` +/// that are not the hazard this pair exists to size. +pub fn census_mint_index_provenance(claimed: bool) { + use std::sync::atomic::Ordering::Relaxed; + let counter = if claimed { + &FIELD_MINT_INDEX_CLAIMED + } else { + &FIELD_MINT_INDEX_PLACEHOLDER + }; + counter.fetch_add(1, Relaxed); +} + /// One field descr's two halves, compared against each other. /// /// `expected` is the slot of the attached parent's `all_fielddescrs` that this @@ -714,10 +866,19 @@ static FIELD_ATTACHED_MISPLACED: std::sync::atomic::AtomicUsize = /// Caller supplies `expected`, because only it holds the parent the producer /// actually attached. Once the descr reaches `get_field_descr` the parent is /// whatever `_cache_size` holds for the key, which is a different question. -pub fn census_attached_index(expected: usize, actual: usize) { +/// +/// `actual` is an `Option` because the producer's slot claim now carries its own +/// provenance, but this pair deliberately folds `None` to `0` before comparing: +/// `0` is the exact value the descr used to carry in that state, so folding +/// keeps `[checked, misplaced]` numerically identical to every reading taken +/// before the widening. The provenance split belongs to the counters that were +/// added to answer it — [`census_mint_index_provenance`] on the mint side and +/// `FIELD_INDEX_UNRESOLVED_{CLAIMED,PLACEHOLDER}` on the resolve side — not +/// here, where redefining `misplaced` would silently move a published number. +pub fn census_attached_index(expected: usize, actual: Option) { use std::sync::atomic::Ordering::Relaxed; FIELD_ATTACHED_CHECKED.fetch_add(1, Relaxed); - if expected != actual { + if expected != actual.unwrap_or(0) { FIELD_ATTACHED_MISPLACED.fetch_add(1, Relaxed); } } @@ -833,6 +994,23 @@ impl GcCache { } } + /// descriptor cardinality — the cardinality [`Self::setup_descrs`] would produce, **without + /// its `set_descr_index` side effect**. + /// + /// The selection-vs-pool fork needs `all_descrs` and the assembler's + /// `opcode_descrs` measured in the same generation: `all_descrs` stable + /// while the pool moves is a *selection* defect; both moving is a *pool* + /// defect. `setup_descrs` cannot serve — it stamps `descr_index` on every + /// entry, so calling it merely to count would perturb the thing measured. + pub fn all_descrs_len(&self) -> usize { + self._cache_size_order.len() + + self._cache_field_order.len() + + self._cache_array_order.len() + + self._cache_arraylen_order.len() + + self._cache_call_order.len() + + self._cache_interiorfield_order.len() + } + /// descr.py:25-47 setup_descrs(). /// /// Iterates per-type caches in fixed group order (size, field, array, @@ -1023,6 +1201,128 @@ impl GcCache { ] } + /// The `unresolved` population, named — one line per distinct mint, most + /// frequent first. + /// + /// `field_position_census`'s fourth number says how many field mints named + /// a field their parent's positional list does not contain. It cannot say + /// WHERE the fix belongs, and that is the whole decision: a population + /// concentrated in one producer is fixed at that producer, one spread + /// across every owner is fixed at the resolution layer. Counting cannot + /// distinguish those two and no amount of re-running changes that, so the + /// records have to be printed. + /// + /// Deliberately NOT reconstructed from the final cache the way + /// [`size_shell_owner_sample`]'s `orphan_fields` section is. That one + /// re-asks the question at exit, against parents that have grown since, and + /// it dedupes into a set — so it answers a neighbouring question and its + /// total does not match this counter's. These rows are the mint events + /// themselves, which is the population the counter counts. + /// + /// The first line is a header carrying the ungated counter next to the + /// number of records actually collected. They agree only if the knob was + /// set before the first mint; printing both makes a late-armed gate read as + /// a short table rather than as a small finding. `all_descrs` is the + /// caller's, for the same reason `descr_set_resolved` rides its own line — + /// a run where the descr pool never loaded reports zero here, and without a + /// denominator that zero is indistinguishable from a clean tree. + /// + /// `shown`, `distinct` and `limit` are all printed, and the knob's own value + /// sets the cap. `limit` is not redundant with `shown`: they coincide only + /// while the table is truncated, so without it a complete table cannot be + /// told from one that happens to fit. `order` is printed for the same + /// reason — rows are ranked by FREQUENCY, so a prefix is the most frequent + /// rows and never a sample, and a reader who takes a proportion from + /// `shown < distinct` is reading a ranking's head. + /// + /// [`size_shell_owner_sample`]: Self::size_shell_owner_sample + pub fn field_position_unresolved_sample(all_descrs: usize) -> Vec { + let limit = field_position_unresolved_limit().unwrap_or(0); + let [parent_absent, parent_empty, rederived, unresolved] = Self::field_position_census(); + let table = FIELD_UNRESOLVED_NAMES + .lock() + .unwrap_or_else(|e| e.into_inner()); + let recorded: usize = table.values().sum(); + // Frequency first, then the key's own order, so the ranking is total + // and two runs of one program print the same bytes. + let mut rows: Vec<(&UnresolvedFieldMint, usize)> = + table.iter().map(|(k, v)| (k, *v)).collect(); + rows.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(b.0))); + // `shown` equals `limit` exactly when the table is truncated, so the cap + // is recoverable from a SHORT table but not from a complete one. Print it + // either way: a reader who sees `distinct=158 shown=158` still has to know + // whether they asked for 158 or for everything, because only the second + // says no further rows exist. + let limit_shown = if limit == usize::MAX { + "all".to_string() + } else { + limit.to_string() + }; + // `claimed + placeholder == unresolved` by construction — both are bumped + // on the same arm, outside the naming gate — so the pair splits the + // COUNT and not the (possibly short) table. Printing it beside + // `recorded` is what lets a reader tell a producer that ranked the field + // wrongly from one that never ranked it at all; while `caller_index` was + // a plain `usize` those were the same rows. + let [unresolved_claimed, unresolved_placeholder] = Self::unresolved_index_provenance(); + let mut lines = vec![format!( + "field_pos_unresolved_names unresolved={unresolved} \ + unresolved_claimed={unresolved_claimed} \ + unresolved_placeholder={unresolved_placeholder} recorded={recorded} \ + distinct={} shown={} limit={limit_shown} order=count-desc,key-asc \ + mints={} all_descrs={all_descrs} names_scope=native-only", + table.len(), + rows.len().min(limit), + parent_absent + parent_empty + rederived + unresolved, + )]; + // The ordering is a total one and it is NOT the population's shape: rows + // are ranked by frequency, so a prefix is the most frequent rows, not a + // sample. Named in the output rather than only in this doc comment, + // because the reader taking a proportion has the line and not the source. + // Two clauses with two different subjects, and only one of them ever went + // stale: the COUNT's export exists now, the LISTING's absence is still + // real. Do not delete this line as stale — it is the only warning a wasm + // reader gets that the rows below are missing rather than empty. + lines.push( + "field_pos_unresolved_note names-listing only: the COUNT reaches wasm as \ + pyre_jit_field_pos_unresolved, but the guest has no stderr, so these per-mint \ + rows never appear there. Read the count; do not read their absence as a zero" + .to_string(), + ); + lines.extend(rows.iter().take(limit).map(|(mint, count)| { + // `none` rather than a number: the whole reason this field is an + // `Option` is that a producer which resolved nothing must not print + // the same token as one that resolved slot 0. + let caller_index = match mint.caller_index { + Some(i) => i.to_string(), + None => "none".to_string(), + }; + format!( + "field_pos_unresolved {count}x {} key={} caller_index={caller_index} \ + parent_fields={} parent=[{}]", + mint.label, mint.lookup_key, mint.parent_len, mint.parent_keys, + ) + })); + lines + } + + /// `[claimed, placeholder]` over the mints `derive_index_in_parent` could not + /// resolve — the split of `field_pos_unresolved` by whether the producer had + /// written an `index_in_parent` at all. + /// + /// Sums to `field_position_census()[3]` exactly. This is the resolve-side + /// half of the question [`census_mint_index_provenance`] asks on the mint + /// side, and the two run in DIFFERENT PROCESSES — pyre mints in + /// `pyre-jit-trace/build.rs` and resolves in the program — so neither pair + /// can be read off the other's numbers. + pub fn unresolved_index_provenance() -> [usize; 2] { + use std::sync::atomic::Ordering::Relaxed; + [ + FIELD_INDEX_UNRESOLVED_CLAIMED.load(Relaxed), + FIELD_INDEX_UNRESOLVED_PLACEHOLDER.load(Relaxed), + ] + } + /// `[checked, misplaced]` over field descrs compared against the parent /// their own producer attached — see [`census_attached_index`]. /// @@ -1044,6 +1344,25 @@ impl GcCache { ] } + /// `[claimed, placeholder]` over every `fielddescrof` mint — see + /// [`census_mint_index_provenance`] for what each arm means and for why the + /// call site is deliberately outside every parent check. + /// + /// Read it as the denominator the other two censuses cannot supply. A + /// `placeholder` of 0 settles the question outright: no mint carried an + /// unwritten index, so no row anywhere downstream can be a placeholder, and + /// every `index_in_parent == 0` in the program is a real slot claim. A + /// nonzero bounds the population instead of splitting it — it says how many + /// unwritten indices were minted, not how many of them reached any + /// particular table. + pub fn mint_index_census() -> [usize; 2] { + use std::sync::atomic::Ordering::Relaxed; + [ + FIELD_MINT_INDEX_CLAIMED.load(Relaxed), + FIELD_MINT_INDEX_PLACEHOLDER.load(Relaxed), + ] + } + /// How many published parents still list no fields, and how many of those /// are shadowing a layout this same cache already knows. /// @@ -1320,10 +1639,22 @@ impl GcCache { fields.iter().position(|f| f.field_key() == field_name) } + /// `label` is the descr's display name, used only to name the mint in the + /// `unresolved` table. It is not consulted for the lookup — `field_name` is + /// the key the parent's list is searched by, and the two disagreeing is one + /// of the states the table exists to report. + /// + /// `caller_index` is `None` when the producer never resolved a slot. The + /// existing counters compare against `caller_index.unwrap_or(0)`, which is + /// the literal the descr used to carry in that state, so their published + /// values are unchanged by the widening; the new information is taken by the + /// `UNRESOLVED_{CLAIMED,PLACEHOLDER}` pair and by the table row, which keep + /// the `Option` intact. fn derive_index_in_parent( parent: Option<&DescrRef>, field_name: &str, - caller_index: usize, + caller_index: Option, + label: &str, ) -> Option { use std::sync::atomic::Ordering::Relaxed; let Some(size_descr) = parent.and_then(|p| p.as_size_descr()) else { @@ -1336,13 +1667,40 @@ impl GcCache { } match Self::find_index_in_parent(parent, field_name) { Some(i) => { - if i != caller_index { + if i != caller_index.unwrap_or(0) { FIELD_INDEX_REDERIVED.fetch_add(1, Relaxed); } Some(i) } None => { FIELD_INDEX_UNRESOLVED.fetch_add(1, Relaxed); + // Outside the naming gate, so the split is taken over the whole + // unresolved population and not over the rows a late-armed knob + // happened to catch. + if caller_index.is_some() { + FIELD_INDEX_UNRESOLVED_CLAIMED.fetch_add(1, Relaxed); + } else { + FIELD_INDEX_UNRESOLVED_PLACEHOLDER.fetch_add(1, Relaxed); + } + if field_position_unresolved_naming_enabled() { + let listed = size_descr.all_fielddescrs(); + let mint = UnresolvedFieldMint { + label: label.to_string(), + lookup_key: field_name.to_string(), + caller_index, + parent_len: listed.len(), + parent_keys: listed + .iter() + .map(|f| f.field_key()) + .collect::>() + .join("|"), + }; + *FIELD_UNRESOLVED_NAMES + .lock() + .unwrap_or_else(|e| e.into_inner()) + .entry(mint) + .or_insert(0) += 1; + } None } } @@ -1354,7 +1712,10 @@ impl GcCache { /// `index_in_parent`: descr.py:228 heaptracker.get_fielddescr_index_in(STRUCT, fieldname). /// The structural slot number within the parent struct's field list. /// Caller must provide it: `heaptracker::get_fielddescr_index_in` runs - /// in `majit-translate`, one crate above this one. + /// in `majit-translate`, one crate above this one. `None` is a caller that + /// never resolved one — the state a deserialized `BhDescr::Field` can be + /// in, and the only state a plain `usize` could not express, since an + /// unwritten claim and a real slot-0 claim are the same integer. /// `flag`: descr.py:226 get_type_flag(FIELDTYPE). /// /// descr.py:234-238: parent_descr = get_size_descr(gccache, STRUCT, vtable). @@ -1378,7 +1739,7 @@ impl GcCache { flag: ArrayFlag, index: u32, virtualizable: bool, - index_in_parent: usize, + index_in_parent: Option, ) -> Arc { // descr.py:234-238: parent_descr = get_size_descr(gccache, STRUCT, vtable) let parent = self._cache_size.get(&struct_key).cloned(); @@ -1389,6 +1750,24 @@ impl GcCache { .and_then(|inner| inner.get(field_name)) .cloned(); if let Some(descr) = cached { + // field-cache identity: does this slot answer for the field the caller asked + // about? Passing the *cached* values for the three components + // whose expected value is derived differently per build profile + // (immutability pair below, `index_in_parent` further down) leaves + // offset / size / type / virtualizable as the discriminator — the + // physical payload, compared identically in every profile. + FIELD_DESCR_CACHE_HITS.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + if !descr.describes_same_field( + offset, + field_size, + field_type, + descr.is_immutable, + descr.is_quasi_immutable(), + virtualizable, + descr.index_in_parent, + ) { + FIELD_DESCR_CACHE_COLLISIONS.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + } // `front/mir.rs` leaves `SemanticProgram::immutable_fields` // empty for the whole LLBC pipeline — Charon serializes doc // comments but not the `#[jit_immutable_fields]` hint — so @@ -1444,7 +1823,7 @@ impl GcCache { size {field_size}, type {field_type:?}, immutable \ {expected_immutable}, quasi {expected_quasi_immutable}, \ vable {virtualizable}, index_in_parent \ - {expected_index_in_parent} [caller said {index_in_parent}])", + {expected_index_in_parent} [caller said {index_in_parent:?}])", descr.name, descr.offset, descr.field_size, @@ -1497,9 +1876,17 @@ impl GcCache { // // So take the index from the parent that will actually be indexed, // rather than from whoever happened to call. + // + // The two fallbacks are not the same question. The parent's answer wins + // when it has one; failing that the caller's claim stands; failing THAT + // there is no claim at all, and `0` is what the descr has always carried + // in that state — written here as an explicit floor rather than as an + // initialiser, so the next reader sees that it is a fallback and not a + // position anyone computed. fd.index_in_parent = - Self::derive_index_in_parent(parent.as_ref(), field_name, index_in_parent) - .unwrap_or(index_in_parent); + Self::derive_index_in_parent(parent.as_ref(), field_name, index_in_parent, &fd.name) + .or(index_in_parent) + .unwrap_or(0); // descr.py:229 `is_quasi_immutable = '%s?' in STRUCT._hints.get( // '_immutable_fields_', ())` parity. The analyzer side reads // `#[jit_immutable_fields(..., "field?", ...)]` via @@ -1718,7 +2105,6 @@ impl GcCache { descr } - // ── External registration (cache-bypass mint sites) ───────────── // // PyPy `gc_cache._cache_*` is populated *exclusively* via the // cache-or-mint `get_*_descr` API. Pyre's lift currently has many @@ -1976,7 +2362,6 @@ impl GcCache { descr } - // ── Per-category snapshot accessors ───────────────────────────── // // `setup_descrs()` returns the full enumeration in PyPy group order; // these accessors expose individual groups for callers that need to @@ -2098,6 +2483,36 @@ pub fn gc_cache() -> &'static Mutex { GC_CACHE.get_or_init(|| Mutex::new(GcCache::new())) } +/// field-cache identity — `get_field_descr` cache hits, and the subset whose cached payload +/// **disagrees** with what the caller asked for. +/// +/// A disagreeing hit means one `(LLType, fieldname)` slot is answering for two +/// different logical fields: the key is not injective over the field universe. +/// That is what can change the number of distinct `Arc`s the descr pool sees — +/// and it is invisible to any artefact diff, which can only show that the bytes +/// moved, never whether the pool over-split or the mint minted twice. +/// +/// **Why this and not "distinct `Arc`s per `(LLType, fieldname)`".** That +/// quantity is identically 1: `_cache_field` *is* keyed by that pair, so the +/// map holds one `Arc` per key by construction and a counter reading it back +/// would report `max = 1` on every possible input. Counting the collisions is +/// the same question asked where it can still be answered. +/// +/// Deliberately **not** `cfg!(debug_assertions)`-gated. The `debug_assert!` +/// in `get_field_descr` already computes a disagreement and discards it in +/// release; a counter that compiled out would read zero and look clean. +static FIELD_DESCR_CACHE_HITS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); +static FIELD_DESCR_CACHE_COLLISIONS: std::sync::atomic::AtomicU64 = + std::sync::atomic::AtomicU64::new(0); + +/// `(hits, collisions)` for the `get_field_descr` cache since process start. +pub fn field_descr_cache_collisions() -> (u64, u64) { + ( + FIELD_DESCR_CACHE_HITS.load(std::sync::atomic::Ordering::Relaxed), + FIELD_DESCR_CACHE_COLLISIONS.load(std::sync::atomic::Ordering::Relaxed), + ) +} + /// The mint arguments behind every `GcCache` slot an `EffectInfo` raw set /// names — the projection of this cache that has to survive the /// analyzer→runtime process split. @@ -2731,7 +3146,6 @@ pub trait FailDescr: Descr { ); } - // ────────────────────────────────────────────────────────────────── // compile.py:855 ResumeGuardDescr._attrs_ = ('rd_numb', 'rd_consts', // 'rd_virtuals', 'rd_pendingfields', 'status') // @@ -2748,7 +3162,6 @@ pub trait FailDescr: Descr { // `ExitFrameWithExceptionDescrRef`) — these never carry resume // data, matching RPython where the `_attrs_` only live on // `AbstractResumeGuardDescr` subclasses. - // ────────────────────────────────────────────────────────────────── /// resume.py:450 — compact resume numbering bytes. fn rd_numb(&self) -> Option<&[u8]> { @@ -5000,7 +5413,15 @@ pub fn make_simple_descr_group_keyed_with_headerless( spec.flag, spec.index, spec.virtualizable, - spec.index_in_parent, + // `Some` because `SimpleFieldDescrSpec.index_in_parent` is a + // plain `usize` and CANNOT express the absence, not because + // every spec resolved a slot: the spec factories in + // `pyre-jit-trace/src/descr.rs` write literal `index_in_parent: + // 0` at a dozen sites. So on this path `unresolved_claimed` is + // an upper bound and `unresolved_placeholder` a lower one. The + // bound is exact only for callers that hand the `Option` in + // themselves — the deserialized-`BhDescr::Field` path. + Some(spec.index_in_parent), ) }) .collect(); @@ -6320,7 +6741,7 @@ mod register_keyed_size_authority_tests { 0, false, // A header-counting producer's number, two slots high. - 3, + Some(3), ) }; let first = lookup(&mut gc); @@ -6356,7 +6777,7 @@ mod register_keyed_size_authority_tests { ArrayFlag::Float, 0, false, - 0, + Some(0), ); assert_eq!(first.index_in_parent, 0); // Second lookup: a header-counting producer hands index 2. No parent @@ -6374,7 +6795,7 @@ mod register_keyed_size_authority_tests { ArrayFlag::Float, 0, false, - 2, + Some(2), ); assert_eq!(second.index_in_parent, 0, "the cached descr wins"); } diff --git a/majit/majit-ir/src/effectinfo.rs b/majit/majit-ir/src/effectinfo.rs index f2336fe0a14..243bef7f76f 100644 --- a/majit/majit-ir/src/effectinfo.rs +++ b/majit/majit-ir/src/effectinfo.rs @@ -35,6 +35,10 @@ impl std::error::Error for UnsupportedFieldExc {} /// these tuples. The key alone identifies the slot; what it does *not* carry /// is the layout needed to create the descr when the slot is empty, which is /// what [`DescrMintSpec`] adds. +/// `Ord` is derived so build-time consumers can order a raw set by its +/// *members* rather than by `Arc::as_ptr`. A heap address orders a set +/// reproducibly within one process and arbitrarily across two, which makes +/// any artifact carrying that order a non-function of its inputs. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub enum DescrSetMember { /// `descr.py:218-239 get_field_descr(gccache, STRUCT, fieldname)` — @@ -1215,9 +1219,7 @@ impl EffectInfo { } } -// ════════════════════════════════════════════════════════════════════════ // effectinfo.py:422-461: CallInfoCollection -// ════════════════════════════════════════════════════════════════════════ /// effectinfo.py:422: `class CallInfoCollection(object)`. /// @@ -1278,7 +1280,6 @@ impl CallInfoCollection { } } -// ════════════════════════════════════════════════════════════════════════ // effectinfo.py:465-547 `compute_bitstrings(all_descrs)`. /// `effectinfo.py:182-184` "no new EffectInfo after compute_bitstrings" /// invariant — flipped to `true` on the first @@ -1632,8 +1633,15 @@ pub fn compute_bitstrings(all_descrs: &[DescrRef], all_eis: &mut [&mut EffectInf // ptr-id is exact. let mut all_sets: Vec<(usize, DescrRef, Vec, Vec)> = Vec::with_capacity(category_descrs.len()); - // Fix iteration order — sort by ptr-id so the popularity-sort - // tie-break below is deterministic across runs. + // Fix the iteration order so the popularity-sort tie-break below + // does not inherit `category_descrs`' arbitrary one. + // + // A ptr-id is a heap address: it orders the walk reproducibly + // within one process and arbitrarily across two. That is the whole + // requirement here — `compute_bitstrings` runs at runtime, and no + // output of it crosses a process boundary. Anything moved from here + // to build time loses the property, because the two sides are two + // processes. let mut sorted_descrs: Vec<(usize, DescrRef)> = category_descrs.into_iter().collect(); sorted_descrs.sort_by_key(|(pid, _)| *pid); for (pid, descr) in sorted_descrs { @@ -1665,7 +1673,9 @@ pub fn compute_bitstrings(all_descrs: &[DescrRef], all_eis: &mut [&mut EffectInf // `effectinfo.py:519-521`: heuristic — sort by len(eisetr) + // len(eisetw) descending so the most popular descrs claim the // low ei_index slots, reducing total bitstring length. Tie- - // break on ptr-id ascending for determinism. + // break on ptr-id ascending, which fixes the order within this + // process (see the sort above for what a ptr-id does and does not + // promise). all_sets.sort_by(|a, b| { (b.2.len() + b.3.len()) .cmp(&(a.2.len() + a.3.len())) diff --git a/majit/majit-ir/src/lib.rs b/majit/majit-ir/src/lib.rs index a60dc8c6d35..8a683ade3a3 100644 --- a/majit/majit-ir/src/lib.rs +++ b/majit/majit-ir/src/lib.rs @@ -18,6 +18,8 @@ pub mod rawbuffer; pub mod resoperation; pub mod resumecode; pub mod resumedata; +#[cfg(feature = "test-support")] +pub mod test_support; pub mod value; // Re-export key types at crate root for convenience. @@ -43,7 +45,9 @@ pub use resoperation::{ Op, OpCode, OpRc, OpRef, RdVirtualInfo, VectorizationInfo, VirtualFieldsInfo, format_trace, }; pub use value::{ - Const, FAILARGS_LIMIT, GcRef, GreenAsI64, GreenKey, GreenType, InputArg, InputArgRc, - JitDriverVar, SharedConstPool, StrEqFn, StrHashFn, Type, Value, VarKind, green_type_to_ir, - make_str_slot, pypyjit_greenkey_uhash, set_str_resolver, set_unicode_resolver, + Const, FAILARGS_LIMIT, GREEN_UHASH_MULT, GREEN_UHASH_SEED, GcRef, GreenAsI64, GreenKey, + GreenType, InputArg, InputArgRc, JitDriverVar, RefReleaseFn, RefRetainFn, RetainedGreens, + SharedConstPool, StrEqFn, StrHashFn, Type, Value, VarKind, green_type_to_ir, green_uhash_step, + make_str_slot, pypyjit_greenkey, pypyjit_greenkey_uhash, set_ref_resolver, set_str_resolver, + set_unicode_resolver, }; diff --git a/majit/majit-ir/src/test_support.rs b/majit/majit-ir/src/test_support.rs new file mode 100644 index 00000000000..77ad740b294 --- /dev/null +++ b/majit/majit-ir/src/test_support.rs @@ -0,0 +1,145 @@ +//! Helpers for downstream tests that need to construct backend input traces. + +use crate::operand::Operand; +use crate::{DescrRef, InputArg, InputArgRc, Op, OpCode, OpRc, OpRef, Type}; + +pub struct RecordedTrace { + pub inputargs: Vec, + pub ops: Vec, +} + +pub struct Trace { + inputargs: Vec, + ops: Vec, + next_position: u32, +} + +impl Trace { + pub fn new() -> Self { + Self { + inputargs: Vec::new(), + ops: Vec::new(), + next_position: 0, + } + } + + pub fn record_input_arg(&mut self, tp: Type) -> OpRef { + assert!(self.ops.is_empty(), "input args must precede operations"); + assert_ne!(tp, Type::Void, "input args cannot be void"); + + let position = self.next_position; + self.inputargs.push(InputArg::from_type_rc(tp, position)); + self.next_position += 1; + OpRef::input_arg_typed(position, tp) + } + + pub fn record_op(&mut self, opcode: OpCode, args: &[OpRef]) -> OpRef { + assert!(!opcode.is_guard(), "use record_guard for guards"); + self.push_op(opcode, args, None, None) + } + + pub fn record_op_with_descr( + &mut self, + opcode: OpCode, + args: &[OpRef], + descr: DescrRef, + ) -> OpRef { + assert!(!opcode.is_guard(), "use record_guard for guards"); + self.push_op(opcode, args, Some(descr), None) + } + + pub fn record_guard( + &mut self, + opcode: OpCode, + args: &[OpRef], + descr: Option, + ) -> OpRef { + assert!(opcode.is_guard(), "opcode is not a guard"); + self.push_op(opcode, args, descr, None) + } + + pub fn record_guard_with_fail_args( + &mut self, + opcode: OpCode, + args: &[OpRef], + descr: Option, + fail_args: &[OpRef], + ) -> OpRef { + assert!(opcode.is_guard(), "opcode is not a guard"); + self.push_op(opcode, args, descr, Some(fail_args)) + } + + pub fn close_loop(&mut self, args: &[OpRef]) { + self.push_op(OpCode::Jump, args, None, None); + } + + pub fn finish(&mut self, args: &[OpRef], descr: DescrRef) { + self.push_op(OpCode::Finish, args, Some(descr), None); + } + + pub fn get_trace(self) -> RecordedTrace { + RecordedTrace { + inputargs: self.inputargs, + ops: self.ops, + } + } + + fn push_op( + &mut self, + opcode: OpCode, + args: &[OpRef], + descr: Option, + fail_args: Option<&[OpRef]>, + ) -> OpRef { + let position = self.next_position; + let opref = OpRef::op_typed(position, opcode.result_type()); + let args = self.bind_operands(args); + let op = match descr { + Some(descr) => Op::with_descr(opcode, &args, descr), + None => Op::new(opcode, &args), + }; + op.pos.set(opref); + if let Some(fail_args) = fail_args { + op.setfailargs(self.bind_operands(fail_args).into_iter().collect()); + } + self.ops.push(OpRc::new(op)); + self.next_position += 1; + opref + } + + fn bind_operands(&self, refs: &[OpRef]) -> Vec { + refs.iter().map(|&opref| self.bind_operand(opref)).collect() + } + + fn bind_operand(&self, opref: OpRef) -> Operand { + if opref.is_none() || opref.is_constant() { + return Operand::from_opref(opref); + } + if matches!( + opref, + OpRef::InputArgInt(_) | OpRef::InputArgFloat(_) | OpRef::InputArgRef(_) + ) { + let inputarg = self + .inputargs + .get(opref.raw() as usize) + .expect("input operand must name a recorded input argument"); + return Operand::from_bound_inputarg(inputarg); + } + + let op_index = (opref.raw() as usize) + .checked_sub(self.inputargs.len()) + .expect("operation operand must follow the input arguments"); + let op = self + .ops + .get(op_index) + .expect("operation operand must name a recorded operation"); + assert_eq!(op.pos.get(), opref, "operation operand type must match"); + Operand::from_bound_op(op) + } +} + +impl Default for Trace { + fn default() -> Self { + Self::new() + } +} diff --git a/majit/majit-ir/src/value.rs b/majit/majit-ir/src/value.rs index 731401e7c2b..6ae79b57cda 100644 --- a/majit/majit-ir/src/value.rs +++ b/majit/majit-ir/src/value.rs @@ -666,6 +666,136 @@ pub fn set_unicode_resolver(eq: StrEqFn, hash: StrHashFn) { let _ = UNICODE_HASH.set(hash); } +/// Frontend hooks that keep a `GreenType::Ref` green's referent alive for as +/// long as a cell stores it. +/// +/// Unlike every other citation in this file, this pair ports an INVARIANT +/// and not a function. There is no upstream symbol to point at: RPython's +/// `JitCell.__init__` (warmstate.py:568-573) does +/// +/// ```python +/// for attrname, _ in green_args_name_spec: +/// setattr(self, attrname, greenargs[i]) +/// ``` +/// +/// and a `setattr` of a gc pointer simply *is* an owning store — the GC is +/// universal, so ownership needs no code. Do not "fix" this comment by +/// citing a `warmstate.py` helper; none exists, and a faithful citation over +/// unfaithful code is worse than no citation. +/// +/// The invariant it restores: `equal_whatever(Ref, ..)` and +/// `hash_whatever(Ref, ..)` both reduce a `Ref` green to its raw address, and +/// **address equality is a correct identity only while both addresses are kept +/// alive by their owners** — two distinct *live* objects cannot share an +/// address. Upstream satisfies this because the cell that stores the key is +/// the owner. Without these hooks a freed referent's address can be recycled +/// by a different object, whose green key is then byte-identical to the dead +/// one's: a true collision in the identity that no comparator can resolve. +/// +/// Ownership belongs to the **stored** key, not to [`GreenKey`] as a type. +/// A key built to *look up* a cell is transient and the caller is executing +/// the very code it names, so it needs no retain; `pyjitpl`'s decision key is +/// a reused thread-local whose slots are overwritten in place, and giving +/// `GreenKey` a `Drop` would leak the old referent and fail to retain the new +/// one on every back edge. Retain where the cell takes its copy, which is +/// also where upstream's `setattr` runs. +pub type RefRetainFn = fn(i64); +pub type RefReleaseFn = fn(i64); + +static REF_RETAIN: std::sync::OnceLock = std::sync::OnceLock::new(); +static REF_RELEASE: std::sync::OnceLock = std::sync::OnceLock::new(); + +/// Frontend-registered `Ref` green ownership hooks. Same init-once contract +/// as [`set_str_resolver`]. Register at JitDriver startup, before any cell +/// stores a typed green key. +pub fn set_ref_resolver(retain: RefRetainFn, release: RefReleaseFn) { + let _ = REF_RETAIN.set(retain); + let _ = REF_RELEASE.set(release); +} + +/// OPEN POLICY FORK — the one place that decides what an unregistered +/// frontend gets. Today: today's behaviour, i.e. no retain, which leaves the +/// address-recycling hazard exactly as it is rather than changing it. +/// +/// This is deliberately NOT [`set_str_resolver`]'s panic-on-unregistered +/// contract. That contract is right for STR because comparing a STR green +/// without the frontend's decoder yields a *wrong answer*, so refusing is +/// strictly better. A `Ref` green compares correctly without a retainer; it +/// is only unsound once a referent is freed and its address reused. Copying +/// the panic here would break every frontend in the tree on day one for a +/// hazard most of them may not have. +/// +/// The alternative under consideration is to require an explicit declaration — +/// either [`set_ref_resolver`] or an explicit "unmanaged" opt-out — and panic +/// when a `Ref` green is stored with neither, converting a silent assumption +/// into a checkable one. Until that is decided, registering is opt-in: a +/// frontend that registers gets the invariant, one that does not is no worse +/// off than before. +fn ref_hooks() -> Option<(RefRetainFn, RefReleaseFn)> { + match (REF_RETAIN.get(), REF_RELEASE.get()) { + (Some(retain), Some(release)) => Some((*retain, *release)), + _ => None, + } +} + +/// Owning handle over the `Ref`-typed greens of one stored [`GreenKey`]. +/// +/// Retains on construction and releases on drop, so a cell holding one keeps +/// its referents alive for exactly the cell's lifetime. Deliberately not +/// `Clone`: a second owner would need a second retain, and every store site +/// should be visible as its own [`RetainedGreens::retain`] call. +/// +/// `GreenType::Str` / `GreenType::Unicode` are the same hazard class — they +/// are pointers too, and `green_type_to_type` maps both to [`Type::Ref`] — but +/// they are not retained here because `majit-macros` refuses those tags at +/// parse time today. If that refusal is lifted, this is the site that must +/// grow with it. +#[derive(Debug, Default)] +pub struct RetainedGreens { + retained: Vec, +} + +impl RetainedGreens { + /// Retain every non-null `Ref` green in `key`. A null slot is skipped: + /// `hash_whatever` already folds null to 0 and there is nothing to own. + pub fn retain(key: &GreenKey) -> Self { + let Some((retain, _)) = ref_hooks() else { + return RetainedGreens::default(); + }; + let mut retained = Vec::new(); + for (i, &value) in key.values.iter().enumerate() { + if key.types.get(i).copied().unwrap_or(GreenType::Int) == GreenType::Ref && value != 0 { + retain(value); + retained.push(value); + } + } + RetainedGreens { retained } + } + + /// Number of referents this handle owns. Feeds the pinned-population + /// counter: the memory an unbounded cell table now holds live is exactly + /// the sum of this over all cells, and it must be readable rather than + /// showing up only as RSS. + pub fn len(&self) -> usize { + self.retained.len() + } + + pub fn is_empty(&self) -> bool { + self.retained.is_empty() + } +} + +impl Drop for RetainedGreens { + fn drop(&mut self) { + let Some((_, release)) = ref_hooks() else { + return; + }; + for &value in &self.retained { + release(value); + } + } +} + /// Pyre canonical `ll_streq` analog for `GreenType::Str` / `GreenType::Unicode`. /// /// Public so consumers can register it at startup via @@ -924,6 +1054,33 @@ pub fn hash_whatever(tp: GreenType, value: i64) -> u64 { } } +/// Seed of `JitCell.get_uhash` — `x = r_uint(-1888132534)` +/// (warmstate.py:586). +pub const GREEN_UHASH_SEED: u64 = (-1888132534_i64) as u64; + +/// Multiplier of `JitCell.get_uhash` — `x = (x ^ y) * r_uint(1405695061)` +/// (warmstate.py:591). +pub const GREEN_UHASH_MULT: u64 = 1405695061; + +/// One `get_uhash` fold step over a single green. +/// +/// Upstream never materialises the greens: `get_uhash(*greenargs)` unrolls +/// over `green_args_name_spec`, which is fixed per JitCell class at +/// translation time (warmstate.py:584-593). A caller that likewise knows its +/// greens statically folds with this instead of building a [`GreenKey`] — +/// [`pypyjit_greenkey_uhash`] for pyre's fixed portal tuple, and the +/// `#[jit_interp]` macro, whose green count and types are known at expansion +/// time. +/// +/// Shared with [`GreenKey::get_uhash`] so an unrolled caller and the +/// vector-based key cannot drift apart: a divergence here would file a loop +/// under a key nothing enters, which reads as a performance result rather +/// than a defect. +#[inline(always)] +pub fn green_uhash_step(x: u64, tp: GreenType, value: i64) -> u64 { + (x ^ hash_whatever(tp, value)).wrapping_mul(GREEN_UHASH_MULT) +} + /// Structured green key — represents the exact values and types of all /// green variables at a particular program point. /// @@ -1013,11 +1170,10 @@ impl GreenKey { /// x = (x ^ y) * r_uint(1405695061) /// return x pub fn get_uhash(&self) -> u64 { - let mut x: u64 = (-1888132534_i64) as u64; + let mut x: u64 = GREEN_UHASH_SEED; for i in 0..self.values.len() { let tp = self.types.get(i).copied().unwrap_or(GreenType::Int); - let y = hash_whatever(tp, self.values[i]); - x = (x ^ y).wrapping_mul(1405695061); + x = green_uhash_step(x, tp, self.values[i]); } x } @@ -1036,13 +1192,37 @@ impl GreenKey { /// merge-point hook hashes this per back-edge, so the hot path must not /// allocate (warmstate.py:584-593 `JitCell.get_uhash`). pub fn pypyjit_greenkey_uhash(pc: usize, is_being_profiled: bool, code_ptr: u64) -> u64 { - let mut x: u64 = (-1888132534_i64) as u64; - x = (x ^ hash_whatever(GreenType::Int, pc as i64)).wrapping_mul(1405695061); - x = (x ^ hash_whatever(GreenType::Int, is_being_profiled as i64)).wrapping_mul(1405695061); - x = (x ^ hash_whatever(GreenType::Ref, code_ptr as i64)).wrapping_mul(1405695061); + let mut x: u64 = GREEN_UHASH_SEED; + x = green_uhash_step(x, GreenType::Int, pc as i64); + x = green_uhash_step(x, GreenType::Int, is_being_profiled as i64); + x = green_uhash_step(x, GreenType::Ref, code_ptr as i64); x } +/// The typed pypyjit portal green tuple `[next_instr:Int, +/// is_being_profiled:Int, pycode:Ref]` (interp_jit.py:67-70) that +/// [`pypyjit_greenkey_uhash`] hashes. +/// +/// The two are a pair and must stay one: the uhash form is the hot +/// back-edge path (it must not allocate), this form is what a cell read +/// compares against (`JitCell.comparekey`, warmstate.py:575-582). Sites +/// that only need to *find a bucket* take the uhash; sites that need to +/// know *which cell in it* take this. Building the tuple here rather +/// than open-coding `vec![pc, 0, code]` at each caller is what keeps the +/// slot order and the `Ref` tag on the pycode from drifting apart — a +/// swapped slot still hashes to something, just not to the same cell. +/// +/// `is_being_profiled` is a real green in the spec even though pyre's +/// JIT path always passes `false`; it is a parameter and not a folded +/// constant so a future profiled portal does not silently share cells +/// with the unprofiled one. +pub fn pypyjit_greenkey(pc: usize, is_being_profiled: bool, code_ptr: u64) -> GreenKey { + GreenKey::with_types( + vec![pc as i64, is_being_profiled as i64, code_ptr as i64], + vec![GreenType::Int, GreenType::Int, GreenType::Ref], + ) +} + /// Macro-emitted bridge used by `#[jit_interp]` to build a typed /// `GreenKey` from heterogeneous green expressions. /// @@ -1160,6 +1340,85 @@ mod tests { assert_ne!(k1.hash_u64(), k3.hash_u64()); } + /// An unrolled [`green_uhash_step`] fold must equal + /// [`GreenKey::get_uhash`] over the same greens, for every arity and + /// type mix. + /// + /// This is the contract that lets a caller who knows its greens + /// statically skip building the key's `values` / `types` vectors — + /// upstream's shape, where `get_uhash(*greenargs)` unrolls over a + /// per-JitCell-class spec (warmstate.py:584-593). The `#[jit_interp]` + /// macro emits exactly this fold. + /// + /// It is a correctness test, not a performance one: the two spellings + /// disagreeing would file a loop under a key nothing enters, so + /// `has_compiled_loop` misses forever and every back edge re-arms + /// tracing. That surfaces as "the JIT got slower", not as a failure — + /// which is why the equality is pinned rather than assumed. + /// + /// `Str` / `Unicode` are excluded here because `hash_whatever` routes + /// them through a resolver a frontend must register first; they are + /// covered in `tests/green_key_str_content_equality.rs`, which does + /// register one. + #[test] + fn unrolled_fold_equals_greenkey_get_uhash() { + // Deterministic sweep — no RNG seed to drift, and every case is + // reproducible by index. Boundary values first: `hash_whatever` + // special-cases 0 for Ref, and Float reinterprets the bits. + let sample_values: [i64; 9] = [ + 0, + 1, + -1, + 42, + i64::MAX, + i64::MIN, + (1.0_f64).to_bits() as i64, + (-0.0_f64).to_bits() as i64, + (f64::NAN).to_bits() as i64, + ]; + let sample_types = [ + GreenType::Int, + GreenType::Float, + GreenType::Ref, + GreenType::Void, + ]; + + let mut checked = 0usize; + // Arity 0 (the empty key) through 8, covering every type at every + // position via an offset walk over both sample tables. + for arity in 0..=8usize { + for offset in 0..sample_values.len() * sample_types.len() { + let mut values = Vec::with_capacity(arity); + let mut types = Vec::with_capacity(arity); + for i in 0..arity { + values.push(sample_values[(offset + i) % sample_values.len()]); + types.push(sample_types[(offset + 3 * i) % sample_types.len()]); + } + + // The unrolled form: what a static caller emits. + let mut unrolled = GREEN_UHASH_SEED; + for i in 0..arity { + unrolled = green_uhash_step(unrolled, types[i], values[i]); + } + + let via_key = GreenKey::with_types(values.clone(), types.clone()).get_uhash(); + assert_eq!( + unrolled, via_key, + "unrolled fold diverged from GreenKey::get_uhash at \ + arity={arity} offset={offset} values={values:?} types={types:?}", + ); + checked += 1; + } + } + + // Guard the guard: an empty sweep would assert nothing. + assert_eq!( + checked, + 9 * 36, + "sweep did not run the expected number of cases", + ); + } + #[test] fn green_repr_returns_per_type_green_type() { assert_eq!(7i64.__green_repr(), (7i64, GreenType::Int)); @@ -1227,6 +1486,11 @@ mod tests { (7, true, 0xdead_beef), (123456, false, 0x7fff_ffff_ffff_fff0), (1, true, 1), + // Width boundaries: `pc` widens usize -> i64 and `code_ptr` + // u64 -> i64, so a sign-extension slip shows up here and + // nowhere in the cases above. + (usize::MAX, false, u64::MAX), + (usize::MAX, true, 0), ]; for &(pc, profiled, code) in cases { let key = GreenKey::with_types( @@ -1238,6 +1502,22 @@ mod tests { key.get_uhash(), "uhash mismatch for (pc={pc}, profiled={profiled}, code={code:#x})" ); + // The constructor is checked against the hand-written literal + // above, not against the uhash it is paired with: comparing the + // two functions to each other would pass just as happily if both + // agreed on a wrong slot order. `values` and `types` are asserted + // separately because `GreenKey`'s `PartialEq` routes through + // `equal_whatever`, which is exactly the layout-sensitive thing + // under test. + let built = pypyjit_greenkey(pc, profiled, code); + assert_eq!( + built.values, key.values, + "greenkey values mismatch for (pc={pc}, profiled={profiled}, code={code:#x})" + ); + assert_eq!( + built.types, key.types, + "greenkey types mismatch for (pc={pc}, profiled={profiled}, code={code:#x})" + ); } } } diff --git a/majit/majit-macros/src/jit_interp/classify.rs b/majit/majit-macros/src/jit_interp/classify.rs index e525be81b1c..387b312c3da 100644 --- a/majit/majit-macros/src/jit_interp/classify.rs +++ b/majit/majit-macros/src/jit_interp/classify.rs @@ -92,8 +92,7 @@ fn classify_arm_body(body: &Expr) -> ArmPattern { ArmPattern::Lowerable } -// ── Pattern detection helpers ──────────────────────────────────────── - +// Pattern detection helpers fn is_break_expr(expr: &Expr) -> bool { matches!(expr, Expr::Break(_)) } @@ -303,8 +302,10 @@ fn check_expr_unsupported(expr: &Expr) -> Option { return Some(reason); } } - if let Some((_, else_expr)) = &if_expr.else_branch { - check_expr_unsupported(else_expr)?; + if let Some((_, else_expr)) = &if_expr.else_branch + && let Some(reason) = check_expr_unsupported(else_expr) + { + return Some(reason); } None } @@ -420,4 +421,39 @@ mod tests { let result = classify_arm_body(&arm.body); assert!(matches!(result, ArmPattern::AbortPermanent)); } + + /// NOT behavioural coverage of `detect_unsupported_pattern` — read the + /// assertion as what it says. + /// + /// `check_stmt_unsupported` and `check_expr_unsupported` are mutually + /// recursive and **neither constructs a `String`**: every `Some(..)` in the + /// cycle forwards the other's return. So the cycle answers `None` for every + /// input, `ArmPattern::Unsupported` (built only at the `detect_unsupported_pattern` + /// call above) is never constructed, and no behavioural test of this cycle can + /// fail. This one pins that dormancy instead, over an arm exercising every + /// construct `check_expr_unsupported` matches. + /// + /// ⇒ When it goes red, someone has added a leaf that mints a reason — which + /// is exactly the moment the recursion arms start carrying information, and + /// the moment each needs a real test. The `Expr::If` else branch is the one + /// to write first: it is the arm whose finding used to be discarded. + /// + /// It is a LOWER BOUND, measured rather than assumed: a leaf added as a new + /// `Expr` arm for a construct this sample does not contain stays invisible to + /// it. A temporary `Expr::Await(_) => Some(..)` leaf left this green while the + /// else-branch recursion was demonstrably broken. The exact question is + /// whether any `Some(..)` in `check_stmt_unsupported`/`check_expr_unsupported` + /// mints a `String` rather than forwarding one; this test samples that, it + /// does not decide it. + #[test] + fn unsupported_cycle_still_mints_no_reason() { + let arm = parse_arm( + "0 => { + let v = if a { b(); } else { match c { 1 => { loop { break; } }, _ => d() } }; + while e { for i in 0..2 { { f(); } } } + },", + ); + let stmts = extract_stmts(&arm.body); + assert_eq!(detect_unsupported_pattern(&stmts), None); + } } diff --git a/majit/majit-macros/src/jit_interp/codegen_state.rs b/majit/majit-macros/src/jit_interp/codegen_state.rs index 8a237f98e99..21ba5cf234d 100644 --- a/majit/majit-macros/src/jit_interp/codegen_state.rs +++ b/majit/majit-macros/src/jit_interp/codegen_state.rs @@ -23,6 +23,21 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T let prebuild_fn_name = format_ident!("__prebuild_jitcode_liveness_{}", func.sig.ident); let dispatch_jitcode_fn_name = format_ident!("__dispatch_jitcode_{}", func.sig.ident); let declare_schema_fn_name = format_ident!("__declare_jit_schema_{}", func.sig.ident); + // Every module-level item this macro emits is suffixed with the annotated + // function's name, because the expansion lands in the CALLER's module and two + // machines in one module would otherwise collide (E0428). These five were + // fixed names while the three above were already suffixed; the split was not + // deliberate. Note the ceiling: unique names let two machines share a module + // only when their `state` types DIFFER -- two machines over the same state + // type still conflict on `impl JitState for #state_type` (E0119), which no + // naming scheme can fix. + let meta_ty = format_ident!("__JitMeta_{}", func.sig.ident); + let sym_ty = format_ident!("__JitSym_{}", func.sig.ident); + // `_name` because `loop_carried_boxes_fn` is already taken further down by the + // TokenStream holding the whole function definition; this is only its ident. + let loop_carried_boxes_fn_name = format_ident!("__jit_loop_carried_boxes_{}", func.sig.ident); + let fresh_alloc_fn = format_ident!("__majit_recursive_fresh_alloc_{}", func.sig.ident); + let fresh_free_fn = format_ident!("__majit_recursive_fresh_free_{}", func.sig.ident); let sf = config.state_fields.as_ref().unwrap(); let unsupported_fields: Vec = sf @@ -180,7 +195,7 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T quote! {} }; - // ── __JitMeta fields: one `{name}_len: usize` per flattened array ── + // `__JitMeta_` fields: one `{name}_len: usize` per flattened array // Virt arrays do NOT store length in meta: `virtualizable.py:150-153` reads // each one off the live object, so it is neither a meta field nor a box. let meta_fields: Vec = arrays @@ -191,10 +206,20 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T }) .collect(); - // ── __JitSym fields ── + // `__JitSym_` fields // scalar → OpRef // flattened array → Vec - // virt array → (OpRef, OpRef) for (data_ptr, len) + // virt array → an `i64` length mirror, and nothing else (see + // `sym_virt_array_fields` below). This line used to read + // `(OpRef, OpRef) for (data_ptr, len)`; no such pair is built here or + // anywhere else. A virt array's base address is NEVER materialised as an + // SSA value: the vable-relative `getarrayitem_vable_*` / + // `setarrayitem_vable_*` ops carry `(fdescr, adescr)` and resolve the base + // inside the op, off the live virtualizable. The only OpRef a + // virtualizable gets is the single `__vable_identity` below. + // Worth stating rather than deleting: read as a promise that the base is + // already available, the old wording collapses the design of anything that + // needs one. let sym_scalar_fields: Vec = scalars .iter() .map(|(_, f)| { @@ -364,7 +389,7 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T // Matches `live_slots_for_state_field_jit` slot order so a // `MIFrame::get_list_of_active_boxes` walk against the canonical // liveness entry decodes back the same OpRefs / values that - // `__JitSym` and the macro-emitted `live/` placeholder + // `__JitSym_` and the macro-emitted `live/` placeholder // refer to. Virt-array populate is deferred — see // the trait-method docstring. let populate_scalar_parts: Vec = scalars @@ -582,7 +607,7 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T // is permanently live, so the canonical entry is just // `[0..total_slots]` of int slots. The `array_lens` slice fed to // `live_slots_for_state_field_jit` enumerates the runtime lengths - // captured in `__JitMeta::_len` (one per flattened array). + // captured in `__JitMeta_::_len` (one per flattened array). let canonical_liveness_array_len_refs: Vec = arrays .iter() .map(|(_, f)| { @@ -722,7 +747,7 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T }) .collect(); - // ── #184 recursive CALL_ASSEMBLER portal entry (JitCodeSym side) ── + // Recursive CALL_ASSEMBLER portal entry on the JitCodeSym side // A recursive callee runs as its own compiled loop with a fresh frame. // `recursive_fresh_entry_reds` allocates a fresh `#state_type` (scalars // zeroed = empty frame; arrays re-allocated at the caller's live @@ -821,7 +846,7 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T quote! {} }; - // ── #184 recursive CALL_ASSEMBLER portal entry (host alloc/free) ── + // Recursive CALL_ASSEMBLER portal entry for host allocation and release // The compiled caller loop cannot `New` a host `#state_type` through the // IR, so the recursive dispatcher records a residual call to these host // helpers: `alloc` returns a fresh `Box::into_raw`-ed `#state_type` @@ -851,7 +876,7 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T quote! { #[doc(hidden)] #[allow(non_snake_case)] - extern "C" fn __majit_recursive_fresh_alloc(__cap: i64) -> i64 { + extern "C" fn #fresh_alloc_fn(__cap: i64) -> i64 { let __fresh: ::std::boxed::Box<#state_type> = ::std::boxed::Box::new(#state_type { #(#fresh_entry_scalar_inits)* #virt_name: ::std::vec![#virt_zero; __cap as usize], @@ -860,7 +885,7 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T } #[doc(hidden)] #[allow(non_snake_case)] - extern "C" fn __majit_recursive_fresh_free(__ptr: i64) { + extern "C" fn #fresh_free_fn(__ptr: i64) { if __ptr != 0 { unsafe { ::core::mem::drop(::std::boxed::Box::from_raw(__ptr as *mut #state_type)); @@ -875,8 +900,8 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T quote! { fn recursive_fresh_alloc_free_targets(&self) -> Option<(*const (), *const ())> { Some(( - __majit_recursive_fresh_alloc as usize as *const (), - __majit_recursive_fresh_free as usize as *const (), + #fresh_alloc_fn as usize as *const (), + #fresh_free_fn as usize as *const (), )) } } @@ -1274,7 +1299,7 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T // builds it: `live_arg_boxes = greenboxes + redboxes` and then // `live_arg_boxes += self.virtualizable_boxes; live_arg_boxes.pop()` // (pyjitpl.py:2981-2989) — `+=` appends, so no element can precede a red. - // ⚠️Splicing the elements before the ref/float scalars (as this did until + // Splicing the elements before the ref/float scalars (as this did until // the order was unified) leaves the JUMP and the Label at the SAME arity // with different slot meanings, so `jump.numargs() == label.numargs()` // (compile.py:334) still passes and nothing downstream catches it. @@ -1341,8 +1366,8 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T /// pyjitpl.py:2981-2989 `live_arg_boxes`, typed. See the emit-site /// comment in `majit-macros/src/jit_interp/codegen_state.rs`. #[allow(unused_variables)] - fn __jit_loop_carried_boxes( - sym: &__JitSym, + fn #loop_carried_boxes_fn_name( + sym: &#sym_ty, __boxes: &[(majit_ir::OpRef, majit_ir::Type)], ) -> Vec<(majit_ir::OpRef, majit_ir::Type)> { let mut args: Vec<(majit_ir::OpRef, majit_ir::Type)> = Vec::new(); @@ -1364,10 +1389,10 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T let collect_jump_args_with_boxes_method: TokenStream = if num_virt_arrays >= 1 { quote! { fn collect_jump_args_with_boxes( - sym: &__JitSym, + sym: &#sym_ty, __boxes: &[(majit_ir::OpRef, majit_ir::Type)], ) -> Vec { - __jit_loop_carried_boxes(sym, __boxes) + #loop_carried_boxes_fn_name(sym, __boxes) .into_iter() .map(|(__opref, _)| __opref) .collect() @@ -1460,6 +1485,63 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T .iter() .map(|(_, f)| quote::format_ident!("{}_value", f.name)) .collect(); + // `clear_sym_inputarg_bindings`: drop every `OpRef` that `create_sym` + // minted off `__offset`, and only those — the `_value` / `_len_value` + // mirrors are concrete runtime data seeded by `initialize_sym`, not + // positions, so they stay. One statement per field the `#sym_ty` + // constructor above lists as `OpRef` / `Vec`; virt arrays carry + // only an `i64` length mirror and so contribute nothing here. + // Counterpart of `clear_sym_binding_parts`, over the identical five + // sources so the two cannot drift: if a kind is added to the clearing and + // not to the count, the assertion at the call site stops covering it. + let count_bound_sym_parts: Vec = create_sym_scalar_names + .iter() + .map(|fname| quote! { if !sym.#fname.is_none() { __bound += 1; } }) + .chain(create_sym_array_names.iter().map(|fname| { + quote! { + for __cell in sym.#fname.iter() { + if !__cell.is_none() { __bound += 1; } + } + } + })) + .chain(has_vable_identity.then(|| { + quote! { if !sym.__vable_identity.is_none() { __bound += 1; } } + })) + .chain( + create_sym_ref_scalar_names + .iter() + .map(|fname| quote! { if !sym.#fname.is_none() { __bound += 1; } }), + ) + .chain( + create_sym_float_scalar_names + .iter() + .map(|fname| quote! { if !sym.#fname.is_none() { __bound += 1; } }), + ) + .collect(); + let clear_sym_binding_parts: Vec = create_sym_scalar_names + .iter() + .map(|fname| quote! { sym.#fname = majit_ir::OpRef::NONE; }) + .chain(create_sym_array_names.iter().map(|fname| { + quote! { + for __cell in sym.#fname.iter_mut() { + *__cell = majit_ir::OpRef::NONE; + } + } + })) + .chain(has_vable_identity.then(|| { + quote! { sym.__vable_identity = majit_ir::OpRef::NONE; } + })) + .chain( + create_sym_ref_scalar_names + .iter() + .map(|fname| quote! { sym.#fname = majit_ir::OpRef::NONE; }), + ) + .chain( + create_sym_float_scalar_names + .iter() + .map(|fname| quote! { sym.#fname = majit_ir::OpRef::NONE; }), + ) + .collect(); let extract_float_scalar_parts: Vec = float_scalars .iter() .map(|(_, f)| { @@ -1640,7 +1722,7 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T // and that lint is deny-by-default in every consumer of this // macro, so a state of arrays only would not compile there. #[allow(clippy::reversed_empty_ranges)] - fn live_value_types(&self, _meta: &__JitMeta) -> Vec { + fn live_value_types(&self, _meta: &#meta_ty) -> Vec { // Value-routing types in `extract_live` order: int scalars, // int array elements, then the ONE virtualizable identity // (Ref), then appended ref scalars (Ref), then appended float @@ -1671,7 +1753,7 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T quote! { fn restore_banked3( &mut self, - meta: &__JitMeta, + meta: &#meta_ty, int_values: &[i64], ref_values: &[i64], float_values: &[i64], @@ -1802,6 +1884,40 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T // statement (`standard_virtualizable_jitcode_argbox` has nothing to // resolve), so a state with `[.. ; virt]` arrays never forms a bridge at // all — every guard exit deopts through the blackhole instead. + // Rebind the vable identity the bridge actually entered with. + // + // `clear_sym_inputarg_bindings` retires this field along with the other + // `create_sym` mints, and the frame-register seeding below has no arm for + // it: `reg_indices` addresses the int/ref/float scalar banks and this + // field is in none of them. Left retired it travels straight into the + // jump args as `OpRef::NONE` (`collect_jump_args` and + // `collect_jump_args_with_boxes` both push it), where the optimizer has + // neither an operand nor a type for it and `not_virtual` faults. + // + // `standard_virtualizable_jitcode_argbox` is the right source rather than + // a constant built from the mirror: it prefers the exact trace-entry red + // inputarg named by `index_of_virtualizable`, falling back to + // `virtualizable_boxes[-1]`, which `#seed_bridge_vable` has just + // populated. Note that seeding does not do this itself — + // `seed_bridge_virtualizable_boxes` takes no `Sym` and writes only the + // ctx-side boxes. + let rebind_bridge_vable_identity: TokenStream = if has_vable_identity { + quote! { + if let Some((_, __vable_op, __vable_val)) = + ctx.standard_virtualizable_jitcode_argbox() + { + sym.__vable_identity = __vable_op; + sym.__vable_identity_value = __vable_val; + if std::env::var("MAJIT_BRIDGE_DEBUG").is_ok() { + eprintln!(" vable identity REBOUND to {:?}", __vable_op); + } + } else if std::env::var("MAJIT_BRIDGE_DEBUG").is_ok() { + eprintln!(" vable identity NOT REBOUND — no standard argbox"); + } + } + } else { + quote! {} + }; let seed_bridge_vable: TokenStream = if num_virt_arrays > 0 { quote! { if let Some(__vinfo) = Self::__build_virtualizable_info() { @@ -1911,10 +2027,28 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T // constant — `num_scalars` — so the identity can be DECLARED the way // `warmspot.py:529-538` declares `index_of_virtualizable`, and // `initialize_virtualizable` can look it up instead of searching the - // reds for a matching pointer. A fixed `[int]` array alongside a - // `[int; virt]` one would make the position depend on that array's - // runtime length; no front-end declares both, and the lookup falls - // back to the pointer match when it happens. + // reds for a matching pointer. + // + // A fixed `[int]` array alongside a `[int; virt]` one — which + // `majit-metainterp/tests/jit_interp_float_state_field.rs` + // `virt_array_with_float_scalar` declares — makes the position + // `num_scalars + sum(array lengths)`, and those lengths are the runtime + // `Vec` lengths this expansion reads back through `meta._len` + // (`create_sym_array_inits`), so no constant can be emitted here — the + // vinfo itself is built once per driver by the `&self`-less + // `__build_virtualizable_info`, before any state instance exists. + // Emitting nothing is therefore correct, but it is NOT harmless on its + // own: it leaves the identity's position unstated, and both consumers + // must resolve it instead of assuming one. + // `MetaInterp::identity_live_position` does resolve it for the runtime + // path — it pointer-matches `vable_ptr` against the reds, so a wrong or + // absent declaration is survivable there. The optimizer has no pointer + // to match against, so with nothing declared it DECLINES to track the + // virtualizable (`VirtualizableConfig::identity_input_index` is `None`) + // rather than probing flat slot 0 — an int scalar on this layout, which + // made every trace abort with VirtualStatesCantMatch. Declining was + // measured to cost nothing here; see + // `tests/jit_interp_fixed_array_identity_slot.rs`. let identity_live_index_stmt: TokenStream = if arrays.is_empty() { quote! { __info.identity_live_index = Some(#num_scalars); } } else { @@ -2005,11 +2139,11 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T /// Compiled loop metadata for state_fields mode: flattened array lengths at trace start. #[derive(Clone)] #[allow(non_camel_case_types)] - struct __JitMeta { + struct #meta_ty { #(#meta_fields)* } - impl __JitMeta { + impl #meta_ty { /// RPython `assembler.py:218-231 get_liveness_info(insn, kind)` /// adapted for flat-state JIT: every state_field slot is /// permanently live, so the canonical `(live_i, live_r, @@ -2235,7 +2369,7 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T /// Symbolic state during tracing: per-field OpRefs. #[allow(non_camel_case_types)] - struct __JitSym { + struct #sym_ty { #(#sym_scalar_fields)* #(#sym_scalar_value_fields)* #(#sym_array_fields)* @@ -2252,7 +2386,7 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T #loop_carried_boxes_fn - impl majit_metainterp::JitCodeSym for __JitSym { + impl majit_metainterp::JitCodeSym for #sym_ty { fn total_slots(&self) -> usize { #num_scalars #(#total_slots_array_parts)* + #num_vable_identity_slots } @@ -2261,7 +2395,7 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T &self, __boxes: &[(majit_ir::OpRef, majit_ir::Type)], ) -> Option> { - Some(__jit_loop_carried_boxes(self, __boxes)) + Some(#loop_carried_boxes_fn_name(self, __boxes)) } fn int_identity_slots_end(&self) -> usize { @@ -2272,6 +2406,14 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T #int_identity_base } + // Mirrors `split_identity_reg_ends`' int end in + // `jitcode_lower/mod.rs` exactly: the working-register floor stops + // after the scalars plus the single vable-identity slot, because a + // virt array's element count is only known from the live object. + fn int_identity_reserved_end(&self) -> usize { + #int_identity_base + #num_scalars + #num_vable_identity_slots + } + fn loop_header_pc(&self) -> usize { self.loop_header_pc } @@ -2407,21 +2549,21 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T } impl majit_metainterp::JitState for #state_type { - type Meta = __JitMeta; - type Sym = __JitSym; + type Meta = #meta_ty; + type Sym = #sym_ty; type Env = #env_type; fn can_trace(&self) -> bool { true } - fn build_meta(&self, _header_pc: usize, _program: &#env_type) -> __JitMeta { - __JitMeta { + fn build_meta(&self, _header_pc: usize, _program: &#env_type) -> #meta_ty { + #meta_ty { #(#build_meta_fields)* } } - fn extract_live(&self, _meta: &__JitMeta) -> Vec { + fn extract_live(&self, _meta: &#meta_ty) -> Vec { let mut values = Vec::new(); #(#extract_scalar_parts)* #(#extract_array_parts)* @@ -2433,7 +2575,7 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T #live_value_types_override - fn create_sym(meta: &__JitMeta, header_pc: usize) -> __JitSym { + fn create_sym(meta: &#meta_ty, header_pc: usize) -> #sym_ty { let mut __offset: usize = 0; #(#create_sym_scalar_inits)* #(#create_sym_array_inits)* @@ -2441,7 +2583,7 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T #create_sym_vable_identity_init #(#create_sym_ref_scalar_inits)* #(#create_sym_float_scalar_inits)* - __JitSym { + #sym_ty { #(#create_sym_scalar_names,)* #(#create_sym_scalar_value_names,)* #(#create_sym_array_names,)* @@ -2457,7 +2599,7 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T } } - fn initialize_sym(&self, sym: &mut __JitSym, _meta: &__JitMeta) { + fn initialize_sym(&self, sym: &mut #sym_ty, _meta: &#meta_ty) { #(#initialize_sym_scalar_parts)* #(#initialize_sym_array_parts)* #(#initialize_sym_virt_array_parts)* @@ -2466,6 +2608,22 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T #(#initialize_sym_float_scalar_parts)* } + // Retires `create_sym`'s `__offset` numbering for callers whose + // trace does not number its inputargs the same way — see the trait + // declaration for why a bridge is such a caller and why a stale + // mint resolves instead of missing. Mirrors the `#sym_ty` + // constructor field-for-field: every `OpRef` it fills from + // `__offset` is cleared here, and nothing else is touched. + fn count_bound_sym_inputargs(sym: &#sym_ty) -> Option { + let mut __bound = 0usize; + #(#count_bound_sym_parts)* + Some(__bound) + } + + fn clear_sym_inputarg_bindings(sym: &mut #sym_ty) { + #(#clear_sym_binding_parts)* + } + // ── Part A (bridge resume-decode). ── // // resume.py:1042-1057 rebuild_from_resumedata parity for the @@ -2483,7 +2641,7 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T // a stack-mutating residual) — not a missing or unseeded bridge. // See `aheui-logo-spin-observer-replay-rootcause.md`. fn rebuild_from_resumedata( - _meta: &mut __JitMeta, + _meta: &mut #meta_ty, fail_arg_types: &[majit_ir::Type], storage: Option<&std::sync::Arc>, ) -> Option { @@ -2588,7 +2746,7 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T // Const → a folded pool constant. MAJIT_BRIDGE_DEBUG dumps it. #[allow(clippy::reversed_empty_ranges)] fn setup_bridge_sym( - sym: &mut __JitSym, + sym: &mut #sym_ty, ctx: &mut majit_metainterp::TraceCtx, resume_data: &majit_metainterp::ResumeDataResult, rd_virtuals: Option<&[std::rc::Rc]>, @@ -2623,6 +2781,7 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T &mut __bridge_cache, ); #seed_bridge_vable + #rebind_bridge_vable_identity let frame = match resume_data.frames.first() { Some(f) => f, None => return, @@ -2669,8 +2828,44 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T let __pos = match __pos { Some(p) => p, None => { + // The guard's resume frame does not carry this + // field's identity register, so there is no red to + // decode — measured on dualtape, where the frame's + // live int set is `[0]` while the two int scalars + // sit at identity registers 1 and 2. + // + // Its concrete value is still known: `initialize_sym` + // read it off the live state at bridge entry and + // `clear_sym_inputarg_bindings` preserves the value + // mirrors. Bind it as a constant, which is exactly + // what the `RebuiltValue::Const` arm below does for a + // field the frame carries already folded — the two + // cases differ in who folded it, not in what the + // bridge should observe. + // + // Leaving it unbound instead hands the optimizer an + // `OpRef::NONE`, which has no operand and no type; + // `materialize_operand_at` and `not_virtual` both + // reject it. + // + // The `.expect` is load-bearing and it has been + // exercised: on `examples/dualtape` this arm is + // reached 24 times in a passing run (counted off + // the `MAJIT_BRIDGE_DEBUG` line below) and fires 0 + // times. So the mirror is populated at every reach + // — the value is recovered from the state, never + // fabricated, and `const_int` never mints a + // stand-in zero. That separation is measured here + // and nowhere else: the dualtape fixture greens on + // no-panic plus correct output, so a fabricated + // value would pass it. Same for the ref and float + // arms below, which share this shape. + let __bits = sym.state_field_value(__k).expect("state field concrete value not initialized"); + let __op = ctx.const_int(__bits); + sym.set_state_field_ref(__k, __op); + sym.set_state_field_value(__k, __bits); if __dbg { - eprintln!(" int scalar {} <- reg {} UNSEEDED (color not in reg_indices.int)", __k, __target); + eprintln!(" int scalar {} <- reg {} ABSENT from frame, bound Const {}", __k, __target, __bits); } continue; } @@ -2722,8 +2917,17 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T let __pos = match __pos { Some(p) => p, None => { + // Ref twin of the int arm above — same reason, same + // remedy, `const_ref` instead of `const_int`. Kept + // symmetric deliberately: a bank left on the bare + // `continue` reintroduces the unbound-field hazard + // for any state that declares one. + let __bits = sym.state_ref_field_value(__j).expect("ref state field concrete value not initialized"); + let __op = ctx.const_ref(__bits); + sym.set_state_ref_field_ref(__j, __op); + sym.set_state_ref_field_value(__j, __bits); if __dbg { - eprintln!(" ref scalar {} <- reg {} UNSEEDED (color not in reg_indices.ref)", __j, __target); + eprintln!(" ref scalar {} <- reg {} ABSENT from frame, bound Const {:#x}", __j, __target, __bits); } continue; } @@ -2772,7 +2976,20 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T let __pos = reg_indices.float.iter().position(|&r| r as usize == __target); let __pos = match __pos { Some(p) => p, - None => continue, + None => { + // Float twin of the int/ref arms above. The mirror + // stores the float's raw bits, so the constant is + // minted from the bit pattern, matching how + // `initialize_sym` and `restore_banked3` carry it. + let __bits = sym.state_float_field_value(__k).expect("float state field concrete value not initialized"); + let __op = ctx.const_float(__bits); + sym.set_state_float_field_ref(__k, __op); + sym.set_state_float_field_value(__k, __bits); + if __dbg { + eprintln!(" float scalar {} <- reg {} ABSENT from frame, bound Const {}", __k, __target, f64::from_bits(__bits as u64)); + } + continue; + } }; match &frame.values[__float_off + __pos] { RebuiltValue::Box(n, kind) if matches!(kind, majit_ir::Type::Float) => { @@ -2799,11 +3016,11 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T } } - fn is_compatible(&self, meta: &__JitMeta) -> bool { + fn is_compatible(&self, meta: &#meta_ty) -> bool { true #(#compat_checks)* } - fn restore(&mut self, _meta: &__JitMeta, values: &[i64]) { + fn restore(&mut self, _meta: &#meta_ty, values: &[i64]) { let mut __offset: usize = 0; #(#restore_scalar_parts)* #(#restore_array_parts)* @@ -2816,7 +3033,7 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T #recover_body } - fn debug_state_fields(&self, _meta: &__JitMeta) -> Option<::std::string::String> { + fn debug_state_fields(&self, _meta: &#meta_ty) -> Option<::std::string::String> { let mut out = ::std::string::String::new(); #(#debug_scalar_state_parts)* #(#debug_array_state_parts)* @@ -2826,7 +3043,7 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T Some(out) } - fn debug_state_live_labels(&self, _meta: &__JitMeta) -> Option<::std::vec::Vec<::std::string::String>> { + fn debug_state_live_labels(&self, _meta: &#meta_ty) -> Option<::std::vec::Vec<::std::string::String>> { let mut labels: ::std::vec::Vec<::std::string::String> = ::std::vec::Vec::new(); #(#debug_scalar_label_parts)* #(#debug_array_label_parts)* @@ -2887,7 +3104,7 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T #state_field_layout_ctor } - fn collect_jump_args(sym: &__JitSym) -> Vec { + fn collect_jump_args(sym: &#sym_ty) -> Vec { let mut args = Vec::new(); #(#collect_scalar_parts)* #(#collect_array_parts)* @@ -2899,7 +3116,7 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T #collect_jump_args_with_boxes_method - fn validate_close(sym: &__JitSym, meta: &__JitMeta) -> bool { + fn validate_close(sym: &#sym_ty, meta: &#meta_ty) -> bool { true #(#validate_array_checks)* } @@ -2910,10 +3127,10 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T // as the dispatch-level `record_state_guard` // (`pyjitpl/dispatch.rs:284`). Calls the macro-emitted // `JitCodeSym::populate_frame_int_regs` to bridge - // `__JitSym` slots onto `MIFrame.int_regs`, then builds a + // `__JitSym_` slots onto `MIFrame.int_regs`, then builds a // single-frame snapshot via the canonical helper. fn populate_frame_for_guard( - sym: &__JitSym, + sym: &#sym_ty, frames: &mut majit_metainterp::MIFrameStack, __op_live: u8, __all_liveness: &[u8], @@ -2961,7 +3178,7 @@ fn generate_state_fields_jit_state(config: &JitInterpConfig, func: &ItemFn) -> T false, __virtualizable_boxes, __virtualref_boxes, - Some((sym.int_identity_slots_base(), sym.int_identity_slots_end())), + Some((sym.int_identity_slots_base(), sym.int_identity_reserved_end())), ); let __root = &mut frames.frames[0]; __root.int_regs[..__n].copy_from_slice(&__saved_int_regs); diff --git a/majit/majit-macros/src/jit_interp/codegen_trace.rs b/majit/majit-macros/src/jit_interp/codegen_trace.rs index a705ddcbc72..63f25a033c8 100644 --- a/majit/majit-macros/src/jit_interp/codegen_trace.rs +++ b/majit/majit-macros/src/jit_interp/codegen_trace.rs @@ -15,6 +15,10 @@ pub fn generate_trace_fn(config: &JitInterpConfig, func: &ItemFn) -> TokenStream let prebuild_fn_name = format_ident!("__prebuild_jitcode_liveness_{}", fn_name); let dispatch_jitcode_fn_name = format_ident!("__dispatch_jitcode_{}", fn_name); let declare_schema_fn_name = format_ident!("__declare_jit_schema_{}", fn_name); + // Must match `codegen_state.rs`'s spelling: the symbolic-state struct is one + // module-level item shared by both emitters, suffixed so two machines can + // live in one module. + let sym_ty = format_ident!("__JitSym_{}", fn_name); let match_expr = find_dispatch_match(&func.block); let Some(match_expr) = match_expr else { @@ -131,7 +135,7 @@ pub fn generate_trace_fn(config: &JitInterpConfig, func: &ItemFn) -> TokenStream #[allow(non_snake_case, unused_variables, unused_mut)] fn #trace_fn_name<__R: majit_metainterp::JitCodeRuntime>( __ctx: &mut majit_metainterp::TraceCtx, - __sym: &mut __JitSym, + __sym: &mut #sym_ty, program: &#env_type, pc: usize, // Slice X-D production wire-up: caller passes a @@ -253,7 +257,7 @@ pub fn generate_trace_fn(config: &JitInterpConfig, func: &ItemFn) -> TokenStream /// triples into the driver-shared `Assembler`, mirroring RPython /// `pyjitpl.py:2255 finish_setup`'s "all `-live-` entries land /// in `asm.all_liveness` before the snapshot" invariant. - /// Invoked from `__JitMeta::install_canonical_liveness` exactly + /// Invoked from `__JitMeta_::install_canonical_liveness` exactly /// once at install time, before /// `JitDriver::install_canonical_liveness` snapshots /// `metainterp_sd.liveness_info`. diff --git a/majit/majit-macros/src/jit_interp/green_type_tag.rs b/majit/majit-macros/src/jit_interp/green_type_tag.rs index 7d3f17558c3..a8318ceb3d3 100644 --- a/majit/majit-macros/src/jit_interp/green_type_tag.rs +++ b/majit/majit-macros/src/jit_interp/green_type_tag.rs @@ -1,15 +1,17 @@ //! Per-green type tag parsing for `#[jit_interp(greens = ...)]`. //! -//! Extends the bracketed `greens = [pc, code: str, env: ref]` syntax so each -//! green can carry an optional type tag. Tagged greens override the trait -//! dispatch in `green_key_expr` (mod.rs:912) so a `&str` green emits -//! `(ptr_bits, GreenType::Str)` directly — letting `equal_whatever` / -//! `hash_whatever` route through the hardcoded `default_str_eq` / -//! `default_str_hash` / `default_unicode_hash` (`majit-ir/src/value.rs`) -//! which mirror `rstr.LLHelpers.ll_streq` / `ll_strhash` over the -//! `*const &'static str` slot ABI (warmstate.py:108-128 `lltype.Ptr` -//! to `rstr.STR / rstr.UNICODE` parity, hardcoded with no frontend -//! override). +//! Extends the bracketed `greens = [pc, env: ref]` syntax so each green can +//! carry an optional type tag. Tagged greens override the trait dispatch in +//! `green_key_expr` (mod.rs:2105), forcing the `GreenType` bucket explicitly +//! instead of letting `GreenAsI64` pick it. +//! +//! `str` and `unicode` parse but are refused. Their codegen ABI +//! (a `*const &'static str` slot mirroring `rstr.STR` / `rstr.UNICODE`, +//! warmstate.py:108-128) is implemented and kept in `mod.rs`, but the slot is +//! `Box::leak`ed per merge-point hit rather than once per JitCell, so the +//! refusal stands until the backing storage is owned by the cell. The refusal +//! lives in `GreenSpec::parse` because that is the sole construction site for +//! both tags. //! //! Untagged greens (the existing form) keep the //! `<_ as majit_ir::GreenAsI64>::__green_repr()` path unchanged. @@ -21,19 +23,26 @@ use syn::{ punctuated::Punctuated, }; -/// Per-green type tag. Maps to `majit_ir::GreenType` at codegen time -/// at codegen time. `Int / Ref / Float` are siblings of the `GreenAsI64` -/// trait's automatic dispatch (the tag forces the bucket explicitly); -/// `Str / Unicode` opt-in to content-comparison through the hardcoded +/// Per-green type tag. Maps to `majit_ir::GreenType` at codegen time. +/// `Int / Ref / Float` are siblings of the `GreenAsI64` trait's automatic +/// dispatch (the tag forces the bucket explicitly). +/// +/// `Str / Unicode` would opt in to content-comparison through the hardcoded /// `default_str_eq` / `default_str_hash` / `default_unicode_hash` in -/// `majit-ir/src/value.rs` (`warmstate.py:108-128 ll_streq` / -/// `ll_strhash` parity, no frontend override). +/// `majit-ir/src/value.rs` (`warmstate.py:108-128 ll_streq` / `ll_strhash` +/// parity, no frontend override) — but both are refused at parse time, see +/// the module doc. The variants are retained so the codegen arms and +/// this mapping stay intact for whoever lifts the refusal. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) enum GreenTypeTag { Int, Ref, Float, + // The parser refuses these until string storage is JitCell-owned, but the + // variants keep the implemented codegen ABI explicit and testable. + #[allow(dead_code)] Str, + #[allow(dead_code)] Unicode, } @@ -56,8 +65,29 @@ impl Parse for GreenSpec { "int" => GreenTypeTag::Int, "ref" => GreenTypeTag::Ref, "float" => GreenTypeTag::Float, - "str" => GreenTypeTag::Str, - "unicode" => GreenTypeTag::Unicode, + // `str` / `unicode` are refused rather than warned about: a + // proc macro has no stable channel for emitting a warning, so + // the alternative is emitting the leaking code next to a + // comment nobody reads. This is the sole construction site for + // both tags, so refusing here refuses them everywhere; the + // codegen arms in `mod.rs` are kept as the shape to re-enable. + tag @ ("str" | "unicode") => { + return Err(syn::Error::new( + ident.span(), + format!( + "green type tag '{tag}' is not supported: the emitted \ + `make_str_slot` Box::leaks a fresh slot on EVERY merge-point hit, \ + not once per JitCell as rstr.STR does, so the leaks grow without \ + bound in a long-running program.\n\ + Lift this refusal once a str/unicode green's backing storage is \ + owned by the JitCell — i.e. once `GreenKey` carries owned string \ + storage for these greens instead of an i64 pointing at a leaked \ + slot, so `make_str_slot` is no longer called per hit.\n\ + Until then, pass the string's identity as a `ref` green, or key on \ + an interned index the interpreter already owns.", + ), + )); + } other => { return Err(syn::Error::new( ident.span(), @@ -111,12 +141,11 @@ mod tests { #[test] fn typed_specs_carry_their_tags() { - let specs = parse_specs("[pc, code: str, env: ref, val: int]").unwrap(); - assert_eq!(specs.len(), 4); + let specs = parse_specs("[pc, env: ref, val: int]").unwrap(); + assert_eq!(specs.len(), 3); assert_eq!(specs[0].type_tag, None); - assert_eq!(specs[1].type_tag, Some(GreenTypeTag::Str)); - assert_eq!(specs[2].type_tag, Some(GreenTypeTag::Ref)); - assert_eq!(specs[3].type_tag, Some(GreenTypeTag::Int)); + assert_eq!(specs[1].type_tag, Some(GreenTypeTag::Ref)); + assert_eq!(specs[2].type_tag, Some(GreenTypeTag::Int)); } #[test] @@ -140,9 +169,38 @@ mod tests { } #[test] - fn float_and_unicode_tags_round_trip() { - let specs = parse_specs("[scale: float, name: unicode]").unwrap(); + fn float_tag_round_trips() { + let specs = parse_specs("[scale: float]").unwrap(); assert_eq!(specs[0].type_tag, Some(GreenTypeTag::Float)); - assert_eq!(specs[1].type_tag, Some(GreenTypeTag::Unicode)); + } + + #[test] + fn str_and_unicode_tags_name_the_leak_and_release_condition() { + for src in ["[code: str]", "[name: unicode]"] { + let err = match parse_specs(src) { + Ok(_) => panic!("{src} must be refused"), + Err(e) => e.to_string(), + }; + assert!( + err.contains("Box::leaks"), + "{src}: leak cause missing: {err}" + ); + assert!( + err.contains("Lift this refusal once"), + "{src}: no release condition: {err}", + ); + } + } + + /// The refusal must not swallow the unknown-tag path: a typo still reports + /// as a typo, not as the string-storage refusal. + #[test] + fn an_unknown_tag_is_still_reported_as_unknown_not_as_the_str_refusal() { + let err = match parse_specs("[pc: string]") { + Ok(_) => panic!("expected an error for unknown tag 'string'"), + Err(e) => e.to_string(), + }; + assert!(err.contains("unknown green type tag"), "{err}"); + assert!(!err.contains("Box::leaks"), "{err}"); } } diff --git a/majit/majit-macros/src/jit_interp/jitcode_lower/api.rs b/majit/majit-macros/src/jit_interp/jitcode_lower/api.rs index da32246ffd4..415014f4930 100644 --- a/majit/majit-macros/src/jit_interp/jitcode_lower/api.rs +++ b/majit/majit-macros/src/jit_interp/jitcode_lower/api.rs @@ -183,10 +183,10 @@ pub(crate) fn try_generate_jitcode_body_parts_with_caller_bindings( body: &Expr, config: Option<&LowererConfig>, caller_locals: &[(String, Binding)], -) -> Option<(GeneratedJitCodeBody, Vec)> { +) -> Result<(GeneratedJitCodeBody, Vec), String> { let stmts = extract_stmts(body); if stmts.is_empty() { - return None; + return Err("arm body has no statements to lower".to_string()); } let mut lowerer = Lowerer::new(config); @@ -223,8 +223,24 @@ pub(crate) fn try_generate_jitcode_body_parts_with_caller_bindings( .max(split_identity_floor(config)) .max(float_identity_floor(config)); - for stmt in &stmts { - lowerer.lower_stmt(stmt)?; + for (i, stmt) in stmts.iter().enumerate() { + if lowerer.lower_stmt(stmt).is_none() { + // The body is already doomed. Keep walking the REMAINING statements + // for their reasons only, discarding whatever they lower, so + // `reason` enumerates the arm's blockers instead of naming whichever + // check happened to fire first. Nothing downstream sees the partial + // state: we return Err below either way. + // + // This reports one reason per offending STATEMENT, never two for + // one statement — each refusal site still returns from `lower_stmt` + // at its first hit. An arm whose blockers all sit inside a single + // statement (dualtape's `b']'` is one `if`/`else`) still reports + // exactly one. + for rest in &stmts[i + 1..] { + let _ = lowerer.lower_stmt(rest); + } + return Err(lowerer.take_body_failure_reason()); + } } annotate_live_markers_with_liveness(&mut lowerer.op_metadata); @@ -234,7 +250,7 @@ pub(crate) fn try_generate_jitcode_body_parts_with_caller_bindings( let liveness_prebuild = liveness_prebuild_tokens(&lowerer.op_metadata, &lowerer.inline_liveness_prebuild); let statements = lowerer.statements; - Some(( + Ok(( GeneratedJitCodeBody { body: quote! { #(#statements)* @@ -261,11 +277,13 @@ pub(crate) fn try_generate_jitcode_pc_return_body_with_caller_bindings( config: Option<&LowererConfig>, caller_locals: &[(String, Binding)], increment: i64, -) -> Option<(GeneratedJitCodeBody, Vec)> { +) -> Result<(GeneratedJitCodeBody, Vec), String> { let stmts = extract_stmts(body); // The predicate (`arm_is_pure_pc_advance`) guarantees a trailing `pc += N`, // which is replaced by the explicit pc-return below. Lower only the work. - let (_pc_advance, work) = stmts.split_last()?; + let Some((_pc_advance, work)) = stmts.split_last() else { + return Err("arm body has no statements to lower".to_string()); + }; let mut lowerer = Lowerer::new(config); lowerer.in_dispatch_arm_body = true; @@ -289,14 +307,37 @@ pub(crate) fn try_generate_jitcode_pc_return_body_with_caller_bindings( .max(split_identity_floor(config)) .max(float_identity_floor(config)); - for stmt in work { - lowerer.lower_stmt(stmt)?; + for (i, stmt) in work.iter().enumerate() { + if lowerer.lower_stmt(stmt).is_none() { + // Same accumulation as the parts entry above: enumerate the arm's + // blockers rather than reporting whichever check fired first. + // + // `work` is `stmts.split_last()`, so the trailing pc-advance is + // NOT in this loop and contributes no reason here. That is correct + // for this entry point — reaching it at all means the advance is + // the arm's final statement and IS the channel out, which is + // exactly the case the green-write refusal does not fire on. + // + // No crate in the tree reaches this loop: it is selected by + // `pc_return_increment`, which is `Some` only under + // `split_dispatch = true`, and a tracked-file sweep finds zero + // crates setting it (positive control: the same sweep reads 3 + // example files for `jit_interp`). Patched for consistency, not for + // yield — the measured yield is all on the parts entry above. + for rest in &work[i + 1..] { + let _ = lowerer.lower_stmt(rest); + } + return Err(lowerer.take_body_failure_reason()); + } } // `pc` is collected as a caller-local (the trailing `pc += N` references it), // so it is pre-bound at its callee reg; the work statements only read it, so // the binding still holds the incoming pc. Return pc + increment. - let pc_reg = lowerer.bindings.get("pc")?.reg; + let Some(pc_binding) = lowerer.bindings.get("pc") else { + return Err("arm body has no `pc` binding for the pc-return writeback".to_string()); + }; + let pc_reg = pc_binding.reg; let tmp_reg = lowerer.alloc_reg(); lowerer.emit_op( OpMeta::linear(OpKind::LoadConstI, vec![], vec![Register::int(tmp_reg)]), @@ -332,7 +373,7 @@ pub(crate) fn try_generate_jitcode_pc_return_body_with_caller_bindings( let liveness_prebuild = liveness_prebuild_tokens(&lowerer.op_metadata, &lowerer.inline_liveness_prebuild); let statements = lowerer.statements; - Some(( + Ok(( GeneratedJitCodeBody { body: quote! { #(#statements)* diff --git a/majit/majit-macros/src/jit_interp/jitcode_lower/dispatch.rs b/majit/majit-macros/src/jit_interp/jitcode_lower/dispatch.rs index 42a6cbe77a2..1e442f86d89 100644 --- a/majit/majit-macros/src/jit_interp/jitcode_lower/dispatch.rs +++ b/majit/majit-macros/src/jit_interp/jitcode_lower/dispatch.rs @@ -1926,6 +1926,82 @@ fn pc_self_increment(expr: &Expr) -> Option { None } +/// The return terminator for a `return` whose operand lowered to `binding` +/// (`None` = no operand, or an operand that did not lower), as the +/// `(reads, emitter)` pair `OpMeta::terminal` + `emit_op` consume. +/// +/// `blackhole.py:841-857`: a typed return reads the source register of its +/// declared kind, so the register must appear in the terminator's reads list — +/// that list is what keeps it alive through the backward liveness walk. +/// `blackhole.py:859-862`: `void_return` has no operand and therefore no reads. +/// +/// Shared by both emission sites — the function-final return after the dispatch +/// loop and the in-arm `return` — so the two cannot drift in which builder call +/// a `BindingKind` maps to. +pub(super) fn typed_return_terminator( + binding: Option, +) -> (Vec, proc_macro2::TokenStream) { + let Some(binding) = binding else { + return (Vec::new(), quote::quote! { __builder.void_return(); }); + }; + let reg = binding.reg; + match binding.kind { + BindingKind::Int => ( + vec![Register::int(reg)], + quote::quote! { __builder.int_return(#reg as u16); }, + ), + BindingKind::Ref => ( + vec![Register::ref_(reg)], + quote::quote! { __builder.ref_return(#reg as u16); }, + ), + BindingKind::Float => ( + vec![Register::float(reg)], + quote::quote! { __builder.float_return(#reg as u16); }, + ), + } +} + +/// Whether `expr` contains a store — a plain `a = b` or a compound `a += b`. +/// +/// Used to keep a *storing* function-final expression out of the walk; see the +/// call site in `lower_dispatch_body` for why a store there is applied twice. +fn expr_stores(expr: &Expr) -> bool { + use syn::visit::Visit; + struct FindStore { + hit: bool, + } + impl<'ast> Visit<'ast> for FindStore { + fn visit_expr(&mut self, e: &'ast Expr) { + match e { + Expr::Assign(_) => self.hit = true, + Expr::Binary(b) if is_assign_op(&b.op) => self.hit = true, + _ => {} + } + syn::visit::visit_expr(self, e); + } + } + let mut finder = FindStore { hit: false }; + finder.visit_expr(expr); + finder.hit +} + +/// Whether `op` is a compound-assignment operator (`+=`, `-=`, ...). +fn is_assign_op(op: &syn::BinOp) -> bool { + matches!( + op, + syn::BinOp::AddAssign(_) + | syn::BinOp::SubAssign(_) + | syn::BinOp::MulAssign(_) + | syn::BinOp::DivAssign(_) + | syn::BinOp::RemAssign(_) + | syn::BinOp::BitXorAssign(_) + | syn::BinOp::BitAndAssign(_) + | syn::BinOp::BitOrAssign(_) + | syn::BinOp::ShlAssign(_) + | syn::BinOp::ShrAssign(_) + ) +} + /// `Some(N)` if `body` is a pure forward-advancing dispatch arm: straight-line /// work followed by a single trailing `pc += N` (N > 0), with no back-edge /// (`can_enter_jit!` / `jit_merge_point!`), no early `return` / `continue` / @@ -1951,6 +2027,16 @@ fn arm_is_pure_pc_advance(body: &Expr) -> Option { hit: bool, } impl<'ast> Visit<'ast> for Forbid { + // Load-bearing beyond "this arm is not a pure advance": a return arm + // must NEVER reach the split path. `dispatch_arm_inline_call_tokens_i` + // wires the sub-JitCode's BC_INT_RETURN into the caller's + // `return_i_reg`, which for `split_dispatch` IS the dispatch loop's + // green pc register (`next_instr = self.OPCODE(oparg, next_instr)`). + // A `return ` lowered into that sub-JitCode would therefore + // write its return VALUE into the green pc — a miscompile that + // compiles clean, not a missing return. Return arms stay force-inlined + // so `Lowerer::lower_return_stmt` emits the terminator in the dispatch + // JitCode itself, where it ends the walk instead of feeding a register. fn visit_expr_return(&mut self, _: &'ast syn::ExprReturn) { self.hit = true; } @@ -2007,6 +2093,89 @@ fn arm_is_pure_pc_advance(body: &Expr) -> Option { Some(increment) } +/// Tests for `arm_is_pure_pc_advance`, the predicate that gates `split_dispatch`. +/// +/// These do not cover a path any crate in this repository takes. No tracked +/// crate sets `split_dispatch = true` — `rg '^\s*split_dispatch\s*=\s*true' +/// $(git ls-files)` returns nothing, tests included. The configuration is +/// shipped only by out-of-tree consumers that path-depend on this crate. These +/// tests are therefore the only in-repository coverage of that configuration. +/// +/// The `^\s*` anchor is load-bearing; do not simplify it away. It restricts +/// the match to the attribute-argument form, which is the only spelling that +/// sets anything. Unanchored, the pattern matches the sentence above — this +/// note is a tracked file, so a census over an unanchored pattern would count +/// this prose as a setter. Anchoring rejects prose without rejecting the +/// attribute spelling it exists to find. +/// +/// The gap that leaves is on the more dangerous half. +/// `dispatch_arm_inline_call_tokens_i` — the emitter these tests exist to keep +/// `return` arms away from — has no test at all: it appears in this file only +/// as its definition, one comment, and one call site. Misrouting a `return` arm +/// into it writes the arm's return VALUE into the green pc, which compiles +/// clean (see the note on `arm_is_pure_pc_advance`'s `visit_expr_return`). +/// +/// Retirement: this note stops being true when +/// `rg '^\s*split_dispatch\s*=\s*true' $(git ls-files)` returns a hit — i.e. +/// when a tracked crate ships the configuration. Run it anchored, for the +/// reason above: unanchored it reports a hit that is this note, and retires a +/// note nothing has retired. At that point replace this note with a real test +/// on the emitter rather than deleting it. `switch_dispatch`, set on the +/// adjacent line by the same out-of-tree consumers, already has a tracked +/// setter in `majit-metainterp/tests/jit_interp_dispatch_ir_shape.rs`, so this is a +/// known-shaped piece of work rather than an aspiration. +#[cfg(test)] +mod arm_is_pure_pc_advance_tests { + use super::*; + + fn body(src: &str) -> Expr { + syn::parse_str::(src).unwrap() + } + + #[test] + fn plain_forward_advance_is_pure() { + assert_eq!(arm_is_pure_pc_advance(&body("{ pc += 3; }")), Some(3)); + assert_eq!(arm_is_pure_pc_advance(&body("{ pc = pc + 2; }")), Some(2)); + } + + /// A `return` arm must NEVER be routed to the split sub-JitCode path: the + /// paired `inline_call__i` wires the callee's BC_INT_RETURN into the + /// caller's `return_i_reg`, which under `split_dispatch` is the dispatch + /// loop's green pc register — so the arm would write its return VALUE into + /// the pc. Return arms stay force-inlined, where + /// `Lowerer::lower_return_stmt` emits the terminator in the dispatch + /// JitCode itself. + #[test] + fn return_arm_is_never_a_pure_pc_advance() { + // Tail return, the shape every corpus interpreter writes. + assert_eq!( + arm_is_pure_pc_advance(&body("{ let r = program[pc + 1]; return state.regs[r]; }")), + None, + ); + // Bare return, and a return that still ends with a forward advance: + // the advance must not make the arm look splittable. + assert_eq!(arm_is_pure_pc_advance(&body("{ return; }")), None); + assert_eq!( + arm_is_pure_pc_advance(&body("{ if done { return state.a; } pc += 2; }")), + None, + ); + } + + #[test] + fn branch_and_back_edge_arms_are_not_pure() { + assert_eq!( + arm_is_pure_pc_advance(&body("{ pc = tgt; continue; }")), + None + ); + assert_eq!( + arm_is_pure_pc_advance(&body("{ can_enter_jit!(d, pc, s, p, || {}); pc += 1; }")), + None, + ); + // Backward / zero advances are not forward progress. + assert_eq!(arm_is_pure_pc_advance(&body("{ pc += 0; }")), None); + } +} + impl<'c> Lowerer<'c> { /// Green-pc inline dispatch pc-write pinning (see `Lowerer::pc_pinned`). /// Lowers `pc += N`, `pc = pc + N`, and the generic branch `pc = ` @@ -2141,34 +2310,56 @@ impl<'c> Lowerer<'c> { /// via `lower_stmt`, and on success drops the arm-local bindings so they /// do not leak into the next arm (matching the isolated sub-JitCode arm /// scope; emitted ops reference concrete registers, so this is safe). - /// Returns `false` and fully rolls back the partial emission when the - /// body does not lower cleanly, so the caller falls back to the - /// sub-JitCode path. - pub(super) fn try_inline_dispatch_arm(&mut self, body: &Expr) -> bool { + /// Returns [`InlineArmOutcome::Rejected`] and fully rolls back the partial + /// emission when the body does not lower cleanly, so the caller falls back + /// to the sub-JitCode path. + /// + /// The body's final statement is marked via `inline_arm_tail_stmt` so a + /// `return` there lowers to a typed return terminator + /// (`Lowerer::lower_return_stmt`); when one was emitted the outcome is + /// [`InlineArmOutcome::InlinedTerminal`] and the caller must suppress the + /// dispatch back-edge. + pub(super) fn try_inline_dispatch_arm(&mut self, body: &Expr) -> InlineArmOutcome { let stmts = extract_stmts(body); let snap_stmts = self.statements.len(); let snap_meta = self.op_metadata.len(); let snap_reg = self.next_reg; + // `next_label` belongs with `snap_stmts`: a lowerer that allocates a + // label and then fails leaves the counter advanced, and this is the + // one place that knows the whole attempt is being discarded. Restoring + // it anywhere the statements survive would re-issue a bound ident (see + // `Lowerer::alloc_label`), so the two are snapshotted and put back + // together. + let snap_label = self.next_label; let snap_bindings = self.bindings.clone(); let snap_opcode = self.opcode_var_name.clone(); self.pc_pinned = true; let mut ok = true; - for stmt in &stmts { + for (idx, stmt) in stmts.iter().enumerate() { + // One-shot marker for the tail statement; `lower_stmt` takes it on + // entry, so only a `return` written directly as the body's last + // statement can reach the terminator emission. + self.inline_arm_tail_stmt = idx + 1 == stmts.len(); if self.lower_stmt(stmt).is_none() { ok = false; break; } } + // Clear unconditionally: `lower_stmt` consumes the flag on entry, but a + // body whose tail statement never reached `lower_stmt` (the `break` + // above) would otherwise leave it set for the next arm. + self.inline_arm_tail_stmt = false; self.pc_pinned = false; if !ok { self.statements.truncate(snap_stmts); self.op_metadata.truncate(snap_meta); self.next_reg = snap_reg; + self.next_label = snap_label; self.bindings = snap_bindings; self.opcode_var_name = snap_opcode; - return false; + return InlineArmOutcome::Rejected; } if std::env::var_os("MAJIT_MACRO_DEBUG").is_some() { @@ -2195,10 +2386,50 @@ impl<'c> Lowerer<'c> { self.bindings = snap_bindings; self.opcode_var_name = snap_opcode; self.next_reg = snap_reg; - true + + // Did the body end in a return terminator? `lower_stmt` takes the + // one-shot tail marker, so only the body's final statement can emit + // one — assert that, because a terminator anywhere but last would mean + // the ops after it are unreachable. + let arm_ops = &self.op_metadata[snap_meta..]; + match arm_ops + .iter() + .position(|m| matches!(m.control, ControlFlowClass::Terminal)) + { + Some(pos) => { + debug_assert_eq!( + pos, + arm_ops.len() - 1, + "a dispatch arm return terminator must be the arm's last op" + ); + InlineArmOutcome::InlinedTerminal + } + None => InlineArmOutcome::Inlined, + } } } +/// Result of [`Lowerer::try_inline_dispatch_arm`]. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub(super) enum InlineArmOutcome { + /// Not inlined: the body did not lower cleanly and the partial emission was + /// rolled back. The caller falls back to the sub-JitCode path. + Rejected, + /// Inlined; control falls out of the arm body and the caller emits the + /// dispatch back-edge. + Inlined, + /// Inlined, and the body ends in a typed return terminator. The caller + /// must emit **neither** the loop back-edge nor the `default_label` jump: + /// the terminator is the arm's own control transfer. + /// + /// This differs from `ArmPattern::Halt`, which diverts to `default_label` + /// precisely *because* it has no operand of its own and must reach the + /// function-final typed return to produce one. A returning arm already + /// carries its value, so routing it through `default_label` would re-lower + /// the function's trailing expression over it. + InlinedTerminal, +} + pub(super) fn lower_dispatch_chain( lowerer: &mut Lowerer, classified_arms: &[crate::jit_interp::classify::ClassifiedArm], @@ -2294,6 +2525,34 @@ pub(super) fn lower_dispatch_chain( ); }); } + // Skipping emits NOTHING — no test, no body, and (unlike the + // three abort-stub sites below) no `record_degraded_arm`, + // because that call lives at stub construction and there is + // no stub here. So the arm used to vanish completely: the + // opcode fell through to `default_label`, which is bound at + // the typed-return terminator, meaning the trace *returns* + // rather than aborting. An interpreter all of whose arms + // took this path installed a dispatch whose entire optimized + // body was that return, while `Traces compiled` and + // `degraded_dispatch_arms()` both read healthy. + // + // Register it here so the next unsupported shape is named + // instead of disappearing. This is a diagnostic, not a + // repair: the arm is still absent from the dispatch, and the + // silent return above is still what happens at runtime. + let interp = &config.state_type_name; + let arm_name = { + let pat = &arm.pat; + quote::quote!(#pat).to_string() + }; + lowerer.emit_aux(quote::quote! { + majit_metainterp::record_degraded_dispatch_arm( + #interp, + #arm_name, + "dispatch arm pattern is not a shape the macro can lower to a \ + constant value; the arm was dropped from the dispatch entirely", + ); + }); continue; // unsupported pattern shape — skip } }; @@ -2366,7 +2625,7 @@ pub(super) fn lower_dispatch_chain( } } - // Green-pc gated inline (Option A, #184): when `pc` is a declared + // Green-pc gated inline (Option A, recursive-call): when `pc` is a declared // green, lower a Lowerable arm body DIRECTLY into this dispatch // JitCode so its pc-writes (operand `pc += N`, branch `pc = target`) // reach the dispatch loop's reg0. A BC_INLINE_CALL into a sub-JitCode @@ -2411,14 +2670,46 @@ pub(super) fn lower_dispatch_chain( } else { None }; - let inlined = pc_return_increment.is_none() + let inline_outcome = if pc_return_increment.is_none() && pc_is_green(config) && matches!( arm.pattern, crate::jit_interp::classify::ArmPattern::Lowerable ) && !lowerer.arm_body_has_infer_call(&arm.original_body) - && lowerer.try_inline_dispatch_arm(&arm.original_body); + { + lowerer.try_inline_dispatch_arm(&arm.original_body) + } else { + InlineArmOutcome::Rejected + }; + let inlined = inline_outcome != InlineArmOutcome::Rejected; + + // Install-time name for an arm that degrades to an abort stub. + // + // `MAJIT_MACRO_DEBUG` below is a proc-macro-time census of EVERY arm: + // it is invisible at install, absent from any rebuild that hits the + // incremental cache, and its `inlined` field does not answer this + // question — `inlined=false` only means "emitted as a sub-JitCode", + // and the `Some(layout)` path below builds a real one. So the abort + // stubs get their own channel, keyed by the machine's `state = T` + // name and the arm's source spelling, staged with the reason at each + // emitting site (`majit_metainterp::record_degraded_dispatch_arm`). + // It is deliberately NOT a `MC_DIAG` bump: slot 41 is the + // `AbortReason::Generic` bucket `jitprof.rs` already documents as + // unclassified, and the `Counters.ABORT_*` ids it indexes mirror + // RPython's `Counters`, so there is no id to spend on this. + let degraded_interp_name = config.state_type_name.clone(); + let degraded_arm_name = { + let pat = &arm.pat; + quote::quote!(#pat).to_string() + }; + let record_degraded_arm = |reason: &str| { + let interp = °raded_interp_name; + let arm_name = °raded_arm_name; + quote::quote! { + majit_metainterp::record_degraded_dispatch_arm(#interp, #arm_name, #reason); + } + }; if std::env::var_os("MAJIT_MACRO_DEBUG").is_some() { let pat = &arm.pat; @@ -2434,10 +2725,11 @@ pub(super) fn lower_dispatch_chain( } }; eprintln!( - "[majit-macro] dispatch arm {} pattern={} inlined={}", + "[majit-macro] dispatch arm {} pattern={} inlined={} outcome={:?}", quote::quote!(#pat), pattern_name, inlined, + inline_outcome, ); } @@ -2493,7 +2785,7 @@ pub(super) fn lower_dispatch_chain( .map(|(generated, layout)| (generated, layout, None)), }; match generated_parts { - Some((generated, layout, pc_return_reg)) => { + Ok((generated, layout, pc_return_reg)) => { let body = generated.body; let liveness_prebuild = generated.liveness_prebuild; lowerer.inline_liveness_prebuild.push(liveness_prebuild); @@ -2539,6 +2831,9 @@ pub(super) fn lower_dispatch_chain( .map(|e| e.callee_reg + 1) .max() .unwrap_or(0); + let record_degraded = record_degraded_arm( + "arm body lowering resolved an unsupported call policy at install", + ); ( quote::quote! { // A runtime-resolved unsupported call policy @@ -2575,6 +2870,7 @@ pub(super) fn lower_dispatch_chain( // shape so the paired BC_INLINE_CALL's // arg copies stay in bounds before the // BC_ABORT. + #record_degraded let mut __sub_builder = majit_metainterp::JitCodeBuilder::new(); __sub_builder.ensure_i_regs(#min_i_regs); __sub_builder.ensure_r_regs(#min_r_regs); @@ -2587,20 +2883,34 @@ pub(super) fn lower_dispatch_chain( inline_call_emit, ) } - None => ( - quote::quote! { - { - let mut __sub_builder = majit_metainterp::JitCodeBuilder::new(); - __sub_builder.abort(); - __sub_builder.finish() - } - }, - dispatch_arm_inline_call_tokens(&[]), - ), + // The lowering already knows WHICH statement stopped it; + // carry that reason to the install-time record instead + // of collapsing every cause into one string, the same + // way `ArmPattern::Unsupported(reason)` is carried below. + Err(reason) => { + let record_degraded = record_degraded_arm(&reason); + ( + quote::quote! { + { + #record_degraded + let mut __sub_builder = majit_metainterp::JitCodeBuilder::new(); + __sub_builder.abort(); + __sub_builder.finish() + } + }, + dispatch_arm_inline_call_tokens(&[]), + ) + } } } // `break` arms (`Halt`) use the same empty body as Nop — no // `BC_ABORT_PERMANENT` is emitted for this loop-exit path. + // + // Nop / Halt / AbortPermanent are DECLARED outcomes, not + // degradations: the source arm asked for exactly this, so + // none of them records a degraded arm. Recording them would + // make the channel fire on every `_ => break` and + // `_ => panic!` in the corpus and stop discriminating. crate::jit_interp::classify::ArmPattern::Nop => ( quote::quote! { majit_metainterp::JitCodeBuilder::new().finish() }, dispatch_arm_inline_call_tokens(&[]), @@ -2619,16 +2929,23 @@ pub(super) fn lower_dispatch_chain( }, dispatch_arm_inline_call_tokens(&[]), ), - crate::jit_interp::classify::ArmPattern::Unsupported(_reason) => ( - quote::quote! { - { - let mut __sub_builder = majit_metainterp::JitCodeBuilder::new(); - __sub_builder.abort(); - __sub_builder.finish() - } - }, - dispatch_arm_inline_call_tokens(&[]), - ), + // `detect_unsupported_pattern` already produced a reason + // String here and it used to be dropped on the floor; carry + // it to the install-time record instead of restating it. + crate::jit_interp::classify::ArmPattern::Unsupported(reason) => { + let record_degraded = record_degraded_arm(reason); + ( + quote::quote! { + { + #record_degraded + let mut __sub_builder = majit_metainterp::JitCodeBuilder::new(); + __sub_builder.abort(); + __sub_builder.finish() + } + }, + dispatch_arm_inline_call_tokens(&[]), + ) + } }; lowerer.emit_op( OpMeta::linear(OpKind::InlineCall, arm_inline_call_reads, vec![]), @@ -2669,7 +2986,16 @@ pub(super) fn lower_dispatch_chain( // `break` exits the source dispatch `while`, so its empty arm must // flow to the function's typed return. Re-entering the loop here // would execute the loop header with the post-HALT pc instead. - if matches!(arm.pattern, crate::jit_interp::classify::ArmPattern::Halt) { + // + // An arm that lowered its own `return` already emitted the terminator + // that ends the walk, so it gets NEITHER edge. Note this is not the + // `Halt` treatment: `break` diverts to `default_label` because its arm + // has no operand and must reach the function-final typed return to get + // one, whereas a returning arm carries its own value and would have the + // function's trailing expression lowered over it. + if matches!(inline_outcome, InlineArmOutcome::InlinedTerminal) { + // No jump: the return terminator is the arm's control transfer. + } else if matches!(arm.pattern, crate::jit_interp::classify::ArmPattern::Halt) { lowerer.emit_jump(&default_label); } else { lowerer.emit_jump(loop_start_label); @@ -3509,37 +3835,26 @@ pub(crate) fn lower_dispatch_body( syn::Stmt::Expr(e, None) => Some(e), _ => None, }); - match return_expr.and_then(|e| lowerer.lower_value_expr(e)) { - Some(binding) => { - let reg = binding.reg; - // blackhole.py:841-857 — typed return reads the source register - // of its declared kind. Walker keeps `reg` alive upstream via - // OpMeta::terminal's reads list. - let (read_reg, emitter) = match binding.kind { - BindingKind::Int => ( - Register::int(reg), - quote::quote! { __builder.int_return(#reg as u16); }, - ), - BindingKind::Ref => ( - Register::ref_(reg), - quote::quote! { __builder.ref_return(#reg as u16); }, - ), - BindingKind::Float => ( - Register::float(reg), - quote::quote! { __builder.float_return(#reg as u16); }, - ), - }; - lowerer.emit_op(OpMeta::terminal(vec![read_reg]), emitter); - } - None => { - // No lowerable return expr: emit void_return. - // blackhole.py:859-862 — void_return has no operand and no reads. - lowerer.emit_op( - OpMeta::terminal(Vec::new()), - quote::quote! { __builder.void_return(); }, - ); - } - } + // A trailing expression that STORES must not be lowered here. `jitdriver.rs`'s + // `TraceAction::Finish` arm hands the post-loop work to native execution and + // writes the walk-final state back for it to read, so a store lowered into the + // walk is applied twice: once on the walk's symbolic state (which the + // write-back then pushes into native `state`) and once by native code. A pure + // trailing expression is idempotent under that division and keeps its typed + // return, so this narrows only the storing case, which falls through to + // `void_return` and ends the walk at the loop exit. + // + // The check is syntactic. A store hidden inside a callee is not caught, but + // such an expression does not lower to a binding anyway and so already + // reaches `void_return`. + // + // A `None` binding here (no trailing expression, one that stores, or one + // that did not lower) falls through to `void_return`. + let binding = return_expr + .filter(|e| !expr_stores(e)) + .and_then(|e| lowerer.lower_value_expr(e)); + let (reads, emitter) = typed_return_terminator(binding); + lowerer.emit_op(OpMeta::terminal(reads), emitter); annotate_live_markers_with_liveness(&mut lowerer.op_metadata); remove_repeated_live(&mut lowerer.op_metadata, &mut lowerer.statements); diff --git a/majit/majit-macros/src/jit_interp/jitcode_lower/helpers.rs b/majit/majit-macros/src/jit_interp/jitcode_lower/helpers.rs index c42079d7891..3eb2535f9ad 100644 --- a/majit/majit-macros/src/jit_interp/jitcode_lower/helpers.rs +++ b/majit/majit-macros/src/jit_interp/jitcode_lower/helpers.rs @@ -1,7 +1,6 @@ use super::*; -// ── Loop control detection ─────────────────────────────────────────── - +// Loop control detection /// Check if a block contains break or continue at the top level (not nested in inner loops). pub(super) fn block_has_loop_control(block: &Block) -> bool { block.stmts.iter().any(stmt_has_loop_control) @@ -32,8 +31,7 @@ pub(super) fn expr_has_loop_control(expr: &Expr) -> bool { } } -// ── Helper functions ───────────────────────────────────────────────── - +// Helper functions /// Extract the get_mut argument from a pool.get_mut(arg) expression. pub(super) fn extract_stmts(expr: &Expr) -> Vec { match expr { @@ -42,22 +40,38 @@ pub(super) fn extract_stmts(expr: &Expr) -> Vec { } } +/// The integer value of a literal appearing as a dispatch-arm pattern. +/// +/// `b'>'` and `'>'` are integer constants that happen to be spelled as +/// characters: a frontend whose opcodes are characters (Brainfuck's `b'>'`, +/// `b'['`, `b']'`) dispatches on exactly the `u8` an `OP_*: u8` constant would +/// produce. Accepting only `Lit::Int` here made every such arm fail extraction, +/// and the caller drops an arm it cannot extract *before* any abort stub is +/// built — so the arm registered nowhere, `degraded_dispatch_arms()` stayed +/// empty, and an interpreter written entirely in byte literals installed a +/// dispatch with no arms at all. Its whole optimized body was the `Finish()` +/// the default label emits. +fn pat_lit_int_value(lit: &Lit) -> Option { + match lit { + Lit::Int(int_lit) => int_lit.base10_parse::().ok(), + Lit::Byte(byte_lit) => Some(i64::from(byte_lit.value())), + // `char` is a Unicode scalar value; go through `u32` so the cast is the + // code point rather than a sign-extension of anything. + Lit::Char(char_lit) => Some(i64::from(char_lit.value() as u32)), + _ => None, + } +} + /// Extract integer literal values from a match arm pattern. /// -/// Supports `Pat::Lit` (integer literals), `Pat::Or` (multiple patterns -/// like `1 | 2 | 3`), and `Pat::Path` (constant paths — evaluated at -/// compile time via `#pat as i64`). +/// Supports `Pat::Lit` (integer, byte and char literals — see +/// [`pat_lit_int_value`]), `Pat::Or` (multiple patterns like `1 | 2 | 3`), and +/// `Pat::Path` (constant paths — evaluated at compile time via `#pat as i64`). /// /// Returns `None` if the pattern contains unsupported constructs. pub(super) fn extract_pat_literals(pat: &Pat) -> Option> { match pat { - Pat::Lit(expr_lit) => { - if let Lit::Int(int_lit) = &expr_lit.lit { - Some(vec![int_lit.base10_parse::().ok()?]) - } else { - None - } - } + Pat::Lit(expr_lit) => Some(vec![pat_lit_int_value(&expr_lit.lit)?]), Pat::Or(pat_or) => { let mut values = Vec::new(); for case in &pat_or.cases { @@ -83,12 +97,8 @@ pub(super) fn extract_pat_literals(pat: &Pat) -> Option> { pub(super) fn extract_pat_value_tokens(pat: &Pat) -> Option> { match pat { Pat::Lit(expr_lit) => { - if let Lit::Int(int_lit) = &expr_lit.lit { - let val: i64 = int_lit.base10_parse().ok()?; - Some(vec![quote! { #val as i64 }]) - } else { - None - } + let val = pat_lit_int_value(&expr_lit.lit)?; + Some(vec![quote! { #val as i64 }]) } Pat::Path(pp) => { let path = &pp.path; diff --git a/majit/majit-macros/src/jit_interp/jitcode_lower/jit_state_analysis.rs b/majit/majit-macros/src/jit_interp/jitcode_lower/jit_state_analysis.rs index f69184cdd77..872b9e4bfb4 100644 --- a/majit/majit-macros/src/jit_interp/jitcode_lower/jit_state_analysis.rs +++ b/majit/majit-macros/src/jit_interp/jitcode_lower/jit_state_analysis.rs @@ -125,6 +125,21 @@ impl<'c> Lowerer<'c> { .iter() .any(|s| self.stmt_references_unknown_local(s)) } + // See the `while`/`loop` note in `expr_modifies_jit_state`: without + // these two arms a loop whose body names user locals the trace + // function does not carry reported no reference at all (compilation-panic). + Expr::While(w) => { + self.expr_references_unknown_local(&w.cond) + || w.body + .stmts + .iter() + .any(|s| self.stmt_references_unknown_local(s)) + } + Expr::Loop(l) => l + .body + .stmts + .iter() + .any(|s| self.stmt_references_unknown_local(s)), // Literals, returns without expression, etc. are safe. _ => false, } @@ -246,13 +261,21 @@ impl<'c> Lowerer<'c> { self.expr_modifies_jit_state(&f.expr) || f.body.stmts.iter().any(|s| self.stmt_modifies_jit_state(s)) } + // `while` and `loop` descend for the same reason `for` does. They + // answered `false` unconditionally until compilation-panic: a body writing jit + // state was reported as writing none, and since the two sibling + // probes were blind in the same way, `lower_stmt_fallback` scored + // the whole loop inert and dropped it from a lowered arm. + Expr::While(w) => { + self.expr_modifies_jit_state(&w.cond) + || w.body.stmts.iter().any(|s| self.stmt_modifies_jit_state(s)) + } + Expr::Loop(l) => l.body.stmts.iter().any(|s| self.stmt_modifies_jit_state(s)), Expr::Field(_) | Expr::Index(_) | Expr::Path(_) | Expr::Lit(_) | Expr::Try(_) - | Expr::Loop(_) - | Expr::While(_) | Expr::Break(_) | Expr::Continue(_) | Expr::Return(_) @@ -335,6 +358,21 @@ impl<'c> Lowerer<'c> { .iter() .any(|stmt| self.stmt_touches_jit_state(stmt)) } + // See the `while`/`loop` note in `expr_modifies_jit_state`: the + // third sibling probe was blind the same way, so nothing in the + // inert conjunction could see a loop body at all (compilation-panic). + Expr::While(w) => { + self.expr_has_jit_state_reference(&w.cond) + || w.body + .stmts + .iter() + .any(|stmt| self.stmt_touches_jit_state(stmt)) + } + Expr::Loop(l) => l + .body + .stmts + .iter() + .any(|stmt| self.stmt_touches_jit_state(stmt)), _ => false, } } @@ -390,6 +428,4 @@ impl<'c> Lowerer<'c> { _ => false, } } - - // ── Core lowering (unchanged logic) ────────────────────────────── } diff --git a/majit/majit-macros/src/jit_interp/jitcode_lower/lower_control.rs b/majit/majit-macros/src/jit_interp/jitcode_lower/lower_control.rs index 5945fbe2348..74a4c637ebb 100644 --- a/majit/majit-macros/src/jit_interp/jitcode_lower/lower_control.rs +++ b/majit/majit-macros/src/jit_interp/jitcode_lower/lower_control.rs @@ -16,10 +16,9 @@ impl<'c> Lowerer<'c> { /// comparison — an `OpKind::BinopF` whose result register is int /// banked. Float arithmetic writes a float result; only the value- /// form `float_lt/le/eq/ne/gt/ge` (`ff>i`) writes an int. A float - /// comparison feeding a conditional guard grows a bridge that hangs - /// the compiled trace (a4e191f71b5), so the branch lowerers roll back - /// and bail to interpreter fallback when the condition lowered through - /// one. + /// comparison feeding a conditional guard grows a bridge that hangs the + /// compiled trace, so the branch lowerers roll back and use interpreter + /// fallback when the condition lowered through one. fn ops_since_contain_float_compare(&self, since: usize) -> bool { self.op_metadata[since..].iter().any(|op| { matches!(op.kind, OpKind::BinopF) @@ -42,8 +41,6 @@ impl<'c> Lowerer<'c> { self.bindings = snap_bindings; return None; } - let else_label = self.alloc_label(); - let end_label = self.alloc_label(); let cond_reg = cond.reg; let then_seq = self.lower_branch_expr(&Expr::Block(syn::ExprBlock { attrs: Vec::new(), @@ -55,6 +52,11 @@ impl<'c> Lowerer<'c> { None => LoweredSequence::default(), }; + // Allocated below the branch lowerings, not above them: both labels are + // forward targets, so where they are defined carries no meaning, and + // either `?` above would otherwise return with `next_label` advanced. + let else_label = self.alloc_label(); + let end_label = self.alloc_label(); self.emit_aux(quote! { let #else_label = __builder.new_label(); }); self.emit_aux(quote! { let #end_label = __builder.new_label(); }); // RPython `flatten.py:259` `-live-` convention: every guard-bearing @@ -96,9 +98,6 @@ impl<'c> Lowerer<'c> { return None; } - let end_label = self.alloc_label(); - self.emit_aux(quote! { let #end_label = __builder.new_label(); }); - // Separate literal/path arms from the wildcard/default arm. // Uses extract_pat_value_tokens (not extract_pat_literals) so // symbolic constants like OP_JMP are accepted alongside literals. @@ -126,6 +125,13 @@ impl<'c> Lowerer<'c> { } } + // Allocated below the arm classification, not above it: `end_label` is + // a forward target, so where it is defined carries no meaning, and the + // `?`s above would otherwise return with `next_label` advanced. The + // classification emits no ops, so the statement stream is unchanged. + let end_label = self.alloc_label(); + self.emit_aux(quote! { let #end_label = __builder.new_label(); }); + let disc_reg = discriminant.reg; for (value_tokens, body) in &guarded_arms { @@ -222,8 +228,6 @@ impl<'c> Lowerer<'c> { Some(()) } - // ── Loop lowering ──────────────────────────────────────────────── - /// Lower `while cond { body }` to a JitCode branch sequence: /// ```text /// loop_start: @@ -240,6 +244,14 @@ impl<'c> Lowerer<'c> { let snap_label = self.next_label; let snap_bindings = self.bindings.clone(); + // `loop_start` has to be marked *before* the condition lowers: + // `mark_label` records the builder's current bytecode position, the + // back edge re-enters there, and the condition must be re-tested on + // every iteration. So these allocations cannot be deferred past the + // first fallible call the way `lower_if_with_loop_control` defers its + // own — both of that function's labels are forward targets, which is + // why it leaks nothing without a restore. Here every exit below has + // to put `next_label` back itself. let loop_start = self.alloc_label(); let loop_end = self.alloc_label(); @@ -248,7 +260,16 @@ impl<'c> Lowerer<'c> { self.emit_label_def(&loop_start); // Evaluate the condition - let cond = self.lower_value_expr(&expr_while.cond)?; + let Some(cond) = self.lower_value_expr(&expr_while.cond) else { + // Two labels and three statements are already emitted; drop them + // so an unlowerable condition leaves no gap in the label counter. + self.statements.truncate(snap_stmts); + self.op_metadata.truncate(snap_meta); + self.next_reg = snap_reg; + self.next_label = snap_label; + self.bindings = snap_bindings; + return None; + }; if self.ops_since_contain_float_compare(snap_meta) { // Guard over a float comparison hangs the compiled trace; roll // back everything emitted for this loop and bail so the arm @@ -434,10 +455,15 @@ impl<'c> Lowerer<'c> { auto_calls: self.auto_calls, inline_liveness_prebuild: Vec::new(), dispatch_tainted_reason: None, + body_failure_reason: None, + nested_failure_reasons: Vec::new(), opcode_var_name: self.opcode_var_name.clone(), in_dispatch_arm_body: self.in_dispatch_arm_body, dispatch_loop_label: self.dispatch_loop_label.clone(), pc_pinned: self.pc_pinned, + // Never inherited: a loop body statement is not the arm body's + // tail, so a `return` inside it must be rejected, not lowered. + inline_arm_tail_stmt: false, }; for stmt in &block.stmts { @@ -446,7 +472,12 @@ impl<'c> Lowerer<'c> { .is_none() { // Fall back: try normal lowering - nested.lower_stmt(stmt)?; + if nested.lower_stmt(stmt).is_none() { + // Carry the diagnosis out before the child is dropped; a + // bare `?` here propagates the failure and loses the reason. + self.absorb_nested_failure(&mut nested); + return None; + } } } @@ -553,9 +584,7 @@ impl<'c> Lowerer<'c> { return None; } - let end_label = self.alloc_label(); let result_reg = self.alloc_reg(); - self.emit_aux(quote! { let #end_label = __builder.new_label(); }); let mut guarded_arms = Vec::new(); let mut default_arm = None; @@ -576,6 +605,13 @@ impl<'c> Lowerer<'c> { } } + // Allocated below the arm classification, not above it: `end_label` is + // a forward target, so where it is defined carries no meaning, and the + // `?` above would otherwise return with `next_label` advanced. The + // classification emits no ops, so the statement stream is unchanged. + let end_label = self.alloc_label(); + self.emit_aux(quote! { let #end_label = __builder.new_label(); }); + let disc_reg = discriminant.reg; for (literals, body) in &guarded_arms { diff --git a/majit/majit-macros/src/jit_interp/jitcode_lower/lower_stmt.rs b/majit/majit-macros/src/jit_interp/jitcode_lower/lower_stmt.rs index 11c135a6cc8..29602949f0e 100644 --- a/majit/majit-macros/src/jit_interp/jitcode_lower/lower_stmt.rs +++ b/majit/majit-macros/src/jit_interp/jitcode_lower/lower_stmt.rs @@ -1,6 +1,22 @@ use super::lower_value::struct_type_id_tokens; use super::*; +/// Joins the refusals accumulated into one `DegradedDispatchArm::reason`. +/// +/// Cross-crate contract. `majit_metainterp::REFUSAL_SEPARATOR` must hold the +/// same bytes — a proc-macro crate cannot export a value to its runtime, so the +/// two literals are mirrored rather than shared. +/// +/// The drift detector is `both_blockers_are_reported` in +/// `majit-metainterp/tests/jit_interp_degraded_arm_accumulates_refusals.rs`. It +/// takes a two-blocker arm's reason from the registry — minted with THIS +/// literal — and splits it with the runtime's, so a change to either side reads +/// one member where it expects two. It has to be that fixture and not the +/// literal corpus in `degraded_arm_refusal_kind.rs`: a recorded string frozen +/// into a `const` was minted by whatever this literal said on the day it was +/// copied, so it goes stale silently when this side changes. +pub(super) const REFUSAL_SEPARATOR: &str = " || "; + impl<'c> Lowerer<'c> { /// If `func` is a registered `residual_writes` mutator, return the /// `struct_field_write_effect_info(...)` expression naming the written @@ -87,6 +103,27 @@ impl<'c> Lowerer<'c> { } pub(super) fn lower_stmt(&mut self, stmt: &Stmt) -> Option<()> { + // A statement that lowers contributes no blockers. An inner attempt can + // refuse and stash its reason before an outer strategy succeeds on the + // same statement — `lower_local` failing into `lower_stmt_fallback`'s + // inert arm is the shape — and reporting that stash would name a + // statement that played no part in the refusal. Drop back to the depth + // this call started at, so only genuinely unlowered statements carry + // reasons upward. + let carried_on_entry = self.nested_failure_reasons.len(); + let lowered = self.lower_stmt_dispatch(stmt); + if lowered.is_some() { + self.nested_failure_reasons.truncate(carried_on_entry); + } + lowered + } + + fn lower_stmt_dispatch(&mut self, stmt: &Stmt) -> Option<()> { + // Consume the one-shot tail marker (see `Lowerer::inline_arm_tail_stmt`). + // Taking it here rather than reading it is what confines the in-arm + // `return` lowering to statement-tail position: every nested + // `lower_stmt` — an `if` body, a loop body — observes `false`. + let is_arm_tail = std::mem::take(&mut self.inline_arm_tail_stmt); match stmt { Stmt::Local(local) => { if let Some(()) = self.lower_local(local) { @@ -95,6 +132,9 @@ impl<'c> Lowerer<'c> { self.lower_stmt_fallback(stmt, "local") } Stmt::Expr(expr, _) => { + if let Expr::Return(ret) = expr { + return self.lower_return_stmt(ret, is_arm_tail); + } if matches!(expr, Expr::Continue(_)) { if let Some(label) = self.dispatch_loop_label.clone() { self.emit_jump(&label); @@ -163,6 +203,54 @@ impl<'c> Lowerer<'c> { } } + /// Lower a `return` statement to its typed return terminator — + /// `int_return` / `ref_return` / `float_return` by operand kind, or + /// `void_return` for a bare `return;`. + /// + /// **Language-gap adaptation, not a parity fix.** `interp_jit.py:95-100` + /// is upstream's single return point: the portal funnels every exit from + /// the bytecode loop through that one `return`, because its opcode + /// implementations raise `Return` rather than returning in place. That + /// shape never produces a `return` inside a dispatch arm, so there is no + /// upstream construct to mirror here. A Rust interpreter instead + /// idiomatically returns straight out of a `match` arm, and the terminator + /// emitted here is what that spelling corresponds to — the same single + /// return point upstream arrives at by unwinding. + /// + /// Accepted **only** in statement-tail position of an inline dispatch arm + /// body (`is_arm_tail`, from `Lowerer::inline_arm_tail_stmt`). A `return` + /// anywhere else is rejected with `None`, which rolls the arm back to the + /// sub-JitCode / abort path. Rejecting is mandatory rather than tidy: this + /// site exists because lowering a `return`'s operand while dropping its + /// control transfer let the arm fall through to the dispatch back-edge and + /// re-enter the loop at the terminal pc, so a walk could only ever end by + /// closing a loop. Reproducing that one level down — for a `return` nested + /// in an `if` — would reintroduce exactly the defect. + fn lower_return_stmt(&mut self, ret: &syn::ExprReturn, is_arm_tail: bool) -> Option<()> { + if !is_arm_tail { + if std::env::var_os("MAJIT_MACRO_DEBUG").is_some() { + eprintln!( + "[majit-macro] lower_stmt rejected `return`: only an inline dispatch \ + arm body's tail statement can carry a return terminator, so this \ + one's control transfer cannot be lowered here: {}", + quote!(#ret) + ); + } + return None; + } + // A bare `return;` has no operand and lowers to `void_return`. An + // operand that fails to lower must NOT fall back to `void_return` — it + // would return the wrong value — so `?` rejects and the caller rolls + // back whatever ops the partial operand lowering emitted. + let binding = match ret.expr.as_deref() { + Some(expr) => Some(self.lower_value_expr(expr)?), + None => None, + }; + let (reads, emitter) = super::dispatch::typed_return_terminator(binding); + self.emit_op(OpMeta::terminal(reads), emitter); + Some(()) + } + /// Last resort for a statement no lowering arm accepted, in the /// state-field dispatch body (`config` present). /// @@ -183,6 +271,135 @@ impl<'c> Lowerer<'c> { /// aborts mid-record. fn lower_stmt_fallback(&mut self, stmt: &Stmt, what: &str) -> Option<()> { self.config?; + // A statement containing a `return` is never inert, however little jit + // state it touches. `if flag { return 0; }` writes no state, reads no + // storage and calls nothing, so the purity test below would classify it + // inert and drop it — silently deleting the control transfer and + // letting the arm fall through to the dispatch back-edge. That is the + // exact defect `lower_return_stmt` exists to fix, so it is refused here + // rather than reproduced one level down. Only a `return` in arm-body + // tail position lowers; it never reaches this fallback. + if stmt_contains_return(stmt) { + if std::env::var_os("MAJIT_MACRO_DEBUG").is_some() { + eprintln!( + "[majit-macro] lower_stmt rejected ({what}): statement encloses a \ + `return` that cannot be lowered in place: {}", + quote!(#stmt) + ); + } + self.record_body_failure("encloses a `return` that cannot be lowered in place", stmt); + return None; + } + // `break` and `continue` are control transfers for exactly the same + // reason `return` is, and the purity test below cannot see either: + // `expr_modifies_jit_state` reports `false` for both, so a statement + // whose only effect is `if cond { continue; }` writes no state, touches + // no storage and calls nothing — it is scored inert, dropped, and the + // arm falls through to the dispatch back-edge with the transfer gone. + // + // The observable symptom is one extra loop iteration. A terminal arm + // spelled `{ store; break }` is an `Expr::Block`, so + // `classify_arm_body` does not reach `ArmPattern::Halt` — + // `is_break_expr` requires the body to be exactly `break` — and the arm + // is lowered, putting its tail `break` on this path. That is the + // defect, not a hypothetical. + // + // Measured coverage, `MAJIT_MACRO_DEBUG` over all 13 examples: this + // guard fires exactly four times — tiny2, tiny3, braininterp and + // dualtape, once each — and zero times in `examples/cel` or + // `examples/tl`. Those two crates do not witness this guard. + // Rebuilding with the guard disabled (confirmed by zero firings) + // leaves every example's output unchanged, because at each of the four + // sites the same arm body also yields `unsupported` — `if target <= pc` + // in tiny2/tiny3, `find_matching_open` in braininterp/dualtape — which + // refuses the arm on its own. The guard is therefore defensive: it + // covers a body reaching this path with no co-occurring refusal, a + // shape the current corpus does not contain. + if stmt_contains_loop_control(stmt) { + if std::env::var_os("MAJIT_MACRO_DEBUG").is_some() { + eprintln!( + "[majit-macro] lower_stmt rejected ({what}): statement encloses a \ + `break`/`continue` that cannot be lowered in place: {}", + quote!(#stmt) + ); + } + self.record_body_failure( + "encloses a `break`/`continue` that cannot be lowered in place", + stmt, + ); + return None; + } + // A write to a green is the third member of the family above, and the + // purity test is blind to it for the same reason it is blind to + // `break`: `stmt_modifies_jit_state` scores writes to `state.*`, and a + // green is a caller local, not state. So `pc += 1` writes no state, + // references no storage and calls nothing — scored inert, dropped, and + // the advance is gone from the arm's jitcode. + // + // Its symptom is worse than the other two. `break`/`continue` cost one + // extra loop iteration; a dropped green-pc advance costs nothing + // visible at all until the walk resumes: the arm emits no + // `BC_INT_ADD`, the green pc advances only by the dispatch prologue's + // read of the opcode byte, and a multi-byte instruction therefore + // resumes on its own operand — which the interpreter decodes as the + // next opcode. The trace is well-formed, records, compiles, and + // computes a wrong answer. + // + // The sub-JitCode arm path is where this lands, because a green write + // has exactly one channel out of a sub-JitCode: the `BC_INT_RETURN` + // that `try_generate_jitcode_pc_return_body_with_caller_bindings` + // emits for an arm whose advance is its *final* statement. An arm + // that spells the advance mid-body (`let r = program[pc]; pc += 1; + // residual(r);`) fails `arm_is_pure_pc_advance`, gets the void + // `inline_call` instead, and has no channel at all. + // + // Measured coverage, `MAJIT_MACRO_DEBUG` per crate over all 13 + // examples (160 dispatch arms): three firings — `tl::ROLL`, + // `tlc::ROLL` and `tlr::ALLOCATE`, one each, and none anywhere else. + // All three are inert today, because each arm ALSO yields + // `unsupported` for a co-occurring statement — + // `storage_roll(state.stack.as_mut_ptr() as usize, ...)`, + // `tlc_roll(...)` and `state.regs = vec![0; n]` — so each degrades to + // an abort stub on its own and its dropped advance never runs. + // Confirmed at runtime, not inferred from the census: `MAJIT_LOG=1 + // cargo test -p tl|tlc|tlr` records exactly `TlState::ROLL`, + // `TlcState::ROLL` and `TlrState::ALLOCATE` as degraded. + // + // So this guard is defensive, like the `break` one above — but it + // brakes a change already in flight rather than a hypothesis. The two + // `ROLL` arms are inert only because the residual's + // `state.stack.as_mut_ptr()` argument has no lowering; give it one and + // both become real sub-JitCodes whose advance is dropped, which is a + // trace that records, compiles, and returns a wrong answer. + if let Some(green) = self + .config + .map(|config| green_idents(config)) + .and_then(|greens| stmt_writes_green(stmt, &greens)) + { + if std::env::var_os("MAJIT_MACRO_DEBUG").is_some() { + eprintln!( + "[majit-macro] lower_stmt rejected ({what}): statement writes the green \ + `{green}`, which this path cannot carry back to the caller: {}", + quote!(#stmt) + ); + } + // The reason says its own scope out loud. Lowering genuinely stops + // at this statement, and a green advance is typically statement 2 of + // 3, so this refusal is the arm's OUTERMOST blocker rather than its + // only one. `record_body_failure` accumulates and the caller keeps + // walking the remaining statements for their reasons, so the ones + // behind the stop (`storage_roll(…)`, `tlc_roll(…)`, + // `state.regs = vec![0; n]`) are reported after it instead of being + // displaced by it — a reader watching `MAJIT_LOG` for their own + // blocker to disappear would otherwise read this refusal alone as + // progress. + self.record_body_failure( + "writes a green this lowering path cannot carry back to the caller \ + (lowering stopped at this statement; any further blockers follow)", + stmt, + ); + return None; + } // Drop only genuinely inert statements: no jit-state write, no // storage/user-local reference, AND no call. A residual call // (e.g. `record_event();` or an unrolled `for _ in 0..4 { @@ -209,11 +426,98 @@ impl<'c> Lowerer<'c> { quote!(#stmt) ); } + self.record_body_failure("has a statement the lowerer cannot express", stmt); None } + /// Record why this body's lowering refused; the first writer keeps the head. + /// + /// The first entry wins because the failure that matters most is the one + /// that stopped lowering. It is no longer true that later statements go + /// unreached: the caller deliberately keeps walking them for their reasons, + /// discarding whatever they lower, so the rest of the string enumerates the + /// blockers behind the stop. + /// + /// Reasons do NOT cross a nested lowerer. `lower_control` and + /// `lower_value` build a child `Lowerer` with its own + /// `body_failure_reason: None` and merge back `next_reg`, `next_label`, + /// `statements` and `op_metadata` — never this field. A blocker found + /// inside a nested block is therefore recorded here and then dropped with + /// the child, which is why an arm whose inner statement is the interesting + /// one (braininterp's and dualtape's `b']'`, whose real blocker is + /// `find_matching_open(program, pc)`) reports only its outer refusal. + /// Measured from the built artifacts, not inferred: + /// `strings target/debug/deps/- | rg 'arm body '` shows those + /// two crates with a single-member reason. That filter is only trustworthy + /// while the spelling below stays newline-free: `strings(1)` ends a run at a + /// newline, so a reason carrying one is read back as two literals, and the + /// second — having lost the `arm body ` prefix — is invisible to the census + /// this comment cites. + /// + /// The statement spelling is carried because the classification alone does + /// not say which lowering rule is missing — "cannot express" reads the same + /// for `find_matching_open(program, pc)` (needs a slice argument) and for + /// `state.stack[n] = v` (needs a computed-index store), and those are + /// different pieces of work. Whitespace runs are collapsed to single spaces + /// BEFORE the truncation, so a whole `if` block cannot bake a multi-line + /// literal into the binary and the length bound counts the characters that + /// actually reach it. + /// + /// The truncation marker is ASCII `...` on purpose: a `…` splits the + /// literal in `strings(1)` output, which is how these reasons get read out + /// of a built artifact. + /// Refusals accumulate; the FIRST one stays the head of the string. + /// + /// The head is byte-identical to what this produced before accumulation + /// existed, which is what lets every example crate's `.contains()` snippet + /// assertion and every landed `RefusalKind` pin keep matching untouched. + /// + /// The separator is ASCII and spaced for the same reason the truncation + /// marker is ASCII `...`: these strings are read out of a built artifact + /// with `strings(1)`, and a multi-byte separator splits the literal there. + pub(super) fn record_body_failure(&mut self, what: &str, stmt: &Stmt) { + const MAX_SPELLING: usize = 80; + // A braced group stringifies across several lines, and `strings(1)` + // ends a run at a newline: an un-normalised spelling reaches the + // artifact as two independent literals whose second half has lost the + // `arm body ` prefix every reader of these reasons matches on. Collapse + // before truncating, so the bound counts the characters that survive + // into the binary rather than characters the reader will never see. + let mut spelling = quote!(#stmt) + .to_string() + .split_whitespace() + .collect::>() + .join(" "); + if spelling.chars().count() > MAX_SPELLING { + spelling = spelling.chars().take(MAX_SPELLING).collect::() + "..."; + } + let entry = format!("arm body {what}: {spelling}"); + match &mut self.body_failure_reason { + // Deduplicated, and not cosmetically: the nested lowerers re-visit + // statements, so without this one blocker can be recorded twice and + // a count-based reader would take the repeat for a second + // mechanism. + Some(existing) => { + if !existing.split(REFUSAL_SEPARATOR).any(|seen| seen == entry) { + existing.push_str(REFUSAL_SEPARATOR); + existing.push_str(&entry); + } + } + None => self.body_failure_reason = Some(entry), + } + } + pub(super) fn lower_local(&mut self, local: &Local) -> Option<()> { - let Pat::Ident(pat_ident) = &local.pat else { + // `let x: i32 = 1;` parses as a `Pat::Type` wrapping the very + // `Pat::Ident` that `let x = 1;` produces. The annotation carries no + // lowering information — the binding kind comes from the initialiser + // via `lower_value_expr` either way — so unwrap it and lower the + // annotated spelling exactly like the bare one. + let mut pat = &local.pat; + while let Pat::Type(pat_type) = pat { + pat = &*pat_type.pat; + } + let Pat::Ident(pat_ident) = pat else { return None; }; let init = local.init.as_ref()?; @@ -1032,8 +1336,6 @@ impl<'c> Lowerer<'c> { None } - // ── Config-aware lowering methods ──────────────────────────────── - pub(super) fn lower_config_call_stmt(&mut self, expr: &Expr) -> Option<()> { let Expr::Call(call) = expr else { return None; @@ -1897,3 +2199,158 @@ impl<'c> Lowerer<'c> { None } } + +/// Idents naming this machine's greens. +/// +/// `LowererConfig::greens` holds the merge point's green *expressions*. Only a +/// plain-ident green names a caller local that a statement can assign to, so a +/// field or index spelling is skipped rather than flattened to a bare name that +/// would collide with an unrelated local of that name. +fn green_idents(config: &LowererConfig) -> Vec { + config + .greens + .iter() + .filter_map(|green| match green { + Expr::Path(p) if p.qself.is_none() => p.path.get_ident().map(|id| id.to_string()), + _ => None, + }) + .collect() +} + +/// Name of the green `stmt` assigns to, if it assigns to one. +/// +/// Covers both spellings the dispatch loops use: the compound +/// `pc += N` (syn 2 parses it as `Expr::Binary` with an assigning `BinOp`) and +/// the plain `pc = `. Only the assignment *target* counts — a green read +/// on the right-hand side is what every arm does and is not a write. +/// +/// Closure bodies and nested items are skipped for the reason +/// [`stmt_contains_return`] skips them: a name bound there is a different +/// binding that merely shares a spelling. +fn stmt_writes_green(stmt: &Stmt, greens: &[String]) -> Option { + use syn::visit::Visit; + struct Probe<'g> { + greens: &'g [String], + hit: Option, + } + impl Probe<'_> { + fn record(&mut self, target: &Expr) { + if self.hit.is_some() { + return; + } + let Expr::Path(p) = target else { return }; + if p.qself.is_some() { + return; + } + let Some(id) = p.path.get_ident() else { return }; + let name = id.to_string(); + if self.greens.iter().any(|green| *green == name) { + self.hit = Some(name); + } + } + } + impl<'ast> Visit<'ast> for Probe<'_> { + fn visit_expr_assign(&mut self, node: &'ast syn::ExprAssign) { + self.record(&node.left); + syn::visit::visit_expr_assign(self, node); + } + fn visit_expr_binary(&mut self, node: &'ast syn::ExprBinary) { + if opcode_for_assign_binop(&node.op).is_some() { + self.record(&node.left); + } + syn::visit::visit_expr_binary(self, node); + } + fn visit_expr_closure(&mut self, _: &'ast syn::ExprClosure) {} + fn visit_item(&mut self, _: &'ast syn::Item) {} + } + let mut probe = Probe { greens, hit: None }; + probe.visit_stmt(stmt); + probe.hit +} + +/// `true` if `stmt` encloses a `return` belonging to the interpreter function +/// being lowered. +/// +/// Closure bodies and nested items are deliberately NOT descended into: a +/// `return` inside `|| { return 1; }` or an inner `fn` exits *that* body, not +/// the dispatch arm, so it is neither lowerable here nor a reason to refuse the +/// enclosing statement. +fn stmt_contains_return(stmt: &Stmt) -> bool { + use syn::visit::Visit; + struct Probe { + hit: bool, + } + impl<'ast> Visit<'ast> for Probe { + fn visit_expr_return(&mut self, _: &'ast syn::ExprReturn) { + self.hit = true; + } + fn visit_expr_closure(&mut self, _: &'ast syn::ExprClosure) {} + fn visit_item(&mut self, _: &'ast syn::Item) {} + } + let mut probe = Probe { hit: false }; + probe.visit_stmt(stmt); + probe.hit +} + +/// Whether `stmt` encloses a `break` or `continue` that targets the dispatch +/// loop being lowered — the loop-control twin of `stmt_contains_return`. +/// +/// `break`/`continue` bind to the *innermost* enclosing loop, so an unlabelled +/// one inside a nested `loop`/`while`/`for` written in the arm body exits that +/// inner loop and never reaches the dispatch back-edge. Descending into those +/// bodies would refuse statements that are perfectly safe to drop, so the probe +/// tracks loop depth and only fires at depth 0 — the same scoping rule +/// `expr_has_loop_control` documents. A *labelled* `break 'l` / `continue 'l` +/// can cross a nested loop, so it counts at any depth. +/// +/// `expr_has_loop_control` is not reused here because it only inspects +/// `Stmt::Expr`: a control transfer in an initializer (`let x = if c { break } +/// else { 1 };`) is invisible to it, and this guard has to see it. Closures and +/// nested items are skipped for the same reason `stmt_contains_return` skips +/// them — a `break` inside them belongs to a different body. +fn stmt_contains_loop_control(stmt: &Stmt) -> bool { + use syn::visit::Visit; + struct Probe { + hit: bool, + loop_depth: u32, + } + impl Probe { + fn record(&mut self, labelled: bool) { + if labelled || self.loop_depth == 0 { + self.hit = true; + } + } + } + impl<'ast> Visit<'ast> for Probe { + fn visit_expr_break(&mut self, node: &'ast syn::ExprBreak) { + self.record(node.label.is_some()); + syn::visit::visit_expr_break(self, node); + } + fn visit_expr_continue(&mut self, node: &'ast syn::ExprContinue) { + self.record(node.label.is_some()); + } + fn visit_expr_loop(&mut self, node: &'ast syn::ExprLoop) { + self.loop_depth += 1; + syn::visit::visit_expr_loop(self, node); + self.loop_depth -= 1; + } + fn visit_expr_while(&mut self, node: &'ast syn::ExprWhile) { + self.loop_depth += 1; + syn::visit::visit_expr_while(self, node); + self.loop_depth -= 1; + } + fn visit_expr_for_loop(&mut self, node: &'ast syn::ExprForLoop) { + self.loop_depth += 1; + syn::visit::visit_expr_for_loop(self, node); + self.loop_depth -= 1; + } + fn visit_expr_closure(&mut self, _: &'ast syn::ExprClosure) {} + fn visit_item(&mut self, _: &'ast syn::Item) {} + } + let mut probe = Probe { + hit: false, + loop_depth: 0, + }; + probe.visit_stmt(stmt); + probe.hit +} diff --git a/majit/majit-macros/src/jit_interp/jitcode_lower/lower_vable.rs b/majit/majit-macros/src/jit_interp/jitcode_lower/lower_vable.rs index 13a748b2225..9c28d7c13c7 100644 --- a/majit/majit-macros/src/jit_interp/jitcode_lower/lower_vable.rs +++ b/majit/majit-macros/src/jit_interp/jitcode_lower/lower_vable.rs @@ -243,15 +243,24 @@ impl<'c> Lowerer<'c> { } let member_name = named_member(&field.member)?; let &(array_index, item_type) = config.vable_arrays.get(&member_name)?; - if matches!(item_type, ValueKind::Ref) { - return None; - } + let opcode = match item_type { + ValueKind::Float => opcode_for_assign_binop_f(&binary.op)?, + ValueKind::Int => opcode_for_assign_binop(&binary.op)?, + ValueKind::Ref => return None, + }; let vable_reg = self.vable_base_reg()?; let idx_binding = self.lower_value_expr(&index_expr.index)?; if !matches!(idx_binding.kind, BindingKind::Int) { return None; } let idx_reg = idx_binding.reg; + let rhs = self.lower_value_expr(&binary.right)?; + match item_type { + ValueKind::Float if !matches!(rhs.kind, BindingKind::Float) => return None, + ValueKind::Int if !matches!(rhs.kind, BindingKind::Int) => return None, + ValueKind::Ref => unreachable!(), + _ => {} + } let ai = array_index as u16; let lhs_reg = self.alloc_reg(); @@ -279,37 +288,24 @@ impl<'c> Lowerer<'c> { ValueKind::Ref => unreachable!(), } - let rhs = self.lower_value_expr(&binary.right)?; let dst = self.alloc_reg(); match item_type { - ValueKind::Float => { - if !matches!(rhs.kind, BindingKind::Float) { - return None; - } - let opcode = opcode_for_assign_binop_f(&binary.op)?; - self.emit_op( - OpMeta::linear( - OpKind::BinopF, - vec![Register::float(lhs_reg), Register::float(rhs.reg)], - vec![Register::float(dst)], - ), - binop_f_emit_tokens(dst, &opcode, lhs_reg, rhs.reg), - ); - } - ValueKind::Int => { - if !matches!(rhs.kind, BindingKind::Int) { - return None; - } - let opcode = opcode_for_assign_binop(&binary.op)?; - self.emit_op( - OpMeta::linear( - OpKind::BinopI, - Register::ints(&[lhs_reg, rhs.reg]), - vec![Register::int(dst)], - ), - binop_i_emit_tokens(dst, &opcode, lhs_reg, rhs.reg), - ); - } + ValueKind::Float => self.emit_op( + OpMeta::linear( + OpKind::BinopF, + vec![Register::float(lhs_reg), Register::float(rhs.reg)], + vec![Register::float(dst)], + ), + binop_f_emit_tokens(dst, &opcode, lhs_reg, rhs.reg), + ), + ValueKind::Int => self.emit_op( + OpMeta::linear( + OpKind::BinopI, + Register::ints(&[lhs_reg, rhs.reg]), + vec![Register::int(dst)], + ), + binop_i_emit_tokens(dst, &opcode, lhs_reg, rhs.reg), + ), ValueKind::Ref => unreachable!(), } diff --git a/majit/majit-macros/src/jit_interp/jitcode_lower/lower_value.rs b/majit/majit-macros/src/jit_interp/jitcode_lower/lower_value.rs index 65676ff21aa..ae7170d8f84 100644 --- a/majit/majit-macros/src/jit_interp/jitcode_lower/lower_value.rs +++ b/majit/majit-macros/src/jit_interp/jitcode_lower/lower_value.rs @@ -284,6 +284,9 @@ impl<'c> Lowerer<'c> { if let Some(binding) = self.lower_wrapping_int_method_call(call) { return Some(binding); } + if let Some(binding) = self.lower_float_bitcast_method_call(call) { + return Some(binding); + } self.lower_method_call_value(call) } Expr::Struct(s) => self.lower_struct_value(s), @@ -291,66 +294,97 @@ impl<'c> Lowerer<'c> { } } + /// `convert_float_bytes_to_longlong` for an already-lowered float source. + fn emit_convert_float_bytes_to_longlong(&mut self, src: Binding) -> Option { + if !matches!(src.kind, BindingKind::Float) { + return None; + } + let src_reg = src.reg; + let dst = self.alloc_reg(); + self.emit_op( + OpMeta::linear( + OpKind::UnaryI, + vec![Register::float(src_reg)], + vec![Register::int(dst)], + ), + quote! { __builder.record_convert_float_bytes_to_longlong(#dst, #src_reg); }, + ); + Some(Binding { + reg: dst, + kind: BindingKind::Int, + depends_on_stack: src.depends_on_stack, + struct_type: None, + }) + } + + /// `convert_longlong_bytes_to_float` for an already-lowered int source. + fn emit_convert_longlong_bytes_to_float(&mut self, src: Binding) -> Option { + if !matches!(src.kind, BindingKind::Int) { + return None; + } + let src_reg = src.reg; + let dst = self.alloc_reg(); + self.emit_op( + OpMeta::linear( + OpKind::UnaryI, + vec![Register::int(src_reg)], + vec![Register::float(dst)], + ), + quote! { __builder.record_convert_longlong_bytes_to_float(#dst, #src_reg); }, + ); + Some(Binding { + reg: dst, + kind: BindingKind::Float, + depends_on_stack: src.depends_on_stack, + struct_type: None, + }) + } + /// Lower the float<->int bitcast intrinsics: `majit_f64_to_bits(f)` (float /// argument → its i64 bit pattern, `convert_float_bytes_to_longlong`) and /// `majit_bits_to_f64(i)` (int bits → float, `convert_longlong_bytes_to_float`). /// Both reinterpret the 64-bit pattern (no value change); a branchless float /// bit-select uses them to stay bit-exact where an arithmetic blend cannot. + /// + /// `f64::to_bits` / `f64::from_bits` are the same two conversions and are + /// accepted here in their UFCS spelling (the method spelling `x.to_bits()` + /// is `lower_float_bitcast_method_call`). Recognizing only the free + /// functions made an arm that reached for the inherent method degrade to an + /// abort stub with nothing naming it — cel's `OP_RETURN_F` and its + /// `OP_LOAD_CONST_F` mirror, the second of which the interpreter had already + /// worked around by hoisting the op out of the traced loop body. fn lower_float_bitcast_call(&mut self, call: &syn::ExprCall) -> Option { + if call.args.len() != 1 { + return None; + } let segments = canonical_expr_segments(&call.func)?; - match segments.last()?.as_str() { - "majit_f64_to_bits" => { - if call.args.len() != 1 { - return None; - } - let src = self.lower_value_expr(&call.args[0])?; - if !matches!(src.kind, BindingKind::Float) { - return None; - } - let src_reg = src.reg; - let dst = self.alloc_reg(); - self.emit_op( - OpMeta::linear( - OpKind::UnaryI, - vec![Register::float(src_reg)], - vec![Register::int(dst)], - ), - quote! { __builder.record_convert_float_bytes_to_longlong(#dst, #src_reg); }, - ); - Some(Binding { - reg: dst, - kind: BindingKind::Int, - depends_on_stack: src.depends_on_stack, - struct_type: None, - }) - } - "majit_bits_to_f64" => { - if call.args.len() != 1 { - return None; - } - let src = self.lower_value_expr(&call.args[0])?; - if !matches!(src.kind, BindingKind::Int) { - return None; - } - let src_reg = src.reg; - let dst = self.alloc_reg(); - self.emit_op( - OpMeta::linear( - OpKind::UnaryI, - vec![Register::int(src_reg)], - vec![Register::float(dst)], - ), - quote! { __builder.record_convert_longlong_bytes_to_float(#dst, #src_reg); }, - ); - Some(Binding { - reg: dst, - kind: BindingKind::Float, - depends_on_stack: src.depends_on_stack, - struct_type: None, - }) - } - _ => None, + let owner = segments + .len() + .checked_sub(2) + .map(|i| segments[i].as_str()) + .unwrap_or(""); + let to_bits = match (owner, segments.last()?.as_str()) { + (_, "majit_f64_to_bits") | ("f64", "to_bits") => true, + (_, "majit_bits_to_f64") | ("f64", "from_bits") => false, + _ => return None, + }; + let src = self.lower_value_expr(&call.args[0])?; + if to_bits { + self.emit_convert_float_bytes_to_longlong(src) + } else { + self.emit_convert_longlong_bytes_to_float(src) + } + } + + /// The inherent-method spelling of `majit_f64_to_bits`: `.to_bits()`. + /// There is no method spelling of the reverse — `f64::from_bits` is an + /// associated function and reaches `lower_float_bitcast_call`. + fn lower_float_bitcast_method_call(&mut self, call: &ExprMethodCall) -> Option { + if call.method != "to_bits" || !call.args.is_empty() { + return None; } + let src = self.lower_value_expr(&call.receiver)?; + self.emit_convert_float_bytes_to_longlong(src) } /// Lower the unsigned integer intrinsics. Both operands are read from the @@ -2230,14 +2264,24 @@ impl<'c> Lowerer<'c> { auto_calls: self.auto_calls, inline_liveness_prebuild: Vec::new(), dispatch_tainted_reason: None, + body_failure_reason: None, + nested_failure_reasons: Vec::new(), opcode_var_name: self.opcode_var_name.clone(), in_dispatch_arm_body: self.in_dispatch_arm_body, dispatch_loop_label: self.dispatch_loop_label.clone(), pc_pinned: self.pc_pinned, + // Never inherited: a nested block statement is not the arm body's + // tail, so a `return` inside it must be rejected, not lowered. + inline_arm_tail_stmt: false, }; for stmt in &stmts { - nested.lower_stmt(stmt)?; + if nested.lower_stmt(stmt).is_none() { + // Carry the diagnosis out before the child is dropped; a bare + // `?` here propagates the failure and loses the reason. + self.absorb_nested_failure(&mut nested); + return None; + } } self.next_reg = self.next_reg.max(nested.next_reg); @@ -2261,13 +2305,23 @@ impl<'c> Lowerer<'c> { auto_calls: self.auto_calls, inline_liveness_prebuild: Vec::new(), dispatch_tainted_reason: None, + body_failure_reason: None, + nested_failure_reasons: Vec::new(), opcode_var_name: self.opcode_var_name.clone(), in_dispatch_arm_body: self.in_dispatch_arm_body, dispatch_loop_label: self.dispatch_loop_label.clone(), pc_pinned: self.pc_pinned, + // Never inherited: a branch arm's value expression is not the arm + // body's tail, so a `return` inside it must be rejected. + inline_arm_tail_stmt: false, }; - let binding = nested.lower_scoped_value_expr(expr)?; + let Some(binding) = nested.lower_scoped_value_expr(expr) else { + // Carry the diagnosis out before the child is dropped; a bare `?` + // here propagates the failure and loses the reason. + self.absorb_nested_failure(&mut nested); + return None; + }; self.next_reg = self.next_reg.max(nested.next_reg); self.next_label = self.next_label.max(nested.next_label); Some(( diff --git a/majit/majit-macros/src/jit_interp/jitcode_lower/lowerer.rs b/majit/majit-macros/src/jit_interp/jitcode_lower/lowerer.rs index 09d7c4226aa..271c9b2cc6f 100644 --- a/majit/majit-macros/src/jit_interp/jitcode_lower/lowerer.rs +++ b/majit/majit-macros/src/jit_interp/jitcode_lower/lowerer.rs @@ -1,3 +1,4 @@ +use super::lower_stmt::REFUSAL_SEPARATOR; use super::*; pub(super) struct Lowerer<'c> { @@ -33,6 +34,29 @@ pub(super) struct Lowerer<'c> { /// requirement that an unrecognized inner while must NOT silently /// pass the existing `BC_GETARRAYITEM_GC_I`-presence gate. pub(super) dispatch_tainted_reason: Option<&'static str>, + /// Why this body's lowering refused, recorded by the FIRST site that + /// returns `None` so the cause survives the `?` that discards it. + /// + /// Without it every refusal reaches `record_degraded_dispatch_arm` as the + /// single string "arm body could not be lowered to a sub-JitCode", which + /// covers at least three unrelated causes — an unsupported statement, an + /// enclosed `return`, and an enclosed `break`/`continue` — so an install + /// -time reader cannot tell which arm needs which lowering rule. + pub(super) body_failure_reason: Option, + /// Diagnoses carried up from child lowerers that refused, held back until + /// [`Lowerer::take_body_failure_reason`] so they can never take the head. + /// + /// A child lowers a nested block *before* this lowerer reaches its own + /// guard for the statement containing it, so merging a child's reason at + /// the failure site would make the innermost blocker the head. The head is + /// contractually the OUTERMOST refusal — every landed `.contains()` pin and + /// every `refusal_kind` assertion reads it — so child reasons are stashed + /// here and appended behind it instead. + /// + /// Dropped when the statement lowers anyway: a failed inner attempt that + /// another strategy recovers from is not a blocker, and reporting it would + /// name a statement that played no part in the refusal. + pub(super) nested_failure_reasons: Vec, /// Name of the LHS variable that received the opcode-fetch /// result, set by `try_lower_opcode_fetch_stmt` when it recognises /// `let = program[]` (or the method-call form @@ -74,6 +98,20 @@ pub(super) struct Lowerer<'c> { /// the sub-JitCode arm path (BC_INLINE_CALL copies args caller→callee /// only, so a sub-JitCode pc-write cannot reach the dispatch reg0). pub(super) pc_pinned: bool, + /// One-shot marker for "the statement `lower_stmt` is about to lower is + /// the inline dispatch arm body's FINAL statement". Set per-statement by + /// `try_inline_dispatch_arm` and **consumed** (`mem::take`) at the top of + /// `lower_stmt`, so it is observable for exactly one statement. + /// + /// Only a `return` in that position lowers to a typed return terminator. + /// The take-on-entry is what bounds the scope: `lower_control` lowers an + /// `if` / loop body's statements through `self.lower_stmt` on this same + /// `Lowerer`, so a nested statement always observes `false` and a `return` + /// there is rejected outright rather than having its operand lowered and + /// its control transfer dropped. Deliberately NOT copied into the nested + /// `Lowerer`s built by `lower_control` / `lower_value` (unlike + /// `pc_pinned`) for the same reason. + pub(super) inline_arm_tail_stmt: bool, } impl<'c> Lowerer<'c> { @@ -99,15 +137,76 @@ impl<'c> Lowerer<'c> { auto_calls: config.map(|cfg| cfg.auto_calls).unwrap_or(false), inline_liveness_prebuild: Vec::new(), dispatch_tainted_reason: None, + body_failure_reason: None, + nested_failure_reasons: Vec::new(), opcode_var_name: None, in_dispatch_arm_body: false, dispatch_loop_label: None, pc_pinned: false, + inline_arm_tail_stmt: false, }; this.install_vable_input_binding(); this } + /// Consume the recorded refusal reason, falling back to the historical + /// generic string when a refusal site has not been taught to record one. + /// + /// The fallback is deliberately the old wording: a site that still reports + /// it is one this change has not reached, and keeping the exact string + /// makes those sites greppable rather than disguising them as classified. + pub(super) fn take_body_failure_reason(&mut self) -> String { + let mut reasons: Vec = Vec::new(); + // The parent's own refusals first, so the head is unchanged by + // propagation: a child reason can extend the string, never re-head it. + if let Some(existing) = self.body_failure_reason.take() { + reasons.extend(existing.split(REFUSAL_SEPARATOR).map(str::to_string)); + } + for carried in std::mem::take(&mut self.nested_failure_reasons) { + reasons.extend(carried.split(REFUSAL_SEPARATOR).map(str::to_string)); + } + // Same dedup rule the single-entry path uses, applied across the join: + // a child re-visits statements the parent also walked, so without this + // one blocker can appear twice and a count-based reader would take the + // repeat for a second mechanism. + let mut seen: Vec<&str> = Vec::new(); + let mut joined = String::new(); + for reason in &reasons { + if seen.contains(&reason.as_str()) { + continue; + } + seen.push(reason); + if !joined.is_empty() { + joined.push_str(REFUSAL_SEPARATOR); + } + joined.push_str(reason); + } + if joined.is_empty() { + // Deliberately the old wording — see this function's doc. + "arm body could not be lowered to a sub-JitCode".to_string() + } else { + joined + } + } + + /// Carry a refused child lowerer's diagnosis into this one. + /// + /// Call at every site that discards a child after it returned `None`. The + /// `?` there propagates the failure faithfully and drops the reason with + /// the child, which is the whole defect: control flow stays correct while + /// the diagnosis is lost, so the arm reports only its outermost refusal and + /// the statement that actually needs a lowering rule is never named. + /// + /// Transitive on purpose — a child's own carried reasons come up too, so + /// depth does not decide whether a blocker is reportable. + pub(super) fn absorb_nested_failure(&mut self, nested: &mut Lowerer<'_>) { + if let Some(reason) = nested.body_failure_reason.take() { + self.nested_failure_reasons.push(reason); + } + self.nested_failure_reasons + .append(&mut nested.nested_failure_reasons); + } + pub(super) fn install_vable_input_binding(&mut self) { let Some(config) = self.config else { return; @@ -145,6 +244,26 @@ impl<'c> Lowerer<'c> { reg } + /// Allocate the ident for a new label. + /// + /// The counter is macro-side bookkeeping only — the runtime label comes + /// from the `__builder.new_label()` statement `emit_aux` pushes beside the + /// ident — so a gap in it costs nothing, right up to `u16::MAX`, where + /// `saturating_add` stops moving and every further allocation hands back + /// the same ident: labels then alias instead of the counter panicking. + /// + /// Rolling `next_label` back is sound only where the statements are + /// truncated with it. The ident is already bound by the emitted + /// `let __jit_label_N = __builder.new_label();`, so restoring the counter + /// while that statement stands re-issues a live ident: the second `let` + /// shadows the first, uses written before it bind to one runtime label and + /// uses after it to another, and the first can be created without ever + /// being marked. Every restore site pairs the two writes for that reason. + /// + /// A lowerer that fails below its first `alloc_label` with the labels + /// already consumed therefore leaves the counter alone; it is put back by + /// `try_inline_dispatch_arm`, which discards the statement stream in the + /// same block. pub(super) fn alloc_label(&mut self) -> syn::Ident { let label = self.next_label; self.next_label = self.next_label.saturating_add(1); diff --git a/majit/majit-macros/src/jit_interp/mod.rs b/majit/majit-macros/src/jit_interp/mod.rs index 5b3cbdcd3d8..cc729fe001e 100644 --- a/majit/majit-macros/src/jit_interp/mod.rs +++ b/majit/majit-macros/src/jit_interp/mod.rs @@ -65,6 +65,16 @@ pub struct JitInterpConfig { pub auto_calls: bool, /// Optional structured green-key expressions for marker rewrite. pub greens: Vec, + /// Whether the attribute spelled a `greens` key at all, as opposed to + /// declaring `greens = []`. + /// + /// `greens` alone cannot answer that: both spellings produce an empty + /// `Vec`, and the merge-point refusal needs to tell them apart. An + /// omitted key is an author who has not considered greens; `greens = []` + /// is an author who has, and says so — the encoded merge point is + /// identical either way, so this is the only place the difference + /// survives. + pub greens_declared: bool, /// Slice (audit Issue #6) — explicit red declarations for the /// dispatch JitCode `BC_JIT_MERGE_POINT` payload. RPython /// `jtransform.py:1700 make_three_lists(op.args[2+num_green_args:])` @@ -707,6 +717,11 @@ impl Parse for JitInterpConfig { )); } + // Read the presence of the key BEFORE the `Option` is collapsed: + // `unwrap_or_default()` below maps an absent `greens` and an explicit + // `greens = []` onto the same empty `Vec`, and downstream there is + // nothing left to recover the difference from. + let greens_declared = greens.is_some(); let greens_specs = greens.unwrap_or_default(); let (green_exprs, green_type_tags): (Vec, Vec>) = greens_specs @@ -721,6 +736,7 @@ impl Parse for JitInterpConfig { calls, auto_calls: auto_calls.unwrap_or(false), greens: green_exprs, + greens_declared, reds: reds.unwrap_or_default(), green_type_tags, virtualizable_decl, @@ -1234,14 +1250,31 @@ fn parse_helpers_list(input: ParseStream) -> syn::Result> { } /// Reject a plain `[int]` state-array that is stored-to inside the traced -/// loop. Such an array is *loop-carried*: its elements live only in trace -/// registers and are NOT restored to the array on a guard deopt — they read -/// back as the pre-loop value, silently producing a wrong result. The -/// supported mechanism for a mutated, loop-carried array is `[int; virt]`, -/// whose stores write through to the heap-backing `Vec` that the deopt path -/// reads directly. A plain `[int]` array that is only *read* in the loop (or -/// only written before it) is loop-invariant and stays valid, so the check +/// loop. Such an array is *loop-carried*, and the observable — measured on +/// `majit/examples/tinyframe` with this refusal disabled — is that the element +/// does not read back its heap value: three committed value tests answer 0 +/// where the program answers 40, 40 and 5, and they answer 0 whatever the +/// array held, including when the register is seeded through the mainloop's +/// own input ahead of every candidate arming pc. The supported declaration for +/// a mutated, loop-carried array is `[int; virt]`, which answers correctly on +/// the same fixtures. A plain `[int]` array that is only *read* in the loop +/// (or only written before it) is loop-invariant and stays valid, so the check /// fires solely on stores reached by the dispatch loop the lowerer traces. +/// +/// Do not read the refusal as asking for a restore path. In the measured +/// subject the trace carries no array operation at all — the recorded body is +/// `IntAdd, IntGt, GuardTrue, GuardFutureCondition, Jump`, the register file +/// is traced as loop-carried inputargs, and nothing lowers to a state-array +/// load or store — so there is no array write for a deopt to fail to restore. +/// What the 0 actually is remains UNMEASURED: the `[int; virt]` arm was never +/// logged beside the plain one, so the two op inventories and the two guards' +/// resume payloads have never been compared. +/// +/// `validate_rejects_loop_carried_plain_array`, `validate_accepts_virt_array` +/// and `validate_accepts_read_only_plain_array` grade *which declarations this +/// function refuses*; none of them grades whether the refused declaration +/// miscompiles. This is a selector with no classifier, and the readback above +/// is the first witness it has ever had. fn validate_state_fields(config: &JitInterpConfig, func: &ItemFn) -> syn::Result<()> { let Some(sf) = &config.state_fields else { return Ok(()); @@ -1270,10 +1303,10 @@ fn validate_state_fields(config: &JitInterpConfig, func: &ItemFn) -> syn::Result span, format!( "state field `{name}` is a plain `[int]` array stored inside the traced loop, \ - so it is loop-carried. A plain `[int]` element is held in a trace register and \ - is not restored to the array when a guard deopts (it reads back as the pre-loop \ - value, silently miscompiling). Declare it as `[int; virt]`: a virtualizable \ - array writes through to the heap-backing Vec that the deopt path reads directly." + so it is loop-carried. A loop-carried plain `[int]` element does not read back \ + its heap value: the traced loop answers as if the element were 0, whatever the \ + array holds, silently miscompiling. Declare it as `[int; virt]`, which answers \ + correctly on the same program." ), )); } @@ -1437,7 +1470,7 @@ fn generate_merge_wrapper(config: &JitInterpConfig, func: &ItemFn) -> TokenStrea // the dispatcher's BC_CALL_ASSEMBLER_* path can route // through the production `Arc` rather // than the synth-Arc `_by_number_typed` fallback. The - // helper also hands over the #184 recursive-call seams + // helper also hands over the recursive-call recursive-call seams // (green-key target resolver, inline decision, and the // `execute_token_raw` concrete executor) wired to the // production warmstate / backend. @@ -1839,12 +1872,15 @@ fn transform_function(config: &JitInterpConfig, func: &ItemFn) -> TokenStream { } // Rewrite the function body, replacing marker macros + let finish_return = finish_return_for(&sig.output); let body = rewrite_body( &block, &merge_fn_name, &config.greens, + config.greens_declared, &config.green_type_tags, config.recursive_entry.as_ref(), + finish_return.as_ref(), ); quote! { @@ -1855,13 +1891,94 @@ fn transform_function(config: &JitInterpConfig, func: &ItemFn) -> TokenStream { } } +/// How a compiled run that ended in FINISH is returned from the portal. +/// +/// `compile.py:623-638` marks a FINISH descr `final_descr = True`: the traced +/// function has RETURNED, and upstream unwinds the portal by raising +/// `jitexc.DoneWithThisFrame*`. `back_edge*` reports `Option`, which +/// has no variant for that, so the result travels out of band in the driver +/// (`take_back_edge_finish_*`) and the expansion turns it back into the portal's +/// own `return`. Which projection applies is decided by the portal's declared +/// return type. +#[derive(Clone, Copy)] +enum FinishReturnKind { + Int, + Float, +} + +#[derive(Clone)] +struct FinishReturn { + kind: FinishReturnKind, + /// The portal's return type, when it is not the projection's own word type + /// (`i64` / `f64`) and therefore needs a cast. `None` means no cast. + cast_to: Option, +} + +/// Classify a portal's return type into a FINISH projection, or `None` when the +/// expansion cannot build that type out of a `majit_ir::Value` (for example a +/// portal returning `String`). `None` leaves the back edge exactly as it was: +/// the driver still returns `Some(target_pc)` on FINISH for those portals. +fn finish_return_for(output: &syn::ReturnType) -> Option { + let syn::ReturnType::Type(_, ty) = output else { + return None; + }; + let syn::Type::Path(type_path) = ty.as_ref() else { + return None; + }; + if type_path.qself.is_some() { + return None; + } + let segment = type_path.path.segments.last()?; + if !segment.arguments.is_none() { + return None; + } + let (kind, is_word_type) = match segment.ident.to_string().as_str() { + "i64" => (FinishReturnKind::Int, true), + "i8" | "i16" | "i32" | "isize" | "u8" | "u16" | "u32" | "u64" | "usize" => { + (FinishReturnKind::Int, false) + } + "f64" => (FinishReturnKind::Float, true), + "f32" => (FinishReturnKind::Float, false), + _ => return None, + }; + Some(FinishReturn { + kind, + cast_to: (!is_word_type).then(|| ty.as_ref().clone()), + }) +} + +impl FinishReturn { + /// The statement that drains the driver's FINISH latch and returns it as the + /// portal's result. Emitted immediately after the `back_edge*` call whose + /// compiled run may have set it, and BEFORE that call's `Some(resume_pc)` is + /// used: on FINISH that pc is the back edge, so resuming there would re-run + /// the loop the compiled run already completed. + fn drain(&self, driver_expr: &Expr) -> TokenStream { + let take = match self.kind { + FinishReturnKind::Int => quote! { take_back_edge_finish_int }, + FinishReturnKind::Float => quote! { take_back_edge_finish_float }, + }; + let returned = match &self.cast_to { + Some(ty) => quote! { __finish_value as #ty }, + None => quote! { __finish_value }, + }; + quote! { + if let Some(__finish_value) = #driver_expr.#take() { + return #returned; + } + } + } +} + /// Rewrite function body: replace jit_merge_point!() and can_enter_jit!() calls. fn rewrite_body( block: &syn::Block, merge_fn_name: &Ident, default_greens: &[Expr], + greens_declared: bool, default_green_type_tags: &[Option], recursive_entry: Option<&Path>, + finish_return: Option<&FinishReturn>, ) -> TokenStream { use syn::visit_mut::VisitMut; @@ -2054,8 +2171,32 @@ fn rewrite_body( } } + /// `interp_jit.py:117-119` `can_enter_jit(next_instr=jumpto, pycode=...)`: + /// the greens a back edge hands the driver name the interpreter state at the + /// JUMP TARGET, not at the back-edge instruction. `jumpto` is passed for + /// `next_instr` — there is no second, current-position green. + /// + /// The `#[jit_interp]` marker form takes the target positionally and fills + /// the greens from the declaration, so a declared position green (`pc`) + /// would otherwise be read at the back edge. Substituting the target for + /// it is what makes `back_edge_structured`'s key equal the key the merge + /// point at that target derives (`TraceCtx::merge_point_green_key_hash`) — + /// which is the key `compile_loop` files the loop under + /// (pyjitpl.py:3183-3189 `original_boxes[:num_green_args]`). Without it the + /// loop is stored under a key nothing enters: the interpreter's + /// `has_compiled_loop` misses forever, every back edge re-arms tracing, and + /// the compiled artifact is never executed. + fn subst_target_for_pc<'a>(expr: &'a Expr, pc: &Expr, target: &'a Expr) -> &'a Expr { + if quote!(#expr).to_string() == quote!(#pc).to_string() { + target + } else { + expr + } + } + fn green_key_expr( target: &Expr, + pc: &Expr, greens: &[Expr], green_type_tags: &[Option], ) -> Option { @@ -2085,16 +2226,57 @@ fn rewrite_body( .enumerate() .map(|(i, expr)| { let tag = green_type_tags.get(i).copied().flatten(); - emit_green_repr(expr, tag) + emit_green_repr(subst_target_for_pc(expr, pc, target), tag) + }) + .collect(); + // Each green's `(i64, GreenType)` pair is bound to a local + // ONCE — the green expressions must not be re-evaluated, so + // the deferred key builder below closes over these `Copy` + // locals rather than over the expressions. + let all_reprs: Vec = + std::iter::once(quote! { <_ as majit_ir::GreenAsI64>::__green_repr(#target) }) + .chain(green_reprs) + .collect(); + let slots: Vec = (0..all_reprs.len()) + .map(|i| { + syn::Ident::new(&format!("__green_slot{i}"), proc_macro2::Span::call_site()) }) .collect(); + let bind: Vec = slots + .iter() + .zip(&all_reprs) + .map(|(slot, repr)| quote! { let #slot = #repr; }) + .collect(); + // `get_uhash` unrolled over the declared greens — the count and + // types are known here, exactly as upstream's + // `green_args_name_spec` is fixed per JitCell class at + // translation time (warmstate.py:584-593). No `values` / + // `types` vectors are built to hash. + let fold: Vec = slots + .iter() + .map(|slot| { + quote! { + __green_hash = + majit_ir::green_uhash_step(__green_hash, #slot.1, #slot.0); + } + }) + .collect(); + let values: Vec = slots.iter().map(|slot| quote! { #slot.0 }).collect(); + let types: Vec = slots.iter().map(|slot| quote! { #slot.1 }).collect(); + // `(hash, make_key)`: the hash is needed on every back edge, the + // typed key only where a cell is installed. Some(quote! { { - let (__values, __types): (Vec, Vec) = vec![ - <_ as majit_ir::GreenAsI64>::__green_repr(#target), - #(#green_reprs),* - ].into_iter().unzip(); - majit_ir::GreenKey::with_types(__values, __types) + #(#bind)* + let mut __green_hash: u64 = majit_ir::GREEN_UHASH_SEED; + #(#fold)* + ( + __green_hash, + move || majit_ir::GreenKey::with_types( + ::std::vec![#(#values),*], + ::std::vec![#(#types),*], + ), + ) } }) } @@ -2103,8 +2285,13 @@ fn rewrite_body( struct MarkerRewriter { merge_fn_name: Ident, default_greens: Vec, + /// Whether the attribute spelled a `greens` key. `default_greens` being + /// empty does not answer this: an omitted key and `greens = []` both + /// arrive here as an empty `Vec`, and only one of them is a mistake. + greens_declared: bool, default_green_type_tags: Vec>, recursive_entry: Option, + finish_return: Option, } impl VisitMut for MarkerRewriter { @@ -2271,6 +2458,79 @@ fn rewrite_body( } } }; + // `; state` selects the single-executor close: the native + // loop resumes at the green pc the merge point reports. A + // driver with no declared greens reports none, and the + // failure is silent in release — + // `run_pending_abort_blackhole` reaches + // `ContinueRunningNormally` with an empty `green_int` + // (`jitdriver.rs:2041-2059`), sets `single_pass_finish`, and + // the dispatch loop ends after exactly `threshold` passes, + // returning a truncated result that still looks plausible. + // The `debug_assert!` naming that contract is compiled out + // in release, so only the ungated `eprintln!` beside it ever + // fires. Refuse at compile time rather than truncate at + // run time. + // + // Wrapped in a block rather than replacing the expansion: + // `parse2` below wants exactly one `Stmt`, and keeping the + // original tokens means the error reported is this one + // instead of a cascade of unresolved names. + // A missing `greens` key is refused below. An explicit + // `greens = []` remains supported, so the diagnostic also + // explains the degenerate trace shape of that deliberate case. + if self.default_greens.is_empty() { + // This is emitted once per macro expansion because an + // explicitly empty green set cannot provide a resume pc. + let krate = std::env::var("CARGO_PKG_NAME").unwrap_or_else(|_| { + "".to_string() + }); + let site = self + .merge_fn_name + .to_string() + .strip_prefix("__merge_") + .map(str::to_owned) + .unwrap_or_else(|| self.merge_fn_name.to_string()); + eprintln!( + "warning: [{}] `fn {}` expands with an empty green set on its \ + `#[jit_interp]` attribute. Its merge point (`{}`) reports no \ + concrete pc, so the tracing walk never advances and every \ + trace attempt re-records one guard triple. Where the \ + attempts survive, that reaches `trace_limit * 4/5` and \ + ships a segmented trace whose compiled loop runs zero \ + iterations; where an arm on the loop's back edge is a \ + degraded stub, they abort inside it and nothing compiles \ + at all. Both outcomes are degenerate. The remedy is to \ + declare the greens the loop is keyed on, e.g. \ + `greens = [pc, program]` — that does not by itself make \ + the back edge lower, which is separate work. If the emptiness is deliberate and spelled \ + `greens = []`, this line is the expected report for it and \ + there is nothing to change.", + krate, + site, + quote!(#driver) + ); + } + let new_tokens = if !self.greens_declared { + let driver_name = quote!(#driver).to_string(); + // State the required metadata rather than enumerating + // merge-point spellings, which may grow independently. + let msg = format!( + "`jit_merge_point!` on `{driver_name}` requires a `greens` key \ + on the enclosing `#[jit_interp]` attribute, and this attribute \ + has none. A merge point resumes the interpreter at the green \ + pc it reports; with no greens declared it reports none, so \ + the tracing walk never advances and what the JIT ships is \ + degenerate — a compiled loop that runs zero iterations, or \ + nothing at all. Declare the greens the loop is keyed on, e.g. \ + `greens = [pc, program]`. If the empty set is deliberate — a \ + fixture grading the empty-greens encoding — spell it \ + `greens = []`, which is supported and is not refused here." + ); + quote! { { compile_error!(#msg); #new_tokens } } + } else { + new_tokens + }; *stmt = syn::parse2(new_tokens).expect("failed to parse merge_point replacement"); } @@ -2411,24 +2671,44 @@ fn rewrite_body( (args.greens.clone(), self.default_green_type_tags.clone()) }; // compile.py:711 parity: back_edge returns - // Some(resume_pc) on guard failure (blackhole - // resume) or FINISH (loop header re-entry). + // Some(resume_pc) on a guard failure (blackhole + // resume) or a back-edge JUMP. // state.restore_values already restores all // state fields (including stacksize) from the // compiled loop's exit state, so no explicit // stacksize reset is needed here. - let back_edge: TokenStream = if let Some(green_key) = - green_key_expr(target_expr, &greens, &green_type_tags) + // + // A run that ended in FINISH is NOT a resume: the + // traced function returned, so the driver's FINISH + // latch is drained first and turned into this + // portal's own `return`. The resume_pc offered + // alongside it is the back edge, and taking it + // would re-run the loop the compiled run already + // completed — once per remaining iteration. + let finish_drain: TokenStream = self + .finish_return + .as_ref() + .map(|finish_return| finish_return.drain(driver_expr)) + .unwrap_or_default(); + let call: TokenStream = if let Some(green_key) = + green_key_expr(target_expr, &pc_expr, &greens, &green_type_tags) { quote! { - if let Some(__resume_pc) = #driver_expr.back_edge_structured(#green_key, #target_expr, #state_expr, #env_expr, #pre_run_expr) { - #pc_expr = __resume_pc; - continue; + { + let (__green_hash, __green_key) = #green_key; + #driver_expr.back_edge_structured(__green_hash, __green_key, #target_expr, #state_expr, #env_expr, #pre_run_expr) } } } else { quote! { - if let Some(__resume_pc) = #driver_expr.back_edge(#target_expr, #state_expr, #env_expr, #pre_run_expr) { + #driver_expr.back_edge(#target_expr, #state_expr, #env_expr, #pre_run_expr) + } + }; + let back_edge: TokenStream = quote! { + { + let __back_edge_resume = #call; + #finish_drain + if let Some(__resume_pc) = __back_edge_resume { #pc_expr = __resume_pc; continue; } @@ -2456,8 +2736,10 @@ fn rewrite_body( let mut rewriter = MarkerRewriter { merge_fn_name: merge_fn_name.clone(), default_greens: default_greens.to_vec(), + greens_declared, default_green_type_tags: default_green_type_tags.to_vec(), recursive_entry: recursive_entry.cloned(), + finish_return: finish_return.cloned(), }; rewriter.visit_block_mut(&mut cloned_block); @@ -2732,7 +3014,7 @@ mod tests { } /// A plain `[int]` array stored inside the traced loop must be rejected: - /// the loop-carried element is lost on a guard deopt. + /// the loop-carried element does not read back its heap value. #[test] fn validate_rejects_loop_carried_plain_array() { let func: ItemFn = parse_quote! { @@ -2810,4 +3092,178 @@ mod tests { ) .expect("read-only plain [int] array must be accepted"); } + + /// A merge point whose enclosing attribute omits the `greens` key must be + /// refused at expansion time. + /// + /// The second arm is the whole point of the test, not a bonus assertion. + /// The two expansions differ by exactly one attribute entry, so a fixture + /// that stops expanding for some unrelated reason emits `compile_error!` + /// for *both* — and only the `with_greens` assertion notices. Checked on + /// its own, the first assertion passes just as happily on a typo'd + /// attribute as on a working guard, which would make this an oracle that + /// cannot fail. + /// + /// THE THIRD ARM IS NOT A THIRD COPY OF THE SECOND. `greens = []` and + /// `greens = [pc, program]` are both "not refused", but they are not + /// refused for different reasons: the second declares greens, the third + /// declares that there are none. The refusal reads the KEY, not the value, + /// and the third arm is the only thing in this crate that says so — drop + /// it and re-keying the guard on `default_greens.is_empty()` passes every + /// remaining assertion here while making the empty-greens fixtures in + /// `majit-metainterp` unrepresentable. + #[test] + fn merge_point_without_a_greens_key_is_refused() { + fn expand(greens: proc_macro2::TokenStream) -> String { + let config: JitInterpConfig = syn::parse2(quote! { + state = S, + env = Bytecode, + #greens + state_fields = { acc: int }, + }) + .expect("fixture attribute must parse"); + let func: ItemFn = parse_quote! { + fn mainloop(program: &Bytecode, threshold: u32) -> i64 { + let mut driver: majit_metainterp::JitDriver = + majit_metainterp::JitDriver::new(threshold); + let mut pc: usize = 0; + let mut state = S { acc: 0 }; + while pc < program.len() { + jit_merge_point!(driver, program, pc; state); + let op = program[pc]; + pc += 1; + match op { + 0 => { state.acc += 1; } + _ => break, + } + } + state.acc + } + }; + transform_jit_interp(config, func).to_string() + } + + let without_greens = expand(quote! {}); + assert!( + without_greens.contains("compile_error"), + "an attribute with no `greens` key must expand to a compile_error; \ + without one the merge point reports no concrete pc and the tracing \ + walk never advances. Expansion was:\n{without_greens}" + ); + assert!( + without_greens.contains("greens"), + "the refusal must name `greens` so the reader knows what to add:\n\ + {without_greens}" + ); + + let with_greens = expand(quote! { greens = [pc, program], }); + assert!( + !with_greens.contains("compile_error"), + "the same interpreter with `greens = [pc, program]` must expand \ + cleanly. A compile_error here means the fixture itself is broken, \ + which would make the assertions above vacuous. Expansion was:\n\ + {with_greens}" + ); + + let empty_greens = expand(quote! { greens = [], }); + assert!( + !empty_greens.contains("compile_error"), + "`greens = []` is a DECLARATION of emptiness and must expand \ + cleanly. The refusal keys on the absence of the key, not on the \ + emptiness of the list: `majit-metainterp`'s dispatch-IR fixtures \ + spell `greens = []` precisely to grade the empty-greens encoding \ + (`num_green_args == 0`, and no `BC_*_GUARD_VALUE` in the prefix, \ + mirroring `jtransform.py:1693-1714 promote_greens`), so refusing \ + this spelling deletes the only tests of the state the refusal \ + above is about. Expansion was:\n{empty_greens}" + ); + } + + /// The refusal above reaches EVERY merge-point form, the bare + /// `jit_merge_point!()` included. + /// + /// THIS ASSERTION USED TO RUN THE OTHER WAY, and the inversion is the + /// record of a real change rather than a tidy-up. The guard was + /// `args.state.is_some() && default_greens.is_empty()`, and the first + /// conjunct confined it to a form a greens-less driver cannot be written + /// in — so it never fired on a crate that needed it. This test pinned that + /// gap, said in its own text that dropping the conjunct must invert the + /// second assertion, and the conjunct is now gone. Anyone re-narrowing the + /// guard reds here and has to invert it back deliberately. + /// + /// A `contains` assertion arms itself and a `!contains` one does not, + /// so the two arms are no longer symmetric in what they prove. Both arms + /// now expect the refusal, which means a fixture that stopped reaching + /// this handler entirely would fail LOUDLY rather than pass quietly — the + /// failure direction is the safe one. The `is_tracing()` count is kept on + /// the bare arm anyway: it reads whether the bare form was processed at + /// all, independently of what the guard decided, and it is the only thing + /// here that can tell "refused" from "never expanded" if the refusal ever + /// moves again. + /// + /// Do not fold this into the test above as a duplicate — that one varies + /// the greens with the form fixed, this one varies the form with the + /// greens fixed, and neither substitutes for the other. + #[test] + fn no_greens_refusal_reaches_every_merge_point_form() { + fn expand(merge_point: proc_macro2::TokenStream) -> String { + let config: JitInterpConfig = syn::parse2(quote! { + state = S, + env = Bytecode, + state_fields = { acc: int }, + }) + .expect("fixture attribute must parse"); + let func: ItemFn = parse_quote! { + fn mainloop(program: &Bytecode, threshold: u32) -> i64 { + let mut driver: majit_metainterp::JitDriver = + majit_metainterp::JitDriver::new(threshold); + let mut pc: usize = 0; + let mut state = S { acc: 0 }; + while pc < program.len() { + #merge_point + let op = program[pc]; + pc += 1; + match op { + 0 => { state.acc += 1; } + _ => break, + } + } + state.acc + } + }; + transform_jit_interp(config, func).to_string() + } + + let state_form = expand(quote! { jit_merge_point!(driver, program, pc; state); }); + assert!( + state_form.contains("compile_error"), + "the `; state` arm is this test's positive control: with no greens \ + it must still be refused, otherwise the bare-form assertion below \ + is measuring a detector that is not running. Expansion was:\n\ + {state_form}" + ); + + let bare_form = expand(quote! { jit_merge_point!(); }); + assert_eq!( + bare_form.matches("is_tracing").count(), + 1, + "arming for the assertion below, which is a `!contains` and cannot \ + arm itself. `is_tracing()` is emitted from exactly two places in \ + this crate, both of them the merge-point handler's own arms, so \ + one occurrence is the fixture's one merge point having been \ + processed. A zero here means the bare form never reached the \ + handler, and the absent `compile_error` below would then be \ + measuring nothing. Expansion was:\n{bare_form}" + ); + assert!( + bare_form.contains("compile_error"), + "the bare form with no `greens` key must be refused too. This \ + assertion was inverted when the `args.state.is_some()` conjunct \ + was dropped; if it now fails, the guard has been re-narrowed to \ + some subset of the merge-point forms. Widen it back rather than \ + inverting this line — a form-dependent refusal cannot fire on the \ + crates that need it, which is what the conjunct did for as long as \ + it stood. Expansion was:\n{bare_form}" + ); + } } diff --git a/majit/majit-macros/src/jit_struct.rs b/majit/majit-macros/src/jit_struct.rs index f66709a8c75..58fe68faa2a 100644 --- a/majit/majit-macros/src/jit_struct.rs +++ b/majit/majit-macros/src/jit_struct.rs @@ -73,7 +73,9 @@ pub(crate) fn expand(_attr: TokenStream, item: TokenStream) -> TokenStream { #flag_tok, u32::MAX, false, - #idx, + // The declaration order of the struct's own named fields — a + // position this macro computes, never an unresolved claim. + Some(#idx), ); } }); diff --git a/majit/majit-macros/src/lib.rs b/majit/majit-macros/src/lib.rs index aeffd51302b..92729392565 100644 --- a/majit/majit-macros/src/lib.rs +++ b/majit/majit-macros/src/lib.rs @@ -375,10 +375,35 @@ fn helper_call_target_fn_name(path: &Path) -> syn::Result { /// consts: those attributes already emit a `__majit_call_policy_` /// associated fn next to the method, so the surrounding `impl` is /// necessarily inherent (a trait impl would reject the foreign -/// associated fn at compile time). `jit_elidable` (a pure pass-through), -/// `look_inside`, and `jit_loop_invariant` emit no policy fn and so stay -/// free-fn-only, to avoid placing a foreign associated const inside a -/// trait impl. +/// associated fn at compile time). `jit_elidable` (a pure pass-through) +/// and `look_inside` emit no policy fn and so stay free-fn-only, to avoid +/// placing a foreign associated const inside a trait impl. +/// +/// `jit_loop_invariant` is also free-fn-only here and NOT for that +/// reason. It is expanded by `expand_call_surface_attr`, which calls +/// `emit_helper_policy_fn` unconditionally and only afterwards consults +/// this table, so a policy fn *is* emitted beside it — carrying an +/// `UNSUPPORTED` body when a receiver makes `emit_helper_call_target_fn` +/// decline. What justifies its `false` is not recorded; it is at worst +/// conservative, since the policy fn would already be rejected inside a +/// trait impl. Do not read that entry as "emits no policy fn". +/// +/// Which attributes emit a policy fn is decided by the call sites of +/// `emit_helper_policy_fn` and by nothing here. Derive the set from +/// them: a list restated beside them goes stale the moment an expander +/// starts or stops calling one, which is what happened to this +/// paragraph. +/// +/// Where to start looking, since the previous paragraph is useless +/// without it: the expanders calling `emit_helper_policy_fn` are +/// `expand_elidable_attribute`, `expand_dont_look_inside_attribute`, +/// `expand_call_surface_attr` and `elidable_promote`, and the spellings +/// each one covers are the `#[proc_macro_attribute]` fns that reach it +/// — several attributes share an expander, so the spellings outnumber +/// the expanders and only the wrappers name them all. That is an entry +/// point and not the authority: a fifth expander would not appear in +/// this sentence, so search for the call sites rather than trusting the +/// four named here. fn rpython_attribute_const_for( attr_name: &str, sig: &syn::Signature, @@ -815,9 +840,14 @@ fn emit_helper_policy_fn( ) -> syn::Result { let helper_name = helper_policy_fn_name(path)?; // `__majit_call_policy_*` visibility follows the user fn so - // external integration tests can read the 4-tuple's trace_target / - // concrete_target function pointers for PyPy `getfunctionptr` - // parity verification. The trailing `i32` carries the wrapper + // external integration tests can read the returned tuple's + // trace_target / concrete_target function pointers for PyPy + // `getfunctionptr` parity verification. The arity is deliberately + // not restated here: the signature below is the only statement of it + // that cannot go stale, and this comment read "4-tuple" while the + // signature returned six — an arity read off prose rather than off + // the type is how a caller ends up destructuring the wrong shape. + // The trailing `i32` carries the wrapper // callable's `_call_aroundstate_target_[1]` (`save_err`) per // `rffi.py:228`; non-`release_gil` policies emit `0i32` // (`RFFI_ERR_NONE`, `rffi.py:80`). @@ -1078,7 +1108,7 @@ pub fn jit_driver(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The JIT can eliminate calls to this function when all arguments are constants. /// `rlib/jit.py:72 elidable` sets `_elidable_function_ = True` and nothing else; -/// the flag travels here as the marker const [`rpython_attribute_const_for`] +/// the flag travels here as the marker const `rpython_attribute_const_for` /// emits, which `front/llbc_hints.rs` harvests from the extracted LLBC, and the /// constant fold reaches the separate `__majit_call_target_*` trampoline. None /// of that is a property of this function's codegen. @@ -1216,7 +1246,7 @@ fn expand_elidable_attribute(item: TokenStream, attr_name: &str) -> TokenStream /// The JIT will not trace into this function; it will be called as a black box. /// `rlib/jit.py:133-140 @dont_look_inside` — sets `_jit_look_inside_ = False` /// (line 139) and nothing else. This expansion carries that flag as the -/// `_jit_look_inside_` marker const [`rpython_attribute_const_for`] emits next +/// `_jit_look_inside_` marker const `rpython_attribute_const_for` emits next /// to the function; `front/llbc_hints.rs` harvests it out of the extracted LLBC /// and `front/mir.rs` turns it into the residual-call decision. The policy is /// therefore a property of the marker, not of the function's codegen. @@ -3112,8 +3142,8 @@ pub fn virtualizable(input: TokenStream) -> TokenStream { /// - `#[vable(frame)]` — frame pointer OpRef /// - `#[vable(field)]` — static virtualizable field OpRef /// - `#[vable(array_base)]` — array base index -/// - `#[vable(locals)]` — symbolic locals Vec -/// - `#[vable(stack)]` — symbolic stack Vec +/// - `#[vable(locals)]` — symbolic locals `Vec` +/// - `#[vable(stack)]` — symbolic stack `Vec` /// - `#[vable(local_types)]` / `#[vable(stack_types)]` — type vectors /// - `#[vable(nlocals)]` / `#[vable(valuestackdepth)]` — shape fields /// diff --git a/majit/majit-metainterp/Cargo.toml b/majit/majit-metainterp/Cargo.toml index e90b03a3d5e..e25c8c32a89 100644 --- a/majit/majit-metainterp/Cargo.toml +++ b/majit/majit-metainterp/Cargo.toml @@ -7,12 +7,7 @@ repository.workspace = true description = "Meta-interpreter and optimizer for majit JIT compiler" [features] -# A backend is mandatory (see the `compile_error!` in pyjitpl/mod.rs). Default -# to dynasm so the crate is buildable/testable on its own (`cargo test -p -# majit-metainterp`); downstream consumers inherit `default-features = false` -# from the workspace dependency and select their backend explicitly, so this -# default never leaks into a cranelift-only build. -default = ["dynasm"] +# Backend selection is mandatory and intentionally has no default. dynasm = ["dep:majit-backend-dynasm"] cranelift = ["dep:majit-backend-cranelift"] diff --git a/majit/majit-metainterp/src/blackhole.rs b/majit/majit-metainterp/src/blackhole.rs index 6217f05c91e..0e93c8c0070 100644 --- a/majit/majit-metainterp/src/blackhole.rs +++ b/majit/majit-metainterp/src/blackhole.rs @@ -63,14 +63,12 @@ impl ExceptionState { } } -// ============================================================================ // RPython blackhole.py parity: BlackholeInterpreter // // Jitcode-based blackhole execution. When a guard fails in compiled code, // resume_in_blackhole reconstructs execution frames from resume data and // runs jitcode bytecodes with concrete values, following ALL code paths // (unlike trace IR which only has the traced path). -// ============================================================================ use crate::jitcode::{self, JitArgKind, JitCode, JitCodeRuntimeExt}; use crate::pyjitpl::{MIFrame, MIFrameStack}; @@ -78,8 +76,7 @@ use crate::pyjitpl::{ call_int_function, call_ref_function, call_void_function, eval_binop_f, eval_binop_i, }; -// ── BlackholeInterpBuilder: setup_insns infrastructure ────────────── -// +// BlackholeInterpBuilder instruction setup // RPython `blackhole.py:52-103` `class BlackholeInterpBuilder` combines // pool management AND dispatch setup. pyre's existing // `BlackholeInterpBuilder` (below, at the pool management section) is the @@ -1642,9 +1639,7 @@ impl BlackholeInterpreter { } } -// ════════════════════════════════════════════════════════════════════════ // bhimpl_*_call_* family (blackhole.py:1095-1320) -// ════════════════════════════════════════════════════════════════════════ // // These methods mirror RPython's blackhole call dispatch table. Each // variant unpacks one of the three calling-convention shapes @@ -2809,12 +2804,22 @@ mod tests { } #[test] - fn state_field_layout_tlr_regs_fixed_array() { - // tlr: `a: int` (scalar 0) + `regs: [int]` (fixed array, here len 8). + fn state_field_layout_one_scalar_then_one_fixed_array() { + // One `int` scalar (slot 0) followed by one fixed array of 8 cells. + // + // Deliberately synthetic, and deliberately unattributed: the layout is + // built from counts, so this test never reads any crate's declaration + // and cannot notice one changing. Naming a crate here would assert a + // shape nothing derives — the previous name and comment claimed this + // was tlr's `a: int` + `regs: [int]`, while tlr declares + // `regs: [int; virt]`, a different `StateFieldKind` on a different + // code path. That false attribution outlived the shape it described + // and was read as evidence that fixed arrays are common in the corpus; + // they are not, and no example crate declares one. let layout = StateFieldLayout::new(1, vec![8], 0, 0); assert_eq!(layout.total_slots(), 1 + 8); assert_eq!(layout.scalar_slot(0), 0); - // regs[0..8] occupy slots 1..9. + // The array's 8 cells occupy slots 1..9. assert_eq!(layout.array_elem_slot(0, 0), 1); assert_eq!(layout.array_elem_slot(0, 7), 8); } @@ -3252,10 +3257,8 @@ mod tests { assert_eq!(exec_binop(OpCode::UintMulHigh, -1, 1), 0); } - // ══════════════════════════════════════════════════════════════════ // Executor edge-case parity tests // Ported from rpython/jit/metainterp/test/test_executor.py - // ══════════════════════════════════════════════════════════════════ // ── Integer overflow boundaries ── @@ -3416,11 +3419,9 @@ mod tests { } } - // ================================================================ // Tests for jitcode-based BlackholeInterpreter. // Upstream parity anchor: `rpython/jit/metainterp/test/test_blackhole.py` // plus the dispatch-loop setup in `rpython/jit/metainterp/blackhole.py`. - // ================================================================ mod bh_interp_tests { use super::super::*; @@ -4908,8 +4909,7 @@ mod tests { } } -// ── bhimpl_* methods (RPython blackhole.py:452+) ──────────────────── -// +// Blackhole operation handlers (`blackhole.py:452+`) // RPython defines each bhimpl_* as a static method decorated with // @arguments("i", "i", returns="i") etc. The handler closure generated // by _get_method decodes args from the bytecode stream and calls the @@ -4919,8 +4919,7 @@ mod tests { // BhOpcodeHandler wrappers. The handler decodes operands, calls the // bhimpl fn, stores the result, and returns the updated position. -// ── handler generators for common patterns ────────────────────────── - +// Handler generators for common argument patterns /// Decode pattern `@arguments("i", "i", returns="i")` — argcodes `"ii>i"`. /// /// Read 2 int-register indices, call bhimpl fn, write result, advance by 3. @@ -4973,8 +4972,7 @@ macro_rules! bhhandler_iii_i { }; } -// ── bhimpl methods (line-by-line from RPython blackhole.py) ───────── - +// Blackhole operations ported from `blackhole.py` /// blackhole.py:454-456 `bhimpl_int_same_as`. fn bhimpl_int_same_as(a: i64) -> i64 { a @@ -5474,7 +5472,7 @@ impl StateFieldLayout { } } - /// Like [`new`] but with ref-typed scalar fields in the ref bank, + /// Like [`Self::new`] but with ref-typed scalar fields in the ref bank, /// starting at `ref_scalar_base`. pub fn with_ref_scalars( num_scalars: usize, @@ -5793,11 +5791,10 @@ bhhandler_ii_i!(handler_int_ne, bhimpl_int_ne); bhhandler_ii_i!(handler_int_gt, bhimpl_int_gt); bhhandler_ii_i!(handler_int_ge, bhimpl_int_ge); -// ── control flow + copy handlers ───────────────────────────────────── - // blackhole.py:638-640 `bhimpl_int_copy(a): return a` — @arguments("i", returns="i"). // Decoded as `i>i` (same as int_same_as). Already have handler_int_same_as. // Wire as alias. +// Control flow and copy handlers bhhandler_i_i!(handler_int_copy, bhimpl_int_same_as); // `int_copy/c>i` — `c`-argcode source: `int_copy` is in USE_C_FORM @@ -6035,11 +6032,10 @@ fn handler_void_return( Err(DispatchError::LeaveFrame) } -// ── float bhimpl methods (RPython blackhole.py:676-808) ───────────── - // RPython stores floats as longlong (i64 bits). pyre stores f64 in // registers_f directly. The bhimpl methods work on f64 values. +// Float operations (`blackhole.py:676-808`) fn bhimpl_float_neg(a: f64) -> f64 { -a } @@ -6361,8 +6357,7 @@ bhhandler_ff_i!(handler_float_ne, |a: f64, b: f64| a != b); bhhandler_ff_i!(handler_float_gt, |a: f64, b: f64| a > b); bhhandler_ff_i!(handler_float_ge, |a: f64, b: f64| a >= b); -// ── unsigned comparison bhimpl (RPython blackhole.py:571-582) ──────── - +// Unsigned comparisons (`blackhole.py:571-582`) fn bhimpl_uint_lt(a: i64, b: i64) -> i64 { ((a as u64) < (b as u64)) as i64 } @@ -6529,8 +6524,7 @@ fn bhimpl_goto(target: usize) -> usize { target } -// ── ref operations (RPython blackhole.py:584-610) ─────────────────── - +// Reference operations (`blackhole.py:584-610`) fn bhimpl_ptr_eq(a: i64, b: i64) -> i64 { (a == b) as i64 } @@ -6613,12 +6607,12 @@ fn handler_float_return( Err(DispatchError::LeaveFrame) } -// ── guard_value — no-op in blackhole (blackhole.py:648-656) ───────── +// Guard-value no-ops (`blackhole.py:648-656`) bhhandler_i_v!(handler_int_guard_value, bhimpl_int_guard_value); bhhandler_r_v!(handler_ref_guard_value, bhimpl_ref_guard_value); bhhandler_f_v!(handler_float_guard_value, bhimpl_float_guard_value); -// ── push/pop (blackhole.py:661-679) ───────────────────────────────── +// Push and pop (`blackhole.py:661-679`) bhhandler_self_i_v!(handler_int_push, bhimpl_int_push); bhhandler_self_r_v!(handler_ref_push, bhimpl_ref_push); bhhandler_self_f_v!(handler_float_push, bhimpl_float_push); @@ -6626,20 +6620,19 @@ bhhandler_self_v_i!(handler_int_pop, bhimpl_int_pop); bhhandler_self_v_r!(handler_ref_pop, bhimpl_ref_pop); bhhandler_self_v_f!(handler_float_pop, bhimpl_float_pop); -// ── record_exact_class/value — no-op (blackhole.py:616-636) ───────── +// Exact-class and exact-value recording no-ops (`blackhole.py:616-636`) bhhandler_ri_v!(handler_record_exact_class, bhimpl_record_exact_class); bhhandler_rr_v!(handler_record_exact_value_r, bhimpl_record_exact_value_r); bhhandler_ii_v!(handler_record_exact_value_i, bhimpl_record_exact_value_i); -// ── cast operations (blackhole.py:800-831) ────────────────────────── +// Cast operations (`blackhole.py:800-831`) bhhandler_f_i!(handler_cast_float_to_int, bhimpl_cast_float_to_int); bhhandler_i_f!(handler_cast_int_to_float, bhimpl_cast_int_to_float); -// ── int_signext (blackhole.py:566-569) ────────────────────────────── +// Integer sign extension (`blackhole.py:566-569`) bhhandler_ii_i!(handler_int_signext, bhimpl_int_signext); -// ── overflow ops (blackhole.py:478-497) ───────────────────────────── - +// Overflow operations (`blackhole.py:478-497`) /// blackhole.py:478-483 `bhimpl_int_add_jump_if_ovf(label, a, b)`. /// On overflow: returns `(None, target)` so the handler jumps to label. /// On success: returns `(Some(sum), pc)` so the handler stores sum at the @@ -6694,8 +6687,7 @@ bhhandler_ovf_jump_ii!(handler_int_add_jump_if_ovf, bhimpl_int_add_jump_if_ovf); bhhandler_ovf_jump_ii!(handler_int_sub_jump_if_ovf, bhimpl_int_sub_jump_if_ovf); bhhandler_ovf_jump_ii!(handler_int_mul_jump_if_ovf, bhimpl_int_mul_jump_if_ovf); -// ── misc simple ops ───────────────────────────────────────────────── - +// Miscellaneous simple operations bhhandler_r_v!(handler_assert_not_none, bhimpl_assert_not_none); bhhandler_r_r!(handler_virtual_ref, bhimpl_virtual_ref); @@ -6745,13 +6737,13 @@ fn handler_unreachable( bhimpl_unreachable() } -// ── cpu-dependent field/array operations ───────────────────────────── // // RPython blackhole.py:1432-1481: bhimpl_getfield_gc_*/setfield_gc_* // These call `cpu.bh_getfield_gc_i(struct_ptr, descr)` etc. // The 'd' argcode is a 2-byte descriptor index into `bh.descrs`. // In pyre, descrs[index] resolves to a field offset (usize). +// CPU-dependent field and array operations /// RPython `blackhole.py:150-157`: read a 2-byte descriptor index from /// bytecode and return `(descr_object, new_position)`. /// @@ -6833,7 +6825,10 @@ fn read_descr_vable_field(bh: &BlackholeInterpreter, code: &[u8], pos: usize) -> is_field_signed: false, is_immutable: false, is_quasi_immutable: false, - index_in_parent: 0, + // No parent list, so there is no slot to claim — `None` rather than a + // `0` that reads as a claim on the first field of a list that does + // not exist here. + index_in_parent: None, parent: None, name: String::new(), owner: String::new(), @@ -6883,7 +6878,10 @@ fn read_descr_vable_array(bh: &BlackholeInterpreter, code: &[u8], pos: usize) -> is_field_signed: false, is_immutable: false, is_quasi_immutable: false, - index_in_parent: 0, + // No parent list, so there is no slot to claim — `None` rather than a + // `0` that reads as a claim on the first field of a list that does + // not exist here. + index_in_parent: None, parent: None, name: String::new(), owner: String::new(), @@ -7058,9 +7056,9 @@ fn handler_arraylen_gc( Ok(pos + 1) } -// ── getarrayitem_gc (blackhole.py:1329-1341) ──────────────────────── // @arguments("cpu", "r", "i", "d", returns="X") +// GC array reads (`blackhole.py:1329-1341`) fn handler_getarrayitem_gc_i( bh: &mut BlackholeInterpreter, code: &[u8], @@ -7098,9 +7096,9 @@ fn handler_getarrayitem_gc_r( Ok(pos + 1) } -// ── setarrayitem_gc (blackhole.py:1350-1358) ──────────────────────── // @arguments("cpu", "r", "i", "X", "d") +// GC array writes (`blackhole.py:1350-1358`) fn handler_setarrayitem_gc_i( bh: &mut BlackholeInterpreter, code: &[u8], @@ -7166,7 +7164,7 @@ fn handler_setarrayitem_gc_i_c( Ok(pos) } -// ── getfield_raw (blackhole.py:1464-1472) ─────────────────────────── +// Raw field reads (`blackhole.py:1464-1472`) fn handler_getfield_raw_i( bh: &mut BlackholeInterpreter, code: &[u8], @@ -7190,7 +7188,7 @@ fn handler_getfield_raw_f( Ok(pos + 1) } -// ── setfield_raw (blackhole.py:1497-1502) ─────────────────────────── +// Raw field writes (`blackhole.py:1497-1502`) fn handler_setfield_raw_i( bh: &mut BlackholeInterpreter, code: &[u8], @@ -7279,7 +7277,7 @@ fn handler_new_array_clear_c( Ok(pos + 1) } -// ── string operations (blackhole.py:1200-1283) ────────────────────── +// String operations (`blackhole.py:1200-1283`) fn handler_strlen( bh: &mut BlackholeInterpreter, code: &[u8], @@ -7367,7 +7365,7 @@ fn handler_newunicode( Ok(position + 2) } -// ── exception handling (blackhole.py:969-1009) ────────────────────── +// Exception handling (`blackhole.py:969-1009`) fn handler_catch_exception( _bh: &mut BlackholeInterpreter, _code: &[u8], @@ -7377,7 +7375,7 @@ fn handler_catch_exception( Ok(position + 2) } -// ── misc no-ops (blackhole.py:1017-1049) ──────────────────────────── +// Miscellaneous no-ops (`blackhole.py:1017-1049`) fn handler_jit_debug( _bh: &mut BlackholeInterpreter, _code: &[u8], @@ -7395,8 +7393,8 @@ bhhandler_v_v!( bhimpl_jit_leave_portal_frame ); -// ── interiorfield_gc (blackhole.py:1411-1429) ─────────────────────── // @arguments("cpu", "r", "i", "d", returns="X") +// Interior GC fields (`blackhole.py:1411-1429`) fn handler_getinteriorfield_gc_i( bh: &mut BlackholeInterpreter, code: &[u8], @@ -7423,8 +7421,6 @@ fn handler_setinteriorfield_gc_i( Ok(pos) } -// ── call operations (blackhole.py:1224-1276) ──────────────────────── - #[inline] fn read_list_i(bh: &BlackholeInterpreter, code: &[u8], pos: usize) -> (Vec, usize) { let count = code[pos] as usize; @@ -7549,6 +7545,7 @@ fn bh_null_arg_report(bh: &BlackholeInterpreter, ar: &[i64], position: usize) { } } +// Call operations (`blackhole.py:1224-1276`) fn handler_residual_call_irf_i( bh: &mut BlackholeInterpreter, code: &[u8], @@ -9344,7 +9341,7 @@ pub fn wire_bhimpl_handlers(builder: &mut BlackholeInterpBuilder) { // before jitcode emission, so neither key reaches `wire_handler`. } -// ── goto_if_not_float (blackhole.py:751-798) ──────────────────────── +// Floating-point conditional branches (`blackhole.py:751-798`) macro_rules! bhhandler_goto_if_not_ff { ($name:ident, $cmp:expr) => { fn $name( @@ -9702,11 +9699,11 @@ fn handler_current_trace_length( Ok(p + 1) } -// ── vable field operations (blackhole.py:1446-1495) ───────────────── // RPython: fielddescr.get_vinfo().clear_vable_token(struct) // return cpu.bh_getfield_gc_*(struct, fielddescr) // pyre: read_descr_vable_field resolves VableField.index → byte offset via VirtualizableInfo. +// Virtualizable field operations (`blackhole.py:1446-1495`) fn handler_getfield_vable_i( bh: &mut BlackholeInterpreter, code: &[u8], @@ -9849,7 +9846,6 @@ fn handler_setfield_vable_f( Ok(p) } -// ── vable array operations (blackhole.py:1374-1409) ───────────────── // @arguments("cpu", "r", "i", "d", "d", returns="X") // Two descriptors: fielddescr (VableArray) + arraydescr (Array). // RPython: fielddescr.get_vinfo().clear_vable_token(vable) @@ -9900,6 +9896,7 @@ fn vable_clear_token_and_get_vinfo( vinfo } +// Virtualizable array operations (`blackhole.py:1374-1409`) fn handler_getarrayitem_vable_i( bh: &mut BlackholeInterpreter, code: &[u8], @@ -10034,7 +10031,7 @@ fn handler_setarrayitem_raw_i( Ok(p) } -// ── conditional call (blackhole.py:1257-1276) ─────────────────────── +// Conditional calls (`blackhole.py:1257-1276`) fn handler_conditional_call_ir_v( bh: &mut BlackholeInterpreter, code: &[u8], @@ -10155,7 +10152,7 @@ fn handler_setlistitem_gc_r( Ok(p) } -// ── switch (blackhole.py:954-960) ─────────────────────────────────── +// Switch dispatch (`blackhole.py:954-960`) /// RPython `blackhole.py:954-960`: /// ```python /// @arguments("i", "d", "pc", returns="L") @@ -10215,8 +10212,8 @@ fn handler_check_resizable_neg_index( Ok(p + 1) } -// ── getarrayitem_gc_f / setarrayitem_gc_f ─────────────────────────── // blackhole.py:1336-1337 bhimpl_getarrayitem_gc_f +// Floating-point GC array operations fn handler_getarrayitem_gc_f( bh: &mut BlackholeInterpreter, code: &[u8], diff --git a/majit/majit-metainterp/src/call_descr.rs b/majit/majit-metainterp/src/call_descr.rs index 6b7c5e388cc..f6b9d836aac 100644 --- a/majit/majit-metainterp/src/call_descr.rs +++ b/majit/majit-metainterp/src/call_descr.rs @@ -488,7 +488,7 @@ pub const LOOPINVARIANT_EFFECT_INFO: EffectInfo = pub enum EffectInfoSlot { /// `EF_CAN_RAISE` — `call.py:300-301 elif self._canraise(op):` /// branch of `getcalldescr`, resolved through - /// [`can_raise_effect_info()`]: `CanRaise` + `Some(empty)` raw sets + /// `can_raise_effect_info()`: `CanRaise` + `Some(empty)` raw sets /// and bitstrings. /// /// Picked by producers that classified the callee by hand — the @@ -593,7 +593,7 @@ pub fn default_effect_for_opcode(opcode: majit_ir::OpCode) -> EffectInfo { /// * [`make_call_descr_from_target_slot`] when a resolved /// [`crate::jitcode::JitCallTarget`] is available — threads the /// macro-time [`EffectInfoSlot`] (`call.py:282-303 getcalldescr` parity). -/// * [`make_call_descr_for_opcode`] when only the call opcode family is +/// * `make_call_descr_for_opcode` when only the call opcode family is /// known (`pyjitpl.py:1991-1995 do_residual_or_indirect_call`'s /// `EF_LOOPINVARIANT` / `EF_ELIDABLE_*` reverse-mapping). /// * [`make_call_descr_with_effect`] when an explicit `EffectInfo` has diff --git a/majit/majit-metainterp/src/compile.rs b/majit/majit-metainterp/src/compile.rs index 86c3aac9d60..c87dc025a18 100644 --- a/majit/majit-metainterp/src/compile.rs +++ b/majit/majit-metainterp/src/compile.rs @@ -116,8 +116,6 @@ fn derive_slot_types( .collect() } -// ── Compilation result types (compile.py) ─────────────────────────────── - /// Static exit metadata for a compiled guard or finish point. #[derive(Debug, Clone)] pub struct CompiledExitLayout { @@ -232,8 +230,6 @@ pub struct DeadFrameArtifacts { pub exception: ExceptionState, } -// ── CompileData input bundles (compile.py:31-139) ─────────────────────── - /// `compile.py:31` `class CompileData(object)`. /// /// PYRE-ADAPTATION: RPython's `CompileData.optimize_trace()` builds the @@ -371,8 +367,6 @@ impl<'a> UnrolledLoopData<'a> { } } -// ── Compilation helper functions ──────────────────────────────────────── - /// Build guard metadata for a compiled trace. /// /// The backend numbers every guard and finish in a single exit table, so this @@ -460,7 +454,9 @@ pub(crate) fn build_guard_metadata>( // the descr's `fail_arg_types()` (post-numbering, post-virtual- // materialization) and mirrors it to `op.fail_arg_types` for // sharing-path guards (mod.rs:3068-3088). After the codex #3 fix - // (tracer-stage descr=None, dbd452a640c), every guard's descr is + // (tracer-stage descr=None -- that hash resolves nowhere in + // this repository, so the mechanism named here is the + // reference), every guard's descr is // minted by `store_final_boxes_in_guard` carrying the // post-numbering type vector, so descr-first priority no longer // exposes stale tracer types. Fall back to `op.fail_arg_types` @@ -2264,7 +2260,6 @@ pub(crate) fn patch_backend_terminal_recovery_layouts_for_trace( } } -// ────────────────────────────────────────────────────────────────────── // `rpython/jit/metainterp/compile.py:623-674` — finish/propagate descrs. // // These are ported as backend-agnostic `FailDescr` impls on the @@ -2280,7 +2275,6 @@ pub(crate) fn patch_backend_terminal_recovery_layouts_for_trace( // (pyjitpl.rs) and `Backend` (majit-backend/lib.rs via the blanket // impl below), so `MetaInterp::new` installs a single `Arc` on both // halves; `attach_descrs_to_cpu` forwards the clones to the backend. -// ────────────────────────────────────────────────────────────────────── // `compile.py:623-672, 1092-1099` `_DoneWithThisFrameDescr` family / // `ExitFrameWithExceptionDescrRef` / `PropagateExceptionDescr`: the @@ -5198,7 +5192,6 @@ pub fn make_compile_loop_version_descr_from(source_op: &majit_ir::Op) -> DescrRe /// `ResumeGuardDescr` (`compile.py:855`) is the single guard-owned /// resume container. -// ── TraceCtx merge-point / inline-tracking methods ────────────────────── // // These are the **compile role** of `TraceCtx`, mirroring RPython's // `pyjitpl.py` merge-point bookkeeping (`current_merge_points`, @@ -5347,7 +5340,7 @@ impl TraceCtx { /// /// `header_pc` identifies the header on its own here: a merge point's /// `green_key` is derived from `(code, header_pc)`, so the reverse scan's - /// first hit is the same entry [`get_merge_point_at`] would return. + /// first hit is the same entry [`Self::get_merge_point_at`] would return. /// Only entries recorded during the walk (`position > 0`) answer here. /// The entry at position 0 is the synthetic trace-start seed, whose boxes /// were built from the trace's own `inputarg_types()`; leaving it out lets @@ -5418,7 +5411,7 @@ impl TraceCtx { /// Record the structured greenkey for the root trace. Called once /// at trace start to seed `green_key_raw` and `root_green_key_raw` /// from the tracer-side `(code_ptr, pc)`. Subsequent back-edge - /// retargeting flows through [`set_green_key`]. + /// retargeting flows through [`Self::set_green_key`]. pub fn set_root_green_key_raw(&mut self, raw: (usize, usize)) { self.green_key_raw = raw; self.root_green_key_raw = raw; diff --git a/majit/majit-metainterp/src/graphpage.rs b/majit/majit-metainterp/src/graphpage.rs index 118b1ebe40f..cddc7cd92e3 100644 --- a/majit/majit-metainterp/src/graphpage.rs +++ b/majit/majit-metainterp/src/graphpage.rs @@ -638,8 +638,17 @@ mod tests { page.compute(&[(&procedure as &dyn ResOpProcedure, 0)], None); - assert!(page.links.iter().any(|(name, _)| name == "i0")); - assert!(page.links.iter().any(|(name, _)| name == "i1")); + // Both links, with the colour each carries. The two membership checks + // this replaces named only the keys: nothing pinned the count, and + // nothing looked at the value side at all, so the colour the page + // assigns was free to change silently. + assert_eq!( + page.links + .iter() + .map(|(key, (name, colour))| (key.as_str(), name.as_str(), *colour)) + .collect::>(), + [("i0", "i0", (128, 0, 96)), ("i1", "i1", (128, 0, 96))] + ); } #[test] diff --git a/majit/majit-metainterp/src/history.rs b/majit/majit-metainterp/src/history.rs index 806989837e5..8e3aa51ec8d 100644 --- a/majit/majit-metainterp/src/history.rs +++ b/majit/majit-metainterp/src/history.rs @@ -46,7 +46,7 @@ pub struct TargetToken { /// is the `jump_target_descr` Arc address. pub token_id: u64, /// compile.py: start_descr — the preamble target token has no virtual - /// state and lives at target_tokens[0]. + /// state and lives at `target_tokens[0]`. pub is_preamble_target: bool, /// Virtual state at this loop entry point. /// Used by _jump_to_existing_trace to check compatibility. @@ -1323,10 +1323,8 @@ mod tests { assert_eq!(trace.inputargs[2].tp, Type::Float); } - // ══════════════════════════════════════════════════════════════════ // History / TreeLoop parity tests // Local parity coverage for history.py TreeLoop structure. - // ══════════════════════════════════════════════════════════════════ #[test] fn test_trace_structure_inputargs_and_ops() { @@ -1505,9 +1503,7 @@ mod tests { ); } - // ══════════════════════════════════════════════════════════════════ // History breadth tests — deeper parity with test_history.py - // ══════════════════════════════════════════════════════════════════ #[test] fn test_trace_ops_with_descrs() { @@ -1960,9 +1956,7 @@ mod tests { assert_eq!(types, vec![Type::Int, Type::Ref, Type::Float]); } - // ══════════════════════════════════════════════════════════════════ // cut_trace_from tests — opencoder.py CutTrace parity - // ══════════════════════════════════════════════════════════════════ #[test] fn test_cut_trace_from_no_escaped_refs() { @@ -2401,10 +2395,8 @@ mod tests { } } - // ══════════════════════════════════════════════════════════════════ // History / TreeLoop parity tests // Local parity coverage for history.py/opencoder.py trace materialization. - // ══════════════════════════════════════════════════════════════════ #[test] fn test_trace_has_inputargs_ops_structure() { @@ -2542,7 +2534,6 @@ mod tests { } } -// ── TraceCtx recording API (History role) ─────────────────────────────── // // Moved from `trace_ctx.rs` — these are the **History role** of `TraceCtx`, // mirroring RPython's `history.py` `History` class: operation recording, @@ -2812,7 +2803,7 @@ impl TraceCtx { self.set_last_guard_resume_position(snapshot_id); } - /// Like [`capture_snapshot_for_last_guard_with_vable_vref`] but stamps + /// Like [`Self::capture_snapshot_for_last_guard_with_vable_vref`] but stamps /// the resume position on the most-recent *guard* op rather than the /// last recorded op. Used when a guard is emitted inside a helper /// (the `_nonstandard_virtualizable` PTR_EQ promote) that records @@ -2840,7 +2831,7 @@ impl TraceCtx { self.set_last_guard_op_resume_position(snapshot_id); } - /// Multi-frame variant of [`capture_snapshot_for_last_guard`]. + /// Multi-frame variant of [`Self::capture_snapshot_for_last_guard`]. /// /// `frames` must be ordered **outermost-first** — `frames[0]` is the /// outermost (root) frame and the last element is the top (currently @@ -2866,7 +2857,7 @@ impl TraceCtx { /// `capture_snapshot_for_last_guard_multi_frame` extended with /// virtualizable / virtualref payloads — see - /// [`capture_snapshot_for_last_guard_with_vable_vref`] for the + /// [`Self::capture_snapshot_for_last_guard_with_vable_vref`] for the /// upstream parity rationale. Multi-frame snapshots that capture a /// guard with a live virtualizable need to carry vable/vref boxes on /// the top (currently-executing) frame so the resume reader's @@ -2899,10 +2890,10 @@ impl TraceCtx { self.set_last_guard_resume_position(snapshot_id); } - /// Like [`capture_snapshot_for_last_guard_multi_frame_with_vable_vref`] but + /// Like [`Self::capture_snapshot_for_last_guard_multi_frame_with_vable_vref`] but /// stamps the resume position on the most-recent *guard* op rather than the /// last recorded op — the multi-frame analog of - /// [`capture_snapshot_for_last_guard_op_with_vable_vref`]. Used when a + /// [`Self::capture_snapshot_for_last_guard_op_with_vable_vref`]. Used when a /// guard emitted inside a helper (the `_nonstandard_virtualizable` PTR_EQ /// promote) records further non-guard ops (`emit_force_virtualizable`'s /// GETFIELD_GC / PTR_NE / COND_CALL) before the caller captures, yet the @@ -3066,7 +3057,6 @@ impl TraceCtx { opref } - // ── Step 2e.2a: split-borrow helpers ────────────────────────────── // // Private `do_*` helpers take `(&mut Trace, ...)` so the caller // performs an explicit field borrow of `self.recorder`. @@ -3384,15 +3374,57 @@ impl TraceCtx { /// still holding the reserved index is a missed re-stamp, and /// `frame_value_count_at` (`pyre-jit-trace`) names it there. /// + /// This doc used to claim the empty boxes were safe, on the grounds + /// that every guard reaching here is constant-narrowed at optimization + /// time — "the array index is a function of the already-promoted-constant + /// `stackpos`" — so `optimize_guard_value` removes it (`rewrite.rs:653`, + /// `actual == expected → Remove`) and it "never reaches the backend, so + /// these empty boxes are never numbered or consumed". **That was false, + /// and it is why the empty `vable_boxes` shipped as a defect.** It holds + /// for an index derived from a promoted-constant `stackpos`, which does + /// fold; it does not hold for a `[T; virt]` array subscripted by a + /// mutable runtime scalar (`state.tape[state.pointer]`), where the + /// `GUARD_VALUE` survives, is numbered, and encodes a **0-length vable + /// section**. Measured across the example crates: five shipped that + /// record and three of them had green suites, because a malformed resume + /// record is only observable through a guard that actually deopts. + /// + /// The vable-array index is now promoted at the walker instead, by + /// `implement_guard_value` (`pyjitpl/dispatch.rs`, + /// `pyjitpl.py:1916-1927`), which routes through `record_state_guard` and + /// therefore captures the live framestack AND the per-trace + /// virtualizable / virtualref boxes. + /// + /// That hoist narrows this path, it does not close it, and the + /// difference is worth stating because the doc above was already once + /// wrong in exactly this direction. `get_arrayitem_vable_index` still + /// carries its `promote_int` call; it is guarded by `index.is_constant()` + /// and so fires only for an index that did not arrive constant. What the + /// hoist bought is that the `pyjitpl/dispatch.rs` family is const *by + /// construction* at all of its index reads. The + /// `jitcode_dispatch/vable_ops.rs` family is gated only on the index + /// having a recorded concrete value (`concrete_of_opref`), which a + /// non-constant `OpRef` can satisfy — and `PYRE_VABLE_IDX_PROBE`'s own + /// caveat, beside that call, says a `NONCONST == 0` reading cannot + /// separate "that family is constant" from "that family was never + /// reached", because the const-by-construction callers dilute it. + /// + /// ⇒ The minimal snapshot is load-bearing ONLY for the + /// `rd_resume_position >= 0` invariant above. Do not read that as "the + /// contents do not matter": they matter to any guard that survives + /// optimization, and whether one survives is a property of the traced + /// program, not of this function. A promote whose argument might not + /// fold belongs at the dispatch layer, not here. + /// /// The genuinely load-bearing promote (`state. = promote(...)`) /// does NOT use this path — it lowers to `BC_*_GUARD_VALUE → /// record_state_guard → build_state_field_snapshot` /// (`pyjitpl/dispatch.rs`), the full-framestack capture already at parity /// with `generate_guard`. Threading the live framestack into this recorder /// would only matter at framestack depth > 1 (inlined frames), which - /// cannot arise until the trace-into machinery exists; a - /// partial box list would otherwise positionally misalign the resume - /// reader's per-frame register layout, so the snapshot stays minimal. + /// cannot arise until the trace-into machinery exists; a partial box list + /// would otherwise positionally misalign the resume reader's per-frame + /// register layout, so the snapshot stays minimal. fn record_guard_with_snapshot( &mut self, opcode: OpCode, @@ -3496,8 +3528,6 @@ impl TraceCtx { self.record_guard(OpCode::GuardNotInvalidated, &[], num_live) } - // ── Generic typed call ────────────────────────────────────────── - /// Record a function call with explicit argument and return types. /// /// `opcode` selects the call family (CallI/R/F/N, CallPureI/R/F/N, etc.). @@ -3576,7 +3606,7 @@ impl TraceCtx { let _ = self.call_typed(OpCode::CallN, func_ptr, args, arg_types, Type::Void); } - /// [`call_void_typed`] for hand-written `extern "C"` helpers whose C + /// [`Self::call_void_typed`] for hand-written `extern "C"` helpers whose C /// signature returns a dummy machine word (`-> i64`, value ignored). /// Records the same `CallN` op through a descr that carries the true /// callee ABI (`make_call_descr_void_word_abi`) so a signature-exact @@ -3638,11 +3668,11 @@ impl TraceCtx { ); } - /// Pure-call analog of [`call_typed_with_effect`] that mirrors + /// Pure-call analog of [`Self::call_typed_with_effect`] that mirrors /// `pyjitpl.py:1941-1958 MIFrame.execute_varargs(opnum, argboxes, /// descr, exc=False, pure=True)` for `EF_ELIDABLE_CANNOT_RAISE` /// callees: records the initial `Call{I,R,F,N}` op, then patches - /// it via [`record_result_of_call_pure`] so the trace ends up with + /// it via [`Self::record_result_of_call_pure`] so the trace ends up with /// `CallPure*` (or a `Const` when all args fold) AND the /// `call_pure_results` cache is populated for cross-trace /// constant folding by the optimizer's pure pass @@ -3691,8 +3721,8 @@ impl TraceCtx { } /// Elidable-can-raise (`EF_ELIDABLE_CAN_RAISE`) counterpart of - /// [`call_typed_with_effect_pure`]: records the `Call{I,R,F,N}` and patches - /// it to `CallPure*` via [`record_result_of_call_pure`] (same pure-folding + /// [`Self::call_typed_with_effect_pure`]: records the `Call{I,R,F,N}` and patches + /// it to `CallPure*` via [`Self::record_result_of_call_pure`] (same pure-folding /// path), but the callee may raise, so the **caller must emit a trailing /// `GuardNoException`** (`pyjitpl.py:2082 handle_possible_exception`, /// `do_residual_call`'s `elif cr:` branch) — **except when the returned @@ -4035,8 +4065,6 @@ impl TraceCtx { self.call_typed(OpCode::CallPureI, func_ptr, args, arg_types, Type::Int) } - // ── Ref/Float call variants ───────────────────────────────────── - /// Record a ref-returning function call (CallR). pub fn call_ref(&mut self, func_ptr: *const (), args: &[OpRef]) -> OpRef { let arg_types = self.infer_arg_types(args); @@ -4966,8 +4994,6 @@ impl TraceCtx { result } - // ── CALL_ASSEMBLER ──────────────────────────────────────────── - #[cfg(test)] fn call_assembler_typed( &mut self, @@ -5169,10 +5195,14 @@ impl TraceCtx { self.record_op_with_descr(OpCode::CallAssemblerR, args, descr) } - /// Arc-carrying sibling of [`Self::call_assembler_red_only_ref`]. - /// RPython records the target `JitCellToken` object directly on + /// Records a red-args-only CALL_ASSEMBLER against a resolved + /// `JitCellToken`. RPython records the target token object directly on /// CALL_ASSEMBLER ops (`compile.py:187`), so production walker paths use /// this once they have resolved or synthesized the token object. + /// + /// The `target_number`-taking form, `call_assembler_red_only_ref`, is + /// `#[cfg(test)]` — this is the only variant that exists in a production + /// build, so it has no sibling to be described against. pub fn call_assembler_red_only_ref_arc( &mut self, target_arc: std::sync::Arc, @@ -5275,8 +5305,6 @@ impl TraceCtx { ) } - // ── Exception handling ────────────────────────────────────────── - /// Record GUARD_EXCEPTION: assert that the pending exception matches /// the given class, and produce a ref to the exception value. pub fn guard_exception(&mut self, exc_class: OpRef, num_live: usize) -> OpRef { @@ -5299,8 +5327,6 @@ impl TraceCtx { self.record_op(OpCode::RestoreException, &[exc_class, exc_value]); } - // ── Object allocation ─────────────────────────────────────────── - /// Record NEW: allocate a new object described by `descr`. pub fn record_new(&mut self, descr: DescrRef) -> OpRef { self.record_op_with_descr(OpCode::New, &[], descr) @@ -5321,8 +5347,6 @@ impl TraceCtx { self.record_op_with_descr(OpCode::NewArrayClear, &[length], descr) } - // ── Virtual references ──────────────────────────────────────── - /// Record VIRTUAL_REF_R: create a virtual reference (ref-typed result). /// /// `virtual_obj` is the real object being wrapped. @@ -5354,8 +5378,6 @@ impl TraceCtx { self.record_op(OpCode::ForceToken, &[]) } - // ── Overflow-checked arithmetic ──────────────────────────────── - /// Record overflow-checked integer add + GuardNoOverflow. /// /// Returns the result OpRef. On overflow at trace time, the caller @@ -5380,8 +5402,6 @@ impl TraceCtx { result } - // ── String operations ─────────────────────────────────────────── - /// Record NEWSTR: allocate a new string with given length. pub fn newstr(&mut self, length: OpRef) -> OpRef { self.record_op(OpCode::Newstr, &[length]) diff --git a/majit/majit-metainterp/src/jit.rs b/majit/majit-metainterp/src/jit.rs index 2a401620ae1..2c21bedabcb 100644 --- a/majit/majit-metainterp/src/jit.rs +++ b/majit/majit-metainterp/src/jit.rs @@ -372,7 +372,6 @@ impl std::fmt::Display for InvalidVirtualRef { impl std::error::Error for InvalidVirtualRef {} -// ---------- implementation-specific ---------- // rlib/jit.py:493 // ── DirectVRef ── @@ -856,7 +855,7 @@ pub fn record_exact_class(value: V, cls: usize) { /// /// rtyper/debug.py:23-26 — `assert x is not None; return x` /// -/// In Rust there is no Python `None` per se; the Option<&T> / Option> +/// In Rust there is no Python `None` per se; the `Option<&T>` / `Option>` /// shape is the closest analog, and `Option::expect("ll_assert_not_none")` /// preserves the upstream assertion semantics. The standalone identity /// form (`x.assert_not_none()` on already-non-None Rust values) is a diff --git a/majit/majit-metainterp/src/jit_state.rs b/majit/majit-metainterp/src/jit_state.rs index e988593a922..d2ff7b5795c 100644 --- a/majit/majit-metainterp/src/jit_state.rs +++ b/majit/majit-metainterp/src/jit_state.rs @@ -109,7 +109,7 @@ impl DeoptMaterializationCache { #[derive(Debug, Clone)] pub struct ResumeDataResult { /// resume.py:1057: per-frame decoded values from rd_numb. - /// Each RebuiltValue::Box(i, kind) → liveboxes[i] in RPython. + /// Each RebuiltValue::Box(i, kind) → `liveboxes[i]` in RPython. pub frames: Vec, /// resume.py:1045: virtualizable boxes (decoded from vable section). pub virtualizable_values: Vec, @@ -202,6 +202,60 @@ pub trait JitState: Sized { /// branch decisions during tracing use the real runtime values. fn initialize_sym(&self, _sym: &mut Self::Sym, _meta: &Self::Meta) {} + /// Drop every symbolic inputarg binding `create_sym` minted, leaving the + /// concrete value mirrors `initialize_sym` seeded intact. + /// + /// `create_sym` numbers each state field `InputArg{Ty}(offset)` by walking + /// the declaration order, and `record_input_arg` numbers the trace's own + /// inputargs from its `op_count`. Both start at 0 and both issue into the + /// one flat position space `OpRef::raw()` addresses, so the two agree only + /// while they enumerate the same sequence. On a loop trace they do: + /// `extract_live` / `live_value_types` emit the same partition in the same + /// order that `create_sym` advances its offset, so field `k` and inputarg + /// `k` name the same value. + /// + /// A bridge breaks that. `Trace::with_input_types(fail_descr.fail_arg_types())` + /// numbers the bridge's inputargs in the guard's failarg order, which has no + /// relation to declaration order, while `create_sym` runs unchanged and + /// mints the same 0-based positions it would for a loop. Each surviving mint + /// is then a well-formed position in a space it was not issued from: + /// `TraceIterator`'s `_cache` is indexed by `raw()` alone, so the lookup + /// resolves against whatever the bridge put at that position and hands back + /// a box for an unrelated value instead of missing. + /// + /// `setup_bridge_sym` is the bridge's binding authority — it decodes each + /// field from the resume data and rebinds it into the failarg space. It + /// binds scalars only, and skips any field the resume frame does not carry, + /// so calling it does not by itself retire the stale mints. Clearing them + /// first makes an unbound field read as absent rather than as a live + /// reference to someone else's slot; `resume.py` has no counterpart to + /// preserve, because upstream a field the resume data did not resurrect + /// simply has no box. + /// + /// The default is a no-op: a `Sym` that holds no `OpRef` has nothing to + /// clear, and the unit-`Sym` test drivers are in that class. + fn clear_sym_inputarg_bindings(_sym: &mut Self::Sym) {} + + /// How many of `sym`'s `OpRef` fields are still bound, or `None` if this + /// state cannot say. + /// + /// This exists to grade the *call* to `clear_sym_inputarg_bindings`, not + /// the function. Nothing downstream can: `setup_bridge_sym` rebinds a + /// field to the same value whether the previous one was `OpRef::NONE` or + /// a stale loop-shaped mint, and an overwrite that ignores its target + /// erases the difference. The only place the two states are + /// distinguishable is the window between the two calls, so the check has + /// to be sited there. + /// + /// `None` rather than `0` for the default deliberately. A state that + /// cannot report has to be skipped, not read as clean — zero bound and + /// no way to count spell the same number, and a default of `0` would + /// make every state that never overrides this pass the assertion in + /// silence. + fn count_bound_sym_inputargs(_sym: &Self::Sym) -> Option { + None + } + /// pyjitpl.py:3062-3070 `_unpack_boxes` parity: read concrete values /// from the live boxes at a successful close-loop back-edge before the /// trace history is cleared. @@ -232,7 +286,7 @@ pub trait JitState: Sized { } /// The code object pointer for green key computation. - /// RPython: jitdriver_sd.jitcodes[jitcode_pos] + /// RPython: `jitdriver_sd.jitcodes[jitcode_pos]` fn code_ptr(&self) -> usize { 0 } @@ -333,7 +387,7 @@ pub trait JitState: Sized { /// Restore from all three typed register banks separately. The default /// preserves existing int/ref interpreters by forwarding to - /// [`restore_banked`] and ignoring the float slice. + /// `restore_banked` and ignoring the float slice. fn restore_banked3( &mut self, meta: &Self::Meta, @@ -398,7 +452,7 @@ pub trait JitState: Sized { Vec::new() } - /// Ref-bank sibling of [`collect_scalar_state_field_values`], in ref-scalar + /// Ref-bank sibling of `collect_scalar_state_field_values`, in ref-scalar /// state-field index order (idx `0..num_ref_scalars`). Raw pointer bits, the /// encoding `registers_r` holds. Read off the same still-live sym, for the /// same reason: the walk keeps ref state fields on the sym and native @@ -427,10 +481,10 @@ pub trait JitState: Sized { /// native state. fn writeback_live_scalar_state_field(&mut self, _field_idx: usize, _value: i64) {} - /// Ref-bank sibling of [`writeback_live_scalar_state_field`]. + /// Ref-bank sibling of `writeback_live_scalar_state_field`. fn writeback_live_ref_scalar_state_field(&mut self, _field_idx: usize, _value: i64) {} - /// Float-bank sibling of [`writeback_live_scalar_state_field`]. `value` + /// Float-bank sibling of `writeback_live_scalar_state_field`. `value` /// is the raw f64 bit carrier from `registers_f`; generated impls convert /// with `f64::from_bits`. fn writeback_live_float_scalar_state_field(&mut self, _field_idx: usize, _value: i64) {} diff --git a/majit/majit-metainterp/src/jitcode/assembler.rs b/majit/majit-metainterp/src/jitcode/assembler.rs index 99d36328f81..656c9c21029 100644 --- a/majit/majit-metainterp/src/jitcode/assembler.rs +++ b/majit/majit-metainterp/src/jitcode/assembler.rs @@ -766,7 +766,8 @@ impl JitCodeBuilder { is_field_signed, is_immutable: false, is_quasi_immutable: false, - index_in_parent: 0, + // Same as the vable synthesizers: no parent, so no slot claim. + index_in_parent: None, parent: None, name: String::new(), owner: String::new(), @@ -818,9 +819,13 @@ impl JitCodeBuilder { // it becomes the descr's `_cache_field` key downstream, so naming the // aggregate hands the leaf's access the aggregate's descr. let slot = field_slot_in(&parent_spec.all_fielddescrs, field_name, offset); + // The miss stays `None`. Collapsing it to `0` here is the same defect + // `fielddescrof` carried: slot 0 of the parent's list is some other + // field, and once the descr is minted nothing downstream can tell a + // failed lookup from a field genuinely at slot 0. let (index_in_parent, name) = slot - .map(|idx| (idx, parent_spec.all_fielddescrs[idx].name.clone())) - .unwrap_or((0, String::new())); + .map(|idx| (Some(idx), parent_spec.all_fielddescrs[idx].name.clone())) + .unwrap_or((None, String::new())); // The registered layout is the record of what `descr.py:218-239 // get_field_descr` derives from FIELDTYPE, so a field it names supplies // its own width and signedness. The IR bank the access lands in @@ -1748,7 +1753,7 @@ impl JitCodeBuilder { /// `add_gc_byte_array_descr` so a wider env element type (`&[i64]`, /// `item_size = 8`) reads the element at byte offset `item_size * index` /// instead of the raw byte at `index`. `base_size = 0` because Rust - /// slice (`&[T]`) data pointers point directly at items[0] with no GC + /// slice (`&[T]`) data pointers point directly at `items[0]` with no GC /// header; the structural tuple (base_size=0, itemsize, item_type=Int, /// signedness) uniquely identifies the descr for `add_bh_descr` dedup. /// For an 8-byte item signedness is moot (full-word load), but for `&[u8]` @@ -2449,7 +2454,7 @@ impl JitCodeBuilder { } /// RPython jtransform.py:1714-1718 handle_jit_marker__loop_header emits - /// SpaceOperation('loop_header', [c_index], None) with + /// `SpaceOperation('loop_header', [c_index], None)` with /// `Constant(jd.index, lltype.Signed)`. blackhole.py:1063 /// bhimpl_loop_header(jdindex) is a no-op; pyjitpl.py:1527 /// opimpl_loop_header records the jitdriver index for the trace. @@ -2521,7 +2526,7 @@ impl JitCodeBuilder { /// `BC_LIVE` slot at the start of every per-pc JitCode (which has no /// per-marker triple of its own — it points at the canonical "all /// live" entry). The 2-byte slot is written as `0x0000` here and - /// back-patched during [`finalize_liveness`] via + /// back-patched during [`Self::finalize_liveness`] via /// `Assembler::ensure_canonical_liveness_offset`. Returns the operand /// offset so callers may chain into a custom patcher if needed. pub fn live_placeholder(&mut self) -> usize { @@ -2538,9 +2543,9 @@ impl JitCodeBuilder { /// Deferred-patch entry point: emit a `live/` /// opcode followed by a 2-byte placeholder offset (mirroring - /// [`live_placeholder`]) and record the per-marker + /// [`Self::live_placeholder`]) and record the per-marker /// `(live_i, live_r, live_f)` triple in `pending_live_triples` so - /// [`finalize_liveness`] can later resolve and patch the offset. + /// [`Self::finalize_liveness`] can later resolve and patch the offset. /// /// Each `live_*` slice must be a sorted+dedup register-set view /// matching the macro lowerer's @@ -2567,7 +2572,7 @@ impl JitCodeBuilder { /// Finalisation step: register every pending /// per-marker liveness triple into `asm` (deduplicating against the /// shared `all_liveness_positions`) and rewrite each corresponding - /// `live/` BC_LIVE slot via [`patch_live_offset`]. + /// `live/` BC_LIVE slot via [`Self::patch_live_offset`]. /// /// Mirrors `assembler.py:146-158`'s per-marker /// `_encode_liveness(live_i, live_r, live_f) → encode_offset(pos)` @@ -2672,7 +2677,7 @@ impl JitCodeBuilder { /// /// assembler.py:181-196 parity: encodes jdindex + 6 typed register /// lists (greens_i, greens_r, greens_f, reds_i, reds_r, reds_f). - /// Each list is [length:u8][reg_indices:u8...]. + /// Each list is `[length:u8][reg_indices:u8...]`. /// /// jdindex is emitted per assembler.py:163,312 USE_C_FORM rules — /// `'c'` (raw signed byte) when fitting in `i8`, otherwise `'i'` @@ -2899,7 +2904,7 @@ impl JitCodeBuilder { /// Void-result sibling of [`Self::recursive_call_int`] /// (`opimpl_recursive_call_v`). Carries no result register — the - /// `result_dst` slot is emitted as the [`jitcode::NO_RETURN_REG`] "no + /// `result_dst` slot is emitted as the `jitcode::NO_RETURN_REG` "no /// result" sentinel the dispatcher decodes to `None`. pub fn recursive_call_void( &mut self, @@ -3867,7 +3872,7 @@ impl JitCodeBuilder { /// float-result sibling. Always uses `IRF_F` per /// `resoperation.py:1238-1248` ("no such thing" `R_F` / `IR_F`) - /// and goes through [`Self::emit_canonical_call_typed_irf_f`] so + /// and goes through `Self::emit_canonical_call_typed_irf_f` so /// the F list count byte is always present. #[allow(dead_code)] pub fn residual_call_float_canonical_via_target( @@ -4508,8 +4513,6 @@ impl JitCodeBuilder { self.num_regs_frozen = true; } - // ── Ref-typed builder methods ───────────────────────────── - pub fn load_const_r_value(&mut self, dst: u16, value: i64) { let const_idx = self.add_const_r(value); self.load_const_r(dst, const_idx); @@ -4621,8 +4624,6 @@ impl JitCodeBuilder { ); } - // ── Float-typed builder methods ─────────────────────────── - pub fn load_const_f_value(&mut self, dst: u16, value: i64) { let const_idx = self.add_const_f(value); self.load_const_f(dst, const_idx); @@ -4922,7 +4923,7 @@ impl JitCodeBuilder { /// distinct entries; in practice the same helper is registered with /// a single classification and the dedup matches the `add_call_target` /// path verbatim. `save_err` defaults to `0` (`RFFI_ERR_NONE`, - /// `rffi.py:80`); release-gil callees use [`add_call_target_with_save_err`] + /// `rffi.py:80`); release-gil callees use [`Self::add_call_target_with_save_err`] /// to thread the wrapper's `_call_aroundstate_target_[1]` /// (`rffi.py:228`) into the dedup key. pub fn add_call_target_with_slot( @@ -5593,8 +5594,7 @@ impl JitCodeBuilder { let target = match self.labels.get(label_idx).copied().flatten() { Some(target) => target as u16, None => { - // Diagnostic for issue #112 scope #3: a label was - // *referenced* (a goto / switch / forwarder operand was + // A label was *referenced* (a goto / switch / forwarder operand was // emitted at these code offsets) but never *marked* (its // target block's `Label` was never emitted). The orthodox // cause is an un-simplified graph reaching flatten — a @@ -5603,8 +5603,7 @@ impl JitCodeBuilder { // offending label index, every referencing code offset, and // the marked/total label counts so the origin can be traced // before the panic, then point at the `simplify_graph` - // passes that remove the shape. Scope #3 concluded that the - // walker-safe subset wired ahead of flatten + // passes that remove the shape. The walker-safe subset wired ahead of flatten // (`eliminate_empty_blocks` + `constfold_exitswitch` + // `remove_trivial_links`, see `pyre-jit/src/jit/simplify.rs` // module doc) already removes every shape that produces an @@ -5621,8 +5620,8 @@ impl JitCodeBuilder { panic!( "jitcode label {label_idx} was never marked \ (referenced at code offsets {referencing_offsets:?}; \ - {marked}/{total} labels marked). Issue #112 unmarked-label \ - gap: a goto/switch/forwarder targets a block whose Label \ + {marked}/{total} labels marked). A goto/switch/forwarder \ + targets a block whose Label \ was never emitted — typically an un-simplified dead/trivial \ forwarder or dead switch arm reaching flatten. The orthodox \ fix is graph normalization via simplify_graph \ @@ -5732,7 +5731,7 @@ impl JitCodeBuilder { let Some(idx) = field_slot_in(&p.all_fielddescrs, name, *offset) else { continue; }; - *index_in_parent = idx; + *index_in_parent = Some(idx); name.clone_from(&p.all_fielddescrs[idx].name); } } @@ -5791,19 +5790,21 @@ impl JitCodeBuilder { match field_slot_in(&p.all_fielddescrs, name, *offset) { Some(idx) => { let slot = &p.all_fielddescrs[idx]; - if *index_in_parent != idx || *name != slot.name { + if *index_in_parent != Some(idx) || *name != slot.name { return Some(format!( "field descr at offset {offset} of type_id {:#x} claims slot \ - {index_in_parent} named {name:?}, but that offset is slot {idx} \ + {index_in_parent:?} named {name:?}, but that offset is slot {idx} \ named {:?}", p.type_id, slot.name, )); } } - None if *index_in_parent != 0 || !name.is_empty() => { + // The unresolved fallback is now a type, not a convention: + // `None` beside an empty name. + None if index_in_parent.is_some() || !name.is_empty() => { return Some(format!( "field descr at offset {offset} does not resolve in type_id {:#x}'s \ - layout yet carries slot {index_in_parent} named {name:?} instead of \ + layout yet carries slot {index_in_parent:?} named {name:?} instead of \ the unresolved fallback", p.type_id, )); @@ -6162,8 +6163,8 @@ mod tests { "the parent is the final merged spec, not the mint-time prefix", ); let (expected_index, expected_name) = match offset { - 8 => (0, "lo"), - 16 => (1, "hi"), + 8 => (Some(0), "lo"), + 16 => (Some(1), "hi"), other => panic!("unexpected field offset {other}"), }; assert_eq!( @@ -6172,7 +6173,8 @@ mod tests { ); assert_eq!(name, expected_name, "and name from that same layout"); assert_eq!( - parent.all_fielddescrs[index_in_parent].offset, offset, + parent.all_fielddescrs[index_in_parent.expect("resolved above")].offset, + offset, "`all_fielddescrs[index_in_parent]` must be this very field \ (`heaptracker.py:60-72` / `:96-112` share one walker upstream)", ); @@ -6237,15 +6239,17 @@ mod tests { let (idx, name) = field_of("leaf"); assert_eq!( (idx, name.as_str()), - (1, "leaf"), + (Some(1), "leaf"), "the emit site's `fieldname` names the field the shared offset cannot", ); let (idx, name) = field_of(""); assert_eq!( (idx, name.as_str()), - (0, ""), + (None, ""), "without a name the ambiguous offset resolves to nothing, so the \ - mint's `unwrap_or((0, String::new()))` fallback stands", + mint's `unwrap_or((None, String::new()))` fallback stands — and \ + `None` rather than `0` is the point: the miss must not spell \ + itself as a claim on the parent's first slot", ); } diff --git a/majit/majit-metainterp/src/jitcode/mod.rs b/majit/majit-metainterp/src/jitcode/mod.rs index e9e58d8a205..ea62ddad778 100644 --- a/majit/majit-metainterp/src/jitcode/mod.rs +++ b/majit/majit-metainterp/src/jitcode/mod.rs @@ -35,7 +35,6 @@ pub use majit_translate::insns::{pyre_extension_insns, wellknown_bh_insns}; /// definition lives in `majit_translate::jitcode::enumerate_vars`. pub use majit_translate::jitcode::enumerate_vars; -// ────────────────────────────────────────────────────────────────── // Runtime descr pool types — RPython // `BlackholeInterpBuilder.descrs` / `BlackholeInterpreter.descrs` // (`blackhole.py:103`, `blackhole.py:288`). @@ -51,7 +50,6 @@ pub use majit_translate::jitcode::enumerate_vars; // These types are runtime-only — they reference raw `*const ()` // trampoline addresses and live `Arc` callee handles, neither // of which has a representation in the codewriter source layer. -// ────────────────────────────────────────────────────────────────── /// Trace-side function target descriptor for `BC_CALL_*` / /// `BC_RESIDUAL_CALL_*`. RPython `blackhole.py:1225-1256` reads the @@ -68,7 +66,7 @@ pub use majit_translate::jitcode::enumerate_vars; /// resolved `JitCallTarget` thread the slot through /// `make_call_descr_from_target_slot` so the recorded descr carries /// the right `EffectInfo` instead of the `default_effect_info()` -/// fallback. The default ([`EffectInfoSlot::CanRaise`]) preserves the +/// fallback. The default ([`crate::call_descr::EffectInfoSlot::CanRaise`]) preserves the /// pre-G-2 behaviour for every existing construction site. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct JitCallTarget { @@ -324,7 +322,7 @@ static GLOBAL_BUILD_DESCR_POOL: std::sync::OnceLock = std::sync /// Install the process-global build-time descr pool. Idempotent: the first /// call wins and later calls are ignored (the pool is a frozen build artifact, -/// identical across callers). See [`GLOBAL_BUILD_DESCR_POOL`]. +/// identical across callers). See `GLOBAL_BUILD_DESCR_POOL`. /// /// `build` runs only on the call that installs the pool. It takes a closure /// rather than a built `Vec` because the callers sit on hot paths — the jd1 @@ -383,7 +381,6 @@ pub struct JitCodeExecState { pub jit_merge_point_offset: Option, } -// ────────────────────────────────────────────────────────────────── // Wrapper `JitCode` — runtime jitcode = canonical core + descr pool. // // RPython parity: @@ -411,7 +408,6 @@ pub struct JitCodeExecState { // dispatch) via `JitCode::from_canonical`. Per-CodeObject runtime // jitcodes are produced directly as wrappers by // `JitCodeBuilder::finish()`. -// ────────────────────────────────────────────────────────────────── /// Runtime JitCode = canonical RPython parity core + descr pool. #[derive(Debug)] @@ -564,7 +560,7 @@ impl JitCode { /// Resolve a `d`/`j` argcode descr for this jitcode. Runtime-emitted /// jitcodes answer from their per-jitcode `exec.descrs` pool; build-time /// (LLBC-extracted) jitcodes carry an empty pool and fall through to the - /// process-global [`GLOBAL_BUILD_DESCR_POOL`] (RPython's single shared + /// process-global `GLOBAL_BUILD_DESCR_POOL` (RPython's single shared /// `Assembler.descrs`). A populated per-jitcode slot always wins, so the /// runtime path stays byte-identical to a direct `exec.descrs` read. pub fn descr_at(&self, index: usize) -> Option<&RuntimeBhDescr> { @@ -604,9 +600,9 @@ impl JitCodeRuntimeExt for JitCode { /// when decoding). The 256-per-kind register limit, the `num_regs < 256` /// assemble-time decline, and every register decode site all rest on this /// width. Encode register operands with `JitCodeBuilder::push_reg_u8` and -/// decode them with [`read_reg`] / `next_reg`, so the width lives in one place. +/// decode them with `read_reg` / `next_reg`, so the width lives in one place. /// -/// ⚠️ This MUST remain `u8`. Do NOT widen it to `u16` (or any wider type): +/// This MUST remain `u8`. Do NOT widen it to `u16` (or any wider type): /// doing so silently desyncs the encoder from the 1-byte register decoders and /// reintroduces exactly the register-width divergence this alias exists to /// prevent. Non-register operands (descr / field / array indexes) are `u16` diff --git a/majit/majit-metainterp/src/jitdriver.rs b/majit/majit-metainterp/src/jitdriver.rs index 8ee44e34b90..520c52021ae 100644 --- a/majit/majit-metainterp/src/jitdriver.rs +++ b/majit/majit-metainterp/src/jitdriver.rs @@ -424,7 +424,7 @@ fn writeback_live_state_scalars_from_blackhole( } /// Whether re-entrant trace continuation is currently suspended — see -/// [`TRACE_CONTINUATION_SUSPENDED`]. +/// `TRACE_CONTINUATION_SUSPENDED`. pub fn trace_continuation_suspended() -> bool { TRACE_CONTINUATION_SUSPENDED.with(|c| c.get()) } @@ -860,7 +860,7 @@ pub struct JitDriverStaticData { /// (`majit_ir::resumedata::set_frame_value_count_fn`), and a driver whose /// frames are numbered elsewhere installs its own decoder here. /// - /// ⚠️ A "try the global store, fall back on failure" scheme cannot replace + /// A "try the global store, fall back on failure" scheme cannot replace /// this. The wrong store frequently *succeeds*: its low indices hold /// unrelated jitcodes that decode at the same pc and silently return a /// mistyped count. A successful decode is not evidence of the right store. @@ -1032,7 +1032,7 @@ impl JitDriverStaticData { /// Per-kind counts of red variables, in `(Int, Ref, Float)` order. /// - /// Companion to [`green_kind_counts`]; together they reproduce the six + /// Companion to [`Self::green_kind_counts`]; together they reproduce the six /// `[I, R, F, I, R, F]` length bytes of `BC_JIT_MERGE_POINT`. pub fn red_kind_counts(&self) -> (usize, usize, usize) { kind_counts(&self.vars, VarKind::Red) @@ -1698,7 +1698,7 @@ impl JitDriver { } /// Registered slot index of this driver's descriptor, populated by - /// [`MetaInterpStaticData::register_jitdriver_sd`]. Mirrors the RPython + /// [`crate::pyjitpl::MetaInterpStaticData::register_jitdriver_sd`]. Mirrors the RPython /// attribute `jitdriver_sd.index` (`rpython/jit/codewriter/call.py:46-47`) /// read by `jtransform.py:1704` and `compile_tmp_callback`. Returns `None` /// before registration runs. @@ -1910,7 +1910,7 @@ impl JitDriver { /// `None` leaves the abort on the pre-existing source-pc handoff — the walk /// did not abort, or the state shape has no seed path (see below). /// - /// ⚠️ **`None` means "declined before the chain ran", and only that.** Once + /// **`None` means "declined before the chain ran", and only that.** Once /// `drive_multi_frame_blackhole` returns, the chain has executed the rest /// of the half-finished opcodes against the real heap; the caller's /// source-pc handoff resumes at a pc dispatch advanced *before* those @@ -2147,6 +2147,45 @@ impl JitDriver { std::mem::replace(&mut self.meta.single_pass_finish, false) } + /// The FINISH arguments of the compiled run a `back_edge*` call just made, + /// when that run ended in FINISH. `compile.py:623-638` marks a FINISH descr + /// `final_descr = True`: the traced function has RETURNED, so the portal + /// must return rather than resume the interpreter — upstream's `handle_fail` + /// raises `jitexc.DoneWithThisFrame*` at this point. The `back_edge*` + /// signature is `Option` and cannot say "returned", so the value + /// travels here. Call immediately after `back_edge*`; consumes (`take`s) it. + /// + /// A caller that drains this must return from the portal without using the + /// `Some(resume_pc)` the same call produced: that pc is the back edge, and + /// resuming there re-runs the loop the compiled run already completed. + pub fn take_back_edge_finish(&mut self) -> Option> { + self.meta.back_edge_finish.take() + } + + /// [`Self::take_back_edge_finish`] projected onto one integer word — the + /// return shape of an `-> i64` `#[jit_interp]` portal. A `Float` finish + /// argument is projected by its bits, matching how such a portal spells a + /// float return (`f64::to_bits() as i64`). + pub fn take_back_edge_finish_int(&mut self) -> Option { + let values = self.meta.back_edge_finish.take()?; + match values.first() { + Some(Value::Int(v)) => Some(*v), + Some(Value::Float(v)) => Some(v.to_bits() as i64), + _ => None, + } + } + + /// [`Self::take_back_edge_finish`] projected onto one float word — the + /// return shape of an `-> f64` `#[jit_interp]` portal. + pub fn take_back_edge_finish_float(&mut self) -> Option { + let values = self.meta.back_edge_finish.take()?; + match values.first() { + Some(Value::Float(v)) => Some(*v), + Some(Value::Int(v)) => Some(f64::from_bits(*v as u64)), + _ => None, + } + } + /// Push the walk's scalar state fields into native `state`. The walk /// advances a scalar (notably a SEL's new `selected`) on the sym only; /// native `state` stays at its trace-start value until this write-back. @@ -2615,7 +2654,7 @@ impl JitDriver { /// Tracing hook — call at the top of the dispatch loop. /// /// If tracing is active, calls `trace_fn` with the active [`MetaInterp`] - /// (via which the closure can obtain the active [`TraceCtx`]) and + /// (via which the closure can obtain the active [`crate::trace_ctx::TraceCtx`]) and /// symbolic state, then handles the result automatically: /// /// - `CloseLoop` → validates depths, collects jump args, compiles @@ -2941,8 +2980,13 @@ impl JitDriver { // Scoped to this arm alone. A `ResumeGuardDescr` has resume // storage, so a specialized label can match and the fallback // need not be reached — and declining the guard origin too - // makes `pi/pi.jinseo` at `MAJIT_THRESHOLD=50` compute wrong - // digits from byte 865, same length, no crash. + // makes `pi/pi.jinseo` compute wrong digits from + // byte 865, same length, no crash. That was + // measured at threshold 50, and `MAJIT_THRESHOLD` + // is NOT read anywhere — the threshold reaches the + // driver only as the `JitDriver::new(threshold)` + // argument, so reproducing this means changing the + // caller, not setting a variable. // // What the widened arm produces, at the op level: the // declined close returns here, the walk runs on and closes a @@ -3012,6 +3056,18 @@ impl JitDriver { // above skipped. crate::mc_diag_bump(50); // bridge_declined_close ctx.note_cross_loop_close_declined(target_key); + } else { + // The gate above declined to attempt this close, so no + // optimizer pass ran and slot 50 must not move. That + // is a different event from "an attempt was declined", + // and without its own slot the two are indistinguishable + // downstream: both render as slot 50 reading zero. + // `result` is initialized to `Declined` at the top of + // this block and is only written inside the gate, so + // this arm is reachable with nothing having been tried + // — the sibling close sites bind `result` from + // `close_bridge` directly and have no such path. + crate::mc_diag_bump(67); // bridge_unattempted_close } ctx.close_greens = None; ctx.close_green_pc = None; @@ -3825,11 +3881,16 @@ impl JitDriver { .or_else(|| self.meta.take_pending_abort_reason()) { Some(r) => r, - None => self - .meta - .blackhole_if_trace_too_long() - .unwrap_or(AbortReason::Generic) - .as_int(), + None => match self.meta.blackhole_if_trace_too_long() { + Some(r) => r.as_int(), + None => { + // Neither a staged reason nor a too-long + // verdict: unclassified, not a bridge giveup. + // Counted here because slot 41 holds both. + crate::mc_diag_bump(71); + AbortReason::Generic.as_int() + } + }, }; // pyjitpl.py:2949-2956 // run_blackhole_interp_to_cancel_tracing: a fresh trace has @@ -4016,20 +4077,34 @@ impl JitDriver { self.back_edge_internal(green_key, None, target_pc, state, env, None, None, pre_run) } + /// Takes the green key's hash eagerly and the key itself lazily. + /// + /// Only the hash is needed unconditionally — it selects the cell and + /// answers `has_compiled_loop`. The typed [`GreenKey`] is read at exactly + /// one place, `Self::maybe_start_tracing`'s `on_back_edge_typed` call, + /// which is reached only after `back_edge_internal`'s early returns + /// (already tracing, `!can_trace`, cross-loop-cut decline, and the whole + /// `has_compiled_loop` branch — i.e. every entry into compiled code). + /// Building it eagerly spent the key's `values` / `types` vectors on those + /// paths and dropped them unread. + /// + /// This is upstream's split: `get_uhash(*greenargs)` hashes the greens in + /// place on every back edge, and the greens are stored on the cell only + /// when one is installed (warmstate.py:584-604). #[cold] #[inline(never)] pub fn back_edge_structured( &mut self, - green_key: GreenKey, + green_key_hash: u64, + make_green_key: impl Fn() -> GreenKey, target_pc: usize, state: &mut S, env: &S::Env, pre_run: impl FnOnce(), ) -> Option { - let key = green_key.hash_u64(); self.back_edge_internal( - key, - Some(green_key), + green_key_hash, + Some(&make_green_key), target_pc, state, env, @@ -4159,7 +4234,14 @@ impl JitDriver { pre_run: impl FnOnce(), ) -> Result, &'static str> { let green_key = D::green_key(green_values)?; - Ok(self.back_edge_structured(green_key, target_pc, state, env, pre_run)) + Ok(self.back_edge_structured( + green_key.hash_u64(), + || green_key.clone(), + target_pc, + state, + env, + pre_run, + )) } /// RPython warmstate.py:482-501 / compile.py:711 parity. @@ -4171,7 +4253,7 @@ impl JitDriver { fn back_edge_internal( &mut self, green_key: u64, - structured_green_key: Option, + structured_green_key: Option<&dyn Fn() -> GreenKey>, target_pc: usize, state: &mut S, env: &S::Env, @@ -4195,8 +4277,35 @@ impl JitDriver { ); } + // Same decline as the `None if is_cross_loop_cut_key(target_key)` arm + // of the CloseLoop bridge path above, on the sibling route: that arm + // refuses an ENTRY BRIDGE into a cross-loop cut's key because the cut + // artifact's entry invariants hold only after the guards preceding the + // cut point have run, and a `ResumeFromInterpDescr` carries no runtime + // values to prove them; on cel's `batch_chain_eager_fold_traps` the + // unproven index bound became an out-of-bounds store. A direct + // interpreter back edge proves no more than that entry bridge does and + // can therefore execute the same invalid store. This route was hidden + // while the key the interpreter entered by and the + // key `compile_loop` filed the cut under could not be equal while + // `can_enter_jit!` read its position green at the back edge instead of + // at the target; closing that made this route reachable. + // + // What the entry would execute, from the cut trace's own head on that + // fixture: `IntAdd(v5, 1)` / `IntMul` / `RawLoadI` and a later + // `RawStore`, with no bound guard on either — the guard was in the ops + // `cut_trace_from` discarded, and `PreambleCompileData` re-optimizes + // from scratch, so nothing re-derives it. Scoped to the generic case + // for that reason: the cutting trace's own closing JUMP and the + // single-pass label handoff pass an explicit dispatch key and arrive + // with the discarded prefix already run. Refusing before + // `has_compiled_loop` leaves the merge point to arm tracing normally + // rather than re-entering an artifact whose entry contract is unmet. + let dispatch_key = dispatch_key.or(single_pass_dispatch_key); + if dispatch_key.is_none() && self.meta.is_cross_loop_cut_key(green_key) { + return None; + } if self.meta.has_compiled_loop(green_key) { - let dispatch_key = dispatch_key.or(single_pass_dispatch_key); let compiled_meta = self.meta.get_compiled_meta(green_key).unwrap().clone(); let descriptor = self.driver_descriptor_for(state, &compiled_meta); if !state.is_compatible(&compiled_meta) { @@ -4349,12 +4458,33 @@ impl JitDriver { } if result.is_finish { + // compile.py:623-638 `_DoneWithThisFrameDescr.final_descr = True`: + // the compiled run ended in FINISH, so the traced function has + // RETURNED. Upstream `handle_fail` raises `jitexc.DoneWithThisFrame*` + // and unwinds the portal; there is no resume point past a final + // descr. `target_pc` is the back edge, so resuming there restarts + // the loop the run just finished — with only the FINISH's own + // argument restored, not the loop-carried state — and the caller + // re-enters compiled code at the next back edge, making one call + // cost one full compiled run per remaining iteration. + // + // Publish the FINISH arguments out of band. Front end A reads the + // same outcome as `DetailedDriverRunOutcome::Finished` and returns + // from the portal; front end B's `Option` signature has + // no variant for "the function returned", so the `#[jit_interp]` + // expansion drains this latch right after the call and returns it + // as the portal's own return value. + self.meta.back_edge_finish = Some(result.typed_values.clone()); let run_meta = result.meta.clone(); if !result.typed_values.is_empty() { state.restore_values(&run_meta, &result.typed_values); } let run_descriptor = self.driver_descriptor_for(state, &run_meta); self.sync_after(state, &run_meta, run_descriptor.as_deref()); + // Kept for callers that cannot consume the latch (a portal whose + // return type the expansion cannot build from a `Value`). Those + // callers see today's behaviour unchanged; a caller that drains + // the latch never reaches this pc. return Some(target_pc); } @@ -4749,21 +4879,17 @@ impl JitDriver { { eprintln!("[callee-rca][crn-state-after]\n{dump}"); } - let mut resume_pc = green_pc.unwrap_or(target_pc); - if selected_dispatch_key != 0 - && !portal_crn_handled - && green_pc.is_some_and(|pc| pc != target_pc) - { - if portal_rca_enabled() { - eprintln!( - "[portal-rca][crn-label-entry-header-resume] \ - target_pc={} green_pc={:?}", - target_pc, green_pc, - ); - } - resume_pc = target_pc; - } - Some(resume_pc) + // The register file just flushed above is the state + // AT the green pc — the blackhole ran the failing + // opcode forward to the next merge point. Resuming + // anywhere else (the loop-header `target_pc` was + // used here for label-entered runs) re-executes the + // header..green_pc opcodes on post-green state, + // corrupting every value they recompute. The entry + // dispatch key does not change the guard's resume + // snapshot, so label-entered runs resume at the + // green pc like every other run. + Some(green_pc.unwrap_or(target_pc)) } // The interpreted frame ran to completion inside the // blackhole: flush, then force the generated mainloop's @@ -4841,14 +4967,38 @@ impl JitDriver { return Some(guard_resume_pc); } - self.maybe_start_tracing(green_key, structured_green_key, target_pc, state, env); + // Re-enter the dispatch loop at `target_pc` so the trace HEADS where its + // live-in snapshot was TAKEN. + // + // `maybe_start_tracing` reads `extract_live_values` off native `state` + // at this call, but nothing is recorded until the next + // `jit_merge_point!`. When the caller reaches that merge point without + // executing anything — a back edge, whose `pc = tgt; continue;` follows + // this macro immediately — the two coincide and the snapshot is exact. + // + // An ENTRY DOOR arms at the pc it is about to execute, so falling + // through here runs one instruction natively, outside the trace and + // after the snapshot. The trace then heads one instruction late while + // its live-ins hold pre-instruction values, and that instruction's + // write is silently dropped: the walk binds the register to its value + // at the arming pc, so the write never reaches the compiled body OR + // native `state`. Returning the target makes the caller `continue` to + // it, and the merge point there records with tracing already live. + // + // For a back edge this is behaviourally identical to the `pc = tgt; + // continue;` it pre-empts (`target_pc == tgt`); 21 of the corpus's 23 + // `can_enter_jit!` sites are that shape. Re-entry cannot loop: the next + // pass returns at the `is_tracing()` guard opening this function. + if self.maybe_start_tracing(green_key, structured_green_key, target_pc, state, env) { + return Some(target_pc); + } None } fn back_edge_or_run_compiled_internal( &mut self, green_key: u64, - structured_green_key: Option, + structured_green_key: Option<&dyn Fn() -> GreenKey>, target_pc: usize, state: &mut S, env: &S::Env, @@ -4983,18 +5133,25 @@ impl JitDriver { fn maybe_start_tracing( &mut self, green_key: u64, - structured_green_key: Option, + structured_green_key: Option<&dyn Fn() -> GreenKey>, target_pc: usize, state: &mut S, env: &S::Env, - ) { + ) -> bool { if spdiag_enabled() { eprintln!("@@@SPDIAG maybe_start_tracing target_pc={target_pc} green_key={green_key}"); } + // Denominator for slots 62/63, bumped before either refusal can return + // so both are readable as fractions rather than bare totals. Only this + // door is counted: `force_start_tracing` and `bound_reached` open with + // the same two checks, and folding three doors into one slot would say + // nothing about any of them. + crate::mc_diag_bump(61); // mst_entered let meta = state.build_meta(target_pc, env); let descriptor = self.driver_descriptor_for(state, &meta); if !self.sync_before(state, &meta, descriptor.as_deref()) { - return; + crate::mc_diag_bump(62); // mst_sync_before_false + return false; } let live_values = state.extract_live_values(&meta); if !Self::live_values_match_descriptor( @@ -5002,17 +5159,24 @@ impl JitDriver { &live_values, state.state_field_layout().total_live_values(), ) { - return; + crate::mc_diag_bump(63); // mst_live_values_mismatch + return false; } match self.meta.on_back_edge_typed( green_key, (state.code_ptr(), target_pc), + // Neither the typed key nor the descriptor is built here. Both are + // read at exactly one site — the `StartTracing` arm inside + // `on_back_edge_typed` — so the factory and a borrow are passed + // instead of a built key and a deep clone. Every earlier return in + // `back_edge_internal`, and both returns above, leave them unbuilt; + // the `Interpret` and `RunCompiled` arms drop them unread. structured_green_key, - descriptor.map(|d| (*d).clone()), + descriptor.as_deref(), &live_values, ) { - BackEdgeAction::Interpret => {} + BackEdgeAction::Interpret => false, BackEdgeAction::StartedTracing => { if spdiag_enabled() { eprintln!( @@ -5026,8 +5190,9 @@ impl JitDriver { state.initialize_sym(&mut sym, &meta); self.sym = Some(sym); self.meta.begin_trace_session(meta); + true } - BackEdgeAction::AlreadyTracing | BackEdgeAction::RunCompiled => {} + BackEdgeAction::AlreadyTracing | BackEdgeAction::RunCompiled => false, } } @@ -5201,8 +5366,7 @@ impl JitDriver { self.meta.set_vable_array_lengths(Vec::new()); let info_clone = self.meta.virtualizable_info().cloned(); if let Some(ref info) = info_clone { - let vable_name = info.name.clone(); - if let Some(ptr) = state.virtualizable_heap_ptr(meta, &vable_name, info) { + if let Some(ptr) = state.virtualizable_heap_ptr(meta, &info.name, info) { self.meta.set_vable_ptr(ptr.cast_const()); } // Fallback cache for layouts that cannot expose array length on @@ -5211,7 +5375,7 @@ impl JitDriver { // with a length_offset; retained so tests that don't stage a // fake heap object still reach `initialize_virtualizable`. let fallback_lengths = state - .virtualizable_array_lengths(meta, &vable_name, info) + .virtualizable_array_lengths(meta, &info.name, info) .unwrap_or_default(); self.meta.set_vable_array_lengths(fallback_lengths); } @@ -5305,7 +5469,10 @@ impl JitDriver { } /// Set a callback for loop compilation events. - pub fn set_on_compile_loop(&mut self, f: impl Fn(u64, usize, usize) + Send + 'static) { + pub fn set_on_compile_loop( + &mut self, + f: impl Fn(u64, usize, usize, &[majit_ir::OpCode]) + Send + 'static, + ) { self.meta.set_on_compile_loop(f); } @@ -6271,7 +6438,7 @@ impl JitDriver { /// `resume_pc` is where interpretation resumes after the guard failure. pub fn start_bridge_tracing( &mut self, - // `pyjitpl.py:2890 handle_guard_failure(self, resumedescr, + // `pyjitpl.py:2914 handle_guard_failure(self, resumedescr, // deadframe)` threads the descr (`resumedescr`) as the // canonical bridge-source identity. The descr Arc returned by // `cpu.get_latest_descr` (`history.py:125`) plays the same role @@ -6443,6 +6610,57 @@ impl JitDriver { let mut sym = S::create_sym(&trace_meta, resume_pc); state.initialize_sym(&mut sym, &trace_meta); + // `create_sym` numbers the state fields off its own `__offset`, which + // matches a loop trace's inputargs because `extract_live` walks the + // same declaration order. This trace's inputargs came from + // `fail_descr.fail_arg_types()` instead (`start_retrace_from_guard`), + // so the two orders are unrelated and a surviving mint names whichever + // failarg happens to sit at that position. `setup_bridge_sym` below is + // the binding authority here; drop the mints so a field it does not + // reach reads as unbound rather than as a live reference to a value it + // never named. The concrete `_value` mirrors `initialize_sym` just + // seeded are untouched. + S::clear_sym_inputarg_bindings(&mut sym); + // Grade the call above, in the one window where it is gradeable. + // + // `setup_bridge_sym` rebinds a field to the same value whether it + // arrived here as `OpRef::NONE` or as the loop-shaped mint + // `create_sym` left, so every observation taken after it is blind to + // whether this line ran. Deleting the call is invisible downstream + // for exactly the kinds that get rebound, which is why two fixtures + // that once caught it stopped: the repairs made the rebinding + // unconditional, and an unconditional overwrite erases its target. + // + // `None` means the state cannot count and the check is skipped — + // never that it is clean. + // + // Scope. Three conditions have to hold together before this line + // grades anything, and each is a way for a run to be green without + // reaching it: `debug_assertions` on, since this is a + // `debug_assert`; a state that overrides `count_bound_sym_inputargs`, + // since the `JitState` default returns `None` and only a + // macro-generated `Sym` carries the override; and a run that forms a + // bridge and arrives here. Miss any one and the deletion mutant + // survives with zero firings, so that run says nothing about this + // line. + // + // Do not name a crate here as the place the mutant dies, or as a + // place it does not. Which crates meet all three conditions is a + // property of the corpus rather than of this file, so a name reads as + // a measurement and then goes stale with nothing turning red — which + // has already happened here in both directions, the second time about + // this crate's own test targets, some of which do carry a + // macro-generated `Sym`. Derive the subject from the conditions. + if let Some(__bound) = S::count_bound_sym_inputargs(&sym) { + debug_assert_eq!( + __bound, 0, + "bridge sym still holds {__bound} loop-shaped OpRef binding(s) \ + after clear_sym_inputarg_bindings; the recorder numbers a \ + bridge by the guard's failarg order, so a surviving mint \ + resolves against whatever that order put at its position \ + rather than missing" + ); + } self.sym = Some(sym); // pyjitpl.py:2890 parity: bridge traces start at resume_pc, not at // function entry (pc=0). Set header_pc so init_symbolic correctly @@ -6455,7 +6673,7 @@ impl JitDriver { // bridge tracing runs synchronously within start_bridge_tracing's // caller scope, so self.meta outlives the callback. let meta_ptr = &self.meta as *const _ as *const (); - // pyjitpl.py:2890 `handle_guard_failure` runs on a MetaInterp whose + // pyjitpl.py:2914 `handle_guard_failure` runs on a MetaInterp whose // `jitdriver_sd` is the guard's owning loop's driver. A bridge trace // that reaches a loop header (`jit_merge_point` → `compile_loop`) // therefore compiles that loop with the same `jitdriver_sd`, so @@ -6658,7 +6876,7 @@ impl JitDriver { trace_id, fail_index, code_ptr, - // `pyjitpl.py:2890` `handle_guard_failure(self, + // `pyjitpl.py:2914` `handle_guard_failure(self, // resumedescr, deadframe)` parity: thread the source // descr Arc obtained from `cpu.get_latest_descr` // through the bridge session so `compile_trace_inner` @@ -6743,10 +6961,16 @@ impl JitDriver { self.meta.warm_state_mut().counter_tick(key_hash); return None; } - let green_key = GreenKey::with_types(green_values.to_vec(), green_types.to_vec()); // Preserve resume_pc from back_edge_structured (guard // failure returns the guard's pc, not the loop header). - return self.back_edge_structured(green_key, target_pc, state, env, pre_run); + return self.back_edge_structured( + key_hash, + || GreenKey::with_types(green_values.to_vec(), green_types.to_vec()), + target_pc, + state, + env, + pre_run, + ); } let meta = self.meta.get_compiled_meta(key_hash)?; @@ -6892,7 +7116,7 @@ impl JitDriver { // resume_in_blackhole(...) // assert 0, "unreachable" // - // pyjitpl.py:2890 `handle_guard_failure(self, resumedescr, + // pyjitpl.py:2914 `handle_guard_failure(self, resumedescr, // deadframe)` reads the bridge source identity directly off // `resumedescr` (`compile.py:707-708` // `_trace_and_compile_from_bridge` chases @@ -7395,6 +7619,42 @@ mod tests { ); } + #[test] + fn maybe_start_tracing_bumps_its_entry_counter_on_the_live_path() { + // Slots 62/63 count `maybe_start_tracing`'s two refusals and slot 61 is + // their denominator, so a refusal reading 0 only means "did not fire" + // once 61 is known to move. Pin the bump to the live back-edge path, + // not to the counter's own definition: an unreachable slot and a + // correctly-quiet one both read 0. `MC_DIAG` is process-global and + // shared with every other test in this binary, so a concurrent bump can + // only inflate the delta — the assertion below is a lower bound. + let before = crate::mc_diag(61); + let mut driver = JitDriver::::new(2); + driver.meta.finish_setup_descrs_for_jitdrivers(); + let key = 4242u64; + let mut state = TypedRestoreState { + live_values: vec![1], + ..Default::default() + }; + // Threshold 2: the first back edge warms up, the second starts tracing. + // Both reach the door, so both are counted. + for _ in 0..2 { + assert!( + driver + .back_edge_or_run_compiled_keyed(key, 7, &mut state, &(), || {}) + .is_none() + ); + } + assert!( + driver.is_tracing(), + "drive must reach the StartTracing arm so that neither refusal fires" + ); + assert!( + crate::mc_diag(61) >= before + 2, + "mst_entered did not move across two back edges that reached the door" + ); + } + #[test] fn run_compiled_detailed_keyed_uses_typed_live_inputs() { let mut driver = JitDriver::::new(2); @@ -7854,7 +8114,7 @@ mod tests { driver.meta.finish_setup_descrs_for_jitdrivers(); let compile_events: Arc>> = Arc::new(Mutex::new(Vec::new())); let events = compile_events.clone(); - driver.set_on_compile_loop(move |green_key, ops_before, ops_after| { + driver.set_on_compile_loop(move |green_key, ops_before, ops_after, _opcodes| { events .lock() .unwrap() @@ -7897,6 +8157,131 @@ mod tests { assert!(events[0].2 > 0, "num_ops_after should be positive"); } + /// Record a straight-line trace of `n_adds` `IntAdd`s closed by a guard. + /// A longer trace ends at a higher `next_global_opref` high-water. + /// `n_adds` must be at least 1 — an op-less trace compiles to `Cancelled`. + fn record_trace_of(driver: &mut JitDriver, n_adds: usize) -> OpRef { + let ctx = driver.meta.trace_ctx().expect("should be tracing"); + let i0 = OpRef::input_arg_int(0); + let c1 = ctx.const_int(1); + let mut sum = i0; + for _ in 0..n_adds { + sum = ctx.record_op(OpCode::IntAdd, &[sum, c1]); + } + let g = ctx.record_guard(OpCode::GuardTrue, &[i0], 0); + ctx.capture_snapshot_for_last_guard(&[sum], 0, 0); + ctx.set_fail_args(g, &[sum]); + sum + } + + /// Drive one compile at `key`: climb the back-edge counter until tracing + /// starts, record `n_adds` adds, compile. + fn compile_once_at(driver: &mut JitDriver, key: u64, n_adds: usize) { + for _ in 0..10 { + driver.meta.on_back_edge(key, &[0]); + if driver.meta.trace_ctx().is_some() { + break; + } + } + assert!( + driver.meta.trace_ctx().is_some(), + "back edges did not start a trace at key={key}" + ); + let sum = record_trace_of(driver, n_adds); + driver.meta.compile_loop(&[sum], ()); + } + + /// Compile twice at one green key and hand back both entries' carried + /// state. + /// + /// The second compile only reaches a `take_entry_for_replace` branch if + /// the key is traceable *again* while its `compiled_loops` entry is still + /// present. Both trace-start doors (`on_back_edge`, `force_start_tracing`) + /// refuse a key whose cell `is_compiled()`, which is + /// `get_procedure_token().is_some()` filtered on `!is_invalidated()` + /// (warmstate.rs:868-871 / :894-897). Invalidating the procedure token + /// clears that predicate and touches no `compiled_loops` entry, so it + /// re-opens tracing and leaves the entry to be displaced — the same + /// desynchronisation quasi-immut invalidation produces in production. + /// + /// Returns `(first.next_global_opref, replacement.next_global_opref, + /// replacement.loop_header_pc)`. + fn compile_twice_at_one_green_key( + key: u64, + header_pc: usize, + first_adds: usize, + second_adds: usize, + ) -> (u32, u32, Option) { + let mut driver = JitDriver::::new(2); + driver.meta.finish_setup_descrs_for_jitdrivers(); + + compile_once_at(&mut driver, key, first_adds); + let first_opref = driver.meta.compiled_loops[&key].next_global_opref; + // `compile_loop` can only ever carry `loop_header_pc` forward, never + // mint one, so stamp the first entry the way a closing bridge does. + driver.meta.record_loop_header_pc(key, header_pc); + + driver.meta.warm_state.invalidate_all(); + assert!( + driver.meta.compiled_loops.contains_key(&key), + "invalidating the token must leave the entry to be displaced" + ); + + compile_once_at(&mut driver, key, second_adds); + assert_eq!( + driver.get_stats().loops_compiled, + 2, + "the second compile did not happen" + ); + let replacement = &driver.meta.compiled_loops[&key]; + ( + first_opref, + replacement.next_global_opref, + replacement.loop_header_pc, + ) + } + + /// `compile_loop`'s replace branch must raise the fresh high-water to the + /// displaced entry's, so OpRefs minted for a later bridge stay disjoint + /// from those the retired loop's bridges already hold. + #[test] + fn a_replacing_compile_carries_the_displaced_entrys_next_global_opref() { + // The `max` is only observable when the replacement's own trace ends + // lower than the displaced entry's. Measure the short trace's + // standalone high-water on this tree rather than pinning a constant. + let mut probe = JitDriver::::new(2); + probe.meta.finish_setup_descrs_for_jitdrivers(); + compile_once_at(&mut probe, 91, 1); + let short_alone = probe.meta.compiled_loops[&91].next_global_opref; + + let (first, replacement, _) = compile_twice_at_one_green_key(77, 4242, 8, 1); + assert!( + short_alone < first, + "fixture is not discriminating: a short trace alone reaches \ + {short_alone}, the long first compile reaches {first} — the \ + carry cannot be distinguished from a fresh computation" + ); + assert_eq!( + replacement, first, + "the replacement entry must inherit the displaced entry's OpRef \ + high-water" + ); + } + + /// `compile_loop`'s replace branch must carry the displaced entry's + /// `loop_header_pc`, or every bridge aiming its closing JUMP at that + /// header is stranded. + #[test] + fn a_replacing_compile_carries_the_displaced_entrys_loop_header_pc() { + let (_, _, loop_header_pc) = compile_twice_at_one_green_key(78, 4242, 8, 1); + assert_eq!( + loop_header_pc, + Some(4242), + "the replacement entry must inherit the displaced entry's close \ + target" + ); + } + #[test] fn test_hook_get_stats_matches_real_compile_count() { let mut driver = JitDriver::::new(2); @@ -8503,6 +8888,49 @@ mod cross_loop_cut_close_tests { ); } + /// The third outcome the `Declined` arm has to keep apart: closing into a + /// key that owns no compiled target fails `has_compiled_targets` + /// (pyjitpl.py:3005 `get_procedure_token` returning nothing), so the close + /// is never evaluated at all. `result` is still its initializer here, so + /// this lands in the same `Declined` arm a rejected attempt lands in — and + /// the two must not report as one event. + /// + /// Both assertions are trace-local on purpose: `MC_DIAG` is process-global + /// and this suite runs in parallel, so slot 50 is checked through the latch + /// it always writes rather than by reading its counter. + #[test] + fn interp_origin_close_into_an_uncompiled_target_is_not_a_declined_attempt() { + let mut driver = JitDriver::::new(2); + driver.meta.finish_setup_descrs_for_jitdrivers(); + // Deliberately never passed to `compile_inner_loop`. + let never_compiled = 7061u64; + assert!( + !driver.has_compiled_loop(never_compiled), + "the fixture's premise: nothing is compiled at this key", + ); + + let before = crate::mc_diag(67); + start_outer_trace(&mut driver, 7062); + close_jumping_into(&mut driver, never_compiled); + + assert!( + crate::mc_diag(67) > before, + "a close that was never attempted must be counted under its own slot", + ); + assert!( + driver + .meta + .trace_ctx() + .is_some_and(|ctx| !ctx.cross_loop_close_declined(never_compiled)), + "nothing rejected this close, so there is no decline to latch — \ + latching it would suppress a retry the gate's own conditions allow", + ); + assert!( + driver.is_tracing(), + "the walk keeps recording, as on the declined-attempt path", + ); + } + /// Retiring the loop retires the cut mark with it, so a later loop compiled /// at the same key is not judged by its predecessor. #[test] diff --git a/majit/majit-metainterp/src/jitprof.rs b/majit/majit-metainterp/src/jitprof.rs index ebefbb3bd69..127d9e78ecd 100644 --- a/majit/majit-metainterp/src/jitprof.rs +++ b/majit/majit-metainterp/src/jitprof.rs @@ -177,15 +177,31 @@ struct TimingState { /// Order is the contract with that host: append only, never reorder. /// /// `ABORT_BRIDGE` is labelled for what it actually counts here, not for its -/// name. [`AbortReason::Generic`] maps to the same integer 13, and that is the +/// name. `AbortReason::Generic` maps to the same integer 13, and that is the /// value `jitdriver`'s reason ladder falls back to whenever a `TraceAction:: /// Abort` arrives with no `SwitchToBlackhole`, no staged reason and a trace -/// that is not too long — which is every walker decline except -/// `ForceQuasiImmutable`. So a nonzero tally here is overwhelmingly -/// "unclassified", and reading it as "a bridge aborted" sends the next reader -/// looking for bridge activity that is not there. +/// that is not too long — which is every walker decline, without exception. +/// So a nonzero tally here is overwhelmingly "unclassified", and reading it as +/// "a bridge aborted" sends the next reader looking for bridge activity that is +/// not there. /// -/// [`AbortReason::Generic`]: crate::pyjitpl::AbortReason::Generic +/// `ABORT_FORCE_QUASIIMMUT` is the one reason nothing on this side can raise, +/// because the quasi-immutable *write* path is unimplemented. `hook_setfield` +/// (`rclass.rs`) genops `jit_force_quasi_immutable`, but no `OpKind` carries it, +/// `jtransform` has no arm for it, it has no jitcode opcode, and there is +/// neither a metainterp opimpl nor a `do_force_quasi_immutable` — so unlike +/// `opimpl_jit_force_quasi_immutable` (`pyjitpl.py:1094-1118`), which raises +/// `SwitchToBlackhole(ABORT_FORCE_QUASIIMMUT)` once the mutate field is +/// non-null, no path here reaches a `SwitchToBlackhole`. The *read* half +/// (`record_quasiimmut_field` → [`majit_ir::OpCode::QuasiimmutField`]) is +/// complete, so the asymmetry is in the port, not in this table. +/// +/// That tally is therefore a true zero rather than a dead slot, and it stays +/// zero for as long as the write path is missing. Nothing currently declares a +/// `?`-suffixed field, so the gap is unreachable rather than latent — but +/// `AbortReason` gains no variant for it either, so the first `?` field anyone +/// declares gets a setfield hook with nothing downstream and no abort. Keep the +/// slot; do not synthesise a producer for it. pub const ABORT_COUNTER_KINDS: &[(i32, &str)] = &[ (counters::ABORT_TOO_LONG, "too_long"), (counters::ABORT_BRIDGE, "bridge_or_generic"), @@ -557,7 +573,7 @@ impl JitProfiler { /// `cpu.tracker.total_freed_loops += 1` parity. Fired from the /// memory manager when an evicted token represents a root loop. /// Hits `self.cpu_tracker` so the paired backend (rebound via - /// [`set_cpu_tracker`]) and profiler share the same per-CPU + /// [`Self::set_cpu_tracker`]) and profiler share the same per-CPU /// instance. pub fn inc_freed_loop(&self) { self.with_cpu_tracker(|t| t.total_freed_loops.fetch_add(1, Ordering::Relaxed)); @@ -692,7 +708,7 @@ impl JitProfiler { /// Note that backend nesting is **reversed** relative to tracing: /// `profiler.start_backend()` opens the outer scope here, while /// `debug_start('jit-tracing')` opens the outer scope in - /// [`enter_tracing`]. PyPy uses both orders depending on the + /// [`Self::enter_tracing`]. PyPy uses both orders depending on the /// callsite — this guard matches each one exactly. pub fn enter_backend(&self) -> ProfilerEventGuard<'_> { self.start_backend(); @@ -872,7 +888,7 @@ enum GuardNesting { /// RAII guard returned by [`JitProfiler::enter_tracing`] / /// [`JitProfiler::enter_backend`]. Drops by firing both the /// profiler-event close and the `debug_stop` close in the LIFO order -/// dictated by [`GuardNesting`], so the profiler stack and the debug +/// dictated by `GuardNesting`, so the profiler stack and the debug /// section stay balanced even when the surrounding body panics. #[must_use = "drop the guard to fire the paired end_* event"] pub struct ProfilerEventGuard<'a> { diff --git a/majit/majit-metainterp/src/lib.rs b/majit/majit-metainterp/src/lib.rs index 9ed643fd862..69c2559bea3 100644 --- a/majit/majit-metainterp/src/lib.rs +++ b/majit/majit-metainterp/src/lib.rs @@ -135,7 +135,7 @@ pub use jitdriver::{ pub use majit_backend::CompiledTraceInfo; pub use pyjitpl::{eval_binop_f, eval_binop_i, eval_float_cmp, eval_unary_f, eval_unary_i}; // Re-export the canonical translate-side Assembler so macro-emitted -// state-field JIT setup (e.g. `__JitMeta::install_canonical_liveness`) +// state-field JIT setup (e.g. `__JitMeta_::install_canonical_liveness`) // can build a fresh Assembler without forcing each user crate to // declare a `majit-translate` dependency. The same pattern is used // for `JitCode` / `BhDescr` re-exports above (`jitcode/mod.rs:4`). @@ -205,7 +205,6 @@ pub fn jit_strict_mode() -> bool { *STRICT } -// ── Cached diagnostic env-var helpers ──────────────────────────────── // // Each env var is read once and cached via OnceLock so hot paths // (back-edge, guard-failure, optimizer) never re-acquire the global @@ -231,6 +230,15 @@ pub fn nbody_debug_enabled() -> bool { *FLAG.get_or_init(|| std::env::var_os("PYRE_NBODY_DEBUG").is_some()) } +/// Constness of every index arriving at `TraceCtx::get_arrayitem_vable_index`. +/// See that function for what the counts do and do not establish — in +/// particular, it is a shared callee and the reading cannot be attributed to a +/// caller family without pairing it with a call-site probe. +pub fn vable_idx_probe_enabled() -> bool { + static FLAG: std::sync::OnceLock = std::sync::OnceLock::new(); + *FLAG.get_or_init(|| std::env::var_os("PYRE_VABLE_IDX_PROBE").is_some()) +} + pub fn mptrace_enabled() -> bool { static FLAG: std::sync::OnceLock = std::sync::OnceLock::new(); *FLAG.get_or_init(|| std::env::var_os("MAJIT_MPTRACE").is_some()) @@ -325,6 +333,271 @@ pub fn step_limit() -> u64 { *VAL } +/// A dispatch arm whose body `#[jit_interp]` could not lower, so the macro +/// substituted a bare `BC_ABORT` sub-JitCode for it. +/// +/// The substitution is deliberate — `make_jitcodes()` builds the portal even +/// when one opcode lowers to a residual the tracer cannot follow, so that +/// opcode aborts the trace instead of disabling the JIT for every other +/// opcode. What was missing is any record of WHICH opcode. At execution the +/// only surviving signal is `abort_trace` falling back to +/// `AbortReason::Generic` → `counters::ABORT_BRIDGE` → `MC_DIAG` slot 41, +/// which `jitprof.rs` documents as overwhelmingly unclassified; by then the +/// arm's identity is gone. This channel keeps it, named, from the point the +/// stub is built. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct DegradedDispatchArm { + /// The machine's declared `state = T` type name (`VmStateF`), which is + /// what identifies one `#[jit_interp]` mainloop among several. + pub interp: &'static str, + /// The arm's match pattern as written in the source (`OP_RETURN_F`). + pub arm: &'static str, + /// Why the body did not lower, staged by the macro at the emitting site. + pub reason: &'static str, +} + +/// The mechanism that refused a dispatch arm, as a value a gate can compare. +/// +/// [`DegradedDispatchArm::reason`] is prose staged by the macro at the emitting +/// site, naming both the refusing mechanism and the offending source. A gate +/// pinning the whole string breaks on every rewording; a gate pinning only the +/// arm NAME cannot see a change of mechanism at all. +/// +/// Pinning only the arm name is insufficient because the arm can remain +/// degraded while its refusing mechanism changes. Tests that care about the +/// mechanism compare this enum as well as the arm name. +// `Ord` so a gate can sort `(arm, RefusalKind)` pairs into a stable order before +// comparing. Arm names lead every such tuple, so the derived variant order never +// decides a comparison; it exists to make the pair sortable at all. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub enum RefusalKind { + /// The arm writes a green this lowering path cannot carry back to the + /// caller. This refusal stops lowering, so it is the arm's OUTERMOST + /// blocker; any later ones are reported behind it in the same `reason`. + /// Read them with [`refusal_kinds`], not [`refusal_kind`]. + GreenWriteback, + /// The lowerer has no expression for one of the arm's statements. + UnlowerableStmt, + /// The arm encloses a `break`/`continue` that cannot be lowered in place. + EnclosedBreakContinue, + /// The arm encloses a `return` that cannot be lowered in place. Sibling of + /// [`Self::EnclosedBreakContinue`]; the lowerer guards the two separately. + EnclosedReturn, + /// The arm body has no statements to lower. + EmptyBody, + /// The arm has no `pc` binding for the pc-return writeback. + NoPcBinding, + /// Lowering resolved an unsupported call policy at install time. + /// + /// The only family raised at INSTALL rather than by the statement lowerer, + /// so it carries no `arm body {what}: {spelling}` shape and names no + /// offending statement — a gate keyed on a source snippet gets nothing here. + UnsupportedCallPolicy, + /// The lowerer's fallback wording, reached when a refusal site never set a + /// specific reason. + /// + /// Deliberately its own variant rather than folded into + /// [`Self::Unclassified`]: the fallback's own doc keeps the exact old string + /// so that sites a refactor never reached stay greppable. Reaching it means + /// "some refusal site is still unconverted", which is a different fact from + /// "majit grew a mechanism nobody has classified". + UnreachedLoweringFallback, + /// No known fragment matched. + /// + /// Deliberate, and the reason this is an enum with an explicit fallthrough + /// rather than a permissive default: bucketing an unrecognised reason into a + /// known family reproduces the defect this type exists to close — a new + /// mechanism arriving under an unchanged value, invisible to every gate. + /// Reaching this variant should fail a gate, not be tolerated by one. + Unclassified, +} + +/// Classify a [`DegradedDispatchArm::reason`] by the mechanism that refused it. +/// +/// Keyed on the shortest fragment that names the mechanism, so rewording the +/// prose around it does not break the gates. Rewording a *fragment* is +/// expected to require an edit here: re-point it rather than relaxing a caller's +/// assertion. +/// +/// One home, because the alternative was measured too — this classifier existed +/// as four copied literals across the example gates and was headed for nine. N +/// copies of a predicate drift apart silently and no single reader can see the +/// divergence, which is the same disease the gates themselves exist to catch. +/// `tests/degraded_arm_refusal_kind.rs` pins the mapping against reasons +/// recorded from the example crates. +/// The families are the macro's whole reachable refusal vocabulary, read off the +/// producers rather than off the reasons that happen to be emitted today — three +/// of the eight are observed in the example corpus and five are not. Listing +/// only the observed ones would make [`RefusalKind::Unclassified`] mean "not +/// seen yet" instead of "majit grew a mechanism", which is the false alarm this +/// type exists to avoid. +/// Joins the refusals accumulated into one [`DegradedDispatchArm::reason`]. +/// +/// Cross-crate contract: mirrors `REFUSAL_SEPARATOR` in majit-macros' +/// `jitcode_lower::lower_stmt`. A proc-macro crate cannot export a value to its +/// runtime, so the two literals are kept in step by +/// `accumulated_reason_splits_into_its_refusals` in +/// `tests/degraded_arm_refusal_kind.rs`, which splits a reason recorded by a +/// real crate and would read one segment where it expects two. +pub const REFUSAL_SEPARATOR: &str = " || "; + +/// The refusals in `reason`, in the order lowering hit them. +pub fn refusal_reasons(reason: &str) -> impl Iterator { + reason.split(REFUSAL_SEPARATOR) +} + +/// Every refusal's family, in order. `refusal_kind(r) == refusal_kinds(r)[0]`. +/// +/// This is the accessor that makes the family distribution a measurement rather +/// than a lower bound: an arm reports its outermost refusal in `reason`'s head, +/// and the rest of the string is what lowering found behind it. +pub fn refusal_kinds(reason: &str) -> Vec { + refusal_reasons(reason).map(refusal_kind_of_one).collect() +} + +/// Classify the FIRST refusal. +/// +/// Must split before matching, and this is not a stylistic preference. The +/// classifier below is an ORDERED chain of `contains` tests, so on an +/// accumulated reason an un-split match would answer with whichever fragment +/// the chain tests earliest — not with the refusal lowering actually hit first. +/// Keeping the first refusal at the head of the string is necessary for that +/// and not sufficient: without this split, adding accumulation silently +/// re-classifies every previously landed pin. +pub fn refusal_kind(reason: &str) -> RefusalKind { + refusal_kind_of_one(refusal_reasons(reason).next().unwrap_or(reason)) +} + +fn refusal_kind_of_one(reason: &str) -> RefusalKind { + if reason.contains("encloses a `return`") { + RefusalKind::EnclosedReturn + } else if reason.contains("encloses a `break`") { + RefusalKind::EnclosedBreakContinue + } else if reason.contains("writes a green") { + RefusalKind::GreenWriteback + } else if reason.contains("cannot express") { + RefusalKind::UnlowerableStmt + } else if reason.contains("no statements to lower") { + RefusalKind::EmptyBody + } else if reason.contains("no `pc` binding") { + RefusalKind::NoPcBinding + } else if reason.contains("unsupported call policy") { + RefusalKind::UnsupportedCallPolicy + } else if reason.contains("could not be lowered to a sub-JitCode") { + RefusalKind::UnreachedLoweringFallback + } else { + RefusalKind::Unclassified + } +} + +static DEGRADED_DISPATCH_ARMS: std::sync::Mutex> = + std::sync::Mutex::new(Vec::new()); + +/// Record that `arm` of `interp` was emitted as an abort stub. +/// +/// Called from the `__dispatch_jitcode_*` body, so it fires when the dispatch +/// JitCode is installed rather than when the trace later walks into the stub. +/// Entries are deduplicated by content: a dispatch JitCode may be built more +/// than once per process, and the fact reported is per-arm, not per-build. +pub fn record_degraded_dispatch_arm(interp: &'static str, arm: &'static str, reason: &'static str) { + let entry = DegradedDispatchArm { + interp, + arm, + reason, + }; + let mut arms = DEGRADED_DISPATCH_ARMS + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()); + if arms.contains(&entry) { + return; + } + if majit_log_enabled() { + eprintln!( + "[jit] degraded dispatch arm: {}::{} lowered to an abort stub ({})", + entry.interp, entry.arm, entry.reason + ); + } + arms.push(entry); +} + +/// Snapshot of every dispatch arm recorded as degraded so far. +pub fn degraded_dispatch_arms() -> Vec { + DEGRADED_DISPATCH_ARMS + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()) + .clone() +} + +/// The shape of a compiled loop body, as a tier gate needs to read it. +/// +/// A compile counter says a trace was *compiled*; an op count says the body is +/// not the degenerate `Finish()`. Neither says the body is a **loop**. A body +/// can be several ops long, carry no back edge, and be cut short by a guard +/// whose only outcome is a bail-out — a compiled trace that can never run a +/// second iteration. That is what a bare `compiles > 0 && ops_after > 1` +/// recipe accepts, and it is what this type is for. +/// +/// Build it from the opcodes `JitDriver::set_on_compile_loop` hands the +/// callback, and read it in the same lock window as the counters: the shape is +/// as process-global as they are. +/// +/// Only meaningful when the gate's subject **contains a loop**. On a +/// straight-line program a body with no back edge is the correct answer, not a +/// defect, and asserting [`Self::closes_a_loop`] there rejects a healthy +/// compile. +/// +/// `Label` is deliberately not part of this. An optimized body can close its +/// back edge without one — tinyframe's carries a `Jump` and no `Label` — so a +/// predicate that also demanded a `Label` would reject a healthy body. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct LoopBodyShape { + /// The body carries a `Jump`: it reaches its own back edge. + pub has_jump: bool, + /// The body carries a `GuardAlwaysFails`: a guard with no passing outcome, + /// so control leaves the compiled body where it sits. + pub has_always_fails: bool, +} + +impl LoopBodyShape { + /// Read the shape off an optimized body. + pub fn of(opcodes: &[majit_ir::OpCode]) -> Self { + Self { + has_jump: opcodes.contains(&majit_ir::OpCode::Jump), + has_always_fails: opcodes.contains(&majit_ir::OpCode::GuardAlwaysFails), + } + } + + /// The body reaches a back edge and is not cut short by a guard that + /// cannot pass. + pub fn closes_a_loop(self) -> bool { + self.has_jump && !self.has_always_fails + } + + /// Why [`Self::closes_a_loop`] is false, phrased for an assertion message. + /// `None` when it is true. + /// + /// Both fields get their own arm, including the one where both are false at + /// once. A first-match chain would report only `has_jump` there, and that is + /// the weaker of the two facts: "no `Jump`" is true of every straight-line + /// body, while `GuardAlwaysFails` names the specific pathology. The reader + /// of a failing gate has nothing but this string. + pub fn why_not(self) -> Option<&'static str> { + match (self.has_jump, self.has_always_fails) { + (true, false) => None, + (true, true) => { + Some("carries a `GuardAlwaysFails`, so control leaves it rather than looping") + } + (false, true) => Some( + "carries no `Jump` AND carries a `GuardAlwaysFails`: it neither reaches a back \ + edge nor has a passing outcome at that guard", + ), + // Also the [`Default`] value, which the gates reset to before the + // hook runs — so this arm reads as "the hook never fired" too. + (false, false) => Some("carries no `Jump`, so it never reaches a back edge"), + } + } +} + /// Result of tracing a single instruction. /// /// Returned by the interpreter's `trace_instruction()` function @@ -544,10 +817,22 @@ pub fn green_key_hash(values: &[i64]) -> u64 { /// as identity over the pointer bits. Mirrors the typed schema that /// `#[jit_interp]` macro-emitted code now produces via /// `GreenKey::with_types`. +/// +/// Folds `green_uhash_step` over the slices directly rather than building a +/// [`majit_ir::GreenKey`] to hash and drop — the key was never returned, so the +/// two `to_vec()`s were pure overhead. Equals +/// `GreenKey::with_types(values.to_vec(), types.to_vec()).hash_u64()`, +/// including the short-`types` padding: `GreenKey::get_uhash` reads types with +/// `.get(i).unwrap_or(Int)`, so a ragged call pads rather than truncates. #[inline] pub fn green_key_hash_typed(values: &[i64], types: &[majit_ir::GreenType]) -> u64 { debug_assert_eq!(values.len(), types.len()); - majit_ir::GreenKey::with_types(values.to_vec(), types.to_vec()).hash_u64() + let mut x = majit_ir::GREEN_UHASH_SEED; + for (i, &value) in values.iter().enumerate() { + let tp = types.get(i).copied().unwrap_or(majit_ir::GreenType::Int); + x = majit_ir::green_uhash_step(x, tp, value); + } + x } // ── we_are_jitted / JIT mode flag ── @@ -588,7 +873,7 @@ pub fn register_stack_almost_full_hook(f: fn() -> bool) { /// Number of `MC_DIAG` slots. Declared once so the counter array and /// `MC_DIAG_LABELS` cannot drift in length — a mismatch is a compile error. -pub const MC_DIAG_SLOTS: usize = 61; +pub const MC_DIAG_SLOTS: usize = 75; /// Diagnostic-only guard-failure → bridge-trace gate tallies, read out via /// the `pyre_jit_mc_diag` guest export. Index legend: 0 = must_compile_with_values @@ -642,6 +927,24 @@ pub const MC_DIAG_SLOTS: usize = 61; /// optimizer deferred an `InvalidLoop` on the root trace, 48 = the root /// `compile_loop` returned `Err`, 49 = a bridge compile came back `Aborted`. /// +/// 70 completes that decomposition from the other end: the abort where *no* +/// reason was staged at all and the code fell back to `AbortReason::Generic`, +/// counted where the fallback is chosen (`pyjitpl.rs abort_trace`, +/// `jitdriver.rs`'s reason ladder). Slot 41 is the total of every +/// `aborted_tracing(ABORT_BRIDGE)` and cannot separate a real bridge giveup +/// from that default, which is what made `abrt_bridge=1` read as evidence of +/// bridge activity that was not there. +/// +/// 70 is NOT `41 - (47+48+49)`, and that subtraction must not be used: +/// 47-49 are bumped at the RAISE, immediately before +/// `return Err(SwitchToBlackhole::giveup())`, while 41 is bumped at the CATCH +/// inside `aborted_tracing`. A giveup raised on a path that never reaches a +/// catch is counted by one and not the other, in a direction nothing +/// announces. Each slot counts its own event at its own site, so a +/// disagreement between them is readable rather than silent — if 47+48+49 ever +/// exceeds what 41 can account for, that difference is a giveup that was +/// raised and never accounted, which has no other detector. +/// /// 50 = `close_bridge` declined while closing a bridge, 51 = bridge close found /// no compiled target, 52 = abort after a declined bridge attempt, 53 = /// `compile_trace` called `compile_bridge` and it returned false. @@ -676,7 +979,115 @@ pub const MC_DIAG_SLOTS: usize = 61; /// skipped its close, and `find_biggest_function` mis-sizes every frame after /// it. A `debug_assert!` cannot see that; the imbalance is only visible across /// a whole run. +/// +/// 61-63 are `maybe_start_tracing`'s two early returns and their denominator, +/// which is 61 and is bumped unconditionally at entry so the two refusals can +/// be read as fractions rather than bare totals: 62 = `sync_before` returned +/// false, 63 = `live_values_match_descriptor` returned false. Both return +/// before `on_back_edge_typed`, so `61 - 62 - 63` is the number of calls that +/// reach the hotness counter at all. The two are counted separately because a +/// deferral past the counter is worth the cost of whichever refusal dominates, +/// and nothing currently says which does; a slot reading 0 across the corpus +/// says its refusal never fires, which is a different design than one that +/// fires often. +/// +/// 64-66 split slot 23, which is bumped on the disjunction +/// `cell.is_compiled() || cell.is_tracing()` and so cannot attribute: the +/// `is_compiled()` term fires on every function-entry probe of every +/// already-compiled key, which is the normal high-rate case, so slot 23 +/// climbs in a healthy tree. Slot 23 stays their total. The two terms are +/// evaluated independently rather than short-circuited, and both are counted, +/// so a cell that is compiled *and* tracing bumps 64 and 65 both — they +/// partition nothing and must not be subtracted from each other. +/// 64 = the `is_compiled()` term was true, 65 = the `is_tracing()` term was +/// true. +/// +/// CORRECTED. An earlier version of this legend said 66 was the +/// discriminator and 65 alone was not, on the premise that "a cell's +/// `JC_TRACING` is legitimately set while a trace is genuinely running, so a +/// non-zero 65 is the healthy reading". **That premise is false at the only +/// production call site.** `should_trace_function_entry` is reached from +/// exactly one production caller, `pyre-jit`'s `try_function_entry_jit`, which +/// guards on `!driver.is_tracing()`. That resolves to +/// `MetaInterp::tracing.is_some()` — one global `Option`, not a per-cell flag — +/// so while the engine is tracing the caller returns a frame earlier and this +/// gate is never reached. +/// +/// ⇒ **In production, every bump of 65 is a cell holding `JC_TRACING` while no +/// trace is running.** 65 is itself the leak signal; there is no healthy 65 at +/// this site. (The function can still be called mid-trace directly, and the +/// unit tests do, so the distinction is production-path-specific.) +/// +/// 66 splits those leaks by AGE rather than into leak-vs-healthy. It counts the +/// subset of 65 whose `cell.tracing_generation` is strictly older than the warm +/// state's, i.e. a session a later `start_tracing_cell` superseded. Only +/// `start_tracing_cell`/`start_tracing_cell_for_key` increment the generation; +/// the `mark_as_being_traced` pair stamp it without incrementing. So `65 > 0` +/// with `66 == 0` is a flag leaking from the most recent session — if anything +/// the more direct miss, since that is exactly the clear the tracing teardown +/// is responsible for. +/// +/// A stale `JC_TRACING` can only sit on a cell that once started tracing. The +/// door counters alone do not prove that the examined cell ever started a +/// trace. `caro_funcentry` must also be nonzero. The keys that trace at back +/// edges are distinct from function-entry keys because `pc` is folded as a green +/// (`majit_ir::pypyjit_greenkey_uhash`), so a loop-header pc and an entry pc +/// are different keys. The probed set and the ever-traced set were disjoint. +/// +/// ⇒ **TWO WITNESSES ARE NEEDED AND THEY ARE NOT THE SAME.** +/// +/// * DOOR-RAN: slots 23, 24 and 25 are each bumped at exactly one site, all +/// three inside `should_trace_function_entry`, so `23 + 24 + 25 > 0` proves +/// the gate executed. **Necessary, not sufficient.** It is also a LOWER BOUND +/// on entries, not a denominator — two `DONT_TRACE_HERE` exits are uncounted +/// — so it cannot carry a rate either. +/// * ARMED: `caro_funcentry` (slot 19) must be `> 0`, or no probed cell was +/// ever in a position to hold the flag. The bump sits at the top of +/// pyre-jit's `compile_and_run_once` above every early return, so a zero +/// means that call was never reached, not that it returned early. But the +/// CALL is unconditional while the SLOT is selected by the `start` arm +/// (`BackEdge => 18`, `FunctionEntry => 19`, `eval.rs:8991-8994`), so 19 +/// counts function-entry starts ONLY — a back-edge-only workload leaves 19 +/// at 0 while 18 climbs. Read 19, never 18 + 19, and never "the arm was +/// entered". +/// +/// The two witnesses sit in one function and in this order: +/// `try_function_entry_jit` calls the door at `eval.rs:9397` and reaches +/// `compile_and_run_once(.., FunctionEntry)` 235 lines later at `:9632`. So in +/// the worked example above the door ran 2540 times and control never once +/// reached the compile call — every probe declined at the door or between it +/// and `:9632`. That is the mechanism behind "the probed set and the +/// ever-traced set were disjoint", and it is also why 23 + 24 + 25 cannot +/// stand in for 19: they are counted on the near side of that gap. +/// +/// `65 == 0` refutes only with **both**. With either missing the reading is +/// NOT EXERCISED — never "clean". The general form, from the first three +/// revisions of this legend, which failed the same way: an exercise witness +/// must witness the ARMING POPULATION, not that the instrument ran. +/// +/// CORRECTED A THIRD TIME, and this one is a different failure. The +/// condition above was right; the sentence describing slot 19 said it was +/// "bumped unconditionally at the top of `compile_and_run_once`", which +/// drops the arm selection and so describes a witness with twice the +/// coverage of the one that exists. The verdict does not move — `19 == 0` +/// is still NOT EXERCISED either way — but the reader's model of the run +/// does, in two ways that matter. On a back-edge workload `18 > 0` with +/// `19 == 0` is flatly impossible under the wrong reading, so a reader +/// resolves the contradiction by distrusting a healthy instrument or the +/// run itself; and a reader who believes any `compile_and_run_once` entry +/// bumps 19 will accept `18` (or `18 + 19`) as an arming witness, which it +/// is not. **A witness's stated domain is part of the witness, and it is +/// the part that gets applied.** (Found by sizes-2 against a census of +/// production readouts; re-verified here at `eval.rs:8991-8994`.) +/// +/// And 64 is narrower than "a compiled callee was probed": the caller has +/// already excluded `has_runnable_compiled_loop` (a driver-side meta table) +/// while 64 reads `cell.is_compiled()` (the warmstate cell token). The two +/// disagree in both directions, so **64 counts cell-vs-meta disagreement.** pub static MC_DIAG: [std::sync::atomic::AtomicU64; MC_DIAG_SLOTS] = { + // `AtomicU64` is not `Copy`, but a repeat expression accepts a path to a + // const item, so the length is taken from `MC_DIAG_SLOTS` rather than from + // a spelled-out row of elements that has to be recounted by hand. const Z: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); [Z; MC_DIAG_SLOTS] }; @@ -745,6 +1156,59 @@ pub const MC_DIAG_LABELS: [&str; MC_DIAG_SLOTS] = [ "ptp_push", "ptp_pop", "forced_never_compiled", + "mst_entered", + "mst_sync_before_false", + "mst_live_values_mismatch", + "stfe_declined_compiled", + "stfe_declined_tracing", + "stfe_declined_tracing_stale", + // Appended, never inserted: a slot's index is its position here, and every + // `mc_diag_bump(N)` in the tree names that position. + "bridge_unattempted_close", + // The producer side of the cross-loop close, which slots 50 and 67 consume. + // They sum: 68 = 69 + (target not compiled), and 69 = 70 + (already latched + // declined). Without 68 a zero at 70 cannot say whether the decision was + // never reached or was reached and answered no — and the corpus reads the + // second case, not the first. + "xloop_close_decision_reached", + "xloop_close_target_compiled", + "xloop_close_published", + "abrt_unclassified_default", + // The three outcomes of one `InvalidLoop` reaching `compile_loop`'s handler. + // They PARTITION that event: the handler either cancels (72) or runs the + // unroll-free retry, which rescues (73) or abandons (74). So + // `72 + 73 + 74` is the number of `InvalidLoop`s raised anywhere in the + // optimizer — a total that `MAJIT_LOG=1` prints independently as + // `[jit] abort trace at key=… (InvalidLoop: …)`, which makes these three + // cross-checkable against a channel that does not go through them. + // + // They are sited at the CONVERGENCE point, not at any raise site. The + // optimizer constructs `InvalidLoop` at ~24 places across six files, in at + // least two families that reach different crates, so a counter at any one + // raise site measures its own family and reads as a corpus figure. Every + // one of those sites arrives here. + // + // WHICH OF 72 AND 73 ANSWERS "how often was the unrolled compile + // abandoned" IS DECIDED BY `max_unroll_loops`, AND AT A LIMIT OF 0 IT IS + // 73. The handler cancels while `cancelled_too_many_times()` is false, i.e. + // while `cancel_count <= max_unroll_loops`, so at a limit of L: + // + // L > 0 — the first L events for a key land in 72 and tracing continues; + // only the L+1'th runs the retry. 73 then counts keys that + // reached the retry rather than abandoned compiles, and a key + // that never exceeds L never appears in 73 at all. + // L == 0 — `1 > 0` holds on the very first `InvalidLoop`, so 72 is never + // bumped and every abandoned unrolled compile goes straight to + // the retry. 73 is then EXACT, and a zero in 72 is 0-out-of-0 + // rather than a report that nothing was abandoned. + // + // A crate that never passes `"max_unroll_loops"` to `jit::set_param` takes + // L from `warmstate::DEFAULT_MAX_UNROLL_LOOPS`, so read that constant before + // reading 72. `TraceCtx::declined_cross_loop_closes` draws its own + // consequence from the same one. + "unroll_cancelled_invalid_loop", + "unroll_free_retry_rescued", + "unroll_free_retry_failed", ]; /// Render every [`MC_DIAG`] tally as space-separated `label=count` pairs. @@ -963,5 +1427,103 @@ mod tests { ); assert_eq!(hash, gk.hash_u64()); } + + /// The direct fold must equal the `GreenKey`-building form it replaced, at + /// every arity and type mix — not just the one pair pinned above. + /// + /// Str / Unicode are excluded: `hash_whatever` routes them through a + /// registered resolver, so they are not hashable from a bare i64 here. + /// + /// The short-`types` padding branch (`.get(i).unwrap_or(Int)`) is NOT + /// covered: `debug_assert_eq!` makes a ragged call panic in a test build, + /// so that branch is reachable only in release. It is preserved by + /// construction, mirroring `GreenKey::get_uhash`. + #[test] + fn green_key_hash_typed_equals_the_greenkey_form_across_arities_and_types() { + use majit_ir::GreenType; + let palette = [GreenType::Int, GreenType::Float, GreenType::Ref]; + let values: [i64; 6] = [0, 1, -1, i64::MAX, i64::MIN, (2.5f64).to_bits() as i64]; + + let mut checked = 0usize; + for arity in 0..=values.len() { + for (offset, tp) in palette.iter().enumerate() { + let vals: Vec = values[..arity].to_vec(); + // Rotate the type assignment so a given arity is exercised with + // several distinct type vectors, not one uniform one. + let types: Vec = (0..arity) + .map(|i| palette[(i + offset) % palette.len()]) + .collect(); + let folded = green_key_hash_typed(&vals, &types); + let built = majit_ir::GreenKey::with_types(vals.clone(), types.clone()).hash_u64(); + assert_eq!( + folded, built, + "arity {arity}, offset {offset} (lead {tp:?}): fold {folded} != built {built}", + ); + checked += 1; + } + } + // Guard the guard: a silently empty sweep would assert nothing. + assert_eq!(checked, 7 * 3, "sweep did not cover the intended cases"); + } + + /// All four `(has_jump, has_always_fails)` combinations, because the + /// both-false one is the arm a first-match chain loses and it is the only + /// one that has to name two facts. + #[test] + fn why_not_names_every_false_term() { + let shape = |has_jump, has_always_fails| LoopBodyShape { + has_jump, + has_always_fails, + }; + + assert_eq!(shape(true, false).why_not(), None); + assert!(shape(true, false).closes_a_loop()); + + let jump_and_fails = shape(true, true).why_not().expect("does not close a loop"); + assert!( + jump_and_fails.contains("GuardAlwaysFails") && !jump_and_fails.contains("no `Jump`"), + "a body with a back edge must not be told it has none: {jump_and_fails}", + ); + + let neither = shape(false, true).why_not().expect("does not close a loop"); + assert!( + neither.contains("no `Jump`") && neither.contains("GuardAlwaysFails"), + "both terms are false, so both must be named: {neither}", + ); + + let no_jump = shape(false, false) + .why_not() + .expect("does not close a loop"); + assert!( + no_jump.contains("no `Jump`") && !no_jump.contains("GuardAlwaysFails"), + "nothing failed at a guard here, so the message must not claim one: {no_jump}", + ); + + // The reset sentinel the gates write before the hook runs is this arm. + assert_eq!(LoopBodyShape::default().why_not(), Some(no_jump)); + } + + /// `of()` reads both fields off the same slice, so a body carrying both + /// opcodes must set both — the census that feeds every gate. + #[test] + fn shape_of_reads_both_opcodes() { + use majit_ir::OpCode; + + assert_eq!(LoopBodyShape::of(&[]), LoopBodyShape::default()); + assert_eq!( + LoopBodyShape::of(&[OpCode::Jump]), + LoopBodyShape { + has_jump: true, + has_always_fails: false, + }, + ); + assert_eq!( + LoopBodyShape::of(&[OpCode::GuardAlwaysFails, OpCode::Jump]), + LoopBodyShape { + has_jump: true, + has_always_fails: true, + }, + ); + } } pub(crate) mod resumecode; diff --git a/majit/majit-metainterp/src/optimizeopt/guard.rs b/majit/majit-metainterp/src/optimizeopt/guard.rs index 9c1023cb9b1..6bea2ffd194 100644 --- a/majit/majit-metainterp/src/optimizeopt/guard.rs +++ b/majit/majit-metainterp/src/optimizeopt/guard.rs @@ -317,7 +317,7 @@ impl Guard { /// subtype tag). Mirrors RPython's: /// descr = myop.getdescr() /// descr.copy_all_attributes_from(other.op.getdescr()) - /// myop.setfailargs(otherop.getfailargs()[:]) + /// `myop.setfailargs(otherop.getfailargs()[:])` /// where `descr` is the strengthened guard's *own* ResumeGuardDescr. pub fn inhert_attributes(&mut self, other: &Guard) { // guard.py:118 diff --git a/majit/majit-metainterp/src/optimizeopt/mod.rs b/majit/majit-metainterp/src/optimizeopt/mod.rs index f6e277ee805..85155e8dc50 100644 --- a/majit/majit-metainterp/src/optimizeopt/mod.rs +++ b/majit/majit-metainterp/src/optimizeopt/mod.rs @@ -740,6 +740,12 @@ pub struct OptContext { pub snapshot_frame_sizes: SnapshotFrameSizes, /// Per-guard virtualizable boxes from tracing-time snapshots. pub snapshot_vable_boxes: SnapshotBoxes, + /// resume.py:399-402 `minimum_virtualizable_size`, handed to + /// `memo.number()` in `store_final_boxes_in_guard`. `-1` disables the + /// `resume.py:236-239` length check; a non-negative value arms it. + /// Copied from `Optimizer::minimum_virtualizable_size`, which + /// `default_pipeline_with_virtualizable` derives from the vable config. + pub minimum_virtualizable_size: i64, /// Per-guard virtualref boxes from tracing-time snapshots. /// resume.py:243-247 _number_boxes consumes vref_array as a section /// after vable_array. opencoder.py:767 records vref_boxes here. @@ -1688,6 +1694,9 @@ impl OptContext { snapshot_boxes: Vec::new(), snapshot_frame_sizes: Vec::new(), snapshot_vable_boxes: Vec::new(), + // resume.py:401-402: `-1` until an Optimizer built with a vable + // config overwrites it in `optimize_with_constants_and_inputs_at`. + minimum_virtualizable_size: -1, snapshot_vref_boxes: Vec::new(), snapshot_frame_pcs: Vec::new(), @@ -2307,6 +2316,9 @@ impl OptContext { snapshot_boxes: Vec::new(), snapshot_frame_sizes: Vec::new(), snapshot_vable_boxes: Vec::new(), + // resume.py:401-402: `-1` until an Optimizer built with a vable + // config overwrites it in `optimize_with_constants_and_inputs_at`. + minimum_virtualizable_size: -1, snapshot_vref_boxes: Vec::new(), snapshot_frame_pcs: Vec::new(), @@ -3062,7 +3074,7 @@ impl OptContext { // `materialize_operand_at`, not `from_bound_op`: a // const-folded entry carries an inline-Const pos, // which resolves to its Const operand. The map keys by this - // res operand (#146/S8); the single-op re-export lookup + // res operand (unsupported-green-type/S8); the single-op re-export lookup // (pure.rs) reproduces it via `materialize_operand_at(source)`. // The operand's identity is the canonical `_forwarded` host // Rc (a synthetic producer registered on first @@ -3189,7 +3201,7 @@ impl OptContext { // `produced` (OpRef dual-key: source + result_opref) is internal // scaffolding for `dep_or_materialize` below, which resolves a // dependency arg by its replay position. `builder_entries` is the - // #146/S8 builder map: ONE entry per short box keyed by the Phase-1 + // unsupported-green-type/S8 builder map: ONE entry per short box keyed by the Phase-1 // carried res box (`produced_op.res`, the same Rc the produce loop // reads as `self.res`). The carried box is invariant to the // invented-name replay-position aliasing the dual key compensates for, @@ -4305,7 +4317,7 @@ impl OptContext { .push((opcode, result, arg0, Some(descr))); } - /// info.py:557 pure_from_args(ARRAYLEN_GC, [op], ConstInt(len)) + /// info.py:557 `pure_from_args(ARRAYLEN_GC, [op], ConstInt(len))` pub fn pure_from_args_arraylen(&mut self, array_ref: OpRef, length: i64) { let len_ref = self.emit_constant_int(length); self.register_pure_from_args1(OpCode::ArraylenGc, array_ref, len_ref); @@ -4794,14 +4806,16 @@ impl OptContext { } } - /// Native `Operand`-in / `Operand`-out resolver: the [`Operand`] form of - /// [`resolve_box_box`](Self::resolve_box_box). Resolves an operand to its + /// Native `Operand`-in / `Operand`-out resolver. Resolves an operand to its /// `_forwarded` terminal WITHOUT minting a wrapper — the box-native walk /// (`arg.get_box_replacement`) and the `OpRef`-store fallback - /// ([`get_box_replacement_operand`](Self::get_box_replacement_operand)) both - /// stay on the `Operand` carrier. Mirrors `resolve_box_box`'s two arms: a - /// bound / const operand walks its own chain and defers to the store only - /// when it self-resolves; an unbound operand resolves positionally. + /// (`get_box_replacement_operand`) both + /// stay on the `Operand` carrier. Two arms: a bound / const operand walks + /// its own chain and defers to the store only when it self-resolves; an + /// unbound operand resolves positionally. + /// + /// `Operand` is the only carrier — the Box-carrier resolver family this + /// used to be described against was removed with `BoxRef` itself. /// /// The heal keys on the bound `Op` host directly off the `Operand`; the /// native walk (`arg.get_box_replacement`) is byte-identical to the legacy @@ -4823,12 +4837,12 @@ impl OptContext { } } - /// `Option`-returning native sibling of [`resolve_operand_operand`], the - /// [`Operand`] form of [`resolve_box_box_opt`](Self::resolve_box_box_opt): + /// `Option`-returning native sibling of + /// [`resolve_operand_operand`](Self::resolve_operand_operand): /// `None` when the operand is a NONE / unresolved position so callers can /// supply their own unbound fallback (a sentinel arg box, a /// `materialize_*` mint) instead of tripping the position-only panic in the - /// total [`get_box_replacement_operand`](Self::get_box_replacement_operand). + /// total `get_box_replacement_operand`. pub fn resolve_operand_operand_opt(&self, arg: &Operand) -> Option { self.heal_arg_to_canonical(arg); @@ -5013,8 +5027,8 @@ impl OptContext { /// ``` /// /// The full lazy-install path (missing-info → `IntBound.unbounded()`) - /// lives in [`Self::getintbound`]; this snapshot is the side-effect- - /// free reader used by gates and read-only intersect comparisons. + /// lives in [`Self::getintbound_handle`]; this snapshot is the side- + /// effect-free reader used by gates and read-only intersect comparisons. pub fn peek_intbound_box( &self, op: &Operand, @@ -5031,7 +5045,7 @@ impl OptContext { /// Clones the inner `PtrInfo` out of its `Rc>` cell, so /// the result is independent of subsequent mutations. For RPython /// object identity (`same_info`, in-place mutation propagation), - /// use [`peek_ptr_info_handle`] which returns the live `Rc`. + /// use [`Operand::ptr_info_handle`], which returns the live `Rc`. pub fn peek_ptr_info(&self, op: &Operand) -> Option { op.get_box_replacement(false).ptr_info().map(|p| p.clone()) } @@ -6338,7 +6352,14 @@ impl OptContext { // resume.py:389-452: delegate to ResumeDataVirtualAdder.finish() let env = OptBoxEnv { ctx: self }; let mut memo = ResumeDataLoopMemo::new(); - let Ok(numb_state) = memo.number(&snapshot, &env, -1) else { + // resume.py:403-405 passes `minimum_virtualizable_size` here, which + // arms the `resume.py:236-239` length check inside `number()`. This + // call site used to hardcode `-1`, so the check — ported faithfully at + // `resume.rs:3951-3958` — was disabled for every guard ever numbered, + // and a 0-length vable section reached the backend unremarked. It then + // surfaced only if the guard was actually deopted, as + // `assert!(vable_size > 0)` at `resume.rs:7030`. + let Ok(numb_state) = memo.number(&snapshot, &env, self.minimum_virtualizable_size) else { return; }; diff --git a/majit/majit-metainterp/src/optimizeopt/optimizer.rs b/majit/majit-metainterp/src/optimizeopt/optimizer.rs index c4e37a1d504..6172186fa28 100644 --- a/majit/majit-metainterp/src/optimizeopt/optimizer.rs +++ b/majit/majit-metainterp/src/optimizeopt/optimizer.rs @@ -463,6 +463,16 @@ pub struct Optimizer { pub snapshot_frame_sizes: SnapshotFrameSizes, /// Per-guard virtualizable boxes from tracing-time snapshots. pub snapshot_vable_boxes: SnapshotBoxes, + /// resume.py:399-402 `minimum_virtualizable_size`, propagated to + /// OptContext for the `memo.number()` call in + /// `store_final_boxes_in_guard`. + /// + /// `-1` disables the check (`resume.py:236-239`); a non-negative value + /// arms it. `virtualizable.py:123-124 minimum_size()` is + /// `num_static_fields`, and the comparison is `>` rather than `>=` + /// because the virtualizable identity occupies one array entry of its + /// own on top of the static fields. + pub minimum_virtualizable_size: i64, /// Per-guard virtualref boxes from tracing-time snapshots. /// resume.py:243-247 _number_boxes reads vref_array as a separate /// section. opencoder.py:767 create_top_snapshot records vref_boxes @@ -1456,6 +1466,8 @@ impl Optimizer { snapshot_boxes: Vec::new(), snapshot_frame_sizes: Vec::new(), snapshot_vable_boxes: Vec::new(), + // resume.py:401-402: `-1` for a jitdriver with no virtualizable. + minimum_virtualizable_size: -1, snapshot_vref_boxes: Vec::new(), snapshot_frame_pcs: Vec::new(), phase1_emit_ops: Vec::new(), @@ -2085,19 +2097,35 @@ impl Optimizer { .count() } - /// optimizer.py: log_loop(ops) - /// Log the optimized trace for debugging/profiling. - pub fn log_optimized_trace(ctx: &OptContext) { - if crate::log_opt_enabled() { - eprintln!( - "[MAJIT] optimized trace: {} ops, {} guards", - ctx.new_operations.len(), - ctx.new_operations - .iter() - .filter(|op| op.opcode.is_guard()) - .count() - ); + /// logger.py:33 `Logger.log_loop(inputargs, operations, ...)`, called once + /// a trace's operation list is final — compile.py:474, :557, :562. + /// + /// Dumps the operation list, not just a count. A count separates a compiled + /// body from a compiled nothing; it cannot separate a working body from a + /// dead one, because deadness is a property of shape. A body that opens by + /// failing a guard has no back edge and runs zero iterations while + /// reporting a healthy-looking op total. + /// + /// `label` names which of the three compile paths produced the trace, since + /// they differ in whether the body was unrolled. + pub fn log_optimized_trace(label: &str, ops: &[T], constants: &C) + where + V: std::fmt::Debug, + T: AsRef, + C: majit_ir::resoperation::ConstLookup, + { + if !crate::log_opt_enabled() { + return; } + eprintln!( + "[MAJIT] optimized trace ({}): {} ops, {} guards", + label, + ops.len(), + ops.iter() + .filter(|op| op.as_ref().opcode.is_guard()) + .count() + ); + eprint!("{}", majit_ir::format_trace(ops, constants)); } /// optimizer.py:127-135 `getnullness(op)` parity (line-by-line port). @@ -2535,6 +2563,7 @@ impl Optimizer { ctx.snapshot_boxes = std::mem::take(&mut self.snapshot_boxes); ctx.snapshot_frame_sizes = std::mem::take(&mut self.snapshot_frame_sizes); ctx.snapshot_vable_boxes = std::mem::take(&mut self.snapshot_vable_boxes); + ctx.minimum_virtualizable_size = self.minimum_virtualizable_size; ctx.snapshot_vref_boxes = std::mem::take(&mut self.snapshot_vref_boxes); ctx.snapshot_frame_pcs = std::mem::take(&mut self.snapshot_frame_pcs); @@ -5452,6 +5481,26 @@ impl Optimizer { /// Create an optimizer with virtualizable config for frame field tracking. pub(crate) fn default_pipeline_with_virtualizable(config: VirtualizableConfig) -> Self { let mut opt = Self::new(); + // resume.py:399-402: + // if self.optimizer.jitdriver_sd.virtualizable_info: + // minimum_virtualizable_size = \ + // self.optimizer.jitdriver_sd.virtualizable_info.minimum_size() + // else: + // minimum_virtualizable_size = -1 + // + // `virtualizable.py:123-124 minimum_size()` returns `num_static_fields`, + // which is what `static_field_offsets` enumerates. + // + // Deviation, deliberate: RPython gates on the STATIC per-jitdriver + // `virtualizable_info`, while this gates on the PER-TRACE config, which + // exists only when `has_virtualizable_boxes()` held at + // `make_optimizer`. Ours is therefore strictly narrower — it cannot + // fire on a trace that never installed a shadow. That direction is the + // safe one (no false positives), but it does mean a trace whose + // `virtualizable_boxes` is `None` while the driver's `vinfo` is `Some` + // still slips past here and is caught only by the reader's + // `assert!(vable_size > 0)` at `resume.rs:7030`. + opt.minimum_virtualizable_size = config.static_field_offsets.len() as i64; opt.add_pass(Box::new(OptIntBounds::new())); opt.add_pass(Box::new(OptRewrite::new())); opt.add_pass(Box::new(OptVirtualize::with_virtualizable(config))); @@ -7095,18 +7144,39 @@ mod tests { let _ = opt.emit_operation(seeded_ops.pop().unwrap(), &mut ctx, false); assert!(!ctx.in_final_emission); - assert!(ctx.new_operations.iter().any(|op| op.opcode == OpCode::New)); - assert!(ctx.new_operations.iter().any(|op| { - op.opcode == OpCode::SetfieldGc - && op.arg(1).to_opref() == OpRef::int_op(11) - && op.has_descr() - })); - // info.py:146-151: force_box emits the ORIGINAL box op, so the - // forced GuardNonnull keeps arg(0) = OpRef::ref_op(10) (matches the virtual's - // original identity). force_box_impl preserves `new_op.pos = opref`. - assert!(ctx.new_operations.iter().any( - |op| op.opcode == OpCode::GuardNonnull && op.arg(0).to_opref() == OpRef::ref_op(10) - )); + + // The whole emission, in order. The three membership checks this + // replaces could see neither the count nor the ordering, and ordering + // is the property under test: forcing the virtual must emit its New and + // SetfieldGc BEFORE the guard that consumes it. + // + // info.py:146-151: force_box emits the ORIGINAL box op, so the forced + // GuardNonnull keeps arg(0) = OpRef::ref_op(10) (the virtual's original + // identity). force_box_impl preserves `new_op.pos = opref`, which is + // why New lands at RefOp(10) rather than at a fresh position. + let emitted: Vec<(OpCode, Vec, bool)> = ctx + .new_operations + .iter() + .map(|op| { + ( + op.opcode, + (0..op.num_args()).map(|i| op.arg(i).to_opref()).collect(), + op.has_descr(), + ) + }) + .collect(); + assert_eq!( + emitted, + [ + (OpCode::New, vec![], true), + ( + OpCode::SetfieldGc, + vec![OpRef::ref_op(10), OpRef::int_op(11)], + true + ), + (OpCode::GuardNonnull, vec![OpRef::ref_op(10)], true), + ] + ); } #[test] diff --git a/majit/majit-metainterp/src/optimizeopt/pure.rs b/majit/majit-metainterp/src/optimizeopt/pure.rs index f9982a6e1cb..e463528c2e2 100644 --- a/majit/majit-metainterp/src/optimizeopt/pure.rs +++ b/majit/majit-metainterp/src/optimizeopt/pure.rs @@ -75,7 +75,7 @@ struct KnownResultEntry { /// /// RPython uses a flat array of Op references, scanned linearly on lookup. /// At limit=16 (pureop_historylength), linear scan beats HashMap because: -/// - No hashing overhead or Vec allocation per lookup +/// - No hashing overhead or `Vec` allocation per lookup /// - Cache-friendly sequential memory access /// - Typical hit is within first few entries pub struct RecentPureOps { @@ -1747,7 +1747,7 @@ mod tests { assert_eq!(result[0].opcode, OpCode::SetfieldGc); } - /// #171/#11 Approach C: two `getarrayitem_gc_pure_r(arr, const_i, descr)` + /// Two `getarrayitem_gc_pure_r(arr, const_i, descr)` operations /// against the SAME (immutable) array, index, and descr are CSE'd by /// OptPure — the second is folded to the first (`make_equal_to`). This /// is the producer-side invariant the canonical-tuple `t[i]` arm relies @@ -2653,7 +2653,7 @@ mod tests { Some(1), ); // Production threads the builder's replay Rc into the pop - // (produce_pure); mirror it so use_box sees one object. #146/S8: the + // (produce_pure); mirror it so use_box sees one object. unsupported-green-type/S8: the // builder keys by the entry res box (`materialize_operand_at(pos)`); the // memoized box for the same position hits. let src1 = ctx.materialize_operand_at(OpRef::int_op(1)); @@ -2999,7 +2999,7 @@ mod tests { // `from_bound_op` reference (Prod(0)), so the fold is observed where it // matters in production: the folded Ref reaches its consumer as an // inline `ConstPtr` operand (later rewritten to `LoadFromGcTable` by the - // GC rewrite). #108: a Ref constant is NEVER exported as a raw `GcRef` + // GC rewrite). compilation-panic: a Ref constant is NEVER exported as a raw `GcRef` // into the backend constant pool — that pool has no GC root walker, so // refs live only in the GC-traced gc_table. (The Int analog DOES export // to the pool; ints carry no GC concern.) @@ -3067,7 +3067,7 @@ mod tests { !matches!(consumer.arg(1).to_opref(), OpRef::ConstInt(_)), "folded CallPureR constant aliased to ConstInt — ConstPtr/ConstInt distinction lost" ); - // #108: no raw GcRef is exported into the backend constant pool. + // compilation-panic: no raw GcRef is exported into the backend constant pool. assert!( constants.values().all(|v| !matches!(v, Value::Ref(_))), "backend constant pool must not retain a raw GcRef (use the gc_table); got {constants:?}" diff --git a/majit/majit-metainterp/src/optimizeopt/rewrite.rs b/majit/majit-metainterp/src/optimizeopt/rewrite.rs index 35752e2bdac..3f7d2bb19ed 100644 --- a/majit/majit-metainterp/src/optimizeopt/rewrite.rs +++ b/majit/majit-metainterp/src/optimizeopt/rewrite.rs @@ -25,10 +25,32 @@ enum LoopInvariantEntry { /// Yield the `OptimizationResult::InvalidLoop` control value so the driver /// (`propagate_from_pass_range`) converts it to `Err(InvalidLoop)` at the /// pass barrier. RPython `raise InvalidLoop`; threaded as a value here so -/// it works under `panic=abort`. Use as `return raise_invalid_loop(msg)`. +/// it works under `panic=abort`. Use as `return raise_invalid_loop(msg, op, ctx)`. +/// +/// The payload is a `&'static str`, so the abandon reason is structurally +/// unable to carry the operation it was raised on: the driver prints +/// `[jit] abort trace at key=… (InvalidLoop: )`, which names a rule and +/// no subject, and nothing downstream can rejoin the two. Every caller here +/// decides on `arg(0)`, so log that operand and the constant it resolved to +/// at the point of decision — that resolved value *is* the proof the guard +/// can never pass, and it is gone by the time the driver sees the reason. #[cold] #[inline(never)] -fn raise_invalid_loop(msg: &'static str) -> OptimizationResult { +fn raise_invalid_loop(msg: &'static str, op: &Op, ctx: &OptContext) -> OptimizationResult { + if crate::majit_log_enabled() { + let arg0 = op.arg(0); + let resolved = ctx + .resolve_operand_operand_opt(&arg0) + .and_then(|b| ctx.get_constant_int_or_bound_box(&b)); + eprintln!( + "[jit] InvalidLoop raised at {:?} pos={:?}: {} (arg0={:?} resolved_const={:?})", + op.opcode, + op.pos.get(), + msg, + arg0.get_box_replacement(false).to_opref(), + resolved, + ); + } OptimizationResult::InvalidLoop(msg) } @@ -657,7 +679,7 @@ impl OptRewrite { if val != 0 { return OptimizationResult::Remove; } - return raise_invalid_loop("GUARD_TRUE proven to always fail"); + return raise_invalid_loop("GUARD_TRUE proven to always fail", op, ctx); } OptimizationResult::PassOn @@ -675,7 +697,7 @@ impl OptRewrite { if val == 0 { return OptimizationResult::Remove; } - return raise_invalid_loop("GUARD_FALSE proven to always fail"); + return raise_invalid_loop("GUARD_FALSE proven to always fail", op, ctx); } OptimizationResult::PassOn @@ -714,7 +736,7 @@ impl OptRewrite { if actual_int == expected_int { return OptimizationResult::Remove; } - return raise_invalid_loop("GUARD_VALUE proven to always fail"); + return raise_invalid_loop("GUARD_VALUE proven to always fail", op, ctx); } } else if let (Some(actual), Some(expected)) = ( ctx.resolve_operand_operand_opt(&arg0) @@ -727,7 +749,7 @@ impl OptRewrite { } match actual { Value::Int(_) | Value::Ref(_) => { - return raise_invalid_loop("GUARD_VALUE proven to always fail"); + return raise_invalid_loop("GUARD_VALUE proven to always fail", op, ctx); } Value::Float(_) => { return OptimizationResult::Remove; @@ -743,7 +765,7 @@ impl OptRewrite { let obj_info = obj_box.as_ref().and_then(|b| ctx.getptrinfo(b)); if let Some(info) = obj_info { if info.is_virtual() { - return raise_invalid_loop("promote of a virtual"); + return raise_invalid_loop("promote of a virtual", op, ctx); } // rewrite.py:307-347: replace_old_guard_with_guard_value if let Some(old_guard) = obj_box @@ -771,6 +793,8 @@ impl OptRewrite { if !c_nonnull { return raise_invalid_loop( "GUARD_VALUE(..., NULL) follows some other guard that it is not NULL", + op, + ctx, ); } // rewrite.py:324-332: previous_classbox = info.get_known_class(cpu) @@ -785,6 +809,8 @@ impl OptRewrite { { return raise_invalid_loop( "GUARD_VALUE proven to always fail (class mismatch)", + op, + ctx, ); } // rewrite.py:333-334: can_replace_guards check. @@ -891,7 +917,7 @@ impl OptRewrite { } // rewrite.py:404-407: known class mismatch is a // proven-fail guard — abort the trace. - return raise_invalid_loop("GUARD_CLASS proven to always fail"); + return raise_invalid_loop("GUARD_CLASS proven to always fail", op, ctx); } } // rewrite.py:408-427: guard strengthening. @@ -1788,7 +1814,7 @@ impl Optimization for OptRewrite { return OptimizationResult::Remove; } if info.is_null() { - return raise_invalid_loop("GUARD_NONNULL proven to always fail"); + return raise_invalid_loop("GUARD_NONNULL proven to always fail", op, ctx); } } // rewrite.py:280-282 postprocess_GUARD_NONNULL: @@ -1816,7 +1842,7 @@ impl Optimization for OptRewrite { return OptimizationResult::Remove; } if info.is_nonnull() { - return raise_invalid_loop("GUARD_ISNULL proven to always fail"); + return raise_invalid_loop("GUARD_ISNULL proven to always fail", op, ctx); } } // rewrite.py:197-198 postprocess_GUARD_ISNULL: @@ -1839,7 +1865,11 @@ impl Optimization for OptRewrite { if let Some(info) = ctx.getptrinfo(&op.arg(0).get_box_replacement(false)) && info.is_null() { - return raise_invalid_loop("GUARD_NONNULL_CLASS proven to always fail"); + return raise_invalid_loop( + "GUARD_NONNULL_CLASS proven to always fail", + op, + ctx, + ); } self.optimize_guard_class(op, ctx) } diff --git a/majit/majit-metainterp/src/optimizeopt/schedule.rs b/majit/majit-metainterp/src/optimizeopt/schedule.rs index cf2812c06c6..d3adfa6d81c 100644 --- a/majit/majit-metainterp/src/optimizeopt/schedule.rs +++ b/majit/majit-metainterp/src/optimizeopt/schedule.rs @@ -54,7 +54,7 @@ pub struct Pack { } /// vector.py: Accumulation pack — tracks reduction operations -/// (e.g., sum += array[i]) that can be vectorized with horizontal +/// (e.g., `sum += array[i]`) that can be vectorized with horizontal /// reduction instructions. #[derive(Clone, Debug)] /// schedule.py:980-1003: AccumPack — accumulation (reduction) pack. @@ -74,7 +74,7 @@ pub struct AccumPack { } /// Accumulation info stored in the accumulation map. -/// schedule.py:649: state.accumulation[arg] = pack +/// schedule.py:649: `state.accumulation[arg] = pack` /// /// schedule.py keys `accumulation` by the failarg box and `getleftmostseed` /// returns a box object; pyre shapes `seed` as a flat `OpRef` and keys @@ -144,8 +144,6 @@ impl GuardAnalysis { } } -// ── schedule.py:584-779: VecScheduleState ───────────────────── - /// schedule.py:584-779: State for vector-aware instruction scheduling. /// Tracks which scalar ops have been mapped to vector ops, handles /// pack/unpack/expand operations, and manages the output op list. @@ -772,8 +770,6 @@ impl VecScheduleState { } } -// ── schedule.py:317-400: turn_into_vector and helpers ───────────────────── - /// Combined failure mode for `optimize_vector` / `run_optimization`, /// mirroring vector.py:154-166's two `except` arms. Callers convert this /// back to a "no-vectorize-this-time" decision and replay the original @@ -1003,8 +999,8 @@ fn get_vec_info(state: &mut VecScheduleState, opref: OpRef, ops: &[OpRc]) -> (ch /// schedule.py:488-502: pack_into_vector — insert `src` at position `tidx` /// in `tgt`, producing a wider vector. /// -/// tgt = [1,2,3,4,_,_,_,_], src = [5,6,_,_] -/// result = [1,2,3,4,5,6,_,_] (tidx=4, scount=2) +/// `tgt = [1,2,3,4,_,_,_,_]`, `src = [5,6,_,_]` +/// `result = [1,2,3,4,5,6,_,_]` (tidx=4, scount=2) pub fn pack_into_vector( state: &mut VecScheduleState, tgt: OpRef, diff --git a/majit/majit-metainterp/src/optimizeopt/unroll.rs b/majit/majit-metainterp/src/optimizeopt/unroll.rs index de7acaed89e..271925bf6e1 100644 --- a/majit/majit-metainterp/src/optimizeopt/unroll.rs +++ b/majit/majit-metainterp/src/optimizeopt/unroll.rs @@ -488,7 +488,7 @@ impl UnrollOptimizer { /// unroll.py:238-242: jump_to_preamble(cell_token, jump_op). /// /// Redirect the closing JUMP to the preamble entry token - /// (target_tokens[0], virtual_state=None). Only changes the + /// (`target_tokens[0]`, `virtual_state=None`). Only changes the /// descriptor, keeping arglist intact — RPython parity. pub fn jump_to_preamble( body_ops: &[majit_ir::OpRc], @@ -5391,14 +5391,14 @@ fn assemble_peeled_trace_with_jump_args( } // RPython parity: each guard in the assembled trace owns a // distinct ResumeGuardDescr with a globally unique fail_index. - // optimizeopt::store_final_boxes_in_guard (mod.rs:3392-3404, - // commit 43c64ee0bb) installs a fresh ResumeGuardDescr on every - // optimizer-routed guard, and Optimizer::_copy_resume_data_from / - // OptContext::emit_guard_operation share-branch (commit - // 329297b38a) call Descr::clone_descr to allocate a fresh - // fail_index for the sharing-path guard. Both phases of the - // unroll optimizer therefore emit body guards with unique - // descrs already; no post-process re-stamping is needed. + // optimizeopt::store_final_boxes_in_guard (mod.rs:3392-3404) + // installs a fresh ResumeGuardDescr on every optimizer-routed + // guard, and Optimizer::_copy_resume_data_from / + // OptContext::emit_guard_operation share-branch call + // Descr::clone_descr to allocate a fresh fail_index for the + // sharing-path guard. Both phases of the unroll optimizer + // therefore emit body guards with unique descrs already; no + // post-process re-stamping is needed. let new_rc = std::rc::Rc::new(new_op); if new_rc.result_type() != Type::Void && !new_rc.pos.get().is_none() { emitted_at.insert(new_rc.pos.get(), new_rc.clone()); @@ -5838,8 +5838,6 @@ mod tests { opt.optimize_with_constants_and_inputs(&ops, &mut majit_ir::ConstMap::new(), 1024) } - // ── Basic peeling ───────────────────────────────────────────────── - #[test] fn test_no_jump_no_unroll() { // Without a Jump back-edge, the pass just buffers and nothing is emitted. @@ -6206,8 +6204,6 @@ mod tests { } } - // ── OpRef remapping ─────────────────────────────────────────────── - #[test] fn test_internal_refs_remapped_in_peeled_copy() { // op0: v0 = IntAdd(v100, v101) -- uses input args @@ -6290,8 +6286,6 @@ mod tests { assert_eq!(result[2].arg(1).to_opref(), OpRef::int_op(101)); } - // ── Guard preservation ──────────────────────────────────────────── - #[test] fn test_guards_duplicated_in_peel() { // Guards in the preamble serve as type checks. @@ -6366,8 +6360,6 @@ mod tests { ); } - // ── Jump args remapping ─────────────────────────────────────────── - #[test] fn test_jump_args_remapped_to_body() { // Jump args should reference the body's ops, not the original positions. @@ -6435,8 +6427,6 @@ mod tests { ); } - // ── Multiple ops in loop body ───────────────────────────────────── - #[test] fn test_multi_op_loop() { let mut ops = vec![ @@ -6484,8 +6474,6 @@ mod tests { assert_eq!(result[9].opcode, OpCode::Jump); } - // ── Setup resets state ──────────────────────────────────────────── - #[test] fn test_setup_resets_state() { let mut pass = OptUnroll::new(); @@ -6503,8 +6491,6 @@ mod tests { assert!(!pass.seen_jump); } - // ── Integration with optimizer ──────────────────────────────────── - #[test] fn test_unroll_standalone_optimizer() { // Run the unroll pass through the optimizer infrastructure. @@ -6587,8 +6573,6 @@ mod tests { assert!(!guards[1].getdescr().unwrap().is_resume_at_position()); } - // ── Chain of references ─────────────────────────────────────────── - #[test] fn test_chain_of_refs_correctly_remapped() { // v0 = IntAdd(v100, v101) diff --git a/majit/majit-metainterp/src/optimizeopt/vector.rs b/majit/majit-metainterp/src/optimizeopt/vector.rs index 4568c8fe64a..71ae0264bce 100644 --- a/majit/majit-metainterp/src/optimizeopt/vector.rs +++ b/majit/majit-metainterp/src/optimizeopt/vector.rs @@ -29,8 +29,6 @@ pub use crate::optimizeopt::schedule::{ unpack_from_vector, }; -// ── vector.py:601-668: Cost models ──────────────────────────────────── - /// Cost model for deciding whether vectorization is profitable. /// /// From rpython/jit/metainterp/optimizeopt/vector.py. @@ -188,8 +186,6 @@ impl Default for CostModel { } } -// ── vector.py:670-678: isomorphic ───────────────────────────────────── - /// vector.py:670-678: isomorphic — two ops can be packed if they have the /// same opcode AND the same vecinfo bytesize. PyPy reads each side through /// `forwarded_vecinfo(op)`, which lives on `op._forwarded`; pyre keeps that @@ -204,8 +200,6 @@ pub fn isomorphic(state: &mut VecScheduleState, l_op: &Op, r_op: &Op) -> bool { l_vecinfo.bytesize == r_vecinfo.bytesize } -// ── vector.py:680-824: PackSet ──────────────────────────────────────── - /// vector.py: PackSet — manages packs and supports merging /// 2-packs into 4-packs (or larger) when possible. #[derive(Clone, Debug, Default)] @@ -652,8 +646,6 @@ impl PackSet { } } -// ── vector.py:35-40: copy_resop ──────────────────────────────────────── - /// vector.py:35-40: copy_resop — clone an op, preserving VectorizationInfo. /// /// In RPython, `get_forwarded()` returns VectorizationInfo if set on the @@ -668,8 +660,6 @@ pub fn copy_resop(op: &Op) -> Op { op.clone() } -// ── vector.py:42-120: VectorLoop ─────────────────────────────────────── - /// vector.py:42-120: VectorLoop — wraps a loop body (Label..operations..Jump) /// for vectorization analysis and transformation. #[derive(Clone, Debug)] @@ -909,8 +899,6 @@ impl VectorLoop { } } -// ── vector.py:122-173: optimize_vector ───────────────────────────────── - /// vector.py:122-173: optimize_vector — top-level entry point. /// /// Creates a VectorizingOptimizer, runs vectorization on the loop, and @@ -969,8 +957,6 @@ pub fn optimize_vector( result } -// ── compile.py:302-308: vectorization post-pass entry ────────────────── - /// compile.py:302-308 — apply the SIMD vectorizer to an optimizer-assembled /// loop and return the rewritten op list. /// @@ -1053,8 +1039,6 @@ pub(crate) fn apply_loop_vectorization( } } -// ── vector.py:175-205: user_loop_bail_fast_path ──────────────────────── - /// vector.py:175-205: user_loop_bail_fast_path — quick pre-check. /// /// Returns `true` if the loop should be SKIPPED (bailed on) for @@ -1104,8 +1088,6 @@ pub fn user_loop_bail_fast_path(loop_: &VectorLoop) -> bool { false } -// ── vector.py:207-600: VectorizingOptimizer ──────────────────────────── - /// vector.py:207-600: VectorizingOptimizer — the vectorization optimizer. /// /// In RPython, this extends `Optimizer` and is the top-level optimizer for @@ -1181,8 +1163,6 @@ impl VectorizingOptimizer { opt } - // ── vector.py:220-271: run_optimization ──────────────────────────── - /// vector.py:220-271: run_optimization — the main vectorization pipeline. /// /// 1. Find smallest type → determine unroll count @@ -1517,8 +1497,6 @@ impl VectorizingOptimizer { Ok((ops, gso_consts)) } - // ── vector.py:273-344: unroll_loop_iterations ────────────────────── - /// vector.py:359-367: linear_find_smallest_type — scan ops for the /// smallest array element byte size to determine SIMD width. pub fn linear_find_smallest_type(&mut self, loop_: &VectorLoop) { @@ -1550,8 +1528,6 @@ impl VectorizingOptimizer { count.saturating_sub(1) // already unrolled once } - // ── vector.py:346-357: copy_guard_descr ──────────────────────────── - /// vector.py:346-357: copy_guard_descr — clone guard descriptor and /// rename fail args during unrolling. fn copy_guard_descr(renamer: &Renamer, copied_op: &mut Op) { @@ -1575,8 +1551,6 @@ impl VectorizingOptimizer { } } - // ── vector.py:378-402: find_adjacent_memory_refs ─────────────────── - /// vector.py:378-402: find_adjacent_memory_refs — seed the packset /// with pairs of adjacent memory accesses. fn find_adjacent_memory_refs( @@ -1738,8 +1712,6 @@ impl VectorizingOptimizer { } } - // ── vector.py:460-494: combine_packset ───────────────────────────── - /// vector.py:460-496: combine_packset — merge adjacent 2-packs into /// larger packs, then split overloaded packs. pub fn combine_packset(&mut self) -> Result<(), NotAVectorizeableLoop> { @@ -1771,8 +1743,6 @@ impl VectorizingOptimizer { Ok(()) } - // ── vector.py:515-521: schedule ──────────────────────────────────── - /// vector.py:515-521: schedule — run the scheduler on the given state. fn schedule_state(_state: &mut VecScheduleState, _graph: &DependencyGraph) { // vector.py:516: state.prepare() — handled by caller @@ -1781,8 +1751,6 @@ impl VectorizingOptimizer { // vector.py:520: state.post_schedule() — handled by caller } - // ── vector.py:523-583: analyse_index_calculations ────────────────── - /// vector.py:523-583: analyse_index_calculations — move guarding /// instructions (and all the instructions the guard needs) to the loop /// header so guards fail "early" and dependencies relax. Without this @@ -1886,8 +1854,6 @@ impl VectorizingOptimizer { if one_valid { Some(graph) } else { None } } - // ── vector.py:585-599: mark_guard ────────────────────────────────── - /// vector.py:585-599: mark_guard — marks a guard as an early exit /// by attaching a CompileLoopVersionDescr and setting failargs to /// the label's input args. @@ -1908,8 +1874,6 @@ impl VectorizingOptimizer { guard_op.setfailargs(loop_.label.getarglist()); } - // ── Optimization trait helper: try_vectorize ─────────────────────── - /// Attempt to vectorize the buffered loop body (Optimization trait path). /// /// This is the sub-pass equivalent of run_optimization, used when @@ -2210,8 +2174,6 @@ impl VectorizingOptimizer { } } -// ── VectorLoop: unroll_loop_iterations ───────────────────────────────── - impl VectorLoop { /// vector.py:273-344: unroll_loop_iterations — unroll the loop body /// `count` times with proper renaming. @@ -2380,7 +2342,6 @@ impl VectorLoop { } } -// ── schedule.py helpers used by the vectorizer ───────────────────────── // These functions are from schedule.py in RPython, not vector.py. // They are placed here because they are called from the vectorizer's // scheduling logic in try_vectorize / run_optimization. @@ -2492,7 +2453,6 @@ pub(crate) fn ensure_args_unpacked( } } -// ── Optimization trait impl (TODO) ────────────────── // In RPython, VectorizingOptimizer extends Optimizer and is called via // optimize_vector(). In Rust, it participates in the Optimizer pipeline // as an Optimization sub-pass. This impl bridges the two worlds. @@ -2620,8 +2580,6 @@ impl Optimization for VectorizingOptimizer { } } -// ── Tests ────────────────────────────────────────────────────────────── - #[cfg(test)] mod tests { use super::*; @@ -3173,25 +3131,55 @@ mod tests { // The pre-vectorize loop is the single tracked version (gso precondition). assert_eq!(info.versions.len(), 1, "exactly one snapshot version"); - // Real vectorization happened: the two adjacent loads became packed - // VEC_LOAD ops and the paired sums a VEC_INT_ADD — which only exists - // now that `to_vector()` maps the memory loads (resoperation.py:1746). - assert!( - ops.iter().any(|op| op.opcode == OpCode::VecLoadI), - "adjacent loads must pack into VecLoadI" - ); - assert!( - ops.iter().any(|op| op.opcode == OpCode::VecIntAdd), - "paired sums must pack into VecIntAdd" + // The whole post-vectorize sequence. `optimize_vector` is a pure + // function of the fixture built above, so this is determined end to end + // rather than sampled — measured, not derived. + // + // The four membership checks this replaces asserted only that VecLoadI, + // VecIntAdd, Label and Jump each appear somewhere. They could not see + // the ten-op pack/expand preamble at all, and they hid a drift from the + // prose beside them: the body emits THREE VecLoadI and TWO VecIntAdd, + // not the "two packed loads and a VEC_INT_ADD" the old comment claimed. + // VecIntAdd exists at all only because `to_vector()` maps the memory + // loads (resoperation.py:1746). Label first and Jump last is the + // preserved loop structure. + assert_eq!( + ops.iter().map(|op| op.opcode).collect::>(), + [ + OpCode::VecExpandI, + OpCode::VecI, + OpCode::VecPackI, + OpCode::VecPackI, + OpCode::VecExpandI, + OpCode::VecI, + OpCode::VecPackI, + OpCode::VecPackI, + OpCode::VecPackI, + OpCode::VecPackI, + OpCode::Label, + OpCode::IntAdd, + OpCode::VecLoadI, + OpCode::VecLoadI, + OpCode::IntAdd, + OpCode::VecIntAdd, + OpCode::VecLoadI, + OpCode::VecUnpackI, + OpCode::VecIntAdd, + OpCode::VecUnpackI, + OpCode::VecUnpackI, + OpCode::Jump, + ] ); - // Loop structure is preserved end to end. - assert!(ops.iter().any(|op| op.opcode == OpCode::Label)); - assert!(ops.iter().any(|op| op.opcode == OpCode::Jump)); + // gso materialized the index-var constant it strength-reduced and the // wiring surfaced it for the caller to register in the constant pool. - assert!( - !gso_consts.is_empty(), - "gso must surface its materialized index constants" + // `IndexMap` is insertion-ordered, so this is a sequence, not a set. + assert_eq!( + gso_consts + .iter() + .map(|(k, v)| (format!("{k:?}"), *v)) + .collect::>(), + [(String::from("ConstInt(8)"), 8)] ); } diff --git a/majit/majit-metainterp/src/optimizeopt/virtualize.rs b/majit/majit-metainterp/src/optimizeopt/virtualize.rs index 541ec0d5503..edaa8cc2f6e 100644 --- a/majit/majit-metainterp/src/optimizeopt/virtualize.rs +++ b/majit/majit-metainterp/src/optimizeopt/virtualize.rs @@ -62,6 +62,43 @@ pub(crate) struct VirtualizableConfig { /// Mirrors `interp_jit.py:67 reds = ['frame', 'ec']` — the non-vable /// extra reds occupy `InputArg` slots `1..1+vable_input_offset`. pub vable_input_offset: usize, + /// Flat input-arg slot holding the virtualizable identity at loop entry. + /// + /// `Some(0)` is the legacy `[frame, vable_scalars.., array_items..]` layout, + /// where the frame leads. The macro state-field JIT mints its inputargs in + /// `[int scalars.., fixed-array cells.., identity]` order instead + /// (`majit-macros/src/jit_interp/codegen_state.rs` `create_sym`), so the + /// identity sits past the scalars and the cells; that position comes from + /// `VirtualizableInfo::identity_live_index`, which the macro emits only when + /// the state declares no fixed array. With one present the position is + /// `num_scalars + sum(fixed array lengths)` — runtime `Vec` lengths, not a + /// macro-expansion constant — so nothing is declared and this is `None`. + /// + /// `None` means no position was declared, and the tracker then + /// DECLINES to track this virtualizable. It must not fall back to slot 0, + /// because that does not fail loudly on the state-field layout: it finds the + /// first int scalar and installs `PtrInfo::Virtualizable` on it, since + /// `inputarg_type` keys on the raw index and never on the `InputArg` variant + /// tag, so a `Ref`-tagged probe resolves to the `Int` slot's host. The + /// preamble then exports that scalar as a `Ref` leaf and the loop-close jump + /// hands back the `Int` it really is — the `expected=Ref actual=Int` cross + /// rejected at `virtualstate.rs` `enum_forced_boxes_for_entry`, i.e. + /// `VirtualStatesCantMatch`, and no trace ever compiles. It only bites when + /// that scalar reaches the Jump as its own inputarg; a scalar reassigned + /// every iteration passes the recomputed value instead and hides it. + /// Declining costs the virtualizable optimization; guessing costs every + /// trace. What declining actually costs on this layout was measured on + /// `tests/jit_interp_fixed_array_identity_slot.rs` and is nil: resolving the + /// slot and declining it give the same compile count and the same trace size + /// (6 ops recorded, 5 after optimization), because `is_standard_ref` never + /// returns true there and no vable array access is ever resolved for + /// mirroring. + /// + /// The decline is not total. `identity_input_ref` returns + /// `Some(input_arg_ref(base))` whenever `ctx.inputarg_base != 0` — a bridge — + /// before it consults this field at all, so a tracker is still installed on + /// that path with this set to `None`. + pub identity_input_index: Option, /// Whether the tracker seeds array-element state from the trace-entry /// input args (`init`'s array loop). /// @@ -124,24 +161,46 @@ impl VirtualizableTracker { self.needs_setup = true; } + /// The input-arg slot the virtualizable identity occupies, or `None` when + /// no sound slot is known and the tracker must decline. + /// + /// A bridge keeps the identity at its own base: its input args are rebuilt + /// from the deadframe frame-first, which is also what `init`'s `base == 0` + /// gate and `is_standard_ref` assume, so the resolved offset applies to the + /// loop entry only — and so does the decline, since the bridge's base is + /// established by the deadframe rather than by the host's layout. + fn identity_input_ref(&self, ctx: &OptContext) -> Option { + let base = ctx.inputarg_base; + if base != 0 { + return Some(OpRef::input_arg_ref(base)); + } + Some(OpRef::input_arg_ref( + self.config.identity_input_index? as u32, + )) + } + /// Apply deferred virtualizable setup if needed. fn ensure_setup(&mut self, ctx: &mut OptContext) { if self.needs_setup { self.needs_setup = false; - let base = ctx.inputarg_base; + // No sound identity slot — decline rather than install + // `PtrInfo::Virtualizable` on whatever inputarg 0 happens to be. + let Some(identity_ref) = self.identity_input_ref(ctx) else { + return; + }; let first_check = ctx - .get_box_replacement_operand_opt(OpRef::input_arg_ref(base)) + .get_box_replacement_operand_opt(identity_ref) .as_ref() .is_some_and(|b| ctx.has_ptr_info(b)); if !first_check { self.init(ctx); let second_check = ctx - .get_box_replacement_operand_opt(OpRef::input_arg_ref(base)) + .get_box_replacement_operand_opt(identity_ref) .as_ref() .is_some_and(|b| ctx.has_ptr_info(b)); if !second_check { { - let b = ctx.materialize_operand_at(OpRef::input_arg_ref(base)); + let b = ctx.materialize_operand_at(identity_ref); ctx.set_ptr_info( &b, PtrInfo::Virtualizable(VirtualizableFieldState { @@ -162,6 +221,11 @@ impl VirtualizableTracker { if ctx.num_inputs() <= 1 { return; } + // Same decline as `ensure_setup`: without a known identity slot there is + // nothing to hang the seeded `VirtualizableFieldState` on. + let Some(identity_ref) = self.identity_input_ref(ctx) else { + return; + }; let mut state = VirtualizableFieldState { fields: vec![], @@ -269,7 +333,7 @@ impl VirtualizableTracker { } } - let b = ctx.materialize_operand_at(OpRef::input_arg_ref(base)); + let b = ctx.materialize_operand_at(identity_ref); ctx.set_ptr_info(&b, PtrInfo::Virtualizable(state)); } @@ -279,8 +343,14 @@ impl VirtualizableTracker { // is the trace's first inputarg, at `inputarg_base`: `0` for // loops/preambles, `bridge_inputarg_base` for bridges (whose parent // loop owns the low OpRef range, so the bridge's own inputargs — frame - // first — start at the shifted base). - match ctx.get_box_replacement_operand_opt(OpRef::input_arg_ref(ctx.inputarg_base)) { + // first — start at the shifted base). A loop whose front end does not + // lead with the identity declares where it does sit + // (`identity_input_index`), and a loop with no known identity slot + // declines: nothing is the standard ref. + match self + .identity_input_ref(ctx) + .and_then(|r| ctx.get_box_replacement_operand_opt(r)) + { Some(std) => b.same_box(&std) && ctx.is_virtualizable(b), None => false, } @@ -856,8 +926,8 @@ impl OptVirtualize { // keys fields by slot instead of indexing an array, so the read needed // nothing to answer and the call was dropped; `vinfo.descr` then stayed // at whatever the allocation set. That descr is what - // `field_slot_disagreement` below reads, so the upgrade has to happen - // for the slot it checks to be the slot upstream would have used. + // `field_slot_identifies` below reads, so the upgrade has to happen for + // the slot it checks to be the slot upstream would have used. // // Only for a virtual: `virtualize.py:185-186` reaches `opinfo.getfield` // under `opinfo.is_virtual()`, and a non-virtual info's descr is @@ -975,8 +1045,8 @@ impl OptVirtualize { // with GETFIELD_GC_R to decide whether the frame is JIT-owned, and // a non-null pointer reads as owned on a frame that has no token. // - // A field the positional list does not hold cannot have been - // stored under its own identity either, so this is exactly + // A field the positional list does not hold cannot have been stored + // under its own identity either, so this is exactly // `virtualize.py:188`'s state: the trace never stored it and the // read answers the zeroed allocation. Skip the slot lookup and // take the zero fold below -- which for `vable_token` is the @@ -2987,6 +3057,118 @@ mod tests { } } + /// A field descr that CLAIMS a slot it does not occupy: `index_in_parent` + /// answers `claimed_idx` while `offset` answers a genuinely different + /// field's address, so the parent's `all_fielddescrs()[claimed_idx]` and + /// this descr do not name the same field. + /// + /// This is descriptor census's "in-range but naming a different slot" mint reduced to + /// two descrs. It is deliberately NOT out-of-range: `get_field` searches a + /// `Vec<(u32, Operand)>` by key and cannot index past its end, so the + /// out-of-range rows reach `force_box_impl`'s `.get(idx).expect(..)`, a + /// different consumer with a different (loud) failure. + #[derive(Debug)] + struct MisindexedFieldDescr { + claimed_idx: u32, + real_offset: usize, + } + + impl Descr for MisindexedFieldDescr { + fn index(&self) -> u32 { + 0xDEAD_0000 | self.claimed_idx + } + fn as_field_descr(&self) -> Option<&dyn FieldDescr> { + Some(self) + } + } + + impl FieldDescr for MisindexedFieldDescr { + fn get_parent_descr(&self) -> Option { + Some(test_parent_size_descr( + self.claimed_idx, + majit_ir::Type::Int, + )) + } + fn index_in_parent(&self) -> usize { + self.claimed_idx as usize + } + fn offset(&self) -> usize { + self.real_offset + } + fn field_size(&self) -> usize { + 8 + } + fn field_type(&self) -> majit_ir::Type { + majit_ir::Type::Int + } + } + + fn misindexed_field_descr(claimed_idx: u32, real_offset: usize) -> DescrRef { + Arc::new(MisindexedFieldDescr { + claimed_idx, + real_offset, + }) + } + + /// A field descr whose parent carries NO layout: `get_parent_descr()` + /// answers a descr for which `as_size_descr()` is `None`. + /// + /// This is the one shape that lets a POPULATED slot coexist with + /// `cur_len == 0`, which is what `init_fields`' first arm needs to be + /// observable on the read path. Two short-circuits do it, both on the same + /// `as_size_descr()`: + /// + /// - `init_fields` opens with + /// `let Some(size_descr) = descr.as_size_descr() else { return; }` + /// (`ptr_info.rs:1051`), so the setfield's own `init_fields` leaves the + /// virtual's descr exactly as the allocation set it. Any + /// size-descr-parented field descr would instead take the `cur_len == 0` + /// arm right there and close the window before the read is reached. + /// - `field_slot_disagreement` opens with `descr.as_size_descr()?` + /// (`:2508`), so the write is not refused and no panic fires. + #[derive(Debug)] + struct NarrowParentFieldDescr { + idx: u32, + } + + impl Descr for NarrowParentFieldDescr { + fn index(&self) -> u32 { + 0xBEEF_0000 | self.idx + } + fn as_field_descr(&self) -> Option<&dyn FieldDescr> { + Some(self) + } + } + + impl FieldDescr for NarrowParentFieldDescr { + fn get_parent_descr(&self) -> Option { + // Deliberately NOT a SizeDescr: `TestArrayDescr` implements only + // `Descr::index`, so `as_size_descr()` falls through to the trait + // default and answers `None`. The `0xA000` tag keeps this parent's + // descr index clear of the bare `idx` that `size_descr`/ + // `field_descr` mint, so nothing keyed on index can confuse them. + Some(Arc::new(TestArrayDescr { + idx: 0xA000 | self.idx, + })) + } + fn index_in_parent(&self) -> usize { + self.idx as usize + } + fn offset(&self) -> usize { + self.idx as usize * 8 + } + fn field_size(&self) -> usize { + 8 + } + fn field_type(&self) -> majit_ir::Type { + majit_ir::Type::Int + } + } + + fn narrow_parent_field_descr(idx: u32) -> DescrRef { + Arc::new(NarrowParentFieldDescr { idx }) + } + fn test_parent_size_descr(idx: u32, field_type: majit_ir::Type) -> DescrRef { let all_fielddescrs: Vec> = (0..=idx) .map(|field_idx| { @@ -3022,6 +3204,13 @@ mod tests { Arc::new(TestSizeDescr { idx }) } + /// An allocation descr that is NOT a `SizeDescr` — `as_size_descr()` + /// answers `None`, so a virtual allocated with it starts at `cur_len == 0` + /// via `init_fields`' `.map(..).unwrap_or(0)` (`ptr_info.rs:1080-1084`). + fn non_size_descr(idx: u32) -> DescrRef { + Arc::new(TestArrayDescr { idx }) + } + fn field_descr(idx: u32) -> DescrRef { Arc::new(TestFieldDescr { idx }) } @@ -3243,6 +3432,7 @@ mod tests { array_field_descrs: vec![], array_lengths: vec![1], vable_input_offset: 0, + identity_input_index: Some(0), track_array_elements: true, }, ))); @@ -3274,6 +3464,7 @@ mod tests { array_field_descrs: vec![], array_lengths: vec![1], vable_input_offset: 0, + identity_input_index: Some(0), track_array_elements: true, }); pass.setup(); @@ -3374,6 +3565,7 @@ mod tests { array_field_descrs: vec![], array_lengths: vec![], vable_input_offset: 0, + identity_input_index: Some(0), track_array_elements: true, }); pass.setup(); @@ -3423,6 +3615,7 @@ mod tests { array_field_descrs: vec![], array_lengths: vec![], vable_input_offset: 0, + identity_input_index: Some(0), track_array_elements: true, }); pass.setup(); @@ -3454,6 +3647,7 @@ mod tests { array_field_descrs: vec![], array_lengths: vec![], vable_input_offset: 0, + identity_input_index: Some(0), track_array_elements: true, }); pass.setup(); @@ -3532,6 +3726,7 @@ mod tests { array_field_descrs: vec![], array_lengths: vec![1], vable_input_offset: 0, + identity_input_index: Some(0), track_array_elements: true, }); pass.setup(); @@ -3578,6 +3773,7 @@ mod tests { array_field_descrs: vec![], array_lengths: vec![1], vable_input_offset: 0, + identity_input_index: Some(0), track_array_elements: true, }); pass.setup(); @@ -3629,6 +3825,7 @@ mod tests { array_field_descrs: vec![], array_lengths: vec![1], vable_input_offset: 0, + identity_input_index: Some(0), track_array_elements: true, }); pass.setup(); @@ -3737,6 +3934,7 @@ mod tests { array_field_descrs: vec![], array_lengths: vec![1], vable_input_offset: 0, + identity_input_index: Some(0), track_array_elements: true, }); pass.setup(); @@ -3851,6 +4049,7 @@ mod tests { array_field_descrs: vec![], array_lengths: vec![1], vable_input_offset: 0, + identity_input_index: Some(0), track_array_elements: true, }); let mut constants: majit_ir::ConstMap = majit_ir::ConstMap::new(); @@ -3884,6 +4083,18 @@ mod tests { let (ops, snapshots) = seed_virtualize_guard_snapshots(&ops); opt.snapshot_boxes = snapshots; + // The optimizer above is configured WITH a virtualizable, so every + // guard snapshot must carry a vable section — `pyjitpl.py:3326-3330` + // makes `virtualizable_boxes` non-empty for the whole life of such a + // trace, and `resume.py:236-239` (armed via + // `minimum_virtualizable_size`) asserts it. Identity first + // (`opencoder.py:718-726`), then this config's one static field and + // its one array item. + opt.snapshot_vable_boxes = vec![Some(vec![ + crate::resume::SnapshotBox::typed(OpRef::input_arg_typed(0, Type::Ref), Type::Ref), + crate::resume::SnapshotBox::typed(OpRef::input_arg_typed(1, Type::Int), Type::Int), + crate::resume::SnapshotBox::typed(OpRef::input_arg_typed(2, Type::Int), Type::Int), + ])]; let result = opt.optimize_with_constants_and_inputs(&ops, &mut constants, 3); let jump = result .iter() @@ -3984,6 +4195,191 @@ mod tests { ); } + /// ```text + /// p0 = new_with_vtable(descr=size1) + /// setfield_gc(p0, i100, descr=field10) # slot 10 <- i100 + /// i1 = getfield_gc_i(p0, descr=misindexed) # claims a slot it does not hold + /// i2 = int_mul(i1, i200) # survives, so i1 is observable + /// ``` + /// + /// The setfield fixes the virtual's descr to `field_descr(10)`'s parent + /// (`optimize_setfield_gc` -> `init_fields`), whose slot list is `0..=10` + /// with `offset == idx * 8`. So `real_offset` is what decides LISTED vs + /// UNLISTED, and it is a parameter rather than a constant precisely so a + /// reader can check that for themselves: `read_slot * 8` makes the claimed + /// slot genuinely hold the field, anything else makes it a false claim. + /// Both legs below pass 24 — slot 3's address — so any `read_slot` other + /// than 3 is unlisted. + /// + /// `IntMul` is not in `OptVirtualize`'s dispatch table, so it survives the + /// pass with its argument resolved — that argument is the only place the + /// read's answer is observable, because the read itself is `Remove`d under + /// BOTH behaviours and an op-count assertion cannot tell them apart. + fn slot_read_trace(read_slot: u32, real_offset: usize) -> Vec { + let mut ops = vec![ + Op::with_descr(OpCode::NewWithVtable, &[], size_descr(1)), + Op::with_descr( + OpCode::SetfieldGc, + &[ + crate::history::test_support::rooted_resop_operand(Type::Ref, 0), + crate::history::test_support::rooted_resop_operand(Type::Int, 100), + ], + field_descr(10), + ), + Op::with_descr( + OpCode::GetfieldGcI, + &[crate::history::test_support::rooted_resop_operand( + Type::Ref, + 0, + )], + misindexed_field_descr(read_slot, real_offset), + ), + Op::new( + OpCode::IntMul, + &[ + crate::history::test_support::rooted_resop_operand(Type::Int, 2), + crate::history::test_support::rooted_resop_operand(Type::Int, 200), + ], + ), + ]; + assign_positions(&mut ops); + ops + } + + /// The answer the surviving `IntMul` received for the folded read, or + /// `None` when the read forwarded a non-constant operand. + fn folded_read_answer(result: &[Op]) -> Option { + assert_eq!( + result.len(), + 1, + "expected only the IntMul to survive; got {:?}", + result.iter().map(|o| o.opcode).collect::>() + ); + assert_eq!(result[0].opcode, OpCode::IntMul); + result[0].arg(0).const_value() + } + + /// THE POSITIVE LEG. A read whose claimed slot IS populated but does + /// NOT hold the field being read must answer the zeroed allocation, not + /// the value the other field stored there. + /// + /// The correct answer is `Value::Int(0)`: `field_slot_identifies` fails, + /// `slot_resolvable` is false, `field_val` is `None`, and the read falls + /// through to `virtualize.py:188-189`'s zero-fold — the guard's own log + /// line says "folding to the zeroed allocation". + /// + /// Before `field_slot_identifies` there was no read-side slot check, so + /// `get_field` found slot 10 populated and forwarded ANOTHER FIELD'S VALUE. + /// The fold is not the harm; the unguarded forward was. + #[test] + fn test_unlisted_slot_read_of_a_populated_slot_answers_the_zeroed_allocation() { + let ops = slot_read_trace(10, 24); + let result = run_pass_typed(&ops, &[100, 200]); + assert_eq!( + folded_read_answer(&result), + Some(Value::Int(0)), + "a read whose slot does not hold it must answer the zeroed \ + allocation; forwarding the populated slot hands back a different \ + field's value" + ); + } + + /// THE NEGATIVE CONTROL, and it must pass with or without the read-side + /// check. Same trace, same descrs, same populated slot 10 — only the + /// claimed slot moves to 5, which nothing ever stored. + /// + /// Both behaviours reach the zero-fold here: with a slot check because the + /// slot does not hold the field, without one because `get_field` finds + /// slot 5 empty. A test that exercised only this case would pass on + /// both branches and prove nothing about either. + #[test] + fn test_unlisted_slot_read_of_an_unpopulated_slot_is_branch_invariant() { + let ops = slot_read_trace(5, 24); + let result = run_pass_typed(&ops, &[100, 200]); + assert_eq!( + folded_read_answer(&result), + Some(Value::Int(0)), + "an unwritten slot folds to the zeroed allocation on either side \ + of the read-side slot check; this leg discriminates nothing and \ + exists to prove the positive leg is not measuring the fold" + ); + } + + /// THE THIRD LEG — the one that makes the read path's `init_fields` + /// (`:867-877`) actually replace the descr, which neither leg above does. + /// + /// Both legs above allocate with `size_descr(1)` and then `setfield` a + /// `field_descr(10)` whose parent IS a `SizeDescr`, so the SETFIELD's own + /// `init_fields` takes the `cur_len == 0` arm at `:749` and leaves + /// `cur_len == 11`. Every slot they read is below that, so the read-side + /// call is a no-op on both — ablating it (`if false &&`) left both green. + /// + /// This leg keeps `cur_len == 0` alive until the READ by denying the + /// setfield a `SizeDescr` parent (`NarrowParentFieldDescr`), which is the + /// only shape that lets a POPULATED slot coexist with `cur_len == 0`: + /// + /// ```text + /// p0 = new_with_vtable(descr=non_size) # as_size_descr() == None => cur_len 0 + /// setfield_gc(p0, i100, descr=narrow(3)) # slot 3 <- i100; init_fields returns + /// # early, disagreement short-circuits + /// i1 = getfield_gc_i(p0, descr=misindexed(3, 56)) # claims slot 3, holds slot 7 + /// i2 = int_mul(i1, i200) # survives, so i1 is observable + /// ``` + /// + /// At the read, `cur_len == 0` fires `init_fields`' first arm and installs + /// the field's own 4-slot parent. `field_slot_identifies` can then see that + /// slot 3 sits at offset 24 while the descr claims offset 56, refuses the + /// resolution, and the read folds to the zeroed allocation. + /// + /// WITHOUT the read-side `init_fields` the descr stays the non-size one, + /// and `field_slot_identifies` FAILS OPEN — its + /// `let Some(size_descr) = descr.as_size_descr() else { return true; }` + /// returns `true` for a descr with no field list at all. `get_field` then + /// finds slot 3 populated and forwards it, so `folded_read_answer` reads + /// `None` (a non-constant operand) instead of `Some(Int(0))`. That is the + /// two-sided ablation: delete the block at `:867-877` and this assertion + /// must go RED, where the two legs above stay green. + #[test] + fn test_read_path_init_fields_upgrades_a_zero_length_descr_before_the_slot_check() { + let mut ops = vec![ + Op::with_descr(OpCode::NewWithVtable, &[], non_size_descr(0x0D)), + Op::with_descr( + OpCode::SetfieldGc, + &[ + crate::history::test_support::rooted_resop_operand(Type::Ref, 0), + crate::history::test_support::rooted_resop_operand(Type::Int, 100), + ], + narrow_parent_field_descr(3), + ), + Op::with_descr( + OpCode::GetfieldGcI, + &[crate::history::test_support::rooted_resop_operand( + Type::Ref, + 0, + )], + // Claims slot 3 (offset 24) but answers slot 7's address. + misindexed_field_descr(3, 56), + ), + Op::new( + OpCode::IntMul, + &[ + crate::history::test_support::rooted_resop_operand(Type::Int, 2), + crate::history::test_support::rooted_resop_operand(Type::Int, 200), + ], + ), + ]; + assign_positions(&mut ops); + let result = run_pass_typed(&ops, &[100, 200]); + assert_eq!( + folded_read_answer(&result), + Some(Value::Int(0)), + "the read-side init_fields must install the field's parent before \ + field_slot_identifies runs; without it the descr carries no field \ + list, the slot check fails open, and the populated slot 3 is \ + forwarded instead of folded" + ); + } + #[test] fn test_setfield_initializes_parent_backed_fielddescrs() { let group = majit_ir::descr::make_simple_descr_group( diff --git a/majit/majit-metainterp/src/optimizeopt/virtualstate.rs b/majit/majit-metainterp/src/optimizeopt/virtualstate.rs index 3b351a5b108..e726265b540 100644 --- a/majit/majit-metainterp/src/optimizeopt/virtualstate.rs +++ b/majit/majit-metainterp/src/optimizeopt/virtualstate.rs @@ -2816,17 +2816,37 @@ fn export_single_value_inner( // is picked by `box.type` which is ALWAYS set on RPython Boxes. // pyre's OptContext::opref_type reconstructs it from value_types // (seeded from trace_inputargs) / producing-op result_type. - // Verified: 0 hits across all 10 benchmarks. Production panics; - // test builds keep a fallback because some unit tests construct - // minimal OptContext without seeding value_types for every OpRef - // that reaches export_state (pre-existing test limitation, not a - // production code path). + // The "0 hits" measured here covered the benchmark population; the + // example crates' test binaries are a population it did not enumerate, + // and one of them hits. `cel tests::policy_gates` reaches this line + // under a plain `cargo test --release -p cel` and panics, running + // `policy::run_gates` — the full pipeline, not the hand-built minimal + // OptContext the fallback below was written for. + // + // `cfg!(test)` is evaluated in majit-metainterp, so that fallback covers + // this crate's own unit tests and nothing else: downstream majit-metainterp + // is a plain dependency, `cfg!(test)` is false inside a downstream test + // binary too, and the panic is armed there. The firing is the proof — had + // the flag been true in cel's build, `Type::Int` would have been returned + // instead. So the fallback disarms the check exactly where this crate can + // exercise it, and leaves it live where a failure is hardest to attribute. let tp = ctx.opref_type(opref).unwrap_or_else(|| { if !cfg!(test) { + // Two different failures reach this line, and the message used to + // spell both of them `None`: `OpRef::None` is the absent-operand + // sentinel arriving as the argument, while a named ref means + // value_types holds no entry for a ref that does exist. Which one + // was seen is the difference between "an unbound operand was + // exported" and "a seeding gap", so name it rather than printing + // a line that reads as a tautology. + let seen = if opref.is_none() { + "the absent-operand sentinel reached export_state" + } else { + "no type recorded for a ref that does exist" + }; panic!( - "not_virtual: opref_type({:?}) returned None — \ + "not_virtual: opref_type({opref:?}) found no type — {seen}; \ RPython box.type is always set (virtualstate.py:360)", - opref, ); } Type::Int diff --git a/majit/majit-metainterp/src/pyjitpl.rs b/majit/majit-metainterp/src/pyjitpl.rs index e54ed9ee1c4..7847e1131d3 100644 --- a/majit/majit-metainterp/src/pyjitpl.rs +++ b/majit/majit-metainterp/src/pyjitpl.rs @@ -629,7 +629,7 @@ fn translate_trace_iter_opref(opref: OpRef, cache: &[Option { pub(crate) front_target_source_positions: Option>, /// Trace id of the root compiled loop. pub(crate) root_trace_id: u64, + /// Loop-header bytecode pc this loop was compiled at. A bridge trace + /// (`is_bridge_trace`) closes by jumping to its parent loop, which lives at + /// this header pc — not at the bridge's own `resume_pc`. + /// + /// Held on the entry rather than in a green-key-keyed side table so it + /// cannot outlive the artifact it describes: a `u64` side table keyed by + /// `get_uhash` is keyed by a *hash*, and nothing makes its retirement + /// atomic with the loop's (merge-point PC). Every replace path must carry it forward + /// off the old entry — a retrace compiles the same loop at the same header, + /// so losing it here silently strands every bridge that closes on it. + /// + /// The five carry-forwards this needs (`compile_loop`, `compile_retrace`, + /// `finish_and_compile`, `compile_simple_loop`, `compile_entry_bridge`) are + /// **not covered by any test**, measured rather than assumed: deleting all + /// five leaves the whole `majit-metainterp` suite green at 1566/1566. The + /// old side table needed no carry-forward because a replace never touched + /// it, so this hazard is new with the move and is currently held by review + /// alone. Before editing a replace path, note that the compiler cannot help + /// you here and neither can the suite. + /// + /// `None` means no header pc was recorded, which is the normal state for an + /// entry installed by any path other than a loop close. + pub(crate) loop_header_pc: Option, /// Metadata for the root loop and any attached bridges, keyed by trace id. pub(crate) traces: indexmap::IndexMap, /// RPython parity: previous compiled entries for this green_key. @@ -978,6 +1027,38 @@ impl CompiledEntry { pub(crate) fn live_token(&self) -> Option> { self.token.upgrade() } + + /// The state a replacement entry must inherit from the entry it displaces. + /// See `CarriedFields`. + pub(crate) fn carried_fields(&self) -> CarriedFields { + CarriedFields { + next_global_opref: self.next_global_opref, + loop_header_pc: self.loop_header_pc, + } + } +} + +/// The fields **every** `compiled_loops` replace path must carry forward off the +/// entry it displaces, bundled so they travel with the removal. +/// +/// Both are entry-local state that describes the *green key*, not the artifact: +/// `next_global_opref` keeps a later bridge's OpRefs disjoint from an earlier +/// one's, and `loop_header_pc` is the header a closing bridge jumps to. A +/// replacement that drops either strands work aimed at the key — silently, and +/// see `loop_header_pc`'s own doc for why neither the compiler nor the suite +/// will tell you. +/// +/// These two are carried at **all five** replace paths. They are deliberately +/// the *only* members: `front_target_tokens` is carried at exactly one site +/// (`compile_entry_bridge`) because the other four **mint** fresh labels, so +/// inheriting it elsewhere would be a behaviour change, not a fix. Likewise +/// `migrate_bridges` is called at three of the five and must stay at the call +/// sites. Adding a field here that is not universal re-introduces exactly the +/// bug this type exists to prevent. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) struct CarriedFields { + pub(crate) next_global_opref: u32, + pub(crate) loop_header_pc: Option, } /// Compute the smallest fresh OpRef strictly above every position @@ -1104,7 +1185,7 @@ fn default_issubclass(typeptr: i64, bounding_class: i64) -> bool { /// /// Bridge-origin descriptor carried from `start_retrace_from_guard` /// through `compile_trace_finish`. RPython stores the equivalent on -/// `self.resumekey` (`pyjitpl.py:2890 handle_guard_failure(self, +/// `self.resumekey` (`pyjitpl.py:2914 handle_guard_failure(self, /// resumedescr, deadframe)`) — the descr Arc itself is the canonical /// bridge-source identity. Pyre carries the same Arc in /// `source_descr`; `(trace_id, fail_index)` remain only as pyre-side @@ -1135,7 +1216,7 @@ pub struct BridgeTraceInfo { /// `self.history` for the duration of a single trace. Bridge origin /// state lives independently on `MetaInterp.bridge_info` so the /// bridge-resume entry can populate it without requiring an active -/// session (`pyjitpl.py:2890` `handle_guard_failure` is called with +/// session (`pyjitpl.py:2914` `handle_guard_failure` is called with /// `self.resumekey` set before the trace's `self.history` exists). pub struct ActiveTraceSession { /// Frontend state snapshot captured at `force_start_tracing` / @@ -1149,11 +1230,6 @@ pub struct MetaInterp { pub(crate) warm_state: WarmEnterState, pub(crate) backend: BackendImpl, pub(crate) compiled_loops: indexmap::IndexMap>, - /// Loop-header bytecode pc per compiled-loop green key. A bridge trace - /// (`is_bridge_trace`) closes by jumping to its parent loop, which lives - /// at this header pc — not at the bridge's own `resume_pc`. Recorded when - /// a loop compiles; queried at `start_bridge_tracing`. - pub(crate) loop_header_pcs: indexmap::IndexMap, /// warmstate.py:564-582 `JitCell.get_jit_cell_at_key` analog for the /// merge-point green vocabulary: the header greens (`(ints, refs, /// floats)`) each compiled loop was traced under, keyed by its green key. @@ -1165,16 +1241,33 @@ pub struct MetaInterp { /// `TraceCtx::cut_inner_green_key`), rather than from a loop closing at its /// own header. /// - /// A cut loop's `front_target_tokens[0]` is not a general procedure entry. - /// For a loop compiled at its own header that slot is the PREAMBLE — the - /// peeled first iteration that re-derives, from the loop's entry state, - /// every invariant the specialized label carries — which is what makes - /// `jump_to_preamble` (unroll.py:238-242) sound as the fallback when no - /// specialized label matches. A cut has no such peeled entry: its first - /// target token is the cut prefix, whose entry invariants were established - /// by the guards the CUTTING trace had already executed before it reached - /// the cut point. Only that trace's own closing JUMP arrives with them - /// proven. + /// A cut loop's entry is not a general procedure entry, even though it is + /// structurally shaped like one. `cut_trace_from_with_consts` does run + /// (`[jit] cut_trace_from: start.op_index=40 original_boxes=33 + /// trace_ops=77`) and the artifact does carry a peeled preamble — + /// `front_target_tokens` is `[preamble(no virtual state), specialized]`, + /// the same pair a loop compiled at its own header gets, which is what + /// makes `jump_to_preamble` (unroll.py:238-242) sound there. + /// + /// What the peel cannot restore is a fact the CUTTING trace proved in the + /// ops the cut discarded. `PreambleCompileData` re-optimizes the cut trace + /// from scratch, so it neither inherits those facts nor re-derives them — + /// it only replays the ops that survived, and an op the tracer recorded + /// WITHOUT a guard because the guard sat in the dropped prefix stays + /// unguarded. On cel's `batch_chain_eager_fold_traps` the cut trace opens + /// with exactly that: + /// + /// ```text + /// v33 = IntAdd(v5, 1) + /// v34 = IntMul(v33, 8) + /// v35 = RawLoadI(v13, v34) <- no bound guard; it was before the cut + /// ... + /// RawStore(v2, v55, v54) <- likewise + /// ``` + /// + /// so an entry that has not run the discarded prefix loads and stores out + /// of bounds. Only the cutting trace's own closing JUMP arrives with those + /// facts proven. pub(crate) cut_compiled_keys: indexmap::IndexSet, /// The [`Self::cut_compiled_keys`] entry the running `compile_loop_body` /// recorded on the way in, kept so `compile_loop` can retire it when the @@ -1203,6 +1296,16 @@ pub struct MetaInterp { /// `break` instead of resuming at the pc). `take`n by the `__merge` wrapper's /// caller. pub(crate) single_pass_finish: bool, + /// The FINISH arguments of a compiled run entered from a back edge, when + /// that run ended in FINISH rather than a back-edge JUMP or a guard + /// failure. `compile.py:623-638` `_DoneWithThisFrameDescr` sets + /// `final_descr = True`: a FINISH means the traced function has RETURNED, + /// and upstream `handle_fail` raises `jitexc.DoneWithThisFrame*` to unwind + /// the portal. There is no resume point past it, so front end B's + /// `Option` back-edge signature cannot express the outcome — + /// this carries the result value out of band instead. Set by + /// `back_edge_internal`, `take`n by `JitDriver::take_back_edge_finish*`. + pub(crate) back_edge_finish: Option>, /// Single-pass tracing: the walk-final scalar state-field values captured /// off the still-live sym at the CloseLoop point (scalar state-field index /// order, idx `0..num_scalars`), BEFORE the CloseLoop arm clears the sym. @@ -1344,7 +1447,7 @@ pub struct MetaInterp { pub(crate) exported_state: Option, /// pyjitpl.py:2373: number of cancelled compilation attempts. pub(crate) cancel_count: u32, - /// issue #108: count of non-`InvalidLoop` panics caught during JIT + /// issue compilation-panic: count of non-`InvalidLoop` panics caught during JIT /// compilation (a JIT bug, not a legitimate trace abort). In strict /// builds these re-raise; in release they are swallowed for graceful /// degradation, so this counter is the telemetry that the JIT was @@ -1654,7 +1757,7 @@ pub struct JitStats { pub loops_aborted: usize, pub bridges_compiled: usize, pub guard_failures: usize, - /// issue #108: non-`InvalidLoop` panics swallowed during compilation + /// issue compilation-panic: non-`InvalidLoop` panics swallowed during compilation /// (graceful degradation in release). Non-zero means the JIT was /// silently disabled for some traces by an internal bug. pub internal_compile_panics: u32, @@ -1667,7 +1770,14 @@ pub struct JitStats { #[derive(Default)] pub struct JitHooks { /// Called when a loop is compiled. Args: (green_key, num_ops_before, num_ops_after). - pub on_compile_loop: Option>, + /// Fires once per successful loop compile with the green key, the op + /// count before and after optimization, and the optimized body's opcode + /// KINDS. The kinds are captured on the same line as the count, so the + /// two always describe the same list; a gate reading one can trust the + /// other. Kinds rather than whole operations because a gate wants to ask + /// shape questions (`Jump` present? `GuardAlwaysFails` absent?) and the + /// full `Op` carries a `ResumeGuardDescr` whose `Debug` is enormous. + pub on_compile_loop: Option>, /// Called when a bridge is compiled. Args: (green_key, fail_index, num_ops). pub on_compile_bridge: Option>, /// Called on guard failure. Args: (green_key, fail_index, fail_count). @@ -2066,7 +2176,7 @@ impl MetaInterp { } /// GC walker for the forced-virtual caches held in - /// [`Self::forced_virtuals`], standing in for the trace `jf_savedata` gets + /// `Self::forced_virtuals`, standing in for the trace `jf_savedata` gets /// as a real GCREF field (`majit-backend/src/jitframe.rs:354`). /// /// Only the ptr half is walked. The int half is `virtuals_int_cache` — @@ -2740,13 +2850,13 @@ impl MetaInterp { warm_state: WarmEnterState::new(threshold), backend: BackendImpl::new(), compiled_loops: indexmap::IndexMap::new(), - loop_header_pcs: indexmap::IndexMap::new(), loop_header_greens: indexmap::IndexMap::new(), cut_compiled_keys: indexmap::IndexSet::new(), speculative_cut_owned_key: None, tracing: None, single_pass_outcome: None, single_pass_finish: false, + back_edge_finish: None, single_pass_scalar_values: None, single_pass_virt_array_values: None, pending_abort_blackhole: None, @@ -2984,6 +3094,27 @@ impl MetaInterp { .as_ref() } + /// Whether the driver in `jitdrivers_sd[index]` carries a + /// `virtualizable_info` — `false` is RPython's *novable* driver. + /// + /// `None` when `index` names no registered slot, which callers must treat + /// as "cannot attribute" rather than defaulting either way: a novable + /// driver's resume data has no vable section, so handing a decoder the + /// wrong answer either skips a section that is present or consumes one + /// that is not. + /// + /// Pairs with `JitCellToken::outermost_jitdriver_index` + /// (`compile.py:168 jitcell_token.outermost_jitdriver_sd = jitdriver_sd`): + /// a guard-failure consumer holding only a descr recovers the owning + /// driver through the token chain and asks this, instead of being handed + /// the answer by its caller. + pub fn jitdriver_has_vinfo(&self, index: usize) -> Option { + self.staticdata + .jitdrivers_sd + .get(index) + .map(|jd| jd.virtualizable_info.is_some()) + } + /// Copy a freshly-snapshotted `all_liveness` /// byte stream into `staticdata.liveness_info` without re-running /// the full `install_canonical_liveness` insn-id seeding. @@ -3030,7 +3161,7 @@ impl MetaInterp { staticdata.liveness_info = all_liveness.to_vec(); } - /// Install a fresh [`ActiveTraceSession`] seeded with the frontend + /// Install a fresh `ActiveTraceSession` seeded with the frontend /// trace metadata. Called from `force_start_tracing` / /// `bound_reached` / `on_back_edge_typed` and the bridge-resume /// path. Panics if a prior session was not cleared — mirrors @@ -3041,10 +3172,10 @@ impl MetaInterp { /// `profiler.start_tracing()` ↔ `end_tracing()` events fire at /// PyPy's `compile_and_run_once` / `handle_guard_failure` entry /// and finally — pyre routes those through - /// [`enter_profiler_tracing`] (called from + /// [`Self::enter_profiler_tracing`] (called from /// `prepare_trace_start_runtime` for roots and /// `start_retrace_from_guard` for bridges) and - /// [`leave_profiler_tracing`] (called from session-close paths). + /// [`Self::leave_profiler_tracing`] (called from session-close paths). pub fn begin_trace_session(&mut self, trace_meta: M) { debug_assert!( self.active_trace_session.is_none(), @@ -3054,7 +3185,7 @@ impl MetaInterp { } /// Attach bridge-origin metadata. Called once at bridge entry - /// (`pyjitpl.py:2890` `handle_guard_failure` sets `self.resumekey`). + /// (`pyjitpl.py:2914` `handle_guard_failure` sets `self.resumekey`). pub fn set_bridge_trace_info(&mut self, bridge: BridgeTraceInfo) { self.bridge_info = Some(bridge); } @@ -3103,7 +3234,7 @@ impl MetaInterp { /// before calling `recorder.finish()` + backend compile. /// /// Does *not* fire `profiler.end_tracing()`: the profiler event - /// scope is owned by [`leave_profiler_tracing`] and matches PyPy's + /// scope is owned by [`Self::leave_profiler_tracing`] and matches PyPy's /// `compile_and_run_once` / `handle_guard_failure` `finally` /// boundary, which is reached *after* the finish-compile body /// runs. Callers fire `leave_profiler_tracing` at the @@ -3121,8 +3252,8 @@ impl MetaInterp { /// `prepare_trace_start_runtime` / `start_retrace_from_guard`. /// Both effects are bundled here because every trace-abort path /// reaches `clear_trace_session` (it is the structural close - /// point); success paths that drain via [`take_trace_meta`] fire - /// [`leave_profiler_tracing`] explicitly at the close-equivalent + /// point); success paths that drain via [`Self::take_trace_meta`] fire + /// [`Self::leave_profiler_tracing`] explicitly at the close-equivalent /// point and reach a no-op here. pub fn clear_trace_session(&mut self) { self.leave_profiler_tracing(); @@ -3137,7 +3268,7 @@ impl MetaInterp { /// `compile_and_run_once` / `handle_guard_failure` are not /// re-entrant on the same MetaInterp). The debug section and /// profiler event are issued together here because the - /// matching close lives in [`leave_profiler_tracing`]; both halves + /// matching close lives in [`Self::leave_profiler_tracing`]; both halves /// must move as a pair to keep the `debug_start`/`debug_stop` /// nesting balanced (PyPy convention). /// @@ -3165,12 +3296,12 @@ impl MetaInterp { /// Open the profiler tracing event scope assuming the /// `jit-tracing` debug section has *already* been opened upstream - /// by the caller. Used by [`prepare_trace_start_runtime`] so the + /// by the caller. Used by `prepare_trace_start_runtime` so the /// debug section can wrap `_setup_once` while the profiler event /// only opens after `_setup_once` completes — matching the /// `debug_start; _setup_once; start_tracing` order at /// `pyjitpl.py:2888-2892`. The matching close still routes - /// through [`leave_profiler_tracing`]. + /// through [`Self::leave_profiler_tracing`]. pub fn open_profiler_tracing_inner(&mut self) { // Same release-build assertion contract as // [`enter_profiler_tracing`] — a second entry would leak a @@ -3187,7 +3318,7 @@ impl MetaInterp { /// `pyjitpl.py:2897 / 2934` /// `profiler.end_tracing(); debug_stop("jit-tracing")` parity — /// close the profiler event scope opened by - /// [`enter_profiler_tracing`], then the matching debug section + /// [`Self::enter_profiler_tracing`], then the matching debug section /// (LIFO unwind matching PyPy's nested `try/finally`). No-op if /// the scope was already closed (mirrors PyPy's `finally` /// semantics: a path that never reached `start_tracing` still @@ -3579,6 +3710,38 @@ impl MetaInterp { } else { None }; + // The `None` arm below mints `OpRef::input_arg_ref(box_ref_index)` — a + // ref-bank ordinal spelled into the flat trace-inputarg space, which is + // sound only at index 0. Two disjoint routes reach it: no declared bank + // index, and a declared index whose live position did not match. + // + // Nothing derives the index-0 property here. Two writers can supply + // `index_of_virtualizable`, and they reach zero for different reasons: + // `set_virtualizable_info` eagerly zeroes a driver whose reds are empty, + // while the `warmspot.py:534-538` port in `codewriter/call.rs` takes the + // virtualizable's reds-relative position, which is zero only when it is + // declared first among the reds. The driver resolved at this site is the + // placeholder at jd slot 0 — empty reds, no virtualizable name — so the + // eager zero is what holds; the reds-order route is not instantiated + // here rather than checked here. `warmspot.py:527-530` leaves the + // not-applicable case at -1, a sentinel that would fail loudly if it + // were ever used as an index. + // + // Assert it where the box is minted, naming both terms and the resolved + // slot: a failure has to say which driver was resolved and which of the + // two routes produced the fallback, or it cannot be told from an + // election problem. + assert!( + !(identity_index.is_none() && box_ref_index != 0), + "initialize_virtualizable: virtualizable red-slot convention violated \ + on jitdriver slot {idx}: identity_index=None, \ + identity_ref_bank_index={:?}, box_ref_index={} (expected 0 on the \ + fallback arm). That arm mints `OpRef::input_arg_ref(box_ref_index)` \ + in the flat inputarg space from a ref-bank ordinal; at a non-zero \ + index the minted box names an unrelated value.", + info.identity_ref_bank_index, + box_ref_index, + ); let virtualizable_box = match identity_index { Some(idx) => OpRef::input_arg_typed(idx as u32, Type::Ref), None => OpRef::input_arg_ref(box_ref_index as u32), @@ -4094,7 +4257,10 @@ impl MetaInterp { } /// Set a callback for loop compilation events. - pub fn set_on_compile_loop(&mut self, f: impl Fn(u64, usize, usize) + Send + 'static) { + pub fn set_on_compile_loop( + &mut self, + f: impl Fn(u64, usize, usize, &[OpCode]) + Send + 'static, + ) { self.hooks.on_compile_loop = Some(Box::new(f)); } @@ -4383,12 +4549,21 @@ impl MetaInterp { } } + /// `green_key_values` and `driver_descriptor` arrive unbuilt — a key + /// factory and a borrow — because only the `StartTracing` arm below + /// consumes them. warmstate.py:446-511 `maybe_compile_and_run` orders the + /// decision the same way: greenargs, hash, `lookup_chain`, then + /// `jitcounter.tick` (`:467`), and only past the counter does it reach + /// `confirm_enter_jit` (`:501`) and the red-argument extraction loop + /// (`:503-506`). `bound_reached` is handed `*args` raw for exactly this + /// reason. Building either eagerly at the call site pays for both on the + /// `NotHot` / `RunCompiled` arms, which drop them unread. pub fn on_back_edge_typed( &mut self, green_key: u64, green_key_raw: (usize, usize), - green_key_values: Option, - driver_descriptor: Option, + green_key_values: Option<&dyn Fn() -> majit_ir::GreenKey>, + driver_descriptor: Option<&JitDriverStaticData>, live_values: &[Value], ) -> BackEdgeAction { if self.tracing.is_some() { @@ -4411,11 +4586,17 @@ impl MetaInterp { HotResult::NotHot => BackEdgeAction::Interpret, HotResult::StartTracing => { self.prepare_trace_start_runtime(); + // The only reader of either value. The key factory closes over + // `Copy` locals bound once at the back edge + // (`jit_interp/mod.rs` `__green_slotN`), and the descriptor + // borrow points at a snapshot `Arc` built by + // `driver_descriptor_for`, so neither observes anything the + // counter consultation above may have changed. self.setup_tracing( green_key, green_key_raw, - green_key_values, - driver_descriptor, + green_key_values.map(|make_key| make_key()), + driver_descriptor.cloned(), live_values, ) } @@ -4636,7 +4817,7 @@ impl MetaInterp { /// routes `BC_CALL_ASSEMBLER_*` against the production /// `Arc` rather than the `_by_number_typed` synth-Arc /// fallback (borrows `compiled_loops` + `warm_state`); - /// 3. `recursive_target` — #184 green-key → `(Arc, + /// 3. `recursive_target` — recursive-call green-key → `(Arc, /// green_key)` resolver for a recursive CALL_ASSEMBLER (mirrors /// `get_loop_token_arc`; only already-compiled callees, the /// pending-token window returns `None` → the dispatcher aborts); @@ -4683,7 +4864,7 @@ impl MetaInterp { } warm_state.find_token_by_number(n).map(Arc::clone) }; - // #184 green-key → token resolver (pyjitpl.py:3593-3599 + // recursive-call green-key → token resolver (pyjitpl.py:3593-3599 // `get_assembler_token`). Resolves only already-compiled callees // through `warm_state.get_compiled`; the pending-token convergence // window returns `None` so the dispatcher aborts and retries (a @@ -4696,7 +4877,7 @@ impl MetaInterp { .get_compiled(green_key) .map(|arc| (Arc::clone(arc), green_key)) }; - // #184 recursive-portal inline decision, sharing + // recursive-call recursive-portal inline decision, sharing // `decide_recursive_inline` with `should_inline_core` so the // dispatch-side and metainterp-side decisions cannot drift. The // `should_disable` (`dont_trace_here` → `disable_noninlinable_function`) @@ -4733,7 +4914,7 @@ impl MetaInterp { ); decision }; - // #184 concrete recursive-callee execution: run the compiled loop + // recursive-call concrete recursive-callee execution: run the compiled loop // through the JITFRAME-ABI `execute_token_raw` and decode the int // FINISH output (mirrors `run_compiled_raw_detailed_with_values`). let recursive_exec = |token: &JitCellToken, reds: &[Value]| -> Option { @@ -4786,7 +4967,6 @@ impl MetaInterp { self.force_finish_trace } - // ── RPython opimpl_* equivalents for virtualizable ────────────── // // pyjitpl.py:1120-1146 `_nonstandard_virtualizable(pc, box, fielddescr)` // is implemented in `TraceCtx::is_nonstandard_virtualizable` with the @@ -5610,7 +5790,7 @@ impl MetaInterp { /// `meta` is interpreter-specific metadata to store alongside the compiled loop. /// pyjitpl.py:2979-3036 `reached_loop_header` → `compile_loop` dispatch. /// - /// This public entry wraps [`Self::compile_loop_body`] so every exit + /// This public entry wraps `Self::compile_loop_body` so every exit /// path restores the RPython invariant that /// `active_trace_session.is_some()` iff `self.tracing.is_some()`. /// Upstream uses `self.history` as a shared mutable object — cancel @@ -6195,6 +6375,10 @@ impl MetaInterp { // re-invoked on the next reached_loop_header. Do NOT call // abort_tracing — TRACING flag must stay active. if !self.cancelled_too_many_times() { + // Slot 72: the unrolled compile was abandoned and + // tracing continues. Bumped per EVENT, so a single + // key contributes once per cancelled attempt. + crate::mc_diag_bump(72); self.exported_state = None; if crate::closedbg_enabled() { eprintln!("@@@CANCEL-SITE line={}", line!()); @@ -6275,6 +6459,13 @@ impl MetaInterp { })); match retry_result { Ok(Ok(retry_ops)) => { + // Slot 73: a loop is about to be compiled + // WITHOUT the peel. This is the silent + // degradation — the retry succeeds, so the + // compile counters, the result and every + // success-keyed gate read exactly as they do + // for a healthy peeled loop. + crate::mc_diag_bump(73); if crate::majit_log_enabled() { eprintln!( "[jit] retry without unroll succeeded at key={}", @@ -6292,6 +6483,11 @@ impl MetaInterp { } Ok(Err(_invalid_loop)) => { // The unroll-free retry also abandoned the trace. + // Slot 74: the only arm of the three that is + // already loud — it ends in `Aborted`, so a + // compile-count gate can see it. Counted anyway + // so 72+73+74 totals the raised `InvalidLoop`s. + crate::mc_diag_bump(74); if crate::majit_log_enabled() { eprintln!( "[jit] retry without unroll hit InvalidLoop at key={}", @@ -6352,12 +6548,18 @@ impl MetaInterp { }; let opt_time = Instant::now().saturating_duration_since(optimize_start); let num_ops_after = optimized_ops.len(); + let opcodes_after: Vec = optimized_ops.iter().map(|op| op.opcode).collect(); if crate::majit_log_enabled() { eprintln!( "[jit] post-opt: {} ops (before: {})", num_ops_after, num_ops_before ); } + crate::optimizeopt::optimizer::Optimizer::log_optimized_trace( + "compile_loop_body", + &optimized_ops, + &constants, + ); // RPython compile.py keeps the root entry contract on the original // loop inputargs. Simple loops synthesize a LABEL from that contract; @@ -6832,7 +7034,12 @@ impl MetaInterp { // RPython parity: keep previous compiled tokens alive so // external target_token JUMPs can redirect to them. let mut previous_tokens: Vec> = Vec::new(); - if let Some(old_entry) = self.compiled_loops.swap_remove(&green_key) { + // Carried forward because this replaces the entry at the same + // green key: the loop still closes at the header it closed at + // before, so dropping the pc here would strand every bridge + // that aims its closing JUMP at it. + let mut carried_loop_header_pc = None; + if let Some((old_entry, carried)) = self.take_entry_for_replace(green_key) { // Cranelift workaround (no RPython counterpart): copy // bridges from old token to new, since Cranelift cannot // patch machine code in-place. No-op for dynasm. @@ -6842,7 +7049,8 @@ impl MetaInterp { // Box Identity Phase E.2b parity: preserve old entry's // high-water so previously stored bridges' OpRefs stay // disjoint from any future bridge. - next_global_opref = next_global_opref.max(old_entry.next_global_opref); + next_global_opref = next_global_opref.max(carried.next_global_opref); + carried_loop_header_pc = carried.loop_header_pc; previous_tokens = self.retire_compiled_entry(green_key, old_entry, &mut traces); } if crate::debug::have_debug_prints() { @@ -6851,10 +7059,25 @@ impl MetaInterp { &format!("compiled_loops.insert green_key={green_key}"), ); } + let front_entry_index = Self::front_entry_index_for(&front_target_tokens); if crate::jitdriver::spdiag_enabled() { - eprintln!("@@@SPDIAG compiled_loops.insert green_key={green_key}"); + let shape: Vec = front_target_tokens + .iter() + .map(|target| { + format!( + "{}{}", + if target.is_preamble_target { "P" } else { "S" }, + target.virtual_state.is_some() as u8, + ) + }) + .collect(); + eprintln!( + "@@@SPDIAG compiled_loops.insert green_key={green_key} \ + cut={} tokens=[{}] front_entry_index={front_entry_index:?}", + self.cut_compiled_keys.contains(&green_key), + shape.join(","), + ); } - let front_entry_index = Self::front_entry_index_for(&front_target_tokens); token.set_retraced_count(unroll_opt.retraced_count); self.compiled_loops.insert( green_key, @@ -6865,6 +7088,7 @@ impl MetaInterp { front_entry_index, front_target_source_positions, root_trace_id: trace_id, + loop_header_pc: carried_loop_header_pc, traces, previous_tokens, // Box Identity Phase E Step 1: record Phase 2's final @@ -6892,7 +7116,7 @@ impl MetaInterp { // explicit metainterp-side bump needed here. if let Some(ref hook) = self.hooks.on_compile_loop { - hook(green_key, num_ops_before, num_ops_after); + hook(green_key, num_ops_before, num_ops_after, &opcodes_after); } // pyjitpl.py:3025: self.exported_state = None self.exported_state = None; @@ -6987,7 +7211,7 @@ impl MetaInterp { self.partial_trace.as_ref() } - /// See [`Self::keep_tracing_after_close`]. Read-and-clear: the answer is + /// See `Self::keep_tracing_after_close`. Read-and-clear: the answer is /// only meaningful to the close that just ran. pub fn take_keep_tracing_after_close(&mut self) -> bool { std::mem::take(&mut self.keep_tracing_after_close) @@ -7065,6 +7289,31 @@ impl MetaInterp { self.keep_tracing_after_close = true; } + /// Removes the `compiled_loops` entry at `green_key`, returning it together + /// with the state a replacement must inherit from it. + /// + /// This is the single door onto a replace: **obtaining the displaced entry + /// without also receiving its `CarriedFields` is not expressible.** That is + /// the whole point — the carry-forward used to be two hand-written lines + /// repeated at every replace path, and dropping them is invisible to the + /// compiler and to the suite alike. + /// + /// This does **not** mean the replace paths are tested. It removes one + /// class of omission; everything else those paths do (bridge migration, + /// label minting, retirement, snapshot handling) remains uncovered, because + /// nothing in this crate can drive a production compile path. + /// The caller still performs `retire_compiled_entry`, `migrate_bridges` and + /// any label carry itself, deliberately: those vary per site and flattening + /// them into here would change behaviour at the sites that omit them. + pub(crate) fn take_entry_for_replace( + &mut self, + green_key: u64, + ) -> Option<(CompiledEntry, CarriedFields)> { + let old_entry = self.compiled_loops.swap_remove(&green_key)?; + let carried = old_entry.carried_fields(); + Some((old_entry, carried)) + } + /// compile.py: has_compiled_targets — check if a green key has /// compiled target tokens that a bridge can jump to. pub fn has_compiled_targets(&self, green_key: u64) -> bool { @@ -7239,7 +7488,7 @@ impl MetaInterp { // // `compile_bridge`'s `PendingBridgeRd` zip is likewise positional over // every bridge inputarg rather than a resume-walked subset. See - // task #37 for the live-filtered channel this site needs before the + // the live-filtered channel for the live-filtered channel this site needs before the // values can be carried again. let bridge_inputargs: Vec = ctx .recorder @@ -7345,7 +7594,7 @@ impl MetaInterp { from_retry: false, }; } - // `pyjitpl.py:2890` `handle_guard_failure(self, + // `pyjitpl.py:2914` `handle_guard_failure(self, // resumedescr, deadframe)` parity: the source descr Arc // is `self.resumekey` (== the descr // `cpu.get_latest_descr(deadframe)` returned). Pyre @@ -7505,7 +7754,7 @@ impl MetaInterp { // `compile.py:392 resumekey.compile_and_attach(metainterp, loop, // inputargs)` — the resumekey decides how a finished retrace is // installed, and a retrace grown from a guard failure carries a - // `ResumeGuardDescr` (`pyjitpl.py:2890 handle_guard_failure(self, + // `ResumeGuardDescr` (`pyjitpl.py:2914 handle_guard_failure(self, // resumedescr, deadframe)` stores it as `self.resumekey`). let retrace_resumekey = self.bridge_info_cloned(); let vable_config = self.current_virtualizable_optimizer_config(); @@ -7851,6 +8100,7 @@ impl MetaInterp { // since the descr-strict dispatch landed. let num_combined_ops = combined_ops.len(); + let opcodes_after: Vec = combined_ops.iter().map(|op| op.opcode).collect(); let has_guard = combined_ops.iter().any(|op| op.opcode.is_guard()); if !has_guard { crate::debug::log_one("jit-abort", "compile_retrace: guardless loop"); @@ -8057,7 +8307,9 @@ impl MetaInterp { ); let mut previous_tokens: Vec> = Vec::new(); - if let Some(old_entry) = self.compiled_loops.swap_remove(&green_key) { + // Carried forward for the same reason as the compile_loop site. + let mut carried_loop_header_pc = None; + if let Some((old_entry, carried)) = self.take_entry_for_replace(green_key) { // Cranelift workaround (no RPython counterpart): copy // bridges from old token to new, since Cranelift cannot // patch machine code in-place. No-op for dynasm. @@ -8065,7 +8317,8 @@ impl MetaInterp { self.backend.migrate_bridges(&old_tok, token.as_ref()); } // Box Identity Phase E.2b parity: see compile_loop site. - next_global_opref = next_global_opref.max(old_entry.next_global_opref); + next_global_opref = next_global_opref.max(carried.next_global_opref); + carried_loop_header_pc = carried.loop_header_pc; previous_tokens = self.retire_compiled_entry(green_key, old_entry, &mut traces); } if crate::debug::have_debug_prints() { @@ -8095,6 +8348,7 @@ impl MetaInterp { front_entry_index, front_target_source_positions: None, root_trace_id: trace_id, + loop_header_pc: carried_loop_header_pc, traces, previous_tokens, // Box Identity Phase E Step 1: see main compile site. @@ -8114,7 +8368,7 @@ impl MetaInterp { // `CompiledLoopToken::new` (model.py:297 parity). if let Some(ref hook) = self.hooks.on_compile_loop { - hook(green_key, 0, num_combined_ops); + hook(green_key, 0, num_combined_ops, &opcodes_after); } self.last_quasi_immutable_deps = quasi_immutable_deps; true @@ -8458,9 +8712,17 @@ impl MetaInterp { // `Counters.ABORT_*` reason; this is the catch that turns it into the // one accounting event, as `aborted_tracing(stb.reason)` does for the // reason the `SwitchToBlackhole` raise carried. - let reason = self - .take_pending_abort_reason() - .unwrap_or(AbortReason::Generic.as_int()); + let reason = match self.take_pending_abort_reason() { + Some(r) => r, + None => { + // Nothing staged a reason, so this abort is unclassified rather + // than the bridge giveup that shares `ABORT_BRIDGE`'s slot. + // Name it where the fallback is chosen; slot 41 is their total + // and cannot separate them after the fact. + crate::mc_diag_bump(71); + AbortReason::Generic.as_int() + } + }; self.aborted_tracing(reason); } @@ -8567,10 +8829,10 @@ impl MetaInterp { /// * `true` → `compile_exit_frame_with_exception` (pyjitpl.py:3238-3245) /// — descr = `sd.exit_frame_with_exception_descr_ref`. /// - /// Returns `Err(SwitchToBlackhole::bad_loop())` on optimizer + /// Returns `Err(SwitchToBlackhole::giveup())` on optimizer /// `InvalidLoop` or backend compile failure, matching /// pyjitpl.py:3220 `compile.giveup()` surfacing as - /// `SwitchToBlackhole(ABORT_BAD_LOOP)`. The caller (typically + /// `SwitchToBlackhole(ABORT_BRIDGE)`. The caller (typically /// `compile_finish_from_active_session`) propagates the error so /// `finishframe`/`finishframe_exception` can translate it into /// `aborted_tracing(reason)` per pyjitpl.py:2491. @@ -8738,6 +9000,7 @@ impl MetaInterp { // new_operations naturally — no restoration needed. let optimized_ops = optimized_ops; let num_ops_after = optimized_ops.len(); + let opcodes_after: Vec = optimized_ops.iter().map(|op| op.opcode).collect(); // optimizer.py:557 self.resumedata_memo.update_counters(profiler) optimizer.update_counters(&self.staticdata.profiler); // RPython compile.py:234 parity: transfer quasi-immutable deps @@ -8760,6 +9023,11 @@ impl MetaInterp { eprintln!("--- finish trace (after opt, before unbox) ---"); eprint!("{}", majit_ir::format_trace(&optimized_ops, &constants)); } + crate::optimizeopt::optimizer::Optimizer::log_optimized_trace( + "finish_and_compile", + &optimized_ops, + &constants, + ); // resume.py:411-417 parity: NONE entries in guard fail_args are // valid (TAGCONST/TAGVIRTUAL slots that resume reconstructs from @@ -8948,11 +9216,14 @@ impl MetaInterp { .map(|tok| tok.get_retraced_count()) .unwrap_or(0); let _had_old = self.compiled_loops.contains_key(&green_key); - if let Some(old_entry) = self.compiled_loops.swap_remove(&green_key) { + // Carried forward for the same reason as the compile_loop site. + let mut carried_loop_header_pc = None; + if let Some((old_entry, carried)) = self.take_entry_for_replace(green_key) { // Box Identity Phase E.2b parity: preserve old entry's // high-water so previously stored bridges' OpRefs stay // disjoint from any future bridge. - next_global_opref = next_global_opref.max(old_entry.next_global_opref); + next_global_opref = next_global_opref.max(carried.next_global_opref); + carried_loop_header_pc = carried.loop_header_pc; previous_tokens = self.retire_compiled_entry(green_key, old_entry, &mut traces); } @@ -8987,6 +9258,7 @@ impl MetaInterp { front_entry_index, front_target_source_positions: None, root_trace_id: trace_id, + loop_header_pc: carried_loop_header_pc, traces, previous_tokens, next_global_opref, @@ -9013,7 +9285,7 @@ impl MetaInterp { ); } if let Some(ref hook) = self.hooks.on_compile_loop { - hook(green_key, num_ops_before, num_ops_after); + hook(green_key, num_ops_before, num_ops_after, &opcodes_after); } } Err(e) => { @@ -9161,6 +9433,7 @@ impl MetaInterp { self.last_quasi_immutable_deps = std::mem::take(&mut optimizer.quasi_immutable_deps); let num_ops_after = optimized_ops.len(); + let opcodes_after: Vec = optimized_ops.iter().map(|op| op.opcode).collect(); if crate::majit_log_enabled() { eprintln!( "[jit] compile_simple_loop: key={}, ops_before={}, ops_after={}", @@ -9169,6 +9442,11 @@ impl MetaInterp { eprintln!("--- simple loop trace (after opt) ---"); eprint!("{}", majit_ir::format_trace(&optimized_ops, &constants)); } + crate::optimizeopt::optimizer::Optimizer::log_optimized_trace( + "compile_simple_loop", + &optimized_ops, + &constants, + ); let optimized_ops = compile::strip_stray_overflow_guards(optimized_ops); @@ -9321,9 +9599,12 @@ impl MetaInterp { }, ); let mut previous_tokens: Vec> = Vec::new(); - if let Some(old_entry) = self.compiled_loops.swap_remove(&green_key) { + // Carried forward for the same reason as the compile_loop site. + let mut carried_loop_header_pc = None; + if let Some((old_entry, carried)) = self.take_entry_for_replace(green_key) { // Box Identity Phase E.2b parity: see finish_and_compile. - next_global_opref = next_global_opref.max(old_entry.next_global_opref); + next_global_opref = next_global_opref.max(carried.next_global_opref); + carried_loop_header_pc = carried.loop_header_pc; previous_tokens = self.retire_compiled_entry(green_key, old_entry, &mut traces); } let front_target_tokens = vec![target_token]; @@ -9337,6 +9618,7 @@ impl MetaInterp { front_entry_index, front_target_source_positions: None, root_trace_id: trace_id, + loop_header_pc: carried_loop_header_pc, traces, previous_tokens, next_global_opref, @@ -9359,7 +9641,7 @@ impl MetaInterp { ); } if let Some(ref hook) = self.hooks.on_compile_loop { - hook(green_key, num_ops_before, num_ops_after); + hook(green_key, num_ops_before, num_ops_after, &opcodes_after); } // compile.py:249: return target_token self.compile_snapshot_refs.clear(); @@ -9398,13 +9680,27 @@ impl MetaInterp { /// Record the loop-header bytecode pc for a compiled-loop green key, so a /// later bridge whose guard belongs to this loop knows where its parent /// loop header lives (the close target of the bridge JUMP). + /// + /// A key with no `compiled_loops` entry is dropped rather than recorded: + /// the pc describes a compiled loop, so with no loop there is nothing for + /// it to describe and no reader that could act on it. Callers record after + /// a compile reports success, at which point the entry exists. pub fn record_loop_header_pc(&mut self, green_key: u64, header_pc: usize) { - self.loop_header_pcs.insert(green_key, header_pc); + if let Some(entry) = self.compiled_loops.get_mut(&green_key) { + entry.loop_header_pc = Some(header_pc); + } } /// Loop-header bytecode pc recorded for a compiled-loop green key. + /// + /// `None` covers both "no loop is compiled at this key" and "one is, but it + /// closed somewhere other than a loop header". Neither is an error: the + /// callers use the pc to aim a bridge's closing JUMP, and not having one is + /// a reason to decline the bridge, never to assume a pc. pub fn loop_header_pc_for(&self, green_key: u64) -> Option { - self.loop_header_pcs.get(&green_key).copied() + self.compiled_loops + .get(&green_key) + .and_then(|entry| entry.loop_header_pc) } /// Record the merge-point green constants a compiled loop was traced @@ -9432,7 +9728,7 @@ impl MetaInterp { /// compile.py:269-270 parity: whether the loop at `green_key` was stored /// under a cross-loop cut's inner jitcell token. /// - /// See [`MetaInterp::cut_compiled_keys`] for why the distinction matters to + /// See `MetaInterp::cut_compiled_keys` for why the distinction matters to /// anything that wants to JUMP into the loop from outside the trace that /// cut it. pub fn is_cross_loop_cut_key(&self, green_key: u64) -> bool { @@ -9924,7 +10220,7 @@ impl MetaInterp { }) } - /// Typed-input counterpart to [`run_compiled_detailed`]. + /// Typed-input counterpart to [`Self::run_compiled_detailed`]. pub fn run_compiled_detailed_with_values( &mut self, green_key: u64, @@ -10283,13 +10579,16 @@ impl MetaInterp { } } - /// Drop the per-loop side tables (`loop_header_pcs`, `loop_header_greens`) - /// when a loop is retired, so they cannot outlive `compiled_loops`. - /// `compiled_key_for_greens` already skips keys without compiled targets, - /// so a leftover entry could not mis-target a bridge — but keeping them - /// would grow both maps without bound over a long run. + /// Drop the per-loop side tables (`loop_header_greens`, + /// `cut_compiled_keys`) when a loop is retired, so they cannot outlive + /// `compiled_loops`. `compiled_key_for_greens` already skips keys without + /// compiled targets, so a leftover entry could not mis-target a bridge — + /// but keeping them would grow both maps without bound over a long run. + /// + /// The loop-header pc used to be retired here too; it now lives on + /// `CompiledEntry::loop_header_pc` and is dropped with the entry, so no + /// caller can forget it. fn forget_loop_side_tables(&mut self, green_key: u64) { - self.loop_header_pcs.swap_remove(&green_key); self.loop_header_greens.swap_remove(&green_key); self.cut_compiled_keys.swap_remove(&green_key); } @@ -10457,8 +10756,6 @@ impl MetaInterp { } } - // ── Call Assembler Support ────────────────────────────────── - /// Get the JitCellToken for a compiled loop (for CALL_ASSEMBLER). /// /// In RPython, `call_assembler` allows JIT code for one function @@ -10922,7 +11219,6 @@ impl MetaInterp { /// recompile that has not happened yet, not for the loops being dropped. pub fn clear_compiled_loops(&mut self) { self.compiled_loops.clear(); - self.loop_header_pcs.clear(); self.loop_header_greens.clear(); self.cut_compiled_keys.clear(); } @@ -11225,8 +11521,6 @@ impl MetaInterp { }) } - // ── Bridge Compilation ────────────────────────────────────── - /// pyjitpl.py:3195 finally: self.history.cut(cut_at) — undo tentative JUMP/FINISH. fn cut_tentative_op(&mut self, cut_at: crate::recorder::TracePosition) { if let Some(ctx) = self.tracing.as_mut() { @@ -11675,6 +11969,7 @@ impl MetaInterp { orig_vable_ptr_entry, ); let num_optimized_ops = optimized_ops.len(); + let opcodes_after: Vec = optimized_ops.iter().map(|op| op.opcode).collect(); let compiled_constants_typed = crate::optimizeopt::optimizer::lower_typed_constants_to_const_pool(&constants); let trace_id = self.alloc_trace_id(); @@ -11833,10 +12128,20 @@ impl MetaInterp { .map(|tok| tok.get_retraced_count()) .unwrap_or(0); let mut previous_tokens: Vec> = Vec::new(); - if let Some(old_entry) = self.compiled_loops.swap_remove(&original_green_key) { + // Carried forward for the same reason as `front_target_tokens` + // just below: the replacement entry inherits the retired loop's + // close target, and a key with labels but no header pc is just + // as unreachable to a closing bridge as one with neither. + let mut carried_loop_header_pc = None; + if let Some((old_entry, carried)) = self.take_entry_for_replace(original_green_key) + { // Box Identity Phase E.2b parity: see finish_and_compile. - next_global_opref = next_global_opref.max(old_entry.next_global_opref); + next_global_opref = next_global_opref.max(carried.next_global_opref); + // Read off `old_entry`, not `carried`: this is the only one + // of the five sites that inherits the retired loop's + // labels, so it is not a `CarriedFields` member. front_target_tokens = old_entry.front_target_tokens.clone(); + carried_loop_header_pc = carried.loop_header_pc; if let Some(old_tok) = old_entry.live_token() { self.backend.migrate_bridges(&old_tok, token.as_ref()); } @@ -11854,6 +12159,7 @@ impl MetaInterp { front_entry_index, front_target_source_positions: None, root_trace_id: trace_id, + loop_header_pc: carried_loop_header_pc, traces, previous_tokens, next_global_opref, @@ -11871,7 +12177,12 @@ impl MetaInterp { // `cpu.tracker.total_compiled_loops` is bumped inside // `CompiledLoopToken::new` (model.py:297 parity). if let Some(ref hook) = self.hooks.on_compile_loop { - hook(original_green_key, bridge_ops.len(), num_optimized_ops); + hook( + original_green_key, + bridge_ops.len(), + num_optimized_ops, + &opcodes_after, + ); } true } @@ -12709,11 +13020,11 @@ impl MetaInterp { /// guard failure point. The resulting trace replaces the original guard. /// /// Returns true if retracing was started. - /// RPython pyjitpl.py:2890 handle_guard_failure parity: + /// RPython pyjitpl.py:2914 handle_guard_failure parity: /// Initialize bridge tracing from a guard failure point. /// Returns (success, is_exception_guard) so the caller can emit /// SAVE_EXC_CLASS + SAVE_EXCEPTION ops for exception bridges. - /// `pyjitpl.py:2890` `handle_guard_failure(self, resumedescr, + /// `pyjitpl.py:2914` `handle_guard_failure(self, resumedescr, /// deadframe)` parity: `descr_arc` is the source guard descr Arc /// (the value `cpu.get_latest_descr(deadframe)` returned) carried /// through as `self.resumekey`. @@ -12850,7 +13161,7 @@ impl MetaInterp { let fail_types = bridge_input_types.to_vec(); - // `pyjitpl.py:2890` `handle_guard_failure(self, resumedescr, + // `pyjitpl.py:2914` `handle_guard_failure(self, resumedescr, // deadframe)` parity: stash `self.resumekey` on MetaInterp so // every downstream lookup (bridge close, compile_trace_inner, // ...) reads the source descr Arc directly instead of doing @@ -12882,7 +13193,7 @@ impl MetaInterp { /// → force_from_resumedata() → materialize all virtuals → save on deadframe. /// /// The forced virtual caches (ptr, int) are stored on - /// [`Self::forced_virtuals`] for the blackhole resumption from the + /// `Self::forced_virtuals` for the blackhole resumption from the /// GUARD_NOT_FORCED — RPython's `AllVirtuals` via `cpu.set_savedata_ref()`. /// They are also returned, which only the unit tests below read. pub fn handle_async_forcing( @@ -13164,8 +13475,6 @@ impl MetaInterp { } } - // ── Guard Failure Recovery ───────────────────────────────── - /// Handle a guard failure: recover interpreter state using resume data. /// /// This is the central guard failure handler, equivalent to RPython's @@ -13371,8 +13680,6 @@ impl MetaInterp { }) } - // ── Retrace Support ────────────────────────────────────── - /// Start retracing from a guard failure point. /// /// When a guard fails too many times, the JIT can start a new trace @@ -13411,8 +13718,6 @@ impl MetaInterp { .is_some() } - // ── Inlining Support ────────────────────────────────────── - /// Check if a function call should be inlined during tracing. /// /// Line-by-line port of `_opimpl_recursive_call` (pyjitpl.py:1375-1423) @@ -13576,7 +13881,6 @@ impl MetaInterp { .unwrap_or(0) } - // ──────────────────────────────────────────────────────────────── // Frame-management surface mirroring pyjitpl.py:2421-2477. // // perform_call → newframe → MIFrame::setup_call (pyjitpl.py:2421- @@ -13597,7 +13901,6 @@ impl MetaInterp { // MetaInterp::framestack), so the canonical body lives on // MetaInterp and acts on the current top-of-framestack // frame implicitly. - // ──────────────────────────────────────────────────────────────── /// pyjitpl.py:2421-2425 `MetaInterp.perform_call(jitcode, boxes, greenkey)`. /// @@ -14901,13 +15204,6 @@ impl MetaInterp { /// ``` /// /// Exception-flavored sibling of `compile_done_with_this_frame`. - /// TODO: shared with that method: the FINISH op - /// emit + `compile.compile_trace` happen at the trace-dispatch - /// `TraceAction::Finish` site (jitdriver.rs:1031), so this method - /// runs only the upstream skeleton — `store_token_in_vable` + - /// `make_fail_descr_typed` for the Ref result-type slot — and - /// surfaces `SwitchToBlackhole` to the caller exactly like - /// `compile_done_with_this_frame`. /// /// The primary exception exit path in pyre is dispatch.rs's /// `unwind_to_exception_handler` at BC_RAISE/BC_RERAISE: when the @@ -14918,10 +15214,9 @@ impl MetaInterp { /// `pyjitpl.py:3238-3245`. This MetaInterp-side hook covers the /// rarer path where an exception surfaces during residual-call /// dispatch (miframe_execute_varargs / do_conditional_call); the - /// `FinishframeExceptionSignal::ExitFrameWithExceptionRef` return - /// from `handle_possible_exception` bubbles up, but the wiring - /// that converts it into a `TraceAction::Finish` dispatch at the - /// MetaInterp call chain is not yet complete (deferred epic). + /// `finishframe_exception` invokes this method when a residual call + /// drains the frame stack, so exceptional exits from both dispatch paths + /// reach the same FINISH compilation helper. pub fn compile_exit_frame_with_exception( &mut self, valuebox: Option, @@ -14951,7 +15246,7 @@ impl MetaInterp { /// pyjitpl.py:3198-3220 + 3238-3245 shared compile-and-finish helper. /// - /// Consumes the [`ActiveTraceSession`] installed by + /// Consumes the `ActiveTraceSession` installed by /// `begin_trace_session` and drives `compile.compile_trace(self, /// self.resumekey, exits)` — the exact RPython call that /// `compile_done_with_this_frame` and @@ -14963,7 +15258,7 @@ impl MetaInterp { /// - `bridge.is_none()` → `finish_and_compile(..., trace_meta)` — /// the root-trace equivalent. /// - /// Returns `Err(SwitchToBlackhole::bad_loop())` if the compile + /// Returns `Err(SwitchToBlackhole::giveup())` if the compile /// gave up (matching `compile.giveup()`). The caller /// (`compile_done_with_this_frame` / `compile_exit_frame_with_exception`) /// propagates the error so `finishframe` / `finishframe_exception` @@ -15375,19 +15670,15 @@ impl MetaInterp { /// valueconst, calldescr) /// ``` /// - /// TODO: pyre has no `cpu.calldescrof_dynamic`, - /// no `CIF_DESCRIPTION_P` layout reader, and no - /// `ffisupport.get_arg_descr` — the upstream specialization - /// reaches into `rpython.rlib.jit_libffi` which has no Rust - /// equivalent in pyre. The early-return contract for + /// MAJIT does not yet expose `cpu.calldescrof_dynamic`, a + /// `CIF_DESCRIPTION_P` layout reader, or `ffisupport.get_arg_descr`. + /// The early-return contract for /// `argboxes[1] not ConstInt` and `cif_description == NULL` is /// preserved so the dispatch in `do_residual_call` (pyjitpl.py:2061) /// falls through to `direct_call_release_gil` / `direct_call_may_force` - /// the same way it would when upstream's `direct_libffi_call` - /// declines to handle the call. Pyre never produces an - /// `OopSpecIndex::LibffiCall` today, so the dispatch path is dead - /// in production; the contract is matched here for the day a host - /// adds libffi support. + /// the same way it does when upstream's `direct_libffi_call` + /// declines to handle the call. The generic residual call preserves + /// semantics; implementing these helpers enables the specialized trace. pub fn direct_libffi_call( &mut self, argboxes: &[OpRef], @@ -16049,26 +16340,15 @@ impl MetaInterp { if let Some(ctx) = self.tracing.as_mut() { ctx.heapcache_invalidate_caches_varargs(opnum1, Some(effectinfo), &opref_args); } - // pyjitpl.py:2074-2077: handle resbox void / make_result_of_lastop. - // The result must be installed before vable_after_residual_call - // and GUARD_NOT_FORCED capture their resume snapshot. Otherwise - // a reused destination register still contains its pre-call - // value, and blackhole resume returns that stale value after an - // asynchronous force. + // pyjitpl.py:2074-2077: handle resbox void / make_result_of_lastop + // — make_result_of_lastop's target_index plumbing is not + // wired here yet; documented above on miframe_execute_varargs. let resbox_pair = match resbox { Some(opref) if descr_view.result_type() != majit_ir::Type::Void => { Some((opref, c_result)) } _ => None, }; - if let (Some((opref, concrete)), Some((kind, target_index))) = (resbox_pair, dst) { - self.framestack.current_mut().make_result_of_lastop( - kind, - target_index, - opref, - concrete, - ); - } // pyjitpl.py:2078: vable_after_residual_call(funcbox) // SwitchToBlackhole(ABORT_ESCAPE, raising_exception=True) // surfaces here when the virtualizable escaped during the @@ -16920,6 +17200,13 @@ impl SwitchToBlackhole { /// (pyjitpl.py:3028). Reserved for callers distinguishing the /// loop-compile failure from the trace-compile (FINISH) failure, /// which is `giveup()` above. + /// + /// It has **no callers**: every compile-gave-up site raises `giveup()`, + /// so `ABORT_BAD_LOOP` reaches its counter only through the direct + /// `pending_abort_reason` write in `compile_trace`. Two doc comments used + /// to promise this constructor as those sites' return value and were + /// wrong about it; the distinction is still worth making, but nothing + /// makes it yet. pub fn bad_loop() -> Self { Self { reason: counters::ABORT_BAD_LOOP, @@ -16953,9 +17240,7 @@ impl std::fmt::Display for ChangeFrame { impl std::error::Error for ChangeFrame {} -// ════════════════════════════════════════════════════════════════════════ // MetaInterpStaticData (pyjitpl.py:2190-2373) -// ════════════════════════════════════════════════════════════════════════ /// pyjitpl.py:2190 `class MetaInterpStaticData(object)`. /// @@ -17152,7 +17437,7 @@ pub struct MetaInterpStaticData { /// PyPy keeps the equivalent cache on `gccache._cache_array` /// (`descr.py:348`). /// - /// Keyed on [`DispatchArrayDescrKey`], which captures the full + /// Keyed on `DispatchArrayDescrKey`, which captures the full /// lltype-discriminant shape carried on `BhDescr::Array` /// (`type_id`, `base_size`, `itemsize`, `len_offset`, `item_type`, /// `is_array_of_pointers`, `is_array_of_structs`, `is_item_signed`, @@ -19028,10 +19313,6 @@ mod metainterp_static_data_tests { a + b * 1000 } - extern "C" fn execute_varargs_ref_helper() -> i64 { - 0xcafe - } - extern "C" fn execute_varargs_void_helper() {} extern "C" fn execute_varargs_float_concrete_helper(a: i64) -> i64 { @@ -19770,67 +20051,6 @@ mod metainterp_static_data_tests { ); } - #[test] - fn force_virtual_residual_call_installs_result_before_guard_snapshot() { - // pyjitpl.py:2074-2079 — make_result_of_lastop(resbox) precedes - // vable_after_residual_call and GUARD_NOT_FORCED. The dispatch - // walker captures the guard's resume snapshot after this helper - // returns, so the destination register must already name the call - // result rather than its stale pre-call value. - use crate::BackEdgeAction; - use crate::jitcode::{JitArgKind, JitCodeBuilder}; - - let mut meta = MetaInterp::<()>::new(0); - meta.finish_setup_descrs_for_jitdrivers(); - let action = - meta.force_start_tracing(0, (0, 0), None, &[Value::Ref(majit_ir::GcRef(0xdead))]); - assert!(matches!(action, BackEdgeAction::StartedTracing)); - - // Give make_result_of_lastop a real ref-typed destination at the - // frame's current post-call pc, matching the dispatcher's state on - // entry to do_residual_call_full. - let mut builder = JitCodeBuilder::new(); - builder.load_const_r_value(0, 0); - builder.inline_call_irf_r(0, &[], &[], &[], Some(0)); - let post_call_pc = builder.current_pos(); - let mut frame = - crate::pyjitpl::MIFrame::new(std::sync::Arc::new(builder.finish()), post_call_pc); - frame.ref_regs[0] = Some(OpRef::input_arg_ref(0)); - frame.ref_values[0] = Some(0xdead); - meta.framestack.push(frame); - - let mut effect = majit_ir::EffectInfo::default(); - effect.extraeffect = majit_ir::effectinfo::ExtraEffect::ForcesVirtualOrVirtualizable; - let descr_view = StubCallDescr { - arg_types: vec![], - result_type: majit_ir::Type::Ref, - effect: effect.clone(), - }; - let descr_ref = majit_ir::descr::make_call_descr(vec![], majit_ir::Type::Ref, effect); - let fnaddr = execute_varargs_ref_helper as *const () as i64; - let funcbox_ref = meta.trace_ctx().expect("active trace").const_ref(fnaddr); - let funcbox = (JitArgKind::Ref, funcbox_ref, fnaddr); - - let (result_op, result_value) = meta - .do_residual_call_full( - funcbox, - &[], - descr_ref, - &descr_view, - post_call_pc, - false, - None, - Some((JitArgKind::Ref, 0)), - ) - .expect("residual call must not abort") - .expect("ref call must produce a result"); - - assert_eq!(result_value, 0xcafe); - let frame = meta.framestack.current_mut(); - assert_eq!(frame.ref_regs[0], Some(result_op)); - assert_eq!(frame.ref_values[0], Some(0xcafe)); - } - #[test] fn miframe_execute_varargs_clears_exception_and_records_call_when_no_exc() { // pyjitpl.py:1942-1957 — without an exception, the call records @@ -21791,6 +22011,7 @@ mod tests { root_trace_id: trace_id, traces, previous_tokens: Vec::new(), + loop_header_pc: None, next_global_opref: 0, }, ); @@ -21856,6 +22077,7 @@ mod tests { root_trace_id: trace_id, traces, previous_tokens: Vec::new(), + loop_header_pc: None, next_global_opref: 0, }, ); @@ -22178,6 +22400,7 @@ mod tests { root_trace_id: trace_id, traces, previous_tokens: Vec::new(), + loop_header_pc: None, next_global_opref: 0, }, ); @@ -22273,6 +22496,7 @@ mod tests { root_trace_id: trace_id, traces, previous_tokens: Vec::new(), + loop_header_pc: None, next_global_opref: 0, }, ); @@ -22584,6 +22808,7 @@ mod tests { root_trace_id: trace_id, traces, previous_tokens: Vec::new(), + loop_header_pc: None, next_global_opref: 0, }, ); @@ -23402,6 +23627,7 @@ mod tests { root_trace_id: 0, traces: indexmap::IndexMap::new(), previous_tokens: Vec::new(), + loop_header_pc: None, next_global_opref: 0, }, ); @@ -23604,7 +23830,15 @@ mod tests { meta.opimpl_getarrayitem_vable_int(0, OpRef::input_arg_ref(0), index, 1, fd24, adesc); if let Some(ctx) = meta.trace_ctx() { let g = ctx.record_guard(OpCode::GuardTrue, &[item], 0); - ctx.capture_snapshot_for_last_guard( + // This trace HAS a virtualizable, so its guard snapshot must carry + // the vable section — `pyjitpl.py:2611-2614 capture_resumedata` + // passes `self.virtualizable_boxes` whenever `vinfo is not None`. + // The plain `capture_snapshot_for_last_guard` hardcodes an empty + // vable array, which encodes a 0-length section and trips the + // `resume.py:236-239` length check now armed in + // `store_final_boxes_in_guard`. + let (vable_boxes, vref_boxes) = ctx.build_snapshot_vable_vref_boxes(); + ctx.capture_snapshot_for_last_guard_with_vable_vref( &[ OpRef::input_arg_ref(0), OpRef::input_arg_int(1), @@ -23612,6 +23846,9 @@ mod tests { ], 0, 0, + 0, + &vable_boxes, + &vref_boxes, ); ctx.set_fail_args( g, @@ -23720,7 +23957,7 @@ mod tests { meta.finish_setup_descrs_for_jitdrivers(); let compile_events: Arc>> = Arc::new(Mutex::new(Vec::new())); let events = compile_events.clone(); - meta.set_on_compile_loop(move |green_key, ops_before, ops_after| { + meta.set_on_compile_loop(move |green_key, ops_before, ops_after, _opcodes| { events .lock() .unwrap() @@ -23848,7 +24085,7 @@ mod tests { let trace_abort_count = Arc::new(Mutex::new(0u32)); let cc = compile_count.clone(); - meta.set_on_compile_loop(move |_, _, _| { + meta.set_on_compile_loop(move |_, _, _, _opcodes| { *cc.lock().unwrap() += 1; }); @@ -23922,7 +24159,7 @@ mod tests { meta.finish_setup_descrs_for_jitdrivers(); let events: Arc>> = Arc::new(Mutex::new(Vec::new())); let ev = events.clone(); - meta.set_on_compile_loop(move |gk, before, after| { + meta.set_on_compile_loop(move |gk, before, after, _opcodes| { ev.lock().unwrap().push((gk, before, after)); }); @@ -24143,6 +24380,7 @@ mod bridge_cell_token_tests { root_trace_id: green_key, traces: indexmap::IndexMap::new(), previous_tokens: Vec::new(), + loop_header_pc: None, next_global_opref: 0, }, ); @@ -24196,6 +24434,7 @@ mod loop_side_table_tests { root_trace_id, traces: indexmap::IndexMap::new(), previous_tokens: Vec::new(), + loop_header_pc: None, next_global_opref: 0, } } @@ -24209,7 +24448,7 @@ mod loop_side_table_tests { } fn has_side_tables(meta: &MetaInterp<()>, green_key: u64) -> bool { - meta.loop_header_pcs.contains_key(&green_key) + meta.loop_header_pc_for(green_key).is_some() || meta.loop_header_greens.contains_key(&green_key) } @@ -24234,7 +24473,8 @@ mod loop_side_table_tests { meta.clear_compiled_loops(); assert!(meta.compiled_loops.is_empty()); - assert!(meta.loop_header_pcs.is_empty()); + assert!(meta.loop_header_pc_for(7).is_none()); + assert!(meta.loop_header_pc_for(8).is_none()); assert!(meta.loop_header_greens.is_empty()); } @@ -24253,4 +24493,109 @@ mod loop_side_table_tests { assert!(!has_side_tables(&meta, 8)); assert!(has_side_tables(&meta, 9)); } + + /// The header pc describes a compiled loop, so a key with no entry has + /// nothing to describe. Recording against one is dropped rather than + /// stored: the old `u64`-keyed side table accepted such a write and kept + /// it forever, since only a loop retirement swept the table and no loop + /// was ever there to retire. + #[test] + fn recording_a_header_pc_for_a_key_with_no_compiled_loop_stores_nothing() { + let mut meta = MetaInterp::<()>::new(1); + + meta.record_loop_header_pc(7, 42); + + assert_eq!( + meta.loop_header_pc_for(7), + None, + "no compiled loop at 7, so no header pc is retained" + ); + assert!(meta.compiled_loops.is_empty(), "and no entry is conjured"); + } + + /// `take_entry_for_replace` is the one door onto a `compiled_loops` + /// replace, and the pair it returns is the mechanism: **the displaced entry + /// cannot be obtained without its carried state.** Ablation — drop either + /// member of `CarriedFields`, or stop copying one in `carried_fields()` — + /// and this goes red. + /// + /// Passing does **not** mean the five production replace paths are + /// covered. It pins the helper's contract, not its use. Nothing in this + /// crate can drive a production compile path, so a site that + /// simply never calls this is still invisible to the suite. + #[test] + fn take_entry_for_replace_returns_the_entry_with_both_carried_fields() { + let mut meta = MetaInterp::<()>::new(1); + let mut entry = compiled_entry(101); + entry.next_global_opref = 37; + entry.loop_header_pc = Some(11); + meta.compiled_loops.insert(7, entry); + + let (old_entry, carried) = meta + .take_entry_for_replace(7) + .expect("the key had an entry"); + + assert_eq!(old_entry.root_trace_id, 101, "the entry itself comes back"); + assert_eq!( + carried, + CarriedFields { + next_global_opref: 37, + loop_header_pc: Some(11), + }, + "and both carried fields travel with the removal" + ); + assert!( + !meta.compiled_loops.contains_key(&7), + "the entry is removed, exactly as the bare swap_remove did" + ); + assert!( + meta.take_entry_for_replace(7).is_none(), + "a key with no entry yields nothing to carry" + ); + } + + /// Pins the hazard every `compiled_loops` replace path has to defend + /// against: the pc now lives *on* the entry, so installing a fresh entry + /// at a key that already had one destroys the pc unless the caller copies + /// it across. `compile_retrace` / `finish_and_compile` / + /// `compile_simple_loop` / `compile_entry_bridge` all replace without + /// re-recording — only `compile_loop`'s caller records afterwards. + /// + /// If this test ever fails it means the entry-replacement contract moved. + /// If a *production* replace path drops the pc, this test still passes — + /// it builds its own entries and never routes through a compile path. That + /// blindness is why the carry is being moved behind + /// `take_entry_for_replace`, where omitting it is not expressible, rather + /// than left as two hand-written lines per site: a test that cannot catch a + /// dropped carry is an argument for making the drop unrepresentable, not + /// for repeating the carry. All five now go through the helper, so the pc + /// arrives at each site whether or not the site remembers to ask for it. + #[test] + fn installing_a_fresh_entry_at_a_live_key_drops_the_header_pc() { + let mut meta = MetaInterp::<()>::new(1); + record_loop(&mut meta, 7, 100); + assert_eq!(meta.loop_header_pc_for(7), Some(7)); + + // A replace that does not carry the pc forward, as the production + // paths would if their `carried_loop_header_pc` were dropped. + meta.compiled_loops.insert(7, compiled_entry(101)); + + assert_eq!( + meta.loop_header_pc_for(7), + None, + "the pc lives on the entry, so a bare replace loses it" + ); + + // And the carry-forward the production sites perform restores it. + let carried = Some(7usize); + let mut fresh = compiled_entry(102); + fresh.loop_header_pc = carried; + meta.compiled_loops.insert(7, fresh); + + assert_eq!( + meta.loop_header_pc_for(7), + Some(7), + "copying old_entry.loop_header_pc across keeps the close target" + ); + } } diff --git a/majit/majit-metainterp/src/pyjitpl/dispatch.rs b/majit/majit-metainterp/src/pyjitpl/dispatch.rs index 7cd5bf54755..100268baac2 100644 --- a/majit/majit-metainterp/src/pyjitpl/dispatch.rs +++ b/majit/majit-metainterp/src/pyjitpl/dispatch.rs @@ -248,7 +248,11 @@ pub fn field_descr_ref_from_bh(descr: &crate::blackhole::BhDescr) -> (usize, maj is_quasi_immutable: *is_quasi_immutable, flag: *field_flag, virtualizable: false, - index_in_parent: *index_in_parent, + // `SimpleFieldDescrSpec` carries a plain + // `usize`, so the absence cannot ride any + // further: `0` is the value this field has + // always had here, spelled as a fallback. + index_in_parent: index_in_parent.unwrap_or(0), }); specs.len() - 1 }); @@ -321,7 +325,7 @@ pub fn field_descr_ref_from_bh(descr: &crate::blackhole::BhDescr) -> (usize, maj *is_immutable, *is_quasi_immutable, *field_flag, - *index_in_parent as u32, + index_in_parent.unwrap_or(0) as u32, false, *index_in_parent, ); @@ -376,7 +380,9 @@ pub fn field_descr_ref_from_bh(descr: &crate::blackhole::BhDescr) -> (usize, maj is_quasi_immutable: *is_quasi_immutable, flag: *field_flag, virtualizable: false, - index_in_parent: *index_in_parent, + // Same floor as the sibling arm above: the spec type + // has no way to say "no slot was claimed". + index_in_parent: index_in_parent.unwrap_or(0), }; let group = majit_ir::descr::make_simple_descr_group_with_flags( u32::MAX, @@ -414,7 +420,7 @@ pub fn field_descr_ref_from_bh(descr: &crate::blackhole::BhDescr) -> (usize, maj *is_immutable, *is_quasi_immutable, *field_flag, - *index_in_parent as u32, + index_in_parent.unwrap_or(0) as u32, false, *index_in_parent, ); @@ -467,7 +473,7 @@ pub fn field_descr_ref_from_bh(descr: &crate::blackhole::BhDescr) -> (usize, maj } } -/// Build a `CanRaise` [`EffectInfo`] whose field write-set names `write_field` +/// Build a `CanRaise` [`majit_ir::effectinfo::EffectInfo`] whose field write-set names `write_field` /// of struct `type_id`, sharing the exact keyed `Arc` that a /// `getfield_gc_i` on the same `(type_id, write_field)` resolves to. A /// residual helper that mutates a struct field through opaque host code — e.g. @@ -651,7 +657,6 @@ pub trait JitCodeSym { /// Update an array state field element's concrete value. fn set_state_array_value(&mut self, _array_idx: usize, _elem_idx: usize, _value: i64) {} - // -- Ref-typed scalar state field support ------------- // // A ref state field (`selected: ref(Stack)`) is tracked in the ref // register bank so its OpRef carries `Type::Ref`; this gives @@ -674,7 +679,6 @@ pub trait JitCodeSym { /// Update a ref-typed scalar state field's concrete value. fn set_state_ref_field_value(&mut self, _field_idx: usize, _value: i64) {} - // -- Float-typed scalar state field support ------------- // // Float state fields live in the float register bank and carry raw f64 // bits in their concrete shadows. @@ -726,6 +730,24 @@ pub trait JitCodeSym { self.total_slots() } + /// One past the last int-bank register the *register allocator* + /// reserves for identity slots, i.e. `int_identity_base + + /// num_scalars + num_vable_identity_slots`. + /// + /// This is deliberately NOT `int_identity_slots_end()`. That one is + /// `base + total_slots()`, and `total_slots()` adds every virt array's + /// **live `Vec::len()`** — a runtime quantity the jitcode lowerer cannot + /// see, so the lowering floor (`split_identity_reg_ends`) stops after the + /// scalars plus the single vable-identity slot. Registers between the two + /// ends are ordinary working registers; only `[base, reserved_end)` is + /// guaranteed to hold nothing but identity. + /// + /// The inline-frame snapshot trim must use THIS end: it blanks the range + /// unconditionally, and blanking a working register would drop live data. + fn int_identity_reserved_end(&self) -> usize { + self.int_identity_slots_end() + } + /// First int-bank register used as a canonical identity slot /// (`int_scalar_base`). Identity slots occupy `[base, end)`; the base /// keeps the dispatch JitCode's int argument registers (`pc` at i0) @@ -735,7 +757,7 @@ pub trait JitCodeSym { 0 } - /// Bridge state-field JIT's `__JitSym` storage onto + /// Bridge state-field JIT's `__JitSym_` storage onto /// `MIFrame.int_regs` / `int_values` ahead of guard capture. /// /// TODO (state-field JIT divergence): @@ -743,13 +765,13 @@ pub trait JitCodeSym { /// directly via `setfield_*` opimpls during dispatch /// (`pyjitpl.py:74-95 MIFrame.setup` + per-opcode register /// assignments). pyre's state-field JIT instead stores OpRefs + - /// concrete values in `__JitSym.` / `_value` etc. + /// concrete values in `__JitSym_.` / `_value` etc. /// because the macro emits per-opcode jitcodes that read state /// directly from the symbolic side-channel. At guard-capture /// time `MIFrame::get_list_of_active_boxes` /// (`pyjitpl/frame.rs:430-440`) still expects live state in /// `int_regs` / `int_values`, so this hook copies the - /// `__JitSym` slots into the frame's banks at the canonical + /// `__JitSym_` slots into the frame's banks at the canonical /// liveness indices defined by `live_slots_for_state_field_jit` /// (orth-6): scalars at `0..num_scalars`, then /// flattened arrays, then virt-array (ptr, len) pairs. Virt- @@ -757,15 +779,17 @@ pub trait JitCodeSym { /// time from the user state's `.as_ptr() as i64` / /// `.len() as i64` (framestack-lift 1) /// — accurate iff the Vec does not reallocate during tracing - /// (true for the 6 macro examples that use fixed-capacity - /// `vec![0i64; program.len()]`). + /// (the examples allocate backing storage once at a fixed capacity + /// before tracing and never grow it). An example that pushes onto + /// that storage, or sizes it from something that changes during the + /// trace, breaks the mirror. /// /// Convergence path: when the macro switches to RPython /// MIFrame-regs storage, this /// method's default no-op impl matches RPython's "regs already /// populated by dispatch" semantics and the macro override drops /// out. Until then, callers with a state-field JIT pass - /// `&__JitSym` here right before invoking + /// `&__JitSym_` here right before invoking /// `TraceRecordBuffer::capture_resumedata` so the framestack-walk /// snapshot has matching slot data. /// @@ -778,19 +802,19 @@ pub trait JitCodeSym { /// calls `capture_resumedata`, every register bank is already /// up-to-date and `MIFrame::get_list_of_active_boxes` reads them /// without any side-channel sync. pyre's macro instead routes - /// the same data through `__JitSym.` for ergonomic reasons + /// the same data through `__JitSym_.` for ergonomic reasons /// (the proc-macro can derive symbolic state-field accesses /// statically), and this trait method is the back-door that /// re-establishes the RPython invariant just before the snapshot /// is captured. Convergence path: (codegen.rs / macro - /// → register-machine jitcode) eliminates `__JitSym` as a + /// → register-machine jitcode) eliminates `__JitSym_` as a /// distinct storage; macro-emitted opimpls then write directly to - /// `MIFrame.regs`, and this method (along with its `__JitSym` + /// `MIFrame.regs`, and this method (along with its `__JitSym_` /// value-mirror seeding from `JitState::initialize_sym`) is /// removed. fn populate_frame_int_regs(&self, _frame: &mut MIFrame) {} - /// [FR] Seed an INLINE recursive-portal callee frame's int register bank + /// \[FR\] Seed an INLINE recursive-portal callee frame's int register bank /// with its FRESH state as compile-time CONSTANTS (scalars zeroed, virt /// arrays sized at the caller's captured capacity). The state-field /// dispatch keeps each scalar (e.g. `stackpos`) in a working int register @@ -804,7 +828,7 @@ pub trait JitCodeSym { /// never reach the inline path. fn seed_recursive_fresh_frame(&self, _frame: &mut MIFrame) {} - /// [FR] Snapshot the sym's WORKING scalar (and fixed-array) state before an + /// \[FR\] Snapshot the sym's WORKING scalar (and fixed-array) state before an /// inline recursive-portal callee overwrites it. `BC_LOAD/STORE_STATE_FIELD` /// read/write the single shared sym (dispatch.rs:2831), not a per-frame /// register, so an inline callee mutates the caller's live scalar state in @@ -817,17 +841,17 @@ pub trait JitCodeSym { None } - /// [FR] Reset the sym's working scalar/fixed-array state to FRESH (zeroed), + /// \[FR\] Reset the sym's working scalar/fixed-array state to FRESH (zeroed), /// so the inline callee starts from a clean state rather than inheriting the /// caller's. Paired with [`Self::snapshot_inline_scalar_state`]. fn reset_inline_scalar_state_fresh(&mut self) {} - /// [FR] Restore the sym's working scalar/fixed-array state from a snapshot + /// \[FR\] Restore the sym's working scalar/fixed-array state from a snapshot /// when the inline callee returns. Paired with /// [`Self::snapshot_inline_scalar_state`]. fn restore_inline_scalar_state(&mut self, _snapshot: Vec<(majit_ir::OpRef, i64)>) {} - /// #184 recursive CALL_ASSEMBLER portal entry: build the fresh-frame + /// recursive-call recursive CALL_ASSEMBLER portal entry: build the fresh-frame /// reds for a recursive callee run. /// /// Returns the typed `Value` reds in `extract_live` order for a freshly @@ -847,7 +871,7 @@ pub trait JitCodeSym { None } - /// #184 recursive CALL_ASSEMBLER portal entry: the host-Rust allocator and + /// recursive-call recursive CALL_ASSEMBLER portal entry: the host-Rust allocator and /// deallocator for a fresh callee state. /// /// Returns `(alloc, free)` raw function addresses where @@ -1041,7 +1065,7 @@ where } /// Slice X-D-aware `JitCodeRuntime` carrying the `label_at` / -/// `jitcell_token_arc_for_number` closures plus the #184 recursive-call +/// `jitcell_token_arc_for_number` closures plus the recursive-call recursive-call /// seams (inline decision, green-key target resolver, concrete loop /// executor). Used by `MetaInterp::trace_jitcode_with_framestack` so the /// dispatcher resolves CALL_ASSEMBLER targets to their production Arcs via @@ -1382,7 +1406,7 @@ where /// Record a state-field-JIT guard. Records the guard with no /// inline `op.fail_args` and attaches a snapshot built from the - /// current `MIFrame`'s `int_regs` (populated from `__JitSym` via + /// current `MIFrame`'s `int_regs` (populated from `__JitSym_` via /// `JitCodeSym::populate_frame_int_regs`). The optimizer's /// `store_final_boxes_in_guard` (`optimizeopt/mod.rs:3200`) then /// derives `op.fail_args` from the snapshot via `_number_boxes`, @@ -1597,7 +1621,7 @@ where // RPython only swaps the top frame pc before // `capture_resumedata`; it never writes portal state into an // inline callee frame. State-field JIT still needs to - // materialize `__JitSym` scalars into an MIFrame register bank, + // materialize `__JitSym_` scalars into an MIFrame register bank, // but the only orthodox destination is the root/portal frame. if crate::bh_debug_enabled() { eprintln!( @@ -1682,7 +1706,10 @@ where after_residual_call, &virtualizable_snapshot, &virtualref_snapshot, - Some((sym.int_identity_slots_base(), sym.int_identity_slots_end())), + Some(( + sym.int_identity_slots_base(), + sym.int_identity_reserved_end(), + )), ); for idx in 0..n { // RPython pyjitpl.py:180-193 leaves the parent frame's @@ -1710,6 +1737,65 @@ where } } + /// pyjitpl.py:1916-1927 `MIFrame.implement_guard_value` — promote a box + /// to a `Const`, guarding that the runtime value still matches. + /// + /// ```python + /// def implement_guard_value(self, box, orgpc): + /// if isinstance(box, Const): + /// return box # no promotion needed, already a Const + /// else: + /// promoted_box = executor.constant_from_op(box) + /// self.metainterp.generate_guard(rop.GUARD_VALUE, box, promoted_box, + /// resumepc=orgpc) + /// self.metainterp.replace_box(box, promoted_box) + /// return promoted_box + /// ``` + /// + /// `generate_guard` is reached here through [`Self::record_state_guard`], + /// which walks the live framestack and the per-trace virtualizable / + /// virtualref boxes (`pyjitpl.py:2591-2625 capture_resumedata`). That is + /// the whole point of promoting at this layer: `TraceCtx::promote_int` + /// (`history.rs`) holds no `MIFrameStack`, so the snapshot it attaches + /// carries empty `boxes` AND an empty `vable_boxes`, and a guard that + /// survives optimization then deopts into a frame with no live registers + /// and a zero-length vable section. + /// + /// `replace_box` has no whole-metainterp equivalent here; writing the + /// promoted constant back into the source register is the same stand-in + /// `BC_SWITCH` already uses for it (`dispatch.rs:4585`), and it is what + /// keeps a second read of the same register from emitting a redundant + /// `GUARD_VALUE`. + fn implement_guard_value( + &mut self, + ctx: &mut TraceCtx, + sym: &mut S, + reg: usize, + box_: OpRef, + runtime_value: i64, + resume_pc: usize, + ) -> OpRef { + // pyjitpl.py:1920-1921 + if box_.is_constant() { + return box_; + } + // pyjitpl.py:1923 `executor.constant_from_op(box)`. + let promoted_box = ctx.const_int(runtime_value); + // pyjitpl.py:1924-1925 + self.record_state_guard( + ctx, + sym, + OpCode::GuardValue, + &[box_, promoted_box], + resume_pc, + false, + ); + // pyjitpl.py:1926 `replace_box`. + self.set_int_reg(reg, Some(promoted_box), Some(runtime_value)); + // pyjitpl.py:1927 + promoted_box + } + /// pyjitpl.py:1622 `MIFrame._create_segmented_trace_and_blackhole`, /// recording half. /// @@ -2235,11 +2321,6 @@ where let jd_box = ctx.const_int(frame.portal_jd as i64); ctx.record_op(OpCode::LeavePortalFrame, &[jd_box]); } - // [FR] A recursive-portal INLINE frame installed its callee's - // fresh standard vable on push; restore the caller's on return. - if frame.portal_vable_saved { - ctx.restore_saved_virtualizable(); - } frame.portal_scalar_state } else { None @@ -3170,6 +3251,48 @@ where self.finish_call_assembler_exception_path(ctx, sym) } + /// Report where a bytecode-level abort was DECIDED, which the merge point + /// structurally cannot. + /// + /// `[jit] trace action at pc=N -> Abort` is emitted by the generated merge + /// point (`majit-macros/src/jit_interp/codegen_trace.rs`), so `N` is the + /// **portal pc** — where the walk started, not where it stopped. Read + /// literally it names the wrong dispatch arm: braininterp's abort reports + /// `pc=7` (`[`) and fires at `pc=14` (`]`). tla's and tinyframe's headers + /// already warned that this pc is the trace START, and the warning did not + /// prevent the misreading — a message that must be read against a comment + /// elsewhere will eventually be read on its own. + /// + /// `depth`/`body` are the other half, and they are the part the opcode + /// cannot supply: **`BC_ABORT` has two emitter families** in `majit-macros` + /// — the per-arm degraded stubs (`jitcode_lower/dispatch.rs`'s + /// `__sub_builder.abort()` sites) and a bounds-check bailout in the + /// dispatch body (`__builder.abort()`). Both emit this one opcode, so + /// seeing it identifies neither the arm nor the family. They are told apart + /// by *where* it ran: a degraded arm aborts inside its own sub-JitCode, an + /// inline frame whose whole body is the single abort byte, so + /// `depth>1 body=1` is the stub and the dispatch body is neither. + /// + /// `JitCode.name` would be the direct answer and is NOT usable here: + /// `majit_metainterp::JitCodeBuilder::new()` takes no name (unlike + /// `majit-translate`'s), so every macro-built JitCode carries `name: ""`. + /// Threading one through the macro's emit sites is the real fix; until then + /// these three numbers discriminate without it. + fn log_bytecode_abort(&mut self, insn: &str) { + if !crate::majit_log_enabled() { + return; + } + let depth = self.frames.len(); + let frame = self.frames.current_mut(); + eprintln!( + "[jit] {insn} decided at pos={} depth={depth} body={} \ + (depth>1 body=1 = a degraded arm stub; the merge point's \ + `trace action at pc=` reports the PORTAL pc, not this one)", + frame.last_opcode_position, + frame.jitcode.code.len(), + ); + } + pub fn run_one_step(&mut self, ctx: &mut TraceCtx, sym: &mut S, _runtime: &R) -> TraceAction { if self.frames.is_empty() { return TraceAction::Continue; @@ -3194,13 +3317,10 @@ where ctx.record_op(OpCode::LeavePortalFrame, &[jd_box]); } // [FR] Restore the caller's sym scalar/fixed-array state that this - // inline recursive-portal frame overwrote, and its nested vable. + // inline recursive-portal frame overwrote. if let Some(snapshot) = finished_frame.portal_scalar_state.clone() { sym.restore_inline_scalar_state(snapshot); } - if finished_frame.portal_vable_saved { - ctx.restore_saved_virtualizable(); - } if let Some(parent) = self.frames.frames.last_mut() && let Some((return_kind, callee_src)) = finished_frame.jitcode.trailing_return_info() @@ -3319,16 +3439,66 @@ where let dest = self.frames.current_mut().next_reg() as usize; let (_, index_concrete) = self.read_int_reg(index_reg); let elem_idx = index_concrete as usize; - let opref = sym.state_array_ref(array_idx, elem_idx); - if let Some(opref) = opref { - let value = sym - .state_array_value(array_idx, elem_idx) - .expect("state array concrete value not initialized"); - self.set_int_reg(dest, Some(opref), Some(value)); - } else { - // Array element beyond initialized range (e.g., push expanded). - // Abort trace -- this path needs dynamic array support. - return TraceAction::Abort; + // Three outcomes, not two. `state_array_ref` returns + // `Option` and `OpRef` *itself* has a `None` variant, so + // "no such cell" and "the cell holds the cleared sentinel" both + // arrive as a `Some`-shaped answer unless they are split here. + // The generated accessor is `self.#field.get(elem_idx).copied()` + // (`codegen_state.rs:329`): an in-range cell always answers + // `Some(..)`, carrying `OpRef::NONE` when it was cleared. + match sym.state_array_ref(array_idx, elem_idx) { + // The cell was retired by `clear_sym_inputarg_bindings` and + // never re-seeded. Refuse: passing the sentinel on binds a + // trace register to an OpRef with no operand or type, so it + // would travel into jump args as a silently wrong binding. + // + // This opcode is reached at least 16 times by the + // fixed-array fixtures and every one + // of those reads carries a real binding, so the sentinel + // count is 0-out-of-16+, not 0-out-of-0. (Measured by + // inverting this arm's guard to `!opref.is_none()`, which + // makes a real binding take the refusal: 16 panics, RC=101 + // in `jit_interp_fixed_array_identity_slot` alone. It is a + // lower bound — that binary aborts the run before the + // second fixture executes.) The surrounding match is live + // code; what has never occurred is the cleared cell. + // + // That zero is structural rather than untested. This opcode addresses + // *fixed* arrays only -- `codegen_state.rs:323` builds its + // arms from `arrays` (`StateFieldKind::Array`), while + // `virt_arrays` is a separate collection reached through + // `BC_GETFIELD_VABLE_*`. A cell can hold the sentinel only + // after a bridge. No example crate declares a fixed array + // (all use `[T; virt]`); the only declarers are two metainterp + // fixtures, and neither bridges -- measured against a + // control that emitted 48 bridge lines on the same run. + // + // So this arm is a brake for the first crate to declare a + // fixed array, NOT evidence that the path is exercised. + // Do not cite a green suite as coverage of it, and do not + // treat it as fixing the accessor: `.copied()` still cannot + // distinguish the two cases at any other call site. + Some(opref) if opref.is_none() => { + panic!( + "state array cell [{array_idx}][{elem_idx}] holds OpRef::NONE: \ + it was retired by `clear_sym_inputarg_bindings` and no bridge \ + seeding arm rebound it. `setup_bridge_sym` has no arm for state \ + arrays, so a crate declaring a fixed `[int]` array reaches this \ + opcode on a bridge with every cell cleared. The fix is a seeding \ + arm, not a weaker read here." + ); + } + Some(opref) => { + let value = sym + .state_array_value(array_idx, elem_idx) + .expect("state array concrete value not initialized"); + self.set_int_reg(dest, Some(opref), Some(value)); + } + None => { + // Array element beyond initialized range (e.g., push expanded). + // Abort trace -- this path needs dynamic array support. + return TraceAction::Abort; + } } } jitcode::insns::BC_STORE_STATE_ARRAY => { @@ -4282,6 +4452,16 @@ where return TraceAction::Abort; }; let (index, index_value) = self.read_int_reg(index_reg); + // pyjitpl.py:1218-1234 `_opimpl_getarrayitem_vable` / + // `_opimpl_setarrayitem_vable` reach the index through + // `implement_guard_value` on an `MIFrame`, which owns the + // framestack the resume snapshot is built from. Promoting here + // rather than inside `TraceCtx::get_arrayitem_vable_index` + // keeps that ownership: the guard gets a full-framestack, + // vable-carrying snapshot instead of the minimal one + // `TraceCtx::promote_int` can build without an `MIFrameStack`. + let index = + self.implement_guard_value(ctx, sym, index_reg, index, index_value, opcode_pc); let guards_before = ctx.num_guards(); let (opref, value) = ctx.vable_getarrayitem_int_indexed( opcode_pc, @@ -4307,6 +4487,16 @@ where return TraceAction::Abort; }; let (index, index_value) = self.read_int_reg(index_reg); + // pyjitpl.py:1218-1234 `_opimpl_getarrayitem_vable` / + // `_opimpl_setarrayitem_vable` reach the index through + // `implement_guard_value` on an `MIFrame`, which owns the + // framestack the resume snapshot is built from. Promoting here + // rather than inside `TraceCtx::get_arrayitem_vable_index` + // keeps that ownership: the guard gets a full-framestack, + // vable-carrying snapshot instead of the minimal one + // `TraceCtx::promote_int` can build without an `MIFrameStack`. + let index = + self.implement_guard_value(ctx, sym, index_reg, index, index_value, opcode_pc); let guards_before = ctx.num_guards(); let (opref, value) = ctx.vable_getarrayitem_ref_indexed( opcode_pc, @@ -4332,6 +4522,16 @@ where return TraceAction::Abort; }; let (index, index_value) = self.read_int_reg(index_reg); + // pyjitpl.py:1218-1234 `_opimpl_getarrayitem_vable` / + // `_opimpl_setarrayitem_vable` reach the index through + // `implement_guard_value` on an `MIFrame`, which owns the + // framestack the resume snapshot is built from. Promoting here + // rather than inside `TraceCtx::get_arrayitem_vable_index` + // keeps that ownership: the guard gets a full-framestack, + // vable-carrying snapshot instead of the minimal one + // `TraceCtx::promote_int` can build without an `MIFrameStack`. + let index = + self.implement_guard_value(ctx, sym, index_reg, index, index_value, opcode_pc); let guards_before = ctx.num_guards(); let (opref, value) = ctx.vable_getarrayitem_float_indexed( opcode_pc, @@ -4357,6 +4557,16 @@ where return TraceAction::Abort; }; let (index, index_value) = self.read_int_reg(index_reg); + // pyjitpl.py:1218-1234 `_opimpl_getarrayitem_vable` / + // `_opimpl_setarrayitem_vable` reach the index through + // `implement_guard_value` on an `MIFrame`, which owns the + // framestack the resume snapshot is built from. Promoting here + // rather than inside `TraceCtx::get_arrayitem_vable_index` + // keeps that ownership: the guard gets a full-framestack, + // vable-carrying snapshot instead of the minimal one + // `TraceCtx::promote_int` can build without an `MIFrameStack`. + let index = + self.implement_guard_value(ctx, sym, index_reg, index, index_value, opcode_pc); let (value, concrete) = self.read_int_reg(src); let guards_before = ctx.num_guards(); let write = match ctx.vable_setarrayitem_indexed( @@ -4391,6 +4601,16 @@ where return TraceAction::Abort; }; let (index, index_value) = self.read_int_reg(index_reg); + // pyjitpl.py:1218-1234 `_opimpl_getarrayitem_vable` / + // `_opimpl_setarrayitem_vable` reach the index through + // `implement_guard_value` on an `MIFrame`, which owns the + // framestack the resume snapshot is built from. Promoting here + // rather than inside `TraceCtx::get_arrayitem_vable_index` + // keeps that ownership: the guard gets a full-framestack, + // vable-carrying snapshot instead of the minimal one + // `TraceCtx::promote_int` can build without an `MIFrameStack`. + let index = + self.implement_guard_value(ctx, sym, index_reg, index, index_value, opcode_pc); let (value, concrete) = self.read_ref_reg(src); let guards_before = ctx.num_guards(); let write = match ctx.vable_setarrayitem_indexed( @@ -4422,6 +4642,16 @@ where return TraceAction::Abort; }; let (index, index_value) = self.read_int_reg(index_reg); + // pyjitpl.py:1218-1234 `_opimpl_getarrayitem_vable` / + // `_opimpl_setarrayitem_vable` reach the index through + // `implement_guard_value` on an `MIFrame`, which owns the + // framestack the resume snapshot is built from. Promoting here + // rather than inside `TraceCtx::get_arrayitem_vable_index` + // keeps that ownership: the guard gets a full-framestack, + // vable-carrying snapshot instead of the minimal one + // `TraceCtx::promote_int` can build without an `MIFrameStack`. + let index = + self.implement_guard_value(ctx, sym, index_reg, index, index_value, opcode_pc); let (value, concrete) = self.read_float_reg(src); let guards_before = ctx.num_guards(); let write = match ctx.vable_setarrayitem_indexed( @@ -5162,6 +5392,21 @@ where // through the `-live-` marker that precedes every // `jit_merge_point` op, which an arbitrary mid-walk position // has no counterpart for. + // Record the interpreter pc this merge point names, for the + // abort-resume correction in + // `trace_jitcode_with_args_and_runtime`. It is written before + // every early return below, because a visit that goes on to + // return `Continue` still passed through a real opcode + // boundary, and that boundary is what the correction needs. + // + // Restricted to `inline_depth() == 0`: the position it competes + // with is the ROOT frame's i0, so an inlined callee's own pc + // would name a position in the wrong code. + if ctx.inline_depth() == 0 { + if let Some(u) = mp_green_pc.and_then(|v| usize::try_from(v).ok()) { + ctx.last_mp_green_pc = Some(u); + } + } if ctx.force_finish_trace() && ctx.num_ops() > ctx.trace_limit() * 4 / 5 { // The loop-vs-bridge split lives inside // `create_segmented_trace`, where upstream keeps it @@ -5394,9 +5639,6 @@ where if let Some(snapshot) = popped.portal_scalar_state.clone() { sym.restore_inline_scalar_state(snapshot); } - if popped.portal_vable_saved { - ctx.restore_saved_virtualizable(); - } // (3) do_recursive_call(assembler_call=True) on the caller // (now current): reuse the existing 8-step // CALL_ASSEMBLER recorder. `set_int_reg(result_dst)` @@ -5431,6 +5673,30 @@ where // caller frame in the walker dispatch loop. return TraceAction::Continue; } + // pyjitpl.py:2975 reached_loop_header, its FIRST statement: + // + // def reached_loop_header(self, greenboxes, redboxes): + // self.heapcache.reset() + // + // A merge point is where another trace may be cut in + // (compile.py:255-256 `trace.cut_trace_from`) and where a + // compiled loop may be entered from the interpreter. So + // nothing recorded past this point may depend on a heapcache + // fact established before it: the guard that proved the fact + // can end up on the far side of a cut, or simply never run on + // an entry that starts here. Resetting forces the tracer to + // re-emit those guards, which is what keeps the ops after the + // merge point self-sufficient. + // + // Placed after the `inline_depth() > 0` recursive-cut branch + // above because that branch is upstream's `else` at + // pyjitpl.py:1579 — it returns instead of calling + // `reached_loop_header`, so the reset must not run on it. + // Upstream reaches the GUARD_FUTURE_CONDITION at :2993 with + // only `remove_consts_and_duplicates` and the virtualizable + // box handling in between, neither of which reads the + // heapcache, so reset-then-guard is the faithful order. + ctx.heap_cache_mut().reset(); // pyjitpl.py:2991-2993 reached_loop_header: generate a dummy // GUARD_FUTURE_CONDITION just before the implicit JUMP so // unroll's `jump_to_existing_trace` has a `patchguardop` @@ -5611,9 +5877,12 @@ where // compile_loop_body) finds it via get_merge_point_at(inner_key, // ctx.header_pc). // - // S0 census (env-gated MAJIT_INNERMP): append-and-observe with - // NO close, to confirm the inner key is stable and detected on - // revisit before enabling the cut close. + // The S0 census that established this — append-and-observe + // with NO close, confirming the inner key is stable and + // detected on revisit before the cut close was enabled — ran + // behind a `MAJIT_INNERMP` gate that no longer exists. Nothing + // reads that name today; re-running the census means adding + // the gate back, not setting a variable. if inner_close && let Some(pc) = mp_green_pc { let header_pc = ctx.header_pc; // pyjitpl.py:3001-3007, which runs BEFORE the @@ -5651,7 +5920,7 @@ where // under the key it is reached by and the jump lands // in code the interpreter can also enter. // - // ⚠ The earlier measurement against this lever — + // The earlier measurement against this lever — // cel's `nested_list_loop_varying_trip_count` keeping // its results and losing its 4 aborts while `spread // 0..32` deopts went 959 → 1763 over 4000 rows and @@ -5700,7 +5969,25 @@ where .has_compiled_targets_fn .as_ref() .is_some_and(|f| f(inner_key)); + // Producer side of slots 50/67, which count only what + // happens once a close has been published. Counted here, + // before the branch, so a zero downstream separates "the + // walk never reached this decision" from "it reached it + // and the target was not compiled" — the two render + // identically in those two slots. `is_some_and` also + // answers false when the callback is absent, so the + // reached-count is what makes an uninstalled + // `has_compiled_targets_fn` visible rather than + // indistinguishable from a real "no". + // + // The sibling `already_compiled_here` on the + // `is_bridge_trace` path above is a DIFFERENT decision — + // whether to append a first-visit merge point — and never + // publishes `close_jump_into_key`, so it deliberately + // carries no slot. + crate::mc_diag_bump(68); // xloop_close_decision_reached if already_compiled_here { + crate::mc_diag_bump(69); // xloop_close_target_compiled // pyjitpl.py:3004-3007 — the merge point just reached already owns a // procedure token, so upstream JUMPs into it rather than deriving a second // copy of that loop by cutting this trace. `compile_trace` raises on @@ -5727,6 +6014,7 @@ where ); } } else { + crate::mc_diag_bump(70); // xloop_close_published ctx.close_greens = Some(mp_greens.clone()); ctx.close_green_pc = Some(pc); ctx.close_jump_into_key = Some(inner_key); @@ -6071,6 +6359,8 @@ where exit_with_exception: false, exc_value: 0, }; + } else { + typed_return_without_caller_destination("BC_INT_RETURN", 'i'); } } // `int_return/c` — USE_C_FORM short source (`assembler.py:312`): @@ -6106,6 +6396,8 @@ where exit_with_exception: false, exc_value: 0, }; + } else { + typed_return_without_caller_destination("BC_INT_RETURN_C", 'i'); } } jitcode::insns::BC_REF_RETURN => { @@ -6137,6 +6429,8 @@ where exit_with_exception: false, exc_value: 0, }; + } else { + typed_return_without_caller_destination("BC_REF_RETURN", 'r'); } } jitcode::insns::BC_FLOAT_RETURN => { @@ -6168,6 +6462,8 @@ where exit_with_exception: false, exc_value: 0, }; + } else { + typed_return_without_caller_destination("BC_FLOAT_RETURN", 'f'); } } jitcode::insns::BC_VOID_RETURN => { @@ -7946,8 +8242,14 @@ where self.pop_exception_frame(ctx); return self.unwind_to_exception_handler(ctx); } - jitcode::insns::BC_ABORT => return TraceAction::Abort, - jitcode::insns::BC_ABORT_PERMANENT => return TraceAction::AbortPermanent, + jitcode::insns::BC_ABORT => { + self.log_bytecode_abort("BC_ABORT"); + return TraceAction::Abort; + } + jitcode::insns::BC_ABORT_PERMANENT => { + self.log_bytecode_abort("BC_ABORT_PERMANENT"); + return TraceAction::AbortPermanent; + } // No BC_NEW_ARRAY / BC_NEW_ARRAY_CLEAR arm by design: the codewriter // never emits these into a dispatched JitCode body (the byte-emit // methods in jitcode/assembler.rs have zero callers; jtransform / @@ -8697,6 +8999,47 @@ where } } +/// A typed `*_return` reached a caller that declared no destination for it. +/// +/// `pyjitpl.py:2503-2509 finishframe` writes the result into `framestack[-1]` +/// whenever the stack is non-empty, and `pyjitpl.py:258-275 +/// make_result_of_lastop` takes the destination from +/// `ord(self.bytecode[self.pc-1])` — the result operand of the CALLER's +/// `inline_call_*` instruction. There is no no-destination case to fall +/// through, and none can arise: `jtransform.py:414-435 rewrite_call` builds the +/// name as `'%s_%s_%s' % (namebase, kinds, reskind)` with `reskind = +/// getkind(op.result.concretetype)[0]`, so the caller's `_i`/`_r`/`_f`/`_v` +/// suffix and the callee's terminator are ONE decision taken once, from the +/// callee's own result type. +/// +/// Pyre encodes the three destinations explicitly (`NO_RETURN_REG` sentinel, +/// decoded at the `BC_INLINE_CALL`) instead of re-reading the caller's operand +/// at return time, which is what makes the disagreement representable here. +/// Reaching this means an encoder minted `inline_call_*_v` for a callee that +/// ends in a typed return: the value has nowhere to go, and continuing would +/// drop it in silence and leave the caller reading a stale register — the same +/// shape as the green write `lower_stmt_fallback` used to discard, one layer +/// down. +/// +/// This does NOT abort the process. `run_to_end` wraps every `run_one_step` +/// in `catch_unwind` (see the `abort_after_panic` site above), so the unwind is +/// caught and converted to [`TraceAction::Abort`] — the trace is discarded and +/// the interpreter answers, with the message printed only under `MAJIT_LOG`. +/// That is the point: it puts this mismatch on the same disposition as the +/// sibling site in [`MetaInterp::run_one_step`]'s finished-frame branch, which +/// reaches the same catch through `expect("inline int return missing caller +/// destination")`. Before this, the two disagreed — that one aborted the trace, +/// this one let the walk continue against a register the callee never wrote. +fn typed_return_without_caller_destination(insn: &str, argcode: char) -> ! { + panic!( + "{insn} returned an `{argcode}` value into a non-empty framestack, but the \ + caller's BC_INLINE_CALL encoded NO_RETURN_REG for that bank. The \ + `inline_call_*_{argcode}` form and the callee's terminator are one \ + decision (jtransform.py:434 `reskind`) and this pair disagrees, so the \ + returned value has no destination" + ); +} + /// Legacy entry point used by tests and integrations that still hold /// `JitCode` by reference and do not pass a `MetaInterp` framestack /// borrow. Allocates a [`StandaloneFrameStack`], pushes the root @@ -8793,6 +9136,41 @@ where { ctx.walk_final_pc = Some(pc); } + // An arm that failed to lower is emitted as a degraded stub: a + // sub-JitCode whose entire body is the single `abort` byte, pushed by + // the arm's `BC_INLINE_CALL` (`jit_interp/jitcode_lower/dispatch.rs` + // emits `__sub_builder.abort()` as the whole body). The arm's own code + // never runs, so the opcode applies NOTHING. + // + // i0 is the wrong resume position for exactly that case, and wrong in + // the silent direction: dispatch advances i0 before running the arm, so + // i0 names the position AFTER an opcode that did not execute. Resuming + // there drops the opcode. The offset is the shared prologue advance, + // not the instruction width, so a multi-byte opcode resumes inside its + // own operands and the interpreter decodes an operand as an opcode. + // + // The opcode's own boundary is the position the walk last passed + // through the merge point, which is where the interpreter is by + // construction re-enterable. Re-running the opcode is sound precisely + // because the stub applied nothing. + // + // `last_mp_green_pc` is `None` for a driver that declares no greens — + // its merge point yields no concrete pc at all — so this correction + // does not apply there and the i0 position stands, unchanged. + let stub_resume_pc = if standalone.frames.frames.len() > 1 + && standalone + .frames + .frames + .last() + .is_some_and(|f| f.jitcode.code.as_slice() == [crate::jitcode::insns::BC_ABORT]) + { + ctx.last_mp_green_pc + } else { + None + }; + if let Some(pc) = stub_resume_pc { + ctx.walk_final_pc = Some(pc); + } // Every frame below the top already carries its own resume position: // `BC_INLINE_CALL` sets `frame.pc = frame.code_cursor` on the caller // before pushing the callee. The top frame's is still the last guard's @@ -8803,7 +9181,16 @@ where // completed step leaves the cursor just past them — where RPython's // `self.pc = position` (`pyjitpl.py:3863`, in the `_get_opimpl_method` // handler) leaves `MIFrame.pc`. - if !std::mem::replace(&mut ctx.abort_after_panic, false) { + // + // A stub abort corrected above declines the conversion for the same + // reason `abort_after_panic` does — the frames name no work the + // blackhole should finish. Publishing them anyway is what makes the + // correction inert: `pending_abort_blackhole` is consumed in preference + // to `single_pass_outcome`, so the stale position wins with no error. + // The flag is taken unconditionally so declining here cannot leave it + // set for the next abort. + let abort_after_panic = std::mem::replace(&mut ctx.abort_after_panic, false); + if !abort_after_panic && stub_resume_pc.is_none() { if let Some(top) = standalone.frames.frames.last_mut() { top.pc = top.code_cursor; } @@ -9804,7 +10191,7 @@ mod tests { is_field_signed: true, is_immutable: false, is_quasi_immutable: false, - index_in_parent: 0, + index_in_parent: Some(0), parent: Some(majit_translate::jitcode::BhSizeSpec { size, type_id, @@ -10011,7 +10398,7 @@ mod tests { } } - /// #184 SLICE 0 — a `BC_RECURSIVE_CALL_INT` whose runtime inlines the + /// recursive-call SLICE 0 — a `BC_RECURSIVE_CALL_INT` whose runtime inlines the /// portal must push the portal frame, trace into it, marshal the /// argument into the portal's register, and write the portal's typed /// return back into the caller's destination register. The portal @@ -10228,7 +10615,7 @@ mod tests { } } - /// #184 S3f — a `BC_RECURSIVE_CALL_INT` whose runtime decides + /// recursive-call S3f — a `BC_RECURSIVE_CALL_INT` whose runtime decides /// `CallAssembler` must run the callee with a FRESH frame: the reds are /// the freshly-allocated callee state (`recursive_fresh_entry_reds`), not /// the caller's registers. Each virt-array `&state` red is recorded as a @@ -12096,6 +12483,77 @@ mod tests { ); } + /// A callee ending in `int_return` under a caller that emitted the VOID + /// `inline_call` form has nowhere to put its result. + /// + /// `jtransform.py:414-435 rewrite_call` derives the `_i`/`_r`/`_f`/`_v` + /// suffix from `getkind(op.result.concretetype)[0]`, so upstream takes the + /// caller's suffix and the callee's terminator from one fact and cannot + /// mint this pair at all. Pyre can, because it encodes the three + /// destinations explicitly with a `NO_RETURN_REG` sentinel; before this was + /// loud, the value was dropped in silence and the caller went on reading a + /// stale register. The negative twin below is the same jitcode pair with + /// the destination declared, and it must still return 7. + /// + /// Asserted as `TraceAction::Abort`, not `#[should_panic]`: `run_to_end` + /// wraps `run_one_step` in `catch_unwind`, so the refusal never leaves the + /// tracer as a panic. A `#[should_panic]` version of this test FAILS with + /// "test did not panic as expected" while the message is printed — which is + /// also the answer to "is the sibling `.expect` at the finished-frame site + /// really louder than this was?". It is not; it aborts, and now so does this. + #[test] + fn typed_return_under_a_void_inline_call_is_refused() { + let mut callee = JitCodeBuilder::new(); + callee.load_const_i_value(0, 7); + callee.int_return(0); + let callee = callee.finish(); + + let mut caller = JitCodeBuilder::new(); + let sub_idx = caller.add_sub_jitcode(callee); + caller.inline_call(sub_idx); + caller.load_const_i_value(0, 0); + caller.int_return(0); + let jitcode = caller.finish(); + + let mut ctx = TraceCtx::for_test(0); + let mut sym = DummySym::default(); + let action = trace_jitcode(&mut ctx, &mut sym, &jitcode, 0, |_pc| 0); + assert!( + matches!(action, TraceAction::Abort), + "a typed return with no caller destination must abort the trace, got {action:?}", + ); + } + + /// Positive control for [`typed_return_under_a_void_inline_call_is_refused`]: + /// the identical pair with the caller declaring reg 0 as the destination + /// writes the callee's 7 through and finishes normally. Without this the + /// `should_panic` above would also pass if the fixture simply failed to + /// reach the inline call. + #[test] + fn typed_return_under_a_matching_inline_call_writes_the_result_through() { + let mut callee = JitCodeBuilder::new(); + callee.load_const_i_value(0, 7); + callee.int_return(0); + let callee = callee.finish(); + + let mut caller = JitCodeBuilder::new(); + let sub_idx = caller.add_sub_jitcode(callee); + caller.inline_call_irf_i(sub_idx, &[], &[], &[], Some(0)); + caller.int_return(0); + let jitcode = caller.finish(); + + let mut ctx = TraceCtx::for_test(0); + let mut sym = DummySym::default(); + let action = trace_jitcode(&mut ctx, &mut sym, &jitcode, 0, |_pc| 0); + let finish_args = match action { + TraceAction::Finish { finish_args, .. } => finish_args, + other => { + panic!("expected the caller to finish with the callee's result, got {other:?}") + } + }; + assert_eq!(ctx.const_value(finish_args[0]), Some(7)); + } + #[test] fn raise_catch_inline_call_routes_to_handler_and_preserves_last_exc_value() { // exc payload must be a valid OBJECTPTR (typeptr at offset 0) so diff --git a/majit/majit-metainterp/src/pyjitpl/frame.rs b/majit/majit-metainterp/src/pyjitpl/frame.rs index e727a44a3e8..d77ea6fb811 100644 --- a/majit/majit-metainterp/src/pyjitpl/frame.rs +++ b/majit/majit-metainterp/src/pyjitpl/frame.rs @@ -88,15 +88,11 @@ pub struct MIFrame { pub float_regs: Vec>, pub float_values: Vec>, pub inline_frame: bool, - /// [FR] True when this is a recursive-portal INLINE frame that installed - /// its callee's fresh standard virtualizable; the frame's pop restores the - /// caller's saved vable via `TraceCtx::restore_saved_virtualizable`. - pub portal_vable_saved: bool, - /// [FR] Saved caller sym scalar/fixed-array state for a recursive-portal + /// \[FR\] Saved caller sym scalar/fixed-array state for a recursive-portal /// INLINE frame; restored into the shared sym when the frame returns so the /// callee's in-place mutation of the single sym doesn't corrupt the caller. pub portal_scalar_state: Option>, - /// [FR] True only for a frame whose push recorded `ENTER_PORTAL_FRAME` + /// \[FR\] True only for a frame whose push recorded `ENTER_PORTAL_FRAME` /// (an inline-pushed portal, dispatch.rs). Its normal-return and /// exception-return pops record the matching `LEAVE_PORTAL_FRAME`, so every /// enter_portal_frame pairs with a leave_portal_frame (pyjitpl.py:2461-2492: @@ -106,7 +102,7 @@ pub struct MIFrame { /// that record no ENTER and must record no LEAVE. The merge-point cut is the /// sole `leave_portal_frame=False` site and re-emits LEAVE itself. pub portal_entered: bool, - /// [FR] The jd_index carried in this frame's `LEAVE_PORTAL_FRAME` op, set at + /// \[FR\] The jd_index carried in this frame's `LEAVE_PORTAL_FRAME` op, set at /// the same push that set `portal_entered`. Unused when `portal_entered` is /// false. pub portal_jd: usize, @@ -187,7 +183,6 @@ impl MIFrame { float_regs: vec![None; regs_and_consts_f], float_values: vec![None; regs_and_consts_f], inline_frame: false, - portal_vable_saved: false, portal_scalar_state: None, portal_entered: false, portal_jd: 0, @@ -906,16 +901,31 @@ impl MIFrame { SnapshotTagged::Const(0, Type::Int) } else if skip_int_identity.is_some_and(|(b, e)| idx >= b && idx < e) && idx < num_regs_i - && self.int_regs[idx].is_none() { - // A non-root (inline split-dispatch) frame reserves the - // virtualizable identity-slot prefix int[base..end) so its - // register file spans them, but the resume seeder fills only - // the ROOT frame's identity slots — here they are unwritten - // holes. Emit a count-preserving Const(0) placeholder (the - // liveness marker's length_i is baked, so dropping would - // desync the decoder); deopt re-derives the real ptr/len - // from the single reconstructed root virtualizable. + // A non-root (inline) frame reserves the virtualizable + // identity-slot prefix int[base..end) so its register file + // spans them, but only the ROOT frame's identity slots are + // meaningful: deopt re-derives the real ptr/len from the + // single reconstructed root virtualizable. Emit a + // count-preserving Const(0) placeholder (the liveness + // marker's length_i is baked, so dropping would desync the + // decoder). + // + // Blank the range UNCONDITIONALLY. An earlier version also + // required `self.int_regs[idx].is_none()`, on the premise + // that a sub-frame's identity slots are always unwritten + // holes. That is false: dualtape's sub-frame at + // `jitcode_pos=9` carries the virtualizable identity in + // int reg 2, bit-identical to the root frame's ref reg 1 + // (`with_vable_input_ref_reg(1)`), so the slot is written + // and the guard skipped the trim. The identity was then + // seeded back as a live Int and used as an array index, + // faulting in `handler_getarrayitem_vable_i`. + // + // `end` here is `int_identity_reserved_end()`, NOT + // `int_identity_slots_end()` — see that method: the latter + // spans the virt arrays' live element counts, and blanking + // those would drop ordinary working registers. SnapshotTagged::Const(0, Type::Int) } else if idx < num_regs_i { let opref = self.int_regs[idx] diff --git a/majit/majit-metainterp/src/recorder.rs b/majit/majit-metainterp/src/recorder.rs index 31cd3249951..437f7eb156a 100644 --- a/majit/majit-metainterp/src/recorder.rs +++ b/majit/majit-metainterp/src/recorder.rs @@ -69,7 +69,7 @@ pub struct Snapshot { /// `jitcode_index` for a frame the recorder minted with no real coordinate. /// -/// [`crate::history::TraceCtx::record_guard_with_snapshot`] attaches a +/// `crate::history::TraceCtx::record_guard_with_snapshot` attaches a /// one-frame snapshot to the interpreter-side promotes purely to satisfy /// `resume.py:396-397`'s `assert resume_position >= 0`. That layer is the /// recorder-side trace buffer and holds no `MIFrameStack`, so it has no @@ -138,7 +138,7 @@ pub struct SnapshotFrame { /// is therefore intentionally absent — adding a virtual-tagged source /// requires a dedicated resume enum, not this snapshot type. /// -/// TAGBOX(n) → value lives in fail_args[n] (deadframe slot n) +/// TAGBOX(n) → value lives in `fail_args[n]` (deadframe slot n) /// TAGCONST(v) → compile-time constant (i64 value) #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum SnapshotTagged { @@ -554,7 +554,7 @@ impl Trace { /// opencoder.py:567-568 `cut_point()` — the recorder's local slice of /// the 5-tuple. `snapshot_data_len` / `snapshot_array_data_len` come - /// from `TraceCtx` (which owns the pyre-only Vec side + /// from `TraceCtx` (which owns the pyre-only `Vec` side /// table); callers should use `TraceCtx::get_trace_position` for a /// fully-populated position. pub fn get_position(&self) -> TracePosition { @@ -964,10 +964,8 @@ mod tests { assert_eq!(guards[0].opcode, OpCode::GuardTrue); } - // ══════════════════════════════════════════════════════════════════ // Opencoder parity tests // Ported from rpython/jit/metainterp/test/test_opencoder.py - // ══════════════════════════════════════════════════════════════════ #[test] fn test_simple_iterator() { @@ -1274,9 +1272,7 @@ mod tests { rec.record_input_arg(Type::Int); } - // ══════════════════════════════════════════════════════════════════ // Opencoder breadth tests — deeper parity with test_opencoder.py - // ══════════════════════════════════════════════════════════════════ #[test] fn test_recorder_const_int_via_constant_oprefs() { diff --git a/majit/majit-metainterp/src/resume.rs b/majit/majit-metainterp/src/resume.rs index e891f4e6e06..1050b8560ae 100644 --- a/majit/majit-metainterp/src/resume.rs +++ b/majit/majit-metainterp/src/resume.rs @@ -35,9 +35,7 @@ fn array_kind_from_descr(arraydescr: Option<&majit_ir::DescrRef>) -> u8 { .unwrap_or(0) } -// ═══════════════════════════════════════════════════════════════ // RPython resume.py:96-139 — structural port (i16 tags). -// ═══════════════════════════════════════════════════════════════ // resume.py:96-97 #[derive(Debug)] @@ -87,6 +85,21 @@ pub const NULLREF: i16 = ((-1i32 << 2) | TAGCONST as i32) as i16; pub const UNINITIALIZED_TAG: i16 = ((-2i32 << 2) | TAGCONST as i32) as i16; pub const TAG_CONST_OFFSET: i32 = 0; +/// Whether the leaf-3 identity-slot census is armed (`MAJIT_LEAF3_PROV=1`). +/// +/// Cached: the census sits on the resume path, which runs once per deopt, and +/// a per-call `var()` would fold the probe's own cost into what it measures. +/// Shape borrowed from `crate::stall_window`/`step_limit` — this file reads no +/// other environment variable, so there is no local idiom to match. +/// +/// Default OFF, unlike those two: this one prints per call, so a default-on +/// would bury every other diagnostic in the corpus. +fn leaf3_prov_enabled() -> bool { + static ARMED: std::sync::LazyLock = + std::sync::LazyLock::new(|| std::env::var("MAJIT_LEAF3_PROV").is_ok_and(|v| v == "1")); + *ARMED +} + /// Ordered livebox map: canonical box (`Rc::ptr_eq`) → i16 tag. /// /// resume.py:137/370: RPython uses `dict` keyed by the actual Box object @@ -678,8 +691,8 @@ pub struct EncodedResumeData { /// compile.py:858 storage.rd_virtuals — live VirtualInfo objects. pub rd_virtuals: Vec, /// resume.py:411 liveboxes — compact TAGBOX(n) → original FailArg index. - /// In RPython, liveboxes[n] is the Box object that was assigned TAGBOX(n). - /// Here, liveboxes[n] is the original deadframe slot index. + /// In RPython, `liveboxes[n]` is the Box object that was assigned TAGBOX(n). + /// Here, `liveboxes[n]` is the original deadframe slot index. pub liveboxes: Vec, /// Per-frame slot count — equivalent to jitcode liveness info. /// RPython uses jitcode.get_live_vars_info(pc) at decode time; @@ -3167,8 +3180,6 @@ impl Default for ResumeDataVirtualAdder { } } -// ── Fail-arg compression ───────────────────────────────────────────── - /// Shared resume data storage that deduplicates common snapshot sections /// across multiple guards in the same trace. /// @@ -3883,8 +3894,8 @@ impl ResumeDataLoopMemo { // RPython Box class identity). The `livebox_types` // HashMap is a legacy side-table that must agree with // `opref.ty()`; a divergence would indicate an - // encoder/decoder mismatch we want to fail-loud on - // (epic #171 will retire the side-table). + // encoder/decoder mismatch we want to fail-loud on. Remove the + // side-table once all consumers use the intrinsic type. if let Some(intrinsic_tp) = opref.ty() { debug_assert_eq!( intrinsic_tp, box_type, @@ -4527,7 +4538,7 @@ impl OptimizerKnowledgeForResume { /// into a single `majit_ir::Const` that carries its own type. #[derive(Clone, Debug, PartialEq)] pub enum DecodedBox { - /// TAGBOX → liveboxes[num] (bridge inputarg / optimizer box). + /// TAGBOX → `liveboxes[num]` (bridge inputarg / optimizer box). LiveBox(majit_ir::OpRef), /// TAGINT / TAGCONST / NULLREF — all Const subtypes. Const(majit_ir::Const), @@ -5340,6 +5351,171 @@ mod tests { assert_eq!(bh.position, 0); } + /// `next_ref_for_resume_slot` recognizes the virtualizable by + /// the TAGGED VALUE of the vable identity, and `NULLREF` is not an identity. + /// + /// `_number_boxes` writes `NULLREF` for every snapshot box whose OpRef is + /// `None`, so it is the encoding shared by every absent ref in the resume. + /// The vable identity is written that way exactly when the host folds its + /// virtualizable out of the backend failargs — which is the case + /// `identity_override` exists to serve. Two slots both spelled `NULLREF` + /// are both null; they are not the same object. Substituting the + /// virtualizable for one hands a live frame pointer to a register the + /// program left empty, and the blackhole then reads a frame where the + /// bytecode expects null. + /// + /// The tag itself is reader-global, not section-local: `decode_ref` + /// resolves against `consts`, `virtuals` and `deadframe`, and `count` is + /// read once when the reader is built. So this needs no second section — + /// one frame with one null ref register exhibits it. + /// Measured when this fixture landed: the control arm passes (r0 is + /// seeded, and null) and the subject arm reads `0xABCD`, the override, out + /// of a register whose resume item was `NULLREF`. + /// + /// Reachability, as far as it is settled. A writers census of the identity + /// slot found two ways it could be `NULLREF`, and they do not have the same + /// status: + /// + /// - via `get_box_replacement(..).is_none()` — impossible. The production + /// `BoxEnv` resolves through `get_replacement_opref`, which returns its + /// argument when the walk finds no producer, so it yields `NONE` only + /// when handed `NONE`, which `_number_boxes` tests one line earlier. + /// - via `raw_opref.is_none()` — open. `build_vable_snapshot_boxes` cannot + /// emit it (it asserts the identity is typed), but that guards snapshot + /// construction, and `TreeLoop::cut_trace_from_with_consts` rewrites the + /// finished snapshot afterwards, mapping unmapped pre-cut refs to `NONE` + /// over `vable_boxes` too. Its seed-or-cancel check walks the snapshots + /// of the post-cut ops while the rewrite walks every snapshot, so a + /// snapshot no post-cut op names is rewritten with nothing able to cancel + /// the compilation. + /// + /// Whether such a snapshot is then read at resume is a runtime question the + /// census cannot answer. The committed corpus observes only `TAGBOX(0)`; + /// whether `NULLREF` is unrepresentable here or merely unobserved is not + /// established. + /// + /// Read the denominator before the ratio. 2999 of those 3298 come from + /// **one** crate, `dualtape` — so the unanimity is not eight crates + /// agreeing, it is one crate plus 299 observations. Four of the twelve + /// (`braininterp`, `cel`, `tiny2`, `tiny3`) never reach this function at + /// all, so the population that can produce evidence is 8 of 12, not 12. + /// + /// And the probe reports the slot's VALUE, not its PROVENANCE. A zero is + /// equally consistent with "the unseeded-snapshot route ran and never + /// yielded `NULLREF`" and with "the route never ran here". The writers + /// census established the route exists; this establishes only that its + /// observable precondition did not occur in this corpus. + /// + /// `override=yes` on 3298/3298, so the assert below is evaluated on every + /// one of them — it is exercised, not merely present. + /// + /// `consume_vable_info` now asserts the identity is not `NULLREF` when an + /// override is supplied, so the aliasing encoding is refused rather than + /// resolved to a wrong slot. + /// + /// Run against the landed assert: the subject arm trips it in + /// `consume_vable_info` rather than reaching the `0xABCD` read. So the + /// refusal is on the path this encoding builds — reached and load-bearing, + /// which a compile cannot establish — and the outcome splits cleanly in + /// two. [`a_null_ref_register_is_seeded_when_no_identity_override_is_supplied`] + /// pins that the guard stays silent without an override; + /// [`a_null_ref_identity_is_refused_when_an_override_is_supplied`] pins that + /// it fires with one. + /// + /// Split rather than converted to one `#[should_panic]`: that attribute is + /// satisfied by EITHER arm panicking, so the control — the half proving the + /// guard does not over-fire — could die of an unrelated cause with the test + /// still green, under a name that still claims both directions. + fn leaf3_resume_null_identity(identity_override: Option) -> (i64, i64) { + use crate::blackhole::BlackholeInterpBuilder; + use crate::jitcode::JitCodeBuilder; + use crate::jitcode::insns::{BC_ABORT, BC_CATCH_EXCEPTION, BC_LIVE, BC_RVMPROF_CODE}; + + let mut writer = crate::resumecode::Writer::new(9); + writer.append_int(0); // items_resume_section (patched below) + writer.append_int(1); // count + writer.append_int(1); // vable_size: the identity only (get_total_size == 0) + writer.append_int(NULLREF as i64); // identity: folded out, so no box + writer.append_int(0); // vref_array length + writer.append_int(0); // jitcode_pos + writer.append_int(0); // pc + writer.append_int(0); // py_pc + writer.append_int(NULLREF as i64); // frame ref register r0: null + writer.patch_current_size(0); + let rd_numb = writer.create_numbering(); + + let mut runtime = JitCodeBuilder::default().finish(); + runtime.body_mut().code = vec![BC_LIVE, 0, 0, BC_ABORT]; + runtime.body_mut().c_num_regs_r = 1; + runtime.body_mut().startpoints = Some([0_usize, 3].into_iter().collect()); + let runtime = std::sync::Arc::new(runtime); + // length_i=0, length_r=1, length_f=0, then the live-register BITMASK + // (`LivenessIterator` scans set bits, it does not read indices): bit 0 + // set names r0 as the one live ref register. + let all_liveness: Vec = vec![0, 1, 0, 0b0000_0001]; + let deadframe = [0x4000_i64]; + let deadframe_types = [majit_ir::Type::Ref]; + + let mut builder = BlackholeInterpBuilder::new(); + builder.setup_cached_control_opcodes( + BC_LIVE as i32, + BC_CATCH_EXCEPTION as i32, + BC_RVMPROF_CODE as i32, + ); + let resolve_jitcode = |_jitcode_pos: i32, _pc: i32| -> Option { + Some(ResolvedJitCode::new(runtime.clone(), 0)) + }; + let (bh, virtualizable_ptr) = blackhole_from_resumedata( + &mut builder, + &resolve_jitcode, + &rd_numb, + &[], + &all_liveness, + &deadframe, + Some(&deadframe_types), + None, // rd_virtuals + None, // rd_guard_pendingfields + None, // vrefinfo + Some(&TestVirtualizableInfo), + None, // ginfo + identity_override, + None, // all_virtuals + &NullAllocator, + ) + .expect("resume should produce a blackhole"); + (virtualizable_ptr, bh.registers_r[0]) + } + + /// CONTROL for [`leaf3_resume_null_identity`]. With no override the + /// substitution cannot happen, so this pins two things the subject test + /// cannot: that the fixture REACHES the register (NULLREF survives the + /// writer, the liveness names r0, and the seeding path runs), and that the + /// refusal added to `consume_vable_info` does NOT fire without an override. + /// + /// Without it, a subject test that panics and a register that was never + /// written are the same observation. + #[test] + fn a_null_ref_register_is_seeded_when_no_identity_override_is_supplied() { + let (virtualizable_ptr, r0) = leaf3_resume_null_identity(None); + assert_eq!(virtualizable_ptr, 0, "NULLREF identity decodes to null"); + assert_eq!(r0, 0, "control: r0 is seeded, and null"); + } + + /// SUBJECT. `r0` holds `NULLREF`, the same spelling the folded-out identity + /// uses, so matching the override by tag would hand a live frame pointer to + /// a register the program left empty. `consume_vable_info` refuses the + /// encoding instead. + /// + /// `expected` is load-bearing: a bare `#[should_panic]` is satisfied by ANY + /// panic, including one from a fixture that stopped building the encoding + /// correctly and died in setup. The substring pins WHICH refusal fired. + #[test] + #[should_panic(expected = "virtualizable identity encoded as NULLREF")] + fn a_null_ref_identity_is_refused_when_an_override_is_supplied() { + const OVERRIDE: i64 = 0xABCD; + leaf3_resume_null_identity(Some(OVERRIDE)); + } + /// resume.py:990-991 `_prepare_virtuals` resets `virtuals_cache` to zeros. /// That is why `blackhole_from_resumedata` must not run `_prepare` on the /// GUARD_NOT_FORCED path (resume.py:1368-1375): there the preloaded cache is @@ -5420,13 +5596,11 @@ mod tests { } } -// ═══════════════════════════════════════════════════════════════ // resume.py:901-1039 AbstractResumeDataReader // resume.py:1354-1601 ResumeDataDirectReader // // Direct reader that decodes resume data and fills blackhole // interpreter registers with concrete values from the deadframe. -// ═══════════════════════════════════════════════════════════════ use crate::blackhole::BlackholeInterpreter; use crate::resumecode::Reader; @@ -5993,7 +6167,7 @@ impl VirtualInfoBlackholeExt for VirtualInfo { /// resume.py:618/634/650 allocate(decoder, index) /// /// Allocate a virtual object and fill in its fields from the decoder. - /// Sets virtuals_cache_ptr[index] before filling fields (for recursive refs). + /// Sets `virtuals_cache_ptr[index]` before filling fields (for recursive refs). fn allocate( &self, decoder: &mut ResumeDataDirectReader, @@ -7052,7 +7226,67 @@ impl<'a> ResumeDataDirectReader<'a> { let tagged_identity = self.resumecodereader.next_item() as i16; let encoded_identity = self.decode_ref(tagged_identity); let virtualizable = identity_override.unwrap_or(encoded_identity); + // MAJIT_LEAF3_PROV census. The assert below refuses ONE value on ONE + // arm; this reports the whole distribution on every call, because + // "NULLREF cannot occur here" and "NULLREF was not observed here" are + // the two readings a silent run is consistent with, and they have + // opposite consequences. Placed before the branch deliberately: a + // census that only sees the override calls cannot measure the + // population the override calls are drawn from. + if leaf3_prov_enabled() { + let tag_name = match (tagged_identity & TAGMASK as i16) as u8 { + TAGCONST => "TAGCONST", + TAGINT => "TAGINT", + TAGBOX => "TAGBOX", + _ => "TAGVIRTUAL", + }; + eprintln!( + "[leaf3-prov] identity tagged={} tag={} val={} nullref={} override={}", + tagged_identity, + tag_name, + tagged_identity >> 2, + if tagged_eq(tagged_identity, NULLREF) { + "yes" + } else { + "no" + }, + if identity_override.is_some() { + "yes" + } else { + "no" + }, + ); + } if identity_override.is_some() { + // `next_ref_for_resume_slot` routes a ref register to the override + // by comparing its tag against `virtualizable_identity_tagged`. That + // is only an identity test while the identity's tag is unique to it: + // `NULLREF` is the shared "no box here" encoding, so an identity + // recorded as `NULLREF` would claim every null ref register in the + // frame and hand each one the virtualizable pointer. + // + // `_number_boxes` emits `NULLREF` for a snapshot box whose `OpRef` + // is `NONE`, and `TreeLoop::cut_trace_from_with_consts` maps an + // unmapped pre-cut ref to `NONE` over `vable_boxes` as well as the + // frame sections. Its seed-or-cancel guard walks the snapshots of + // the post-cut ops, so a snapshot no post-cut op names is remapped + // without ever being able to cancel the compilation — the two loops + // iterate different populations. Refuse the aliasing encoding here + // rather than resolve a wrong slot silently. + // + // This BOUNDS the defect rather than closing it. It refuses the + // identity encoding it can name; the unseeded-snapshot case just + // described, which is the only remaining route, is still defaulted + // rather than refused. `NONE` there is a well-formed value standing + // in for an answer nobody computed, and that is precisely why + // nothing downstream can catch it — it is indistinguishable from + // the `NONE` that legitimately encodes a genuinely absent box. + assert!( + !tagged_eq(tagged_identity, NULLREF), + "virtualizable identity encoded as NULLREF while an identity \ + override is supplied: the tag is shared with every null ref \ + register, so the override cannot be matched to one slot" + ); self.virtualizable_identity_tagged = Some(tagged_identity); self.virtualizable_identity_override = identity_override; } else { diff --git a/majit/majit-metainterp/src/resume_box_reader.rs b/majit/majit-metainterp/src/resume_box_reader.rs index 41ddc1b0305..14cc9175589 100644 --- a/majit/majit-metainterp/src/resume_box_reader.rs +++ b/majit/majit-metainterp/src/resume_box_reader.rs @@ -836,7 +836,7 @@ pub fn materialize_bridge_virtual( } /// resume.py:1245-1264 `decode_box` symbolic parity for an already-decoded -/// [`RebuiltValue`]: mint the bridge `OpRef` (typed `InputArg` for a live box, +/// [`majit_ir::resumedata::RebuiltValue`]: mint the bridge `OpRef` (typed `InputArg` for a live box, /// const for a pooled const, recursively materialized virtual for a virtual). pub fn rebuilt_value_to_opref( ctx: &mut crate::TraceCtx, diff --git a/majit/majit-metainterp/src/trace_ctx.rs b/majit/majit-metainterp/src/trace_ctx.rs index c85e72977a8..6625d0773f0 100644 --- a/majit/majit-metainterp/src/trace_ctx.rs +++ b/majit/majit-metainterp/src/trace_ctx.rs @@ -64,7 +64,9 @@ fn descr_to_bh_field_descr(descr: &DescrRef) -> Option>, /// Lengths of each virtualizable array field, needed for flat index computation. virtualizable_array_lengths: Option>, - /// [FR] Saved standard-virtualizable state, pushed when a recursive-portal - /// INLINE frame installs its callee's fresh vable and popped when that - /// frame returns — the single standard vable nested across the inline call. - #[allow(clippy::type_complexity)] - portal_vable_saves: Vec<( - Option>, - Option>, - Option>, - Option>, - Option>, - )>, /// Live virtualizable heap pointer (pyjitpl.py:3446 write_boxes target). /// Mirrored from `MetaInterp::vable_ptr` at trace/bridge-entry. Used by /// `synchronize_virtualizable` to write `virtualizable_values` back to @@ -356,7 +347,7 @@ pub struct TraceCtx { pub has_compiled_targets_fn: Option bool>>, /// pyjitpl.py:3005 `ptoken = self.get_procedure_token(greenboxes)` for the /// greens of the merge point just reached, in the same `(ints, refs, - /// floats)` slot grouping as [`Self::close_greens`]. `Some(key)` iff a + /// floats)` slot grouping as `Self::close_greens`. `Some(key)` iff a /// compiled loop with jumpable targets already lives at those greens /// (`MetaInterp::compiled_key_for_greens`, which folds in /// `has_compiled_targets`). @@ -374,7 +365,7 @@ pub struct TraceCtx { /// `start_bridge_tracing` and leaves the default `false` for /// primary entries. /// - /// ⚠️This is NOT `self.partial_trace`. That flag is set only by + /// This is NOT `self.partial_trace`. That flag is set only by /// `retrace_needed` (pyjitpl.py:2438-2439) and means "this is a /// RETRACE"; a bridge from a guard failure has `partial_trace = None` /// and takes every `if not self.partial_trace:` branch. Pyre has no @@ -487,6 +478,21 @@ pub struct TraceCtx { /// `MetaInterp::single_pass_outcome` (set before `compile_loop` drains the /// ctx) to the merge-point hook. pub walk_final_pc: Option, + /// Interpreter pc named by the most recent root-frame merge point the walk + /// passed through — the last position at which the interpreter was, by + /// construction, re-enterable. + /// + /// Distinct from [`Self::walk_final_pc`], which the abort path derives from + /// the root frame's i0 and which therefore names the position AFTER the + /// opcode the walk stopped in. The two agree only when the walk stopped at + /// an opcode boundary. `trace_jitcode_with_args_and_runtime` prefers this + /// one for a degraded-stub abort, where the opcode provably applied + /// nothing. + /// + /// `None` for a driver that declares no greens: its merge point yields no + /// concrete pc, so there is nothing to record and the correction that reads + /// this does not apply. + pub last_mp_green_pc: Option, /// Set when the abort came out of a panic caught around `run_one_step` /// rather than a decision the walk took. The unwind can leave the frame's /// `code_cursor` anywhere inside the panicking instruction, so the frames @@ -953,7 +959,7 @@ impl TraceCtx { /// `executor.py:200 do_getfield_raw_{i,r,f}` analog — read a raw /// field at `struct_ptr + descr.offset` via `cpu.bh_getfield_raw_*`. - /// Distinct from [`field_sanity_load`] which dispatches the GC + /// Distinct from [`Self::field_sanity_load`] which dispatches the GC /// variant (`executor.py:188 do_getfield_gc_*`). Used when the /// recorded opcode is `GetfieldRaw{I,R,F}` rather than /// `GetfieldGc{I,R,F}`. @@ -993,7 +999,7 @@ impl TraceCtx { /// `executor.py:132 do_getarrayitem_raw_{i,f}` analog — read a raw /// array element via `cpu.bh_getarrayitem_raw_*`. Distinct from - /// [`array_sanity_load`] which dispatches the GC variant + /// [`Self::array_sanity_load`] which dispatches the GC variant /// (`executor.py:117 do_getarrayitem_gc_*`). Raw arrays carry the /// array pointer as an `int` (`arraybox.getint()` upstream), not a /// `getref_base()` projection — callers must pass the raw pointer @@ -1029,7 +1035,7 @@ impl TraceCtx { } } - /// Array-side analogue of [`field_sanity_load`]. `executor.execute` + /// Array-side analogue of [`Self::field_sanity_load`]. `executor.execute` /// dispatches GETARRAYITEM_GC_{I,R,F} through `do_getarrayitem_gc_*` /// (executor.py:206-212); pyre's `kind` selects between the three /// variants. Returns `Some(value)` when `self.cpu` is wired and the @@ -1076,7 +1082,7 @@ impl TraceCtx { /// Extracts the index ConstInt's `getint()` value (returns `None` /// on non-ConstInt operands, matching the upstream early-out at /// `heapcache.py:543`) and routes the lookup through the indexcache - /// (heap_array_cache[descr][index_value]). Inside the indexcache, + /// (`heap_array_cache[descr][index_value]`). Inside the indexcache, /// `array` is canonicalised by `_unique_const_heuristic` against /// the per-CacheEntry `last_const_box` (heapcache.py:96-104) so two /// distinct ConstPtr OpRefs for the same gcref share the same @@ -1511,7 +1517,7 @@ impl TraceCtx { ) } - /// Like [`for_test_types`] but seeds the trace green key (and thus + /// Like [`Self::for_test_types`] but seeds the trace green key (and thus /// `root_green_key`). A unit test that drives a loop-closing /// `jit_merge_point` uses this to model the trace as having STARTED at /// that loop header. @@ -1559,7 +1565,6 @@ impl TraceCtx { virtualizable_live_null_slots: None, virtualizable_info: None, virtualizable_array_lengths: None, - portal_vable_saves: Vec::new(), virtualizable_heap_ptr: None, header_pc: 0, cut_inner_green_key: None, @@ -1585,6 +1590,7 @@ impl TraceCtx { last_traced_pc: 0, initial_inputarg_consts: vec![], walk_final_pc: None, + last_mp_green_pc: None, abort_after_panic: false, aborted_framestack: None, walk_final_reds: Vec::new(), @@ -1645,7 +1651,6 @@ impl TraceCtx { virtualizable_live_null_slots: None, virtualizable_info: None, virtualizable_array_lengths: None, - portal_vable_saves: Vec::new(), virtualizable_heap_ptr: None, header_pc: 0, cut_inner_green_key: None, @@ -1671,6 +1676,7 @@ impl TraceCtx { last_traced_pc: 0, initial_inputarg_consts: vec![], walk_final_pc: None, + last_mp_green_pc: None, abort_after_panic: false, aborted_framestack: None, walk_final_reds: Vec::new(), @@ -2052,7 +2058,7 @@ impl TraceCtx { } } - /// See [`TraceCtx::close_jump_into_key`]. Read-and-clear: the answer is only + /// See `TraceCtx::close_jump_into_key`. Read-and-clear: the answer is only /// meaningful to the close that just set it. pub fn take_close_jump_into_key(&mut self) -> Option { self.close_jump_into_key.take() @@ -2061,7 +2067,7 @@ impl TraceCtx { /// Mark that the current back-edge was reached inside an inline callee /// frame and must not be unrolled (opimpl_jit_merge_point /// portal_call_depth>0). The trace step drains this via - /// [`take_inline_loop_abort`] and aborts the trace. + /// [`Self::take_inline_loop_abort`] and aborts the trace. pub fn request_inline_loop_abort(&mut self) { self.inline_loop_abort_pending = true; } @@ -2235,7 +2241,7 @@ impl TraceCtx { /// static field + array element in the same flat layout as /// `VirtualizableInfo::get_index_in_array`. `vable_ref` / `vable_ref_value` /// are the OpRef and concrete of the virtualizable object (frame pointer). - /// Boxes layout: [field0, ..., fieldN, arr[0], ..., arr[M], vable_ref] + /// Boxes layout: `[field0, ..., fieldN, arr[0], ..., arr[M], vable_ref]` /// where `boxes[-1]` is the standard virtualizable identity (RPython parity). pub fn init_virtualizable_boxes( &mut self, @@ -2272,39 +2278,13 @@ impl TraceCtx { self.virtualizable_array_lengths = Some(array_lengths.to_vec()); } - /// [FR] The current standard virtualizable's info (shape), if any. A + /// \[FR\] The current standard virtualizable's info (shape), if any. A /// recursive-portal INLINE callee shares the caller's vable shape (same /// kernel), so it seeds its fresh vable with this same info. pub fn current_virtualizable_info(&self) -> Option> { self.virtualizable_info.clone() } - /// [FR] Save the current standard-virtualizable state onto the portal - /// nesting stack, before a recursive-portal INLINE frame installs its - /// callee's fresh vable via `init_virtualizable_boxes`. - pub fn push_saved_virtualizable(&mut self) { - self.portal_vable_saves.push(( - self.virtualizable_boxes.clone(), - self.virtualizable_values.clone(), - self.virtualizable_info.clone(), - self.virtualizable_array_lengths.clone(), - self.virtualizable_live_null_slots.clone(), - )); - } - - /// [FR] Restore the caller's standard-virtualizable state when a - /// recursive-portal INLINE frame returns. No-op if the stack is empty. - pub fn restore_saved_virtualizable(&mut self) { - if let Some((boxes, values, info, lengths, live_null_slots)) = self.portal_vable_saves.pop() - { - self.virtualizable_boxes = boxes; - self.virtualizable_values = values; - self.virtualizable_live_null_slots = live_null_slots; - self.virtualizable_info = info; - self.virtualizable_array_lengths = lengths; - } - } - /// Collect the current virtualizable boxes (for close_loop / finish). /// Returns `None` if no standard virtualizable is active. pub fn collect_virtualizable_boxes(&self) -> Option> { @@ -2417,7 +2397,7 @@ impl TraceCtx { boxes[..end].copy_from_slice(elements); } - /// [`collect_virtualizable_boxes`] with each slot paired with its declared + /// [`Self::collect_virtualizable_boxes`] with each slot paired with its declared /// [`Type`] (`virtualizable_slot_type`); identity LAST, as always. /// /// pyjitpl.py:2981-2989 builds ONE `live_arg_boxes` and hands it to both @@ -2698,7 +2678,7 @@ impl TraceCtx { } } - /// Field-aware variant of [`synchronize_virtualizable`] for the bridge + /// Field-aware variant of [`Self::synchronize_virtualizable`] for the bridge /// resume convergence path. Differs from the generic-bits version in /// two ways that match `sync_virtualizable_after_guard_failure` /// (`pyre-jit/src/eval.rs:5709`): @@ -2959,10 +2939,23 @@ impl TraceCtx { } /// Length of the symbolic virtualizable shadow, or `None` when no - /// virtualizable is bound. Probe-only accessor used by the - /// `MAJIT_PROBE_BRIDGE`-gated logging in pyre's bridge setup + - /// `push_typed_value` to surface bound-check off-by-ones before - /// `set_virtualizable_entry_at` panics. + /// virtualizable is bound. + /// + /// NOT probe-only, whatever an older revision of this comment said. Three + /// callers, all on correctness paths, none diagnostic: + /// + /// * `pyre-jit-trace/src/trace_opcode.rs` — bounds check whose failure calls + /// `request_trace_abort()`, so a trace resolves through the interpreter + /// instead of `set_virtualizable_entry_at` panicking. + /// * `pyre-jit-trace/src/jitcode_dispatch/mod.rs` — `append_virtualizable_boxes`, + /// the shape fix that makes the merge-point `live_arg_boxes` match the + /// JUMP `close_loop_args_at` records. + /// * the same file's register-bank candidate fill, which needs the length to + /// decide what liveness left unfilled. + /// + /// The `MAJIT_PROBE_BRIDGE` logging this once served is gone — no such gate + /// is read anywhere. Deleting this accessor as dead probe scaffolding would + /// take the abort guard with it. pub fn virtualizable_boxes_len(&self) -> Option { self.virtualizable_boxes.as_ref().map(|boxes| boxes.len()) } @@ -2977,7 +2970,7 @@ impl TraceCtx { /// / `_list_of_boxes` returning a 0-length array). /// Walker precondition for [`Self::build_snapshot_vable_vref_boxes`]: /// every virtualizable box (including the identity at `[-1]`) must carry - /// `OpRef::ty()` — the invariant [`crate::pyjitpl::build_vable_snapshot_boxes`] + /// `OpRef::ty()` — the invariant `crate::pyjitpl::build_vable_snapshot_boxes` /// enforces by panicking. A deeper inlined / recursive frame can leave /// the identity box untyped, so the full-body walker calls this before /// recording a guard snapshot and aborts the trace into the trait @@ -3137,18 +3130,23 @@ impl TraceCtx { /// Drop the tracing-time virtualizable_boxes mirror. /// - /// Used at bridge entry: `init_symbolic` seeds the cache with OpRefs + /// **Dormant — no caller.** The bridge-entry protocol below describes what + /// this is *for*, not what currently happens; nothing invokes it, so no + /// bridge entry clears the mirror today. The upstream counterpart it is + /// modelled on is real, so the disposition is to wire it rather than delete + /// it — see `rpython/jit/metainterp/pyjitpl.py:3400-3430`. + /// + /// Intended use is bridge entry: `init_symbolic` seeds the cache with OpRefs /// derived from the *parent* loop's `vable_array_base`, but the /// bridge owns a fresh inputarg stream (its own `OpRef::from_raw(0..N)` bound - /// to parent-guard fail_args). Keeping the parent seed makes + /// to parent-guard fail_args). Keeping the parent seed would make /// subsequent `vable_getarrayitem_*` / `vable_setarrayitem_*` reads - /// return stale parent-loop OpRefs; clearing forces the vable path + /// return stale parent-loop OpRefs; clearing would force the vable path /// to fall through to the raw `GetarrayitemGc` / `SetarrayitemGc` /// (`ctx.has_virtualizable_boxes() == false` branch) until the - /// bridge itself reseeds via resume data — matching - /// rpython/jit/metainterp/pyjitpl.py:3400-3430 where the - /// `virtualizable_boxes` are rebuilt from the guard's resume data - /// before the bridge replays any vable op. + /// bridge itself reseeds via resume data — matching the upstream site + /// above, where the `virtualizable_boxes` are rebuilt from the guard's + /// resume data before the bridge replays any vable op. pub fn clear_virtualizable_boxes(&mut self) { self.virtualizable_boxes = None; } @@ -4486,6 +4484,39 @@ impl TraceCtx { index_runtime_value: i64, fdescr: &DescrRef, ) -> Option { + // `PYRE_VABLE_IDX_PROBE`: does a non-constant index ever arrive here? + // + // Prints on BOTH branches deliberately. A probe that prints only on + // the branch it is hunting cannot distinguish "never taken" from + // "never reached" — both read as silence. + // + // Reading the counts. This is a SHARED CALLEE, and its callers do + // not agree about constness: + // - `pyjitpl/dispatch.rs` hoists `implement_guard_value` at all six + // of its sites, so every index arriving from there is CONST *by + // construction* and carries no information. + // - `jitcode_dispatch/specialize.rs` passes a `const_int` literal, + // likewise CONST by construction. + // - `jitcode_dispatch/vable_ops.rs` passes a raw int register, + // gated only on the index having a recorded *concrete value* + // (`concrete_of_opref`), which a non-constant OpRef can satisfy. + // That is the only family whose constness is an open question. + // So `NONCONST > 0` is conclusive, but `NONCONST == 0` is NOT + // evidence that the open family is constant — it is equally + // consistent with that family never being reached, since the + // constant-by-construction callers dilute the reading. To attribute, + // pair this with a probe at the `vable_ops.rs` index read itself. + // + // Measured readings: dualtape CONST=7204 NONCONST=0 (all from the + // hoisted dispatch.rs family, i.e. the hoist working as designed). + if crate::vable_idx_probe_enabled() { + let constness = if index.is_constant() { + "CONST" + } else { + "NONCONST" + }; + eprintln!("[vable-idx-probe] {constness} pc={pc} value={index_runtime_value}"); + } // indexbox = self.implement_guard_value(indexbox, pc) let promoted_index = if index.is_constant() { index @@ -5175,8 +5206,6 @@ mod tests { } } - // ── M1 · opref_to_box bridge tests ───────────────────────────────── - /// M1: non-constant OpRefs (inputargs + recorded op results) map /// straight to Box::ResOp(opref.raw()). No constant-pool lookup. #[test] diff --git a/majit/majit-metainterp/src/virtualizable.rs b/majit/majit-metainterp/src/virtualizable.rs index 4229ff9729a..2a186b2b64f 100644 --- a/majit/majit-metainterp/src/virtualizable.rs +++ b/majit/majit-metainterp/src/virtualizable.rs @@ -838,7 +838,7 @@ impl VirtualizableInfo { &self._array_field_descrs } - /// virtualizable.py:81: vinfo.static_field_by_descrs[fielddescr] + /// virtualizable.py:81: `vinfo.static_field_by_descrs[fielddescr]` /// Descriptor-identity lookup (linear scan via IndexMap). pub fn static_field_by_descr(&self, descr: &DescrRef) -> Option { self.static_field_by_descrs @@ -846,7 +846,7 @@ impl VirtualizableInfo { .copied() } - /// virtualizable.py:83: vinfo.array_field_by_descrs[arrayfielddescr] + /// virtualizable.py:83: `vinfo.array_field_by_descrs[arrayfielddescr]` /// Descriptor-identity lookup (linear scan via IndexMap). pub fn array_field_by_descr(&self, descr: &DescrRef) -> Option { self.array_field_by_descrs @@ -942,6 +942,36 @@ impl VirtualizableInfo { array_field_descrs: self.array_field_descrs().to_vec(), array_lengths: vec![], vable_input_offset: 0, + // Same declaration the resume path reads + // (`MetaInterp::identity_live_position`): the loop's inputargs are + // its reds, so the identity's position among the reds IS its flat + // input-arg slot. + // + // `identity_live_index == None` is overloaded, so the layout + // decides what it means. `identity_ref_bank_index` is the same + // structural discriminator `elements_carried_via_shadow` uses: + // + // - `None` — the legacy frame-first (PyFrame) layout, whose reds are + // `[frame, extra_reds.., vable_scalars.., array_items..]`. The + // frame IS flat slot 0, so slot 0 is the answer, not a fallback. + // - `Some(_)` — the banked-identity (macro state-field) layout, + // whose reds are `[int scalars.., fixed-array cells.., identity]`. + // Slot 0 there is an int scalar. Only a declaration names the + // identity, and the macro can emit one only when the state has no + // fixed array (`codegen_state.rs` `identity_live_index_stmt`) — + // with one present the position depends on that array's runtime + // length. `MetaInterp::current_virtualizable_optimizer_config` + // patches in the position the trace resolved against `vable_ptr`; + // absent both, this stays `None` and `VirtualizableTracker` + // declines to track. Declining costs optimization; probing slot 0 + // costs correctness — it installs `PtrInfo::Virtualizable` on an + // Int scalar and the loop-close Jump then fails to match the + // Ref-typed preview (`VirtualStatesCantMatch`), so nothing + // compiles at all. + identity_input_index: match self.identity_ref_bank_index { + None => Some(0), + Some(_) => self.identity_live_index, + }, track_array_elements: !self.elements_carried_via_shadow(), } } @@ -970,7 +1000,7 @@ impl VirtualizableInfo { self.array_fields.iter().position(|a| a.name == name) } - /// virtualizable.py:71: self.static_field_descrs[field_index] + /// virtualizable.py:71: `self.static_field_descrs[field_index]` /// Returns the cached FieldDescr for a static field. /// Descriptors are built once in set_parent_descr(), not per-call. pub fn static_field_descr(&self, field_index: usize) -> DescrRef { @@ -993,7 +1023,7 @@ impl VirtualizableInfo { .expect("token_field_descr called before set_parent_descr") } - /// virtualizable.py:73: self.array_field_descrs[array_index] + /// virtualizable.py:73: `self.array_field_descrs[array_index]` /// Returns the cached FieldDescr for an array pointer field. pub fn array_pointer_field_descr(&self, array_index: usize) -> DescrRef { self._array_field_descrs[array_index].clone() @@ -1007,7 +1037,7 @@ impl VirtualizableInfo { self._array_field_struct_descrs[array_index].clone() } - /// virtualizable.py:58: self.array_descrs[array_index] + /// virtualizable.py:58: `self.array_descrs[array_index]` /// Returns the pre-built array descriptor for the given array field. pub fn array_item_descr(&self, array_index: usize) -> DescrRef { self.array_descrs[array_index].clone() @@ -1262,7 +1292,7 @@ impl VirtualizableInfo { /// /// RPython equivalent: `vinfo.load_list_of_boxes(virtualizable)` /// - /// Returns a flat array: [field0, field1, ..., array0[0], ..., array0[N], ...] + /// Returns a flat array: `[field0, field1, ..., array0[0], ..., array0[N], ...]` /// Array lengths are read from the actual object (not from a side-channel). /// /// # Safety @@ -2383,6 +2413,41 @@ mod tests { assert!(config.array_lengths.is_empty()); } + /// `identity_live_index == None` means two different things, and the layout + /// — not the absent declaration — decides which. + #[test] + fn test_to_optimizer_config_identity_slot_is_layout_discriminated() { + // Legacy frame-first (PyFrame): the frame IS flat slot 0, so an absent + // declaration is not a gap. + let legacy = VirtualizableInfo::new(0); + assert_eq!(legacy.identity_ref_bank_index, None); + assert_eq!(legacy.identity_live_index, None); + assert_eq!( + legacy.to_optimizer_config().identity_input_index, + Some(0), + "the frame-first layout leads with the identity", + ); + + // Banked-identity (macro state-field) with a declaration: honour it. + let mut declared = VirtualizableInfo::without_vable_token(); + declared.identity_ref_bank_index = Some(1); + declared.identity_live_index = Some(3); + assert_eq!(declared.to_optimizer_config().identity_input_index, Some(3)); + + // Banked-identity with NO declaration — what the macro emits for a state + // carrying a fixed `[int]` array, whose identity slot depends on that + // array's runtime length. Slot 0 is an int scalar there, so the config + // must carry no slot at all and let `VirtualizableTracker` decline. + let mut undeclared = VirtualizableInfo::without_vable_token(); + undeclared.identity_ref_bank_index = Some(1); + assert_eq!(undeclared.identity_live_index, None); + assert_eq!( + undeclared.to_optimizer_config().identity_input_index, + None, + "an undeclared banked identity must not fall back to slot 0", + ); + } + #[test] fn test_load_list_of_boxes_reads_from_object() { // RPython parity: vinfo.load_list_of_boxes() reads from actual object. @@ -2883,19 +2948,21 @@ pub(crate) unsafe fn bhimpl_arraylen_vable(vable_ptr: *const u8, array: &VableAr } } -/// Read a value from a virtualizable array item. -/// blackhole.py:1374-1387 bhimpl_getarrayitem_vable_* parity. -pub(crate) unsafe fn vable_read_array_item( +/// Address of item 0 of a virtualizable array field. +/// +/// The three storage kinds reach the items through different indirections, and +/// both the item read and the item write below resolved that separately. They +/// now share this, so an item access and a whole-array access can never +/// disagree about where the items start. +/// +/// Null when the owning pointer is null; callers must keep treating that as +/// "no items", not as address zero. +pub(crate) unsafe fn bhimpl_arraybase_vable( vable_ptr: *const u8, array: &VableArrayInfo, - index: usize, -) -> i64 { +) -> *const u8 { unsafe { - // Stride from the field's array descriptor: a pointer array is - // `size_of::()` (4 bytes on wasm32) while an `i64` payload - // array is a fixed 8, regardless of word width. - let item_size = array.item_size; - let data_ptr = match array.storage { + match array.storage { VableArrayStorage::EmbeddedArray { ptr_offset } => { let container = *(vable_ptr.add(array.field_offset) as *const *const u8); *(container.add(ptr_offset) as *const *const u8) @@ -2907,7 +2974,23 @@ pub(crate) unsafe fn vable_read_array_item( VableArrayStorage::RustVec { data_ptr_fn, .. } => { data_ptr_fn(vable_ptr as *mut u8) as *const u8 } - }; + } + } +} + +/// Read a value from a virtualizable array item. +/// blackhole.py:1374-1387 bhimpl_getarrayitem_vable_* parity. +pub(crate) unsafe fn vable_read_array_item( + vable_ptr: *const u8, + array: &VableArrayInfo, + index: usize, +) -> i64 { + unsafe { + // Stride from the field's array descriptor: a pointer array is + // `size_of::()` (4 bytes on wasm32) while an `i64` payload + // array is a fixed 8, regardless of word width. + let item_size = array.item_size; + let data_ptr = bhimpl_arraybase_vable(vable_ptr, array); if data_ptr.is_null() { 0 } else { @@ -2934,22 +3017,17 @@ pub(crate) unsafe fn vable_write_array_item( // `size_of::()` (4 bytes on wasm32) while an `i64` payload // array is a fixed 8, regardless of word width. let item_size = array.item_size; + let data_ptr = bhimpl_arraybase_vable(vable_ptr, array) as *mut u8; // `owner_ptr` is the block base the GC would know, i.e. before the // items offset — the barrier argument. `data_ptr` is items-adjusted - // and is not a valid object address. - let (data_ptr, owner_ptr) = match array.storage { - VableArrayStorage::EmbeddedArray { ptr_offset } => { - let container = *(vable_ptr.add(array.field_offset) as *const *mut u8); - let data = *(container.add(ptr_offset) as *const *mut u8); - (data, data) - } + // and is not a valid object address, so the two only coincide where + // there is no items offset to undo. + let owner_ptr = match array.storage { + VableArrayStorage::EmbeddedArray { .. } => data_ptr, VableArrayStorage::DirectPointer => { - let arr_ptr = *(vable_ptr.add(array.field_offset) as *const *mut u8); - (arr_ptr.add(array.items_offset), arr_ptr) - } - VableArrayStorage::RustVec { data_ptr_fn, .. } => { - (data_ptr_fn(vable_ptr) as *mut u8, std::ptr::null_mut()) + *(vable_ptr.add(array.field_offset) as *const *mut u8) } + VableArrayStorage::RustVec { .. } => std::ptr::null_mut(), }; if !data_ptr.is_null() { let dest = data_ptr.add(index * item_size); diff --git a/majit/majit-metainterp/src/warmstate.rs b/majit/majit-metainterp/src/warmstate.rs index ed50f899dc8..4098a2a6406 100644 --- a/majit/majit-metainterp/src/warmstate.rs +++ b/majit/majit-metainterp/src/warmstate.rs @@ -10,7 +10,7 @@ use std::sync::atomic::{AtomicU64, Ordering}; use std::time::Duration; use majit_backend::JitCellToken; -use majit_ir::{GreenKey, Type}; +use majit_ir::{GreenKey, RetainedGreens, Type}; use std::sync::Arc; use crate::counter::{DEFAULT_SIZE, JitCounter}; @@ -124,6 +124,22 @@ pub struct BaseJitCell { /// Migration of all callers from hash-only to typed keys is /// unfinished. pub comparekey: Option, + /// Owns the `Ref`-typed referents named by `comparekey`, for exactly this + /// cell's lifetime — the `setattr` half of `JitCell.__init__` + /// (warmstate.py:568-573), which pyre stored as a bare `i64` and so never + /// owned. Without it a referent can be freed and its address reused by a + /// different object, whose green key is then byte-identical to the dead + /// one's; `comparekey_matches` returns `true` and the new object silently + /// inherits this cell and its compiled loop token. + /// + /// Scope: this closes the hazard on the **typed** path only. The legacy + /// hash-only flow leaves `comparekey` `None`, so it stores no key, owns + /// nothing, and has **no comparator at all** — a collision there is + /// unresolvable by any mechanism rather than resolvable-but-unsound. That + /// is a different and worse hazard, not a smaller one. + /// + /// Empty unless a frontend registered `majit_ir::set_ref_resolver`. + pub retained_greens: RetainedGreens, } impl BaseJitCell { @@ -138,9 +154,24 @@ impl BaseJitCell { abort_count: 0, next: None, comparekey: None, + retained_greens: RetainedGreens::default(), } } + /// Store `key` as this cell's `comparekey` **and** take ownership of the + /// `Ref` referents it names, in one statement. + /// + /// The two must not be set separately: a `comparekey` without its + /// `retained_greens` is exactly the defect this pairing exists to close — + /// a stored address the cell does not own. `comparekey` stays `pub` for + /// the fixtures that construct chains directly, so this is the invariant + /// by convention rather than by type; every production writer goes + /// through here. + pub fn set_comparekey(&mut self, key: &GreenKey) { + self.retained_greens = RetainedGreens::retain(key); + self.comparekey = Some(key.clone()); + } + /// warmstate.py:575-582 `JitCell.comparekey(*greenargs2)`. /// /// Returns `true` iff this cell's stored typed greens match @@ -311,6 +342,32 @@ pub struct JitStats { pub num_disable_noninlinable_function: usize, /// Total number of BaseJitCells. pub num_cells: usize, + /// Referents pinned alive by stored typed green keys, summed over every + /// cell in every chain. + /// + /// This is the resource cost of the ownership invariant (`RetainedGreens`) + /// against a `cells` map with no size bound: nothing caps how many cells + /// accumulate, so the pinned set grows with them. Reported so the growth + /// is a number somebody can read rather than latent RSS — the condition + /// under which pinning is allowed to ship ahead of a bound. + /// + /// WHICH WINDOW THIS IS TAKEN IN. The number is a **running total at + /// the moment of the call**, not a settled figure, and it is not + /// monotonic. Cells *are* dropped in production — `install_new_cell` + /// unlinks every chained cell whose `should_remove_jitcell()` holds + /// (which is the default for a cold, tokenless cell), and `cleanup_chain` + /// drops the whole chain — and each drop releases that cell's retains. + /// So a cold tree, a tree mid-warmup and a settled tree give three + /// different answers for the same program, and a reading is only + /// comparable against another taken at the same point. + /// + /// Do not quote it as "how many cells pin a referent" without saying + /// after how many portal entries it was read. `gc_cells` has no + /// production caller, so the one eviction path that would make this fall + /// sharply is currently unreachable. + /// + /// Zero unless a frontend registered `set_ref_resolver`. + pub num_pinned_refs: usize, } pub struct WarmEnterState { @@ -493,9 +550,21 @@ impl WarmEnterState { } } + /// `cells.values()` yields chain heads. Every sweep over the whole table + /// must walk `next` as well, or it silently skips chained cells. A chain + /// needs no hash collision: one green key reached + /// through a hash-only writer and then a typed one produces two cells in + /// one bucket, because a `comparekey: None` cell can never match a typed + /// lookup, so `ensure_cell_for_key` misses and chains past it. Proven by + /// `one_key_through_a_hash_and_a_typed_entry_point_builds_a_chain`, + /// which uses only public entry points on a single key. pub fn clear_all_loop_tokens(&mut self) { - for cell in self.cells.values_mut() { - cell.loop_token = None; + for head in self.cells.values_mut() { + let mut cur = Some(head); + while let Some(cell) = cur { + cell.loop_token = None; + cur = cell.next.as_deref_mut(); + } } } @@ -813,7 +882,7 @@ impl WarmEnterState { /// Typed-key variant of [`Self::force_start_tracing`]: reads the /// matching cell by comparekey and force-starts tracing on it via - /// [`Self::start_tracing_cell_for_key`], so a force-started cell + /// `Self::start_tracing_cell_for_key`, so a force-started cell /// (function-entry / can_enter_jit) carries a `comparekey` like the /// [`Self::maybe_compile_with_key`] path. pub fn force_start_tracing_for_key(&mut self, key: &GreenKey) -> HotResult { @@ -895,6 +964,11 @@ impl WarmEnterState { /// and the outer (starting) cell's TRACING is cleared separately /// by the `clear_tracing_flag` call in the tracing entry point's /// finally block (warmstate.py:444 parity). + /// + /// Installs the cell under the bare hash, so it cannot set `comparekey` + /// and the cell it creates is one no chain walk will ever match. Prefer + /// [`Self::attach_procedure_to_interp_for_key`] wherever the green key + /// itself is in scope. pub fn attach_procedure_to_interp( &mut self, green_key_hash: u64, @@ -909,10 +983,39 @@ impl WarmEnterState { cell.set_procedure_token(token, false) } + /// Typed form of [`Self::attach_procedure_to_interp`]. + /// + /// `JitCell.__init__` (warmstate.py:610-616) always stores the green args + /// on the cell, so upstream has no cell that a chain walk can fail to + /// match. The hash form creates one: `cells.entry(hash)` leaves + /// `comparekey` unset, `comparekey_matches` refuses it unconditionally, + /// and `install_new_cell` links later survivors ahead of it — so the same + /// green key ends up with a hash-installed cell at the bucket head and a + /// typed cell behind it, holding different token and flag state. + pub fn attach_procedure_to_interp_for_key( + &mut self, + key: &GreenKey, + token: impl Into>, + ) -> Option> { + let token = token.into(); + self.ensure_cell_for_key(key); + let cell = self + .lookup_chain_with_key_mut(key) + .expect("ensure_cell_for_key just installed a cell matching this key"); + cell.flags &= !jc_flags::TRACING; + cell.set_procedure_token(token, false) + } + /// warmstate.py:716-723 `cell.set_procedure_token(procedure_token, tmp=True)`. /// /// Installs a temporary CALL_ASSEMBLER fallback token without /// changing the tracing flags or compiled state. + /// + /// No typed twin: this has no callers anywhere in the workspace, so it + /// creates no cells and contributes nothing to the head/tail split that + /// [`Self::attach_procedure_to_interp_for_key`] exists to close. Adding + /// one would be a second uncalled entry point. Give it a typed form at + /// the point a caller appears, not before. pub fn attach_tmp_callback_to_interp( &mut self, green_key_hash: u64, @@ -939,6 +1042,44 @@ impl WarmEnterState { } } + /// Typed-key variant of [`Self::clear_tracing_flag`], and the missing half + /// of a live pair. + /// + /// `JC_TRACING` is *set* through [`Self::mark_as_being_traced_for_key`], + /// which installs through [`Self::ensure_cell_for_key`] and therefore + /// writes the cell that matches the key. The hash form above clears + /// whichever cell happens to *head* the bucket. Once both spellings have + /// written one key those are two different cells + /// (`one_key_through_a_hash_and_a_typed_entry_point_builds_a_chain`), and + /// `install_new_cell` prepends survivors so the typed cell is the one that + /// is *not* the head. The set then lands on the tail and the clear on the + /// head, the flag is never cleared, and every gate that reads the head — + /// `counter_would_fire`, `counter_tick_checked`, `maybe_compile`, + /// `force_start_tracing`, `should_trace_function_entry` — refuses that key + /// from then on. + /// + /// Deliberately does NOT route through `ensure_cell_for_key` the way the + /// `_for_key` writers do: clearing a flag must not install a cell. A key + /// with no cell has no flag to clear, which is the hash form's behaviour + /// too. + /// + /// No caller yet. Unlike [`Self::attach_tmp_callback_to_interp`], which + /// got a stated reason instead of a twin because nothing calls it + /// anywhere, the hash form here has a live production caller in + /// `pyre-jit` (the `finally` clear after tracing ends) that still passes a + /// hash. Converting it is caller-side work in another crate and lands + /// separately. + pub fn clear_tracing_flag_for_key(&mut self, key: &GreenKey) { + if let Some(cell) = self.lookup_chain_with_key_mut(key) { + cell.flags &= !jc_flags::TRACING; + } + } + + /// No typed twin: this has no callers anywhere in the workspace — the only + /// occurrence in the tree is this definition. A twin here would be a + /// second uncalled entry point, so it gets a reason instead, the same call + /// [`Self::attach_tmp_callback_to_interp`] got. Give it a typed form at + /// the point a caller appears, not before. pub fn take_procedure_token(&mut self, green_key_hash: u64) -> Option> { self.cells .get_mut(&green_key_hash) @@ -1015,24 +1156,70 @@ impl WarmEnterState { self.cells.get(&green_key_hash) } + /// Typed-key variant of [`Self::get_cell`]: + /// `warmstate.py:596-604 JitCell.get_jitcell(*greenargs)`. + /// + /// Walks the chain by `comparekey`, so it selects the cell belonging to + /// this key rather than whichever cell happens to head the bucket. That + /// distinction is load-bearing today, not once the table is bucketed: a + /// hash-only writer installs a cell with `comparekey: None`, which + /// `comparekey_matches` refuses unconditionally, so one key can already + /// own two cells in one bucket with no hash collision at all + /// (`one_key_through_a_hash_and_a_typed_entry_point_builds_a_chain`). + /// `install_new_cell` links survivors ahead of the new cell, so the + /// hash-installed one is the head and the typed one is *not* — reading + /// the head here returned the wrong cell. + /// + /// The reason to route through the key rather than the hash at all: + /// every upstream cell lookup takes greenargs and compares them + /// (`get_jitcell` :596, `_ensure_jit_cell_at_key` :631, + /// `dont_trace_here` :644, `mark_as_being_traced` :649 — warmstate.py). + /// Upstream has exactly one bare-hash entry point, + /// `trace_next_iteration_hash` (warmstate.py:622-623), and it touches + /// **the counter, not the cell**. + /// That is the line: a hash is enough to find a bucket, never enough to + /// pick a cell out of one. + pub fn get_cell_for_key(&self, key: &GreenKey) -> Option<&BaseJitCell> { + self.lookup_chain_with_key(key) + } + /// TODO: walk the warmstate cells to find a /// `JitCellToken` by number. Used by `MetaInterp::record_loop_or_bridge` /// to widen the CALL_ASSEMBLER keepalive search to cover targets - /// that live only on a `BaseJitCell.loop_token` (most importantly - /// tmp-callback installs at `attach_tmp_callback_to_interp`) and - /// are not yet — or never — registered in `MetaInterp::compiled_loops`. + /// that live only on a `BaseJitCell.loop_token` and are not yet — or + /// never — registered in `MetaInterp::compiled_loops`. + /// + /// This doc previously named tmp-callback installs at + /// `attach_tmp_callback_to_interp` as the most important such target. + /// That function has no callers anywhere in the workspace, so it + /// installs nothing and cannot be what this helper covers. The + /// surviving population is whatever `attach_procedure_to_interp` wrote + /// but `record_loop_or_bridge` has not registered; nobody has measured + /// it, so treat the need for this fallback as unquantified rather than + /// established. /// /// RPython equivalent does not exist because upstream descrs hold /// the `JitCellToken` object directly (`compile.py:187 isinstance(descr, /// JitCellToken)`) — no number→token resolution is needed. This /// helper is removed by Slice X-D once `CallAssemblerDescr` / /// `LoopTargetDescr` carry the owning `Arc`. + /// Walks each chain, not just its head — see `clear_all_loop_tokens`. + /// A token living on a chained cell was previously unfindable here, and + /// this is the fallback `with_trace_ctx_and_token_resolver` reaches when + /// no `compiled_loops` entry matches (`pyjitpl.rs:4663`). pub fn find_token_by_number(&self, token_number: u64) -> Option<&Arc> { - self.cells.values().find_map(|cell| { - cell.loop_token - .as_ref() - .filter(|tok| tok.number == token_number) - }) + for head in self.cells.values() { + let mut cur = Some(head); + while let Some(cell) = cur { + if let Some(tok) = cell.loop_token.as_ref() { + if tok.number == token_number { + return Some(tok); + } + } + cur = cell.next.as_deref(); + } + } + None } /// `rpython/jit/metainterp/warmstate.py:714-723` `get_assembler_token`. @@ -1347,6 +1534,32 @@ impl WarmEnterState { } } + /// Typed-key variant of [`Self::mark_as_being_traced`]: + /// `warmstate.py:649-651 mark_as_being_traced(*greenargs)`, which reaches + /// its cell through `_ensure_jit_cell_at_key(*greenargs)` — i.e. by + /// comparekey, never by hash alone. + /// + /// Installs through [`Self::ensure_cell_for_key`] rather than + /// `cells.entry(hash)`, so the cell it marks carries a `comparekey` (and + /// the `RetainedGreens` that come with it) instead of being a + /// comparator-less cell that no later typed lookup can ever match — the + /// same shape [`Self::disable_noninlinable_function_for_key`] uses. + /// + /// `tracing_generation` is read before the borrow because the cell + /// borrows `self.cells` mutably. + pub fn mark_as_being_traced_for_key(&mut self, key: &GreenKey) { + self.ensure_cell_for_key(key); + let tracing_generation = self.tracing_generation; + let cell = self + .lookup_chain_with_key_mut(key) + .expect("ensure_cell_for_key just installed a cell matching this key"); + cell.flags |= jc_flags::TRACING; + if cell.flags & jc_flags::TRACING_OCCURRED == 0 { + cell.state = BaseJitCellState::Tracing; + cell.tracing_generation = tracing_generation; + } + } + /// Restore warm-state parameters to rlib/jit.py:588-605 PARAMETERS defaults. pub fn set_default_params(&mut self) { self.set_threshold(DEFAULT_THRESHOLD); // 1039 @@ -1372,6 +1585,10 @@ impl WarmEnterState { /// /// The next tracing run for this green key should segment instead of /// repeatedly aborting once it approaches the trace limit. + /// + /// Installs under the bare hash, so the cell it creates carries no + /// `comparekey`. Prefer [`Self::mark_force_finish_tracing_for_key`] + /// wherever the green key itself is in scope. pub fn mark_force_finish_tracing(&mut self, green_key_hash: u64) { let cell = self .cells @@ -1380,6 +1597,19 @@ impl WarmEnterState { cell.flags |= jc_flags::FORCE_FINISH; } + /// Typed form of [`Self::mark_force_finish_tracing`]. + /// + /// `FORCE_FINISH` is sticky and never cleared explicitly, so setting it + /// on the wrong cell of a bucket is permanent: the key that needs + /// segmenting keeps aborting while an unrelated key segments forever. + pub fn mark_force_finish_tracing_for_key(&mut self, key: &GreenKey) { + self.ensure_cell_for_key(key); + let cell = self + .lookup_chain_with_key_mut(key) + .expect("ensure_cell_for_key just installed a cell matching this key"); + cell.flags |= jc_flags::FORCE_FINISH; + } + /// warmstate.py:439 `bool(cell.flags & JC_FORCE_FINISH)` — read the sticky /// segmenting flag at loop entry. RPython never clears this flag /// explicitly: `should_remove_jitcell` (warmstate.py:222) keeps the cell @@ -1397,6 +1627,14 @@ impl WarmEnterState { /// Mirrors PyPy's `JitCell.trace_next_iteration()` in warmstate.py: /// it does not force tracing right now, it only raises the hot counter /// to ~threshold so the next hit converges quickly. + /// + /// Takes the bare hash on purpose, and it is the one shape allowed to: + /// `_trace_next_iteration` (warmstate.py:617-619) hashes the greenargs and + /// calls `jitcounter.change_current_fraction` — no cell is looked up, so + /// the hash is the whole identity the operation needs. Upstream exposes + /// exactly this as `trace_next_iteration_hash` (warmstate.py:622-623). + /// The moment a cell read is added here, this needs a `&GreenKey` like + /// [`Self::get_cell_for_key`]'s cohort. pub fn trace_next_iteration(&mut self, green_key_hash: u64) { self.counter.change_current_fraction(green_key_hash, 0.98); } @@ -1407,8 +1645,56 @@ impl WarmEnterState { pub fn should_trace_function_entry(&mut self, green_key_hash: u64) -> bool { let mut cleanup_dead_token_cell = false; if let Some(cell) = self.cells.get(&green_key_hash) { - if cell.is_compiled() || cell.is_tracing() { + // Slot 23 is the total; 64/65 are its two terms, evaluated + // independently rather than short-circuited so a cell that is both + // reaches both tallies. `is_compiled()` fires on every probe of + // every compiled key, so 23 alone cannot attribute a decline. + let compiled = cell.is_compiled(); + let tracing = cell.is_tracing(); + if compiled || tracing { crate::mc_diag_bump(23); + if compiled { + crate::mc_diag_bump(64); + } + if tracing { + crate::mc_diag_bump(65); + // 65 is NOT a "healthy while a trace runs" reading in + // production, and an earlier version of this comment said + // it was. The only production caller + // (`pyre-jit`'s try_function_entry_jit) guards on + // `!driver.is_tracing()`, which is + // `MetaInterp::tracing.is_some()` — one global Option, not + // a per-cell flag. So while the engine traces, the caller + // returns a frame earlier and this gate is never reached. + // Every production bump of 65 is therefore a cell holding + // JC_TRACING while no trace is running, i.e. a leak on its + // own. The function itself can still be called mid-trace + // directly, and the unit tests below do exactly that. + // + // 66 splits those leaks by AGE, not into leak vs healthy: + // a generation older than the warm state's means the + // session that set the flag was superseded by a later + // trace start. 65 > 0 with 66 == 0 is the flag leaking + // from the most recent session, which is if anything the + // more direct miss. + // + // A ZERO HERE NEEDS TWO WITNESSES, NOT ONE. That the + // door ran (23 + 24 + 25 > 0) does not mean any probed + // cell could ever have held the flag: a stale JC_TRACING + // only sits on a cell that once started tracing, and a + // workload that never arms function-entry tracing gives + // 65 == 0 by construction. The arming witness is + // `caro_funcentry` (slot 19), bumped at the top of + // pyre-jit's `compile_and_run_once` above every early + // return — but on the `FunctionEntry` arm ONLY, since the + // slot is selected by `start` (`BackEdge` bumps 18). So a + // back-edge-only workload leaves 19 at 0 while 18 climbs, + // and 18 is NOT a substitute. Without 19 > 0 a 0 here is + // NOT EXERCISED, not clean. See MC_DIAG's legend. + if cell.tracing_generation < self.tracing_generation { + crate::mc_diag_bump(66); + } + } return false; } if cell.flags & jc_flags::DONT_TRACE_HERE != 0 { @@ -1477,6 +1763,20 @@ impl WarmEnterState { /// GUARD_NOT_INVALIDATED to fail on the next execution. /// /// Returns the number of loops invalidated. + /// + /// No typed twin, and unlike the other two abstainers this one is not a + /// choice. Its green keys arrive as the *values* of `quasiimmut_deps`, + /// which stores them as `u64` hashes, so there is no caller that holds a + /// `GreenKey` to hand one down: a twin cannot be given a key to take until + /// that table is widened. Dead in production today — its only registrar, + /// `register_quasiimmut_dependency`, has no production caller either, so + /// `quasiimmut_deps` is empty and this returns 0 at the first line. + /// + /// That is what makes it a trap rather than a bug: [`Self::invalidate_all`] + /// below was fixed to walk chains because *"skipping a cell is a WRONG + /// ANSWER rather than a leak"*, and this targeted sibling still reads the + /// bucket head. Whoever wires the registrar inherits that, and a sweep + /// selecting whole-map `.values()` walks will not see it. pub fn invalidate_quasiimmut(&mut self, qmut_key: u64) -> usize { let deps = match self.quasiimmut_deps.swap_remove(&qmut_key) { Some(deps) => deps, @@ -1500,11 +1800,19 @@ impl WarmEnterState { /// /// This is a brute-force invalidation used when the specific qmut_key /// is not known (e.g., bulk invalidation after a class hierarchy change). + /// Walks each chain, not just its head — see `clear_all_loop_tokens`. + /// This is the sweep where skipping a cell is a WRONG ANSWER rather than a + /// leak: a cell whose token is not invalidated keeps running compiled code + /// built under an assumption that has just been retracted. pub fn invalidate_all(&mut self) { - for cell in self.cells.values_mut() { - if let Some(token) = &cell.loop_token { - token.invalidate(); - cell.state = BaseJitCellState::Invalidated; + for head in self.cells.values_mut() { + let mut cur = Some(head); + while let Some(cell) = cur { + if let Some(token) = &cell.loop_token { + token.invalidate(); + cell.state = BaseJitCellState::Invalidated; + } + cur = cell.next.as_deref_mut(); } } self.quasiimmut_deps.clear(); @@ -1527,6 +1835,11 @@ impl WarmEnterState { /// This is the low-level state-machine driver. Most callers should use /// the higher-level methods (`maybe_compile`, `finish_tracing`, /// `attach_procedure_to_interp`, `abort_tracing`) which call this internally. + /// + /// No typed twin: every caller is in this file's own test module, so the + /// comparator-less cells it installs exist only in fixtures. That also + /// makes it a way for a test to build the split-cell state deliberately. + /// A twin becomes necessary if production ever calls this. pub fn transition_cell(&mut self, green_key_hash: u64, new_state: BaseJitCellState) { let cell = self .cells @@ -1755,18 +2068,39 @@ impl WarmEnterState { } /// Get a snapshot of current JIT statistics. + /// + /// Counts every cell in every chain, not just the bucket heads. + /// + /// A chain needs no hash collision: a `comparekey: None` cell — what every + /// bare-hash writer installs — can never match a typed lookup, so + /// `ensure_cell_for_key` misses on a key that already has a cell and + /// chains past it. See + /// `one_key_through_a_hash_and_a_typed_entry_point_builds_a_chain`. + /// + /// So `num_cells` and `cells.len()` are **already** capable of + /// disagreeing, and the difference is the count of keys that reached + /// both an untyped and a typed writer. Counting the cells is the + /// definition the field documents ("Total number of BaseJitCells"); + /// counting the map entries counts buckets, which is a different number. + /// + /// `num_pinned_refs` sums `retained_greens` per cell, and a + /// chained cell pins referents just as a head does. So the "adds zero + /// today" note above applies to the state counters only. pub fn get_stats(&self) -> JitStats { - let mut stats = JitStats { - num_cells: self.cells.len(), - ..Default::default() - }; - for cell in self.cells.values() { - match cell.state { - BaseJitCellState::Compiled => stats.num_compiled += 1, - BaseJitCellState::Tracing => stats.num_tracing += 1, - BaseJitCellState::Invalidated => stats.num_invalidated += 1, - BaseJitCellState::DontTraceHere => stats.num_disable_noninlinable_function += 1, - BaseJitCellState::NotHot => {} + let mut stats = JitStats::default(); + for head in self.cells.values() { + let mut cur = Some(head); + while let Some(cell) = cur { + stats.num_cells += 1; + stats.num_pinned_refs += cell.retained_greens.len(); + match cell.state { + BaseJitCellState::Compiled => stats.num_compiled += 1, + BaseJitCellState::Tracing => stats.num_tracing += 1, + BaseJitCellState::Invalidated => stats.num_invalidated += 1, + BaseJitCellState::DontTraceHere => stats.num_disable_noninlinable_function += 1, + BaseJitCellState::NotHot => {} + } + cur = cell.next.as_deref(); } } stats @@ -1899,10 +2233,30 @@ impl WarmEnterState { /// `(code_a, pc_a)` and `(code_b, pc_b)` with the same `get_uhash` /// no longer alias to the same cell. /// - /// Currently a `&self` view: `WarmEnterState`'s mutable lookup - /// helpers (`maybe_compile`, `should_start_dont_trace_here_trace`) - /// keep using the legacy hash-only path until a follow-up - /// migrates them. + /// Collision resolution is not why this walk earns its keep today. + /// The common case is a chain built from ONE key: a hash-only creator + /// installs a cell that can carry no `comparekey`, the typed path then + /// installs its own, and `install_new_cell` links the survivor ahead — + /// so the bucket holds two cells for one key with no collision anywhere. + /// Reading `cells.get(&hash)` gets whichever happens to head the bucket. + /// A full-width hash makes collisions impractical but says nothing about + /// multiple cells linked under one hash. See + /// `one_key_through_a_hash_and_a_typed_entry_point_builds_a_chain`. + /// + /// The tracing lifecycle is migrated — `start_tracing_cell_for_key`, + /// `finish_tracing_for_key`, `abort_tracing_for_key` and their siblings + /// walk the chain through `lookup_chain_with_key_mut`. What still reads + /// the bucket HEAD is the `u64` API surface kept for callers that hold + /// only a hash: `clear_loop_token`, `counter_would_fire`, `counter_tick`, + /// `counter_tick_checked`, `maybe_compile`, `force_start_tracing`, + /// `finish_tracing`, `abort_tracing`, `clear_tracing_flag`, `get_cell`, + /// `should_trace_function_entry`, `invalidate_quasiimmut`. Those cannot + /// move until their callers carry a `GreenKey`, which is the same reason + /// `MetaInterp`'s `compiled_loops` / `cut_compiled_keys` are `u64`-keyed: + /// the hash, not the green tuple, is the identity currency across the + /// metainterp. (`loop_header_pcs` was a third such table; it now lives on + /// `CompiledEntry::loop_header_pc`, so it is reached through + /// `compiled_loops`' `u64` key rather than carrying one of its own.) pub fn lookup_chain_with_key(&self, key: &GreenKey) -> Option<&BaseJitCell> { let hash = key.get_uhash(); let mut cell = self.cells.get(&hash); @@ -1940,15 +2294,17 @@ impl WarmEnterState { /// `HashMap` to a chain-aware container. /// /// On a miss (no chained cell matches the typed key) the helper - /// allocates a new cell with `comparekey = Some(key.clone())` and - /// installs it at the head of the chain via `install_new_cell`, - /// matching upstream's `jitcounter.install_new_cell` semantics. + /// allocates a new cell, stores the key through `set_comparekey` — + /// which also takes ownership of the key's `Ref` referents, the + /// `setattr` half of `JitCell.__init__` — and installs it at the head + /// of the chain via `install_new_cell`, matching upstream's + /// `jitcounter.install_new_cell` semantics. pub fn ensure_cell_for_key(&mut self, key: &GreenKey) { if self.lookup_chain_with_key(key).is_some() { return; } let mut newcell = BaseJitCell::new(); - newcell.comparekey = Some(key.clone()); + newcell.set_comparekey(key); self.install_new_cell(key.get_uhash(), Some(newcell)); } } @@ -2480,6 +2836,56 @@ mod tests { assert_eq!(mgr.alive_count(), 2); } + /// Pins the PRODUCTION default, which is upstream's **test** default. + /// + /// The tests above prove the memmgr mechanism is a faithful port. What + /// nothing watched is which mode a real `WarmEnterState` ships in. + /// `WarmEnterState::new` (the only production constructor — `pyjitpl.rs` + /// `warm_state: WarmEnterState::new(threshold)`) builds + /// `MemoryManager::new(0)`, so `next_check == -1` and + /// `_kill_old_loops_now` is unreachable: `alive_loops` never prunes. + /// + /// Upstream splits these two defaults and pyre collapsed them: + /// + /// | | `loop_longevity` | + /// |---|---| + /// | `rlib/jit.py:594` PARAMETERS — **production** | **1000** | + /// | `warmspot.py:9` `jittify_and_run` — **test harness** | **0** | + /// | pyre, every path unless `PYRE_JIT` overrides | **0** | + /// + /// So this asserts a **divergence**, not a desired value. It is here so + /// the literal is watched: changing `MemoryManager::new(0)` at the + /// construction site turns eviction on across the JIT, and that is a + /// behavioural change (it starts feeding `try_to_free_some_loops`) which + /// must be measured, not slipped in. If you are here because this test + /// failed, that is the intended alarm — see default-retirement. + /// + /// This governs `alive_loops` (loop TOKENS) only. The `cells` map is a + /// different population and is NOT bounded by `max_age`; it sheds + /// entries only through `install_new_cell`'s `should_remove_jitcell` + /// gate (typed-key eviction). Turning `max_age` on does not bound the cell table. + /// + /// `install_new_cell` runs the gate over the whole existing chain at the + /// bucket on *every* typed install, collision or not — so the gate is + /// reached routinely, it simply keeps most cells (a token, `TRACING`, + /// `DONT_TRACE_HERE` without a dead token, or `FORCE_FINISH` all veto + /// removal). The unbounded-growth conclusion survives; the stated reason + /// for it does not. + #[test] + fn production_warmstate_ships_loop_eviction_disabled() { + let ws = WarmEnterState::new(3); + assert_eq!( + ws.memory_manager.next_check, -1, + "next_check must be -1 (eviction disabled) — memmgr.py:43-44", + ); + assert_eq!( + ws.memory_manager.loop_longevity_param(), + 0, + "production ships loop_longevity=0, upstream's TEST default; \ + rlib/jit.py:594 gives production 1000", + ); + } + #[test] fn test_loop_aging_refresh() { // keep_loop_alive resets `looptoken.generation` to @@ -2961,6 +3367,130 @@ mod tests { assert_eq!(cell.tracing_generation, 2); } + /// Slot 66 fires only for a cell whose `JC_TRACING` outlived the session + /// that set it; slot 65 also counts a decline taken while a trace is + /// running. + /// + /// This test calls `should_trace_function_entry` DIRECTLY, so it can + /// reach the gate mid-trace. Production cannot: the one production caller + /// guards on `!driver.is_tracing()`, so there every 65 is already a leak + /// and 66 only says how old. Do not read this test as evidence that a + /// production 65 is healthy — see the slot legend in `lib.rs`. + /// + /// Deltas are asserted as lower bounds because `MC_DIAG` is a process-wide + /// static — a concurrent test bumping the same slot can only inflate them. + #[test] + fn stale_tracing_generation_is_counted_apart_from_a_live_trace() { + let mut ws = WarmEnterState::new(2); + + // Cell A is marked as being traced under the current generation. + ws.mark_as_being_traced(0xA); + let cell = ws.get_cell(0xA).expect("mark_as_being_traced installs it"); + assert!(cell.is_tracing(), "fixture: A carries JC_TRACING"); + assert_eq!( + cell.tracing_generation, + ws.tracing_generation(), + "fixture: A's generation is the live one, so it is NOT stale yet" + ); + + // While A's trace is still the live one, the decline is the healthy + // case: 65 moves, 66 must not. + let live_65 = crate::mc_diag(65); + let live_66 = crate::mc_diag(66); + assert!(!ws.should_trace_function_entry(0xA)); + assert!( + crate::mc_diag(65) >= live_65 + 1, + "a decline on the tracing term bumps 65" + ); + assert_eq!( + crate::mc_diag(66), + live_66, + "a LIVE trace must not read as stale — this is what 65 alone cannot say" + ); + + // Starting a trace on another key supersedes A's session without + // clearing A's flag: only start_tracing_cell increments the generation. + assert!(matches!(ws.maybe_compile(0xB), HotResult::NotHot)); + assert!(matches!(ws.maybe_compile(0xB), HotResult::StartTracing)); + let cell = ws.get_cell(0xA).expect("A is still installed"); + assert!(cell.is_tracing(), "fixture: A's flag was never cleared"); + assert!( + cell.tracing_generation < ws.tracing_generation(), + "fixture: A's session has been superseded" + ); + + let stale_66 = crate::mc_diag(66); + assert!(!ws.should_trace_function_entry(0xA)); + assert!( + crate::mc_diag(66) >= stale_66 + 1, + "a stale JC_TRACING at this gate is what slot 66 reports" + ); + } + + /// The set/clear pair for `JC_TRACING` has to agree on which cell it is + /// talking about. `mark_as_being_traced_for_key` writes the cell matching + /// the key; `clear_tracing_flag` clears the bucket head. When a hash-only + /// writer got there first those are different cells, and the clear misses. + /// + /// This demonstrates the mechanism on a constructed fixture. It does NOT + /// establish that production reaches this configuration — that is what the + /// `stfe_declined_tracing_stale` counter is for. + #[test] + fn a_typed_clear_reaches_the_cell_a_typed_mark_wrote() { + let mut ws = WarmEnterState::new(100); + let key = GreenKey::new(vec![100, 200]); + let hash = key.get_uhash(); + + // Fixture: a hash-only writer heads the bucket, so a later typed + // install chains behind it rather than finding it. + ws.disable_noninlinable_function(hash); + assert!( + ws.lookup_chain_with_key(&key).is_none(), + "fixture: the hash-only cell carries no comparekey, so a typed \ + probe cannot see it — this is what makes the chain", + ); + + ws.mark_as_being_traced_for_key(&key); + assert_eq!( + ws.get_stats().num_cells, + 2, + "fixture: one green key, two cells", + ); + assert!( + ws.lookup_chain_with_key(&key) + .expect("the typed cell exists") + .is_tracing(), + "fixture: the typed cell is the one carrying JC_TRACING", + ); + assert!( + !ws.get_cell(hash) + .expect("the bucket head exists") + .is_tracing(), + "fixture: the bucket HEAD never got the flag — so a head-only \ + clear has nothing to do and cannot fix the tail", + ); + + // The hash form clears the head, which never had the flag. + ws.clear_tracing_flag(hash); + assert!( + ws.lookup_chain_with_key(&key) + .expect("the typed cell exists") + .is_tracing(), + "the hash clear missed the cell the mark wrote: this is the stuck \ + flag that makes every bare-head gate refuse the key", + ); + + // The typed form reaches it. + ws.clear_tracing_flag_for_key(&key); + assert!( + !ws.lookup_chain_with_key(&key) + .expect("the typed cell exists") + .is_tracing(), + "the typed clear selects by comparekey, so it reaches the cell the \ + typed mark wrote", + ); + } + #[test] fn test_jitcell_should_remove() { // A freshly created cell with no token and no flags should be removable @@ -3208,6 +3738,93 @@ mod tests { ); } + // Sentinel-tagged retain/release log for the ownership test below. + // + // `set_ref_resolver` is a process-global `OnceLock`, so exactly one test in + // this binary may register and it cannot be scoped to a single case. + // Everything is therefore asserted by SENTINEL VALUE rather than by call + // count: any other fixture that installs a Ref green while this resolver is + // live would move a count, but cannot forge these addresses. + static RETAIN_LOG: std::sync::Mutex> = std::sync::Mutex::new(Vec::new()); + static RELEASE_LOG: std::sync::Mutex> = std::sync::Mutex::new(Vec::new()); + + fn test_retain(value: i64) { + RETAIN_LOG.lock().unwrap().push(value); + } + + fn test_release(value: i64) { + RELEASE_LOG.lock().unwrap().push(value); + } + + /// warmstate.py:568-573 — a cell's stored green key OWNS its `Ref` + /// referents, so the address it names cannot be freed and recycled by a + /// different object while the cell is alive. + /// + /// Four legs, and the second is the localization control: + /// + /// 1. the `Ref` slot is retained on install; + /// 2. an `Int` slot holding an equally pointer-shaped value is **not** + /// retained — ownership must key off the declared `GreenType`, not off + /// "looks like an address", which is the only way it can be right; + /// 3. a null `Ref` is skipped (`hash_whatever` folds null to 0, and there + /// is nothing to own); + /// 4. dropping the cell releases exactly what it retained. + #[test] + fn stored_green_key_owns_its_ref_referents() { + majit_ir::set_ref_resolver(test_retain, test_release); + + const REF_GREEN: i64 = 0x5EED_0001; + const INT_GREEN: i64 = 0x5EED_0002; + + let key = GreenKey::with_types( + vec![7, INT_GREEN, REF_GREEN, 0], + vec![Type::Int, Type::Int, Type::Ref, Type::Ref], + ); + + { + let mut ws = WarmEnterState::new(3); + ws.ensure_cell_for_key(&key); + + let retained = RETAIN_LOG.lock().unwrap().clone(); + assert!( + retained.contains(&REF_GREEN), + "installing a cell must retain its Ref green; log={retained:x?}" + ); + assert!( + !retained.contains(&INT_GREEN), + "an Int green must NOT be retained even when its value is \ + pointer-shaped — ownership keys off GreenType, not off the \ + bit pattern; log={retained:x?}" + ); + assert!( + !retained.contains(&0), + "a null Ref green has no referent to own; log={retained:x?}" + ); + + assert_eq!( + ws.get_stats().num_pinned_refs, + 1, + "the pinned-population counter must see exactly the one Ref \ + referent this cell owns" + ); + + assert!( + !RELEASE_LOG.lock().unwrap().contains(&REF_GREEN), + "nothing may be released while the cell that owns it is alive" + ); + } + + let released = RELEASE_LOG.lock().unwrap().clone(); + assert!( + released.contains(&REF_GREEN), + "dropping the cell must release its Ref green; log={released:x?}" + ); + assert!( + !released.contains(&INT_GREEN), + "an unretained Int green must never be released; log={released:x?}" + ); + } + /// warmstate.py:626-641 + 596-604 — `ensure_cell_for_key` allocates /// a fresh cell on miss and `lookup_chain_with_key` returns it on a /// repeat probe with the same typed greens. @@ -3231,6 +3848,62 @@ mod tests { ); } + /// warmstate.py:644-646 `dont_trace_here(*greenargs)` — the typed and + /// hash entry points reach the same cell today but do NOT leave it in the + /// same state, and this pins the difference. + /// + /// Both install on a miss, and on an *empty* table — which is what each + /// arm below starts from — they land in the same bucket. Do not read + /// that as "they reach the same cell": on a table where the other form + /// already wrote, they do not, and no hash collision is required for + /// that. See + /// `one_key_through_a_hash_and_a_typed_entry_point_builds_a_chain`. + /// What differs is what the installed cell knows about itself: the typed + /// form goes through `ensure_cell_for_key`, so the cell stores its + /// `comparekey` — and with it the `RetainedGreens` that make the stored + /// address own its referent. The hash form cannot, because a hash is not + /// invertible. + /// + /// This is the whole observable delta of routing an `interp_jit.py` + /// helper through the typed key, so it is asserted rather than described: + /// a cell without a `comparekey` is one that a chain walk can never + /// identify, which is exactly what bucketing (typed-key bucketing) would need it to do. + #[test] + fn dont_trace_here_typed_form_stores_a_comparekey_and_the_hash_form_does_not() { + let key = GreenKey::new(vec![7, 11]); + + let mut typed = WarmEnterState::new(100); + typed.disable_noninlinable_function_for_key(&key); + let typed_cell = typed + .get_cell(key.get_uhash()) + .expect("typed form installs a cell"); + assert!( + typed_cell.flags & jc_flags::DONT_TRACE_HERE != 0, + "typed form must set DONT_TRACE_HERE" + ); + assert_eq!( + typed_cell.comparekey.as_ref(), + Some(&key), + "typed form must store the greens it was called with" + ); + + let mut hashed = WarmEnterState::new(100); + hashed.disable_noninlinable_function(key.get_uhash()); + let hashed_cell = hashed + .get_cell(key.get_uhash()) + .expect("hash form installs a cell"); + assert!( + hashed_cell.flags & jc_flags::DONT_TRACE_HERE != 0, + "hash form must set DONT_TRACE_HERE — the flag is not the delta" + ); + assert!( + hashed_cell.comparekey.is_none(), + "hash form has no greens to store; if this ever becomes Some, the \ + typed/hash split has been closed somewhere else and this test is \ + the wrong guard" + ); + } + /// warmstate.py:596-604 — chain walk distinguishes hash collisions: /// two distinct GreenKeys sharing one bucket must each resolve to /// their own cell via `comparekey` rather than aliasing. The test @@ -3479,6 +4152,95 @@ mod tests { assert_eq!(count, 0, "cached lookups must not invoke make_token"); } + /// A hash-form write and a typed-form read of the SAME green key land on + /// DIFFERENT cells, and the state the hash form wrote is invisible to the + /// typed reader. + /// + /// This is the behavioural consequence of + /// `one_key_through_a_hash_and_a_typed_entry_point_builds_a_chain`: that + /// fixture shows the chain forms, this one shows what the chain costs. + /// + /// `disable_noninlinable_function` is reached in production from + /// `pyre-jit-trace/src/state.rs:3189` and `pyjitpl.rs:5256/5312`; + /// `maybe_compile_with_key` is the typed back-edge path (`pyjitpl.rs:4393`). + /// + /// The state does not merely move — it SPLITS, and the two reader + /// families see opposite halves. `DONT_TRACE_HERE` ends up on the head, + /// `TRACING` on the chained typed cell. So a bare-head reader + /// (`self.cells.get(&hash)`, ~26 of them here) sees the mark but not the + /// tracing state, while a typed reader (`lookup_chain_with_key`) sees the + /// tracing state but not the mark. Neither sees the whole cell. + /// + /// SCOPE. What is proven here is the split and the route change. The + /// hash-marked key reaches `StartTracing` on the THRESHOLD tick by the + /// ordinary counter route, because the typed decision never saw the mark; + /// the typed-marked key reaches it on the FIRST tick by + /// `should_start_dont_trace_here_trace` (warmstate.py:483-491), which is + /// the rule upstream intends to apply. Both trace in the end, so this is + /// NOT demonstrated to be a user-visible wrong answer — it is a lost + /// decision input. Whether a production key reaches both entry points, and + /// in which order, is a runtime question this fixture does not answer. + #[test] + fn a_hash_write_and_a_typed_read_of_one_key_use_different_cells() { + let mut ws = WarmEnterState::new(3); + let key = GreenKey::new(vec![7, 9]); + ws.disable_noninlinable_function(key.get_uhash()); + + // Ticks 1-2 under threshold, tick 3 fires — the ORDINARY counter + // route, i.e. the mark above was never consulted. + assert!(matches!(ws.maybe_compile_with_key(&key), HotResult::NotHot)); + assert!(matches!(ws.maybe_compile_with_key(&key), HotResult::NotHot)); + assert!( + matches!(ws.maybe_compile_with_key(&key), HotResult::StartTracing), + "the hash-written DONT_TRACE_HERE never reached the typed decision", + ); + + // One bucket, two cells: the split itself. + assert_eq!(ws.cells.len(), 1, "one green key, so one bucket"); + assert_eq!(ws.get_stats().num_cells, 2, "but two cells"); + + // `install_new_cell` folds the SURVIVOR in front of the newcomer + // (counter.py:253-254 `cell.next = keep; keep = cell`), so the + // HASH-written cell stays the head and the TYPED cell is chained + // behind it. This is the direction that matters: every bare-head + // reader — `self.cells.get(&hash)`, ~26 of them in this file — reads + // the head, which is the cell WITHOUT the comparekey. + let head = ws.lookup_chain(key.get_uhash()).expect("head present"); + assert!( + head.comparekey.is_none(), + "the head is the hash-written cell — a hash is not invertible, so \ + it can store no comparekey", + ); + assert_ne!( + head.flags & jc_flags::DONT_TRACE_HERE, + 0, + "the head still holds the mark the hash form wrote", + ); + let typed_cell = head.next.as_deref().expect("typed cell chained behind"); + assert_eq!( + typed_cell.comparekey.as_ref(), + Some(&key), + "the typed install carries the comparekey and is NOT the head", + ); + assert!( + typed_cell.is_tracing(), + "the typed cell is the one the tracing transition wrote to, so the \ + two halves of this key's state now live on two different cells", + ); + + // Control: the typed form of the same mark keeps ONE cell and takes + // the dont-trace-here route on the very first tick. + let mut typed = WarmEnterState::new(3); + let key2 = GreenKey::new(vec![7, 9]); + typed.disable_noninlinable_function_for_key(&key2); + assert!( + matches!(typed.maybe_compile_with_key(&key2), HotResult::StartTracing), + "the typed mark IS consulted, so the dont-trace-here rule applies \ + at once instead of waiting for the counter", + ); + assert_eq!(typed.get_stats().num_cells, 1, "no split on the typed path"); + } + /// warmstate.py:446-511 — typed variant of `maybe_compile_and_run`. /// Upstream installs the JitCell lazily at `bound_reached` /// (warmstate.py:425-444): each tick under threshold returns @@ -3525,6 +4287,90 @@ mod tests { assert!(cell.is_tracing(), "JC_TRACING flag set on threshold tick"); } + /// The three whole-table sweeps must walk each chain, not just its head. + /// + /// `cells.values()` yields chain HEADS, so a sweep that does not follow + /// `next` silently skips every chained cell. "majit-metainterp: count + /// chained cells in get_stats, not bucket heads" fixed exactly + /// this in `get_stats`; the fix was filed at one access path and three + /// siblings kept the defect. + /// + /// `invalidate_all` is the one that costs a wrong answer rather than a + /// leak: a chained cell whose token is never invalidated keeps running + /// compiled code built under a retracted assumption. + /// + /// The token lives on the CHAINED cell and the head is left tokenless, so + /// a head-only sweep reaches nothing at all — the assertions below fail on + /// every one of the three before the fix. + #[test] + fn whole_table_sweeps_reach_chained_cells_not_only_heads() { + let mut ws = WarmEnterState::new(100); + + let key_head = GreenKey::new(vec![100, 200]); + let key_tail = GreenKey::new(vec![300, 400]); + let bucket = key_tail.get_uhash(); + + // TRACING keeps the head non-removable so the second install chains + // behind it rather than replacing it (counter.py:246-256). + let mut head = BaseJitCell::new(); + head.flags |= jc_flags::TRACING; + head.comparekey = Some(key_head.clone()); + ws.install_new_cell(bucket, Some(head)); + + const CHAINED_TOKEN: u64 = 0x5EED_0003; + let mut tail = BaseJitCell::new(); + tail.flags |= jc_flags::TRACING; + tail.comparekey = Some(key_tail.clone()); + tail.loop_token = Some(make_token(CHAINED_TOKEN)); + ws.install_new_cell(bucket, Some(tail)); + + // The token is on the chained cell, never on the head. + let chain_head = ws.lookup_chain(bucket).expect("bucket has a head"); + assert!( + chain_head.loop_token.is_none(), + "fixture requires a tokenless head, or a head-only sweep would pass" + ); + assert!( + chain_head + .next + .as_deref() + .is_some_and(|c| c.loop_token.is_some()), + "fixture requires the token on the CHAINED cell" + ); + + // 1. find_token_by_number + assert!( + ws.find_token_by_number(CHAINED_TOKEN).is_some(), + "a token on a chained cell must be findable" + ); + + // 2. invalidate_all — the correctness one. + ws.invalidate_all(); + let chained = ws + .lookup_chain(bucket) + .and_then(|h| h.next.as_deref()) + .expect("chain survives invalidate_all"); + assert!( + chained + .loop_token + .as_ref() + .is_some_and(|t| t.is_invalidated()), + "invalidate_all must invalidate a CHAINED cell's token — skipping \ + it leaves compiled code live under a retracted assumption" + ); + + // 3. clear_all_loop_tokens + ws.clear_all_loop_tokens(); + let chained = ws + .lookup_chain(bucket) + .and_then(|h| h.next.as_deref()) + .expect("chain survives clear_all_loop_tokens"); + assert!( + chained.loop_token.is_none(), + "clear_all_loop_tokens must clear a CHAINED cell's token" + ); + } + /// warmstate.py:455-465 — `JitCell.get_jitcell_for_args(*greenargs)` /// walks the per-bucket chain by `comparekey` to read AND mutate /// the cell associated with `greenargs`. A hash-only delegate @@ -3607,6 +4453,237 @@ mod tests { ); } + /// A chain does NOT need a hash collision. ONE green key reached + /// through both a hash-only entry point and a typed one builds a two-cell + /// chain in a single bucket. + /// + /// The mechanism has no probabilistic step in it: + /// 1. a hash-only writer installs a cell with `comparekey: None`; + /// 2. `DONT_TRACE_HERE` with no token makes `should_remove_jitcell()` + /// false (warmstate.rs:241-257), so the cell survives the next install; + /// 3. `lookup_chain_with_key` cannot match a `None` comparekey — that is + /// asserted by `comparekey_matches_only_with_stored_key` — so + /// `ensure_cell_for_key` misses and calls `install_new_cell`; + /// 4. `install_new_cell` (counter.py:246-256) links the survivor behind + /// the newcomer. + /// + /// Every other chain fixture in this module forces its collision by + /// installing two comparekeys under one `get_uhash()` by hand, and says + /// so. This one uses only public entry points on a single key, which is + /// why it is the one that settles whether chains occur in practice. + #[test] + fn one_key_through_a_hash_and_a_typed_entry_point_builds_a_chain() { + let mut ws = WarmEnterState::new(100); + let key = GreenKey::new(vec![100, 200]); + + // Hash-only writer (what `dont_trace_here` did before it was routed + // through the typed form). + ws.disable_noninlinable_function(key.get_uhash()); + assert_eq!(ws.get_stats().num_cells, 1, "one cell after the hash write"); + assert!( + ws.lookup_chain_with_key(&key).is_none(), + "the hash-only cell stores no comparekey, so a typed probe for the \ + SAME key cannot see it — this is the step that makes the chain", + ); + + // Typed writer, same key. + ws.ensure_cell_for_key(&key); + + assert_eq!(ws.cells.len(), 1, "still ONE bucket — no collision here"); + assert_eq!( + ws.get_stats().num_cells, + 2, + "one green key, two cells: the typed install could not find the \ + hash-only cell and chained past it", + ); + } + + /// The typed `_for_key` forms must select the cell belonging to the key, + /// not whichever cell heads the bucket. + /// + /// Non-vacuity comes from the fixture, not from trust: both tests first + /// assert that the bucket head is the *comparator-less* cell, so a + /// head-reading implementation is provably looking at the wrong object. + /// Against the hash-delegating forms these read `None` and `false` + /// respectively. + #[test] + fn mark_as_being_traced_for_key_marks_the_keys_own_cell_not_the_bucket_head() { + let mut ws = WarmEnterState::new(100); + let key = GreenKey::new(vec![300, 400]); + + // A hash-only writer squats the bucket with a comparator-less cell. + ws.disable_noninlinable_function(key.get_uhash()); + assert!( + ws.lookup_chain_with_key(&key).is_none(), + "fixture: the hash-only cell stores no comparekey, so the key \ + owns nothing yet", + ); + + ws.mark_as_being_traced_for_key(&key); + + // Delegating to the hash form set TRACING on the comparator-less head + // and left the key still owning no cell at all. + let cell = ws + .lookup_chain_with_key(&key) + .expect("the key must own a cell reachable by comparekey"); + assert!( + cell.flags & jc_flags::TRACING != 0, + "TRACING must land on the key's own cell", + ); + assert_eq!( + cell.state, + BaseJitCellState::Tracing, + "and so must the state transition", + ); + } + + #[test] + fn get_cell_for_key_returns_the_keys_cell_not_whichever_heads_the_bucket() { + let mut ws = WarmEnterState::new(100); + let key = GreenKey::new(vec![500, 600]); + + ws.disable_noninlinable_function(key.get_uhash()); + ws.ensure_cell_for_key(&key); + assert_eq!(ws.cells.len(), 1, "one bucket"); + assert_eq!(ws.get_stats().num_cells, 2, "two cells in it"); + + assert!( + !ws.get_cell(key.get_uhash()) + .expect("bucket is occupied") + .comparekey_matches(&key), + "fixture: `install_new_cell` links the surviving hash-only cell \ + AHEAD of the new typed one, so the HEAD is the comparator-less \ + cell and a head-reading lookup returns the wrong cell here", + ); + + let cell = ws.get_cell_for_key(&key).expect("the key owns a cell"); + assert!( + cell.comparekey_matches(&key), + "get_cell_for_key must return the cell that matches the key", + ); + } + + /// The typed creators must write to the key's own cell, not squat a new + /// comparator-less one beside it. + /// + /// Same fixture discipline as the pair above: a hash-only writer takes the + /// bucket first and the assertion that it owns nothing typed runs *before* + /// the call under test, so a form that delegated to the hash creator would + /// be provably writing to the comparator-less head. Against the hash forms + /// these read `None` at the `lookup_chain_with_key` line. + #[test] + fn attach_procedure_to_interp_for_key_installs_on_the_keys_own_cell() { + let mut ws = WarmEnterState::new(100); + let key = GreenKey::new(vec![700, 800]); + + ws.disable_noninlinable_function(key.get_uhash()); + assert!( + ws.lookup_chain_with_key(&key).is_none(), + "fixture: the hash-only cell carries no comparekey", + ); + + let token = Arc::new(JitCellToken::new(ws.alloc_token_number())); + ws.attach_procedure_to_interp_for_key(&key, Arc::clone(&token)); + + let cell = ws + .lookup_chain_with_key(&key) + .expect("the key must own a cell reachable by comparekey"); + assert!( + cell.get_procedure_token().is_some(), + "the procedure token must land on the key's own cell", + ); + assert_eq!( + cell.flags & jc_flags::TRACING, + 0, + "and TRACING must be cleared on that same cell", + ); + } + + #[test] + fn mark_force_finish_tracing_for_key_sets_the_flag_on_the_keys_own_cell() { + let mut ws = WarmEnterState::new(100); + let key = GreenKey::new(vec![900, 1000]); + + ws.disable_noninlinable_function(key.get_uhash()); + assert!( + ws.lookup_chain_with_key(&key).is_none(), + "fixture: the hash-only cell carries no comparekey", + ); + + ws.mark_force_finish_tracing_for_key(&key); + + let cell = ws + .lookup_chain_with_key(&key) + .expect("the key must own a cell reachable by comparekey"); + assert!( + cell.flags & jc_flags::FORCE_FINISH != 0, + "FORCE_FINISH is sticky and never cleared, so landing it on the \ + wrong cell of the bucket is permanent", + ); + } + + /// `get_stats` counts every cell in a chain, not just the bucket head. + /// + /// Non-vacuity: a chain is the *only* shape that separates the two + /// implementations, so this fixture hands `install_new_cell` a shared + /// bucket directly — as the collision fixture above does, and for the + /// same reason (`cells` is keyed by the full `get_uhash()`, so two real + /// green keys will not collide). Head and tail are put in *different* + /// states so a head-only reader is caught twice: once on the total and + /// once on the per-state split. + /// + /// The parenthetical above says only that a *collision* is + /// impractical to reach from two real keys; it is not a claim that + /// chains are rare. The fixture directly above shows a chain forming + /// from a SINGLE key with no collision at all. + /// + /// Measured against the previous body (`num_cells: self.cells.len()` + /// plus a `cells.values()` loop): fails `num_cells` 1 vs 2. The + /// per-state assertions below are not separately observed in that run — + /// the first failure ends it — but they cover the same head-only read + /// on a second axis, so a future body that fixes the total and keeps a + /// head-only state walk still fails here. + #[test] + fn get_stats_counts_chained_cells_not_just_bucket_heads() { + let mut ws = WarmEnterState::new(100); + let key_head = GreenKey::new(vec![100, 200]); + let key_tail = GreenKey::new(vec![300, 400]); + let bucket = key_head.get_uhash(); + + // TRACING keeps the tail non-removable so the second install chains + // it rather than dropping it (counter.py:246-256 should_remove gate). + let mut tail = BaseJitCell::new(); + tail.state = BaseJitCellState::Tracing; + tail.flags |= jc_flags::TRACING; + tail.comparekey = Some(key_tail); + ws.install_new_cell(bucket, Some(tail)); + + let mut head = BaseJitCell::new(); + head.state = BaseJitCellState::Compiled; + head.flags |= jc_flags::TRACING; + head.comparekey = Some(key_head); + ws.install_new_cell(bucket, Some(head)); + + // Precondition: one map entry holding a two-cell chain. Without + // this the assertions below would pass for the wrong reason. + assert_eq!(ws.cells.len(), 1, "fixture must build ONE bucket"); + assert!( + ws.lookup_chain(bucket) + .and_then(|h| h.next.as_deref()) + .is_some(), + "fixture must build a TWO-cell chain, or it cannot tell the \ + implementations apart", + ); + + let stats = ws.get_stats(); + assert_eq!(stats.num_cells, 2, "both chained cells must be counted"); + assert_eq!(stats.num_compiled, 1, "head is Compiled"); + assert_eq!( + stats.num_tracing, 1, + "the chained tail's Tracing state is invisible to a head-only reader", + ); + } + /// warmstate.py:425-444 + 596-604 — typed-key transition writes /// land on the matching chained cell, not the bucket head. A /// collision-shaped chain `head=A → tail=B` followed by enough diff --git a/majit/majit-metainterp/tests/degraded_arm_refusal_kind.rs b/majit/majit-metainterp/tests/degraded_arm_refusal_kind.rs new file mode 100644 index 00000000000..844325ebeac --- /dev/null +++ b/majit/majit-metainterp/tests/degraded_arm_refusal_kind.rs @@ -0,0 +1,291 @@ +//! `refusal_kind` against the reasons the example crates actually record. +//! +//! The classifier keys on prose fragments emitted by the macro, so the only +//! thing that makes it trustworthy is a corpus of strings taken from real runs. +//! Every reason below was captured under `MAJIT_LOG=1` — the recorder prints it +//! itself, so no probe edit is needed to re-take them: +//! +//! ```text +//! MAJIT_LOG=1 cargo test --manifest-path majit/examples//Cargo.toml -- --nocapture +//! ``` +//! +//! When a family is added, add it HERE as well as in `refusal_kind`. This file +//! is the one place the whole known corpus is visible; the example gates each +//! see only their own crate's reasons and cannot tell a new mechanism from a +//! reworded one. + +use majit_metainterp::{REFUSAL_SEPARATOR, RefusalKind, refusal_kind, refusal_kinds}; + +const TL_ROLL: &str = "arm body writes a green this lowering path cannot carry \ + back to the caller (lowering stopped at this statement; \ + any further blockers follow): pc += 1;"; + +/// `tl`'s `ROLL` again, originally recorded with the pc-return gate term dropped +/// so the arm reached the channel. The mechanism refusing it changes; the arm +/// name does not. This pair is the whole reason `RefusalKind` exists — see +/// `refusal_kind_separates_the_two_reasons_one_arm_produced`. +/// +/// A MUTATED BUILD IS NO LONGER NEEDED TO SEE THIS. Now that lowering keeps +/// walking after its first refusal, the unmodified build records this exact +/// string as the SECOND member of `TL_ROLL_ACCUMULATED` — asserted byte for +/// byte by `the_accumulated_tl_reason_is_its_two_known_members`. The A/B that +/// produced it stays documented because it is how the second mechanism was +/// first found, not because it is still the only way to reach it. +const TL_ROLL_ON_PC_CHANNEL: &str = "arm body has a statement the lowerer \ + cannot express: \ + storage_roll(state.stack.as_mut_ptr() as \ + usize, state.stackpos, r);"; + +/// `tl`'s `PUSHARG` — an unlowerable store into the stack array. +const TL_PUSHARG: &str = "arm body has a statement the lowerer cannot express: \ + state.stack [state.stackpos as usize] = inputarg;"; + +/// `braininterp`'s loop-end arm, refused because it encloses loop control. +const BF_LOOP_END: &str = "arm body encloses a `break`/`continue` that cannot \ + be lowered in place: if state.tape [state.pointer as \ + usize] != 0"; + +/// `tiny2`/`tiny3`'s `OP_PUSH_INT` and `OP_PUSH_FLOAT` — the widening operand +/// read. +const TINY_PUSH: &str = "arm body has a statement the lowerer cannot express: \ + let value = i64 ::from_le_bytes([program [pc], program \ + [pc + 1], program [pc + ...)"; + +const TL_ROLL_ACCUMULATED: &str = "arm body writes a green this lowering path \ + cannot carry back to the caller (lowering \ + stopped at this statement; any further \ + blockers follow): pc += 1; || arm body has a \ + statement the lowerer cannot express: \ + storage_roll(state.stack.as_mut_ptr() as \ + usize, state.stackpos, r);"; + +/// `tlr`'s allocation arm, with green writeback and reallocation blockers. +const TLR_ALLOCATE: &str = "arm body writes a green this lowering path cannot \ + carry back to the caller (lowering stopped at this \ + statement; any further blockers follow): pc += 1; || \ + arm body has a statement the lowerer cannot express: \ + state.regs = vec! [0; n];"; + +/// `lower_stmt.rs:249` — the sibling guard to `break`/`continue`. +const SRC_ENCLOSED_RETURN: &str = + "arm body encloses a `return` that cannot be lowered in place: return acc;"; + +/// `api.rs:189` and `:271`. No `{what}: {spelling}` shape — a bare `Err`. +const SRC_EMPTY_BODY: &str = "arm body has no statements to lower"; + +/// `api.rs:306`, on the pc-return path. +const SRC_NO_PC_BINDING: &str = "arm body has no `pc` binding for the pc-return writeback"; + +/// `dispatch.rs:2826` — the only family raised at INSTALL rather than by the +/// statement lowerer, so it names no offending statement at all. +const SRC_UNSUPPORTED_CALL_POLICY: &str = + "arm body lowering resolved an unsupported call policy at install"; + +/// `lowerer.rs:145`. Its own doc keeps this exact wording so unconverted refusal +/// sites stay greppable, which is why it gets a variant instead of falling into +/// `Unclassified`: reaching it means "a site is unconverted", not "majit grew a +/// mechanism". +const SRC_UNREACHED_FALLBACK: &str = "arm body could not be lowered to a sub-JitCode"; + +#[test] +fn refusal_kind_covers_every_reachable_producer_string() { + assert_eq!( + refusal_kind(SRC_ENCLOSED_RETURN), + RefusalKind::EnclosedReturn + ); + assert_eq!(refusal_kind(SRC_EMPTY_BODY), RefusalKind::EmptyBody); + assert_eq!(refusal_kind(SRC_NO_PC_BINDING), RefusalKind::NoPcBinding); + assert_eq!( + refusal_kind(SRC_UNSUPPORTED_CALL_POLICY), + RefusalKind::UnsupportedCallPolicy + ); + assert_eq!( + refusal_kind(SRC_UNREACHED_FALLBACK), + RefusalKind::UnreachedLoweringFallback + ); +} + +/// `UnsupportedCallPolicy` names no offending statement, and the others do. +/// +/// Seven of the eight families are minted by the statement lowerer and carry +/// `arm body {what}: {spelling}` — `record_body_failure` at `lower_stmt.rs:417` +/// appends `: ` and a (possibly truncated) rendering of the statement. Two of +/// those seven are bare `Err`s with no statement in hand, and the eighth is +/// raised at INSTALL, before any statement is under consideration. +/// +/// This is the one family where the usual evidence is **structurally absent**: +/// a gate that pairs `(arm, RefusalKind)` with "and the reason names this source +/// snippet" — which every example gate does — has nothing to match here. Pinned +/// as a test rather than left in a doc comment, because the next person to write +/// such a gate will discover it as a puzzling failure otherwise. +#[test] +fn only_the_statement_lowerer_families_name_an_offending_statement() { + // `: ` introduces the statement spelling. The install-time string has no + // statement, so it must not look as though it does. + for (reason, kind) in [ + (TL_ROLL, RefusalKind::GreenWriteback), + (TL_PUSHARG, RefusalKind::UnlowerableStmt), + (BF_LOOP_END, RefusalKind::EnclosedBreakContinue), + (SRC_ENCLOSED_RETURN, RefusalKind::EnclosedReturn), + ] { + assert_eq!(refusal_kind(reason), kind); + assert!( + reason.contains(": "), + "{kind:?} is minted by record_body_failure and must carry `: `: {reason}" + ); + } + for (reason, kind) in [ + ( + SRC_UNSUPPORTED_CALL_POLICY, + RefusalKind::UnsupportedCallPolicy, + ), + (SRC_EMPTY_BODY, RefusalKind::EmptyBody), + (SRC_NO_PC_BINDING, RefusalKind::NoPcBinding), + ( + SRC_UNREACHED_FALLBACK, + RefusalKind::UnreachedLoweringFallback, + ), + ] { + assert_eq!(refusal_kind(reason), kind); + assert!( + !reason.contains(": "), + "{kind:?} names no offending statement, so a snippet-keyed gate has \ + nothing to match — it must not appear to carry one: {reason}" + ); + } +} + +/// Every reachable string maps to a DISTINCT family. +/// +/// A consolidation that quietly collapses two families into one passes every +/// existing gate — the arms still degrade, the names are unchanged, and each +/// crate compares against whatever the merged value now is. This is the check +/// that a merge cannot silently pass. +#[test] +fn the_eight_reachable_strings_do_not_collapse_onto_each_other() { + let all = [ + TL_ROLL, + TL_PUSHARG, + BF_LOOP_END, + SRC_ENCLOSED_RETURN, + SRC_EMPTY_BODY, + SRC_NO_PC_BINDING, + SRC_UNSUPPORTED_CALL_POLICY, + SRC_UNREACHED_FALLBACK, + ]; + let mut kinds: Vec = all.iter().map(|s| refusal_kind(s)).collect(); + let before = kinds.len(); + kinds.sort(); + kinds.dedup(); + assert_eq!( + kinds.len(), + before, + "two reachable reason strings now classify the same, so any gate \ + comparing them is blind to a change between those two mechanisms: {:?}", + all.iter() + .map(|s| (refusal_kind(s), &s[..40.min(s.len())])) + .collect::>() + ); + assert!( + !kinds.contains(&RefusalKind::Unclassified), + "a reachable producer string reached Unclassified — add its family here \ + and in refusal_kind, do not widen an existing fragment to absorb it" + ); +} + +/// Checks every refusal string captured from the example corpus against its +/// expected family. +#[test] +fn refusal_kind_classifies_the_recorded_corpus() { + assert_eq!(refusal_kind(TL_ROLL), RefusalKind::GreenWriteback); + assert_eq!( + refusal_kind(TL_ROLL_ON_PC_CHANNEL), + RefusalKind::UnlowerableStmt + ); + assert_eq!(refusal_kind(TL_PUSHARG), RefusalKind::UnlowerableStmt); + assert_eq!( + refusal_kind(BF_LOOP_END), + RefusalKind::EnclosedBreakContinue + ); + assert_eq!(refusal_kind(TINY_PUSH), RefusalKind::UnlowerableStmt); + assert_eq!( + refusal_kind(TL_ROLL_ACCUMULATED), + RefusalKind::GreenWriteback + ); + assert_eq!(refusal_kind(TLR_ALLOCATE), RefusalKind::GreenWriteback); +} + +/// An accumulated reason exposes both its writeback and statement-lowering +/// blockers. +#[test] +fn an_accumulated_reason_reports_every_blocker() { + let kinds = refusal_kinds(TLR_ALLOCATE); + assert_eq!( + kinds, + vec![RefusalKind::GreenWriteback, RefusalKind::UnlowerableStmt], + "tlr's ALLOCATE has two blockers and this must read both, the green \ + advance first; a length of 1 means this crate's REFUSAL_SEPARATOR no \ + longer matches the one the recorded string was minted with" + ); + assert!( + TLR_ALLOCATE.contains("state.regs = vec! [0; n];"), + "the member behind the head must still name the reallocation — that \ + statement is the only thing this reason reports that nothing else does" + ); +} + +/// The accumulated TL reason is exactly its two known members joined by the +/// shared separator. +#[test] +fn the_accumulated_tl_reason_is_its_two_known_members() { + assert_eq!( + TL_ROLL_ACCUMULATED, + format!("{TL_ROLL}{REFUSAL_SEPARATOR}{TL_ROLL_ON_PC_CHANNEL}"), + "the recorded string must be its head and its tail joined by the \ + separator; a mismatch means one of the three literals was re-recorded \ + without the others" + ); +} + +/// The property the classifier exists for: one arm, two mechanisms, and the +/// name is the same in both. +/// +/// A gate keyed on the degraded-arm NAME reads identically for these two — that +/// is exactly what happened, across three A/B arms, with the suite green in all +/// three. If a later edit makes these classify the same, every such gate goes +/// quietly blind again. +/// +/// Implied by `refusal_kind_classifies_the_recorded_corpus` and cannot fail +/// while that passes: it pins both strings to distinct variants already. Kept as +/// a separate test because it names the property and fails with a message about +/// it, not as independent coverage. Do not cite it as a second check. +#[test] +fn refusal_kind_separates_the_two_reasons_one_arm_produced() { + assert_ne!( + refusal_kind(TL_ROLL), + refusal_kind(TL_ROLL_ON_PC_CHANNEL), + "the two reasons ROLL produced now classify the same, so a cause gate \ + can no longer distinguish the arms it exists to distinguish" + ); +} + +/// A mechanism no fragment matches must reach `Unclassified` rather than land in +/// a known family. +/// +/// Do not "fix" a failing gate by widening a fragment until this passes as +/// something else. `Unclassified` reaching a gate means majit grew a refusal +/// family; the fix is to add the family here, with its recorded string. +#[test] +fn an_unknown_mechanism_is_not_bucketed_into_a_known_family() { + assert_eq!( + refusal_kind("arm body does something no fragment has been written for"), + RefusalKind::Unclassified + ); + // Sharper: a reason containing the word "cannot" but not the fragment must + // still be unclassified. `braininterp`'s reads "cannot be lowered in place", + // which is one word away from the `cannot express` fragment. + assert_eq!( + refusal_kind("arm body cannot be lowered for a brand new reason"), + RefusalKind::Unclassified + ); +} diff --git a/majit/majit-metainterp/tests/jit_driver_runtime_parity.rs b/majit/majit-metainterp/tests/jit_driver_runtime_parity.rs index 1dc734a1248..b0f60cbd7f9 100644 --- a/majit/majit-metainterp/tests/jit_driver_runtime_parity.rs +++ b/majit/majit-metainterp/tests/jit_driver_runtime_parity.rs @@ -1346,13 +1346,29 @@ fn runtime_driver_preserves_structured_green_key_and_descriptor_on_trace_start() assert!( driver - .back_edge_structured(green_key.clone(), 7, &mut state, &(), || {}) + .back_edge_structured( + green_key.hash_u64(), + || green_key.clone(), + 7, + &mut state, + &(), + || {} + ) .is_none() ); - assert!( - driver - .back_edge_structured(green_key.clone(), 7, &mut state, &(), || {}) - .is_none() + // Crossing the threshold starts tracing, which returns the target pc so the + // caller re-enters the dispatch loop where the trace heads. `None` would + // mean no trace started at all. + assert_eq!( + driver.back_edge_structured( + green_key.hash_u64(), + || green_key.clone(), + 7, + &mut state, + &(), + || {} + ), + Some(7) ); assert!(driver.is_tracing()); @@ -1383,10 +1399,12 @@ fn runtime_driver_attaches_descriptor_on_keyed_trace_start_without_structured_gr .back_edge_keyed(key, 11, &mut state, &(), || {}) .is_none() ); - assert!( - driver - .back_edge_keyed(key, 11, &mut state, &(), || {}) - .is_none() + // Crossing the threshold starts tracing, which returns the target pc so the + // caller re-enters the dispatch loop where the trace heads. `None` would + // mean no trace started at all. + assert_eq!( + driver.back_edge_keyed(key, 11, &mut state, &(), || {}), + Some(11) ); assert!(driver.is_tracing()); @@ -1424,11 +1442,14 @@ fn declarative_driver_trait_builds_runtime_driver_without_manual_descriptor_plum .expect("green key should build") .is_none() ); - assert!( + // Crossing the threshold starts tracing, which returns the target pc so the + // caller re-enters the dispatch loop where the trace heads. `None` would + // mean no trace started at all. + assert_eq!( driver .back_edge_declarative::(&[13, 21], 13, &mut state, &(), || {}) - .expect("green key should build") - .is_none() + .expect("green key should build"), + Some(13) ); assert!(driver.is_tracing()); @@ -1526,11 +1547,14 @@ fn declarative_driver_preserves_typed_red_inputargs_on_trace_start() { .expect("green key should build") .is_none() ); - assert!( + // Crossing the threshold starts tracing, which returns the target pc so the + // caller re-enters the dispatch loop where the trace heads. `None` would + // mean no trace started at all. + assert_eq!( driver .back_edge_declarative::(&[17, 23], 17, &mut state, &(), || {}) - .expect("green key should build") - .is_none() + .expect("green key should build"), + Some(17) ); assert!(driver.is_tracing()); diff --git a/majit/majit-metainterp/tests/jit_interp_block_arm_break.rs b/majit/majit-metainterp/tests/jit_interp_block_arm_break.rs new file mode 100644 index 00000000000..547116125bd --- /dev/null +++ b/majit/majit-metainterp/tests/jit_interp_block_arm_break.rs @@ -0,0 +1,132 @@ +//! Ensures a terminal dispatch arm spelled `{ ; break }` does not lose +//! its control transfer during lowering. +//! +//! A block arm is lowered statement by statement, so an unsupported terminal +//! `break` must degrade the arm instead of being discarded as inert. + +use majit_metainterp::{Assembler, JitCode, JitDriver}; + +pub type Bytecode = [u8]; + +/// `regs[0] -= 1`, advance. +const OP_DEC: u8 = 1; +/// Back edge: jump to 0 while `regs[0] != 0`, else fall past. +const OP_BACK: u8 = 2; +/// The arm under test: a *block* body whose tail is `break`. +const OP_FIN: u8 = 3; +/// Reached only if `OP_FIN`'s `break` was dropped. Bumps the same counter, +/// which is what turns the dropped transfer into a wrong number rather than a +/// silent no-op. +const OP_TICK: u8 = 4; +/// Bare `break` arm — `ArmPattern::Halt`, the spelling that always worked — +/// so a dropped `break` terminates the program instead of spinning. +const OP_END: u8 = 5; + +struct BlockBreakState { + regs: Vec, +} + +#[majit_macros::jit_interp( + state = BlockBreakState, + env = Bytecode, + greens = [pc, program], + state_fields = { + regs: [int; virt], + }, +)] +#[allow(unused_assignments, unused_variables)] +fn dispatch_block_break(program: &Bytecode, threshold: u32) -> i64 { + let mut driver: JitDriver = JitDriver::new(threshold); + let mut pc: usize = 0; + let mut state = BlockBreakState { + regs: vec![0i64; 2], + }; + state.regs[0] = program[program.len() - 1] as i64; + { + use majit_metainterp::JitState as _; + state + .build_meta(0, program) + .install_canonical_liveness(&mut driver); + } + loop { + jit_merge_point!(); + let opcode = program[pc]; + match opcode { + OP_DEC => { + state.regs[0] = state.regs[0] - 1; + pc = pc + 1; + } + OP_BACK => { + if state.regs[0] != 0 { + pc = 0; + continue; + } else { + pc = pc + 1; + } + } + // The shape under test. A block body, not a bare `break`. + OP_FIN => { + state.regs[1] = state.regs[1] + 1; + pc = pc + 1; + break; + } + OP_TICK => { + state.regs[1] = state.regs[1] + 1; + pc = pc + 1; + } + _ => break, + } + } + state.regs[1] +} + +/// `[OP_DEC, OP_BACK, OP_FIN, OP_TICK, OP_END, n]` — the trailing byte seeds +/// `regs[0]`, so the loop runs `n` times before reaching `OP_FIN`. +fn program_for(n: u8) -> Vec { + vec![OP_DEC, OP_BACK, OP_FIN, OP_TICK, OP_END, n] +} + +fn install() -> JitCode { + let mut asm = Assembler::new(); + asm.set_canonical_liveness_triple(vec![0], vec![], vec![0]); + __prebuild_jitcode_liveness_dispatch_block_break(&mut asm); + let _ = asm.ensure_canonical_liveness_offset(); + __dispatch_jitcode_dispatch_block_break(&mut asm, 0i64) + .expect("dispatch lower must succeed for fixture") +} + +/// Confirms that degradation preserves the arm's interpreted result instead +/// of silently dropping the terminal `break`. +#[test] +fn a_degraded_terminal_arm_still_answers_correctly() { + for n in [17u8, 18u8] { + let ticks = dispatch_block_break(&program_for(n), 4); + assert_eq!( + ticks, 1, + "n={n}: OP_FIN must run exactly once however the arm is lowered" + ); + } +} + +/// The refusal must be *visible*, not merely correct. +/// +/// Before the guard the arm lowered and silently misbehaved; after it, the arm +/// cannot lower and is recorded. Asserting the name pins that the arm reached +/// the fallback rather than being fixed somewhere upstream — if a later change +/// lowers a block `break` properly, this fires and should be deleted, not +/// relaxed. +#[test] +fn the_block_break_arm_is_recorded_as_degraded() { + let _ = install(); + let arms: Vec = majit_metainterp::degraded_dispatch_arms() + .into_iter() + .filter(|e| e.interp == "BlockBreakState") + .map(|e| e.arm.to_string()) + .collect(); + assert!( + arms.iter().any(|a| a == "OP_FIN"), + "OP_FIN must be recorded as degraded: its block body ends in `break`, \ + which cannot be lowered in place, so the arm degrades to an abort stub \ + and runs interpreted; recorded={arms:?}" + ); +} diff --git a/majit/majit-metainterp/tests/jit_interp_compound_assign_lowering.rs b/majit/majit-metainterp/tests/jit_interp_compound_assign_lowering.rs new file mode 100644 index 00000000000..68c5514c48c --- /dev/null +++ b/majit/majit-metainterp/tests/jit_interp_compound_assign_lowering.rs @@ -0,0 +1,214 @@ +//! Identifies which part of `state.regs[] += 1` prevents sub-JitCode +//! lowering. +//! +//! The fixture varies compound assignment, computed indexing, and same-slot +//! read-modify-write independently against a common control. + +use majit_metainterp::{Assembler, JitCode, JitDriver}; + +pub type Bytecode = [u8]; + +const OP_CTRL_ADD: u8 = 1; +const OP_COMPOUND_LETIDX: u8 = 2; +const OP_RMW_LETIDX: u8 = 3; +const OP_PLAIN_COMPUTEDIDX: u8 = 4; +const OP_COMPOUND_COMPUTEDIDX: u8 = 5; + +struct CompoundAssignState { + regs: Vec, +} + +#[majit_macros::jit_interp( + state = CompoundAssignState, + env = Bytecode, + greens = [pc, program], + state_fields = { + regs: [int; virt], + }, +)] +#[allow(unused_assignments, unused_variables)] +fn dispatch_compound_assign(program: &Bytecode, threshold: u32) -> i64 { + let mut driver: JitDriver = JitDriver::new(threshold); + let mut pc: usize = 0; + let mut state = CompoundAssignState { + regs: vec![0i64; 4], + }; + { + use majit_metainterp::JitState as _; + state + .build_meta(0, program) + .install_canonical_liveness(&mut driver); + } + loop { + jit_merge_point!(); + let opcode = program[pc]; + match opcode { + // Control: three distinct let-bound indices, no compound operator, + // no same-slot read-modify-write. Known to lower. + OP_CTRL_ADD => { + let a = program[pc + 1] as usize; + let b = program[pc + 2] as usize; + let d = program[pc + 3] as usize; + state.regs[d] = state.regs[a] + state.regs[b]; + pc += 4; + } + // Ingredient 1 alone: compound operator, index is a `let`-bound + // local. (Also same-slot RMW, implicitly — `+=` cannot avoid it.) + OP_COMPOUND_LETIDX => { + let i = program[pc + 1] as usize; + state.regs[i] += 1; + pc += 2; + } + // Ingredient 3 alone: same-slot read-modify-write written out, + // index is a `let`-bound local, no compound operator. + OP_RMW_LETIDX => { + let i = program[pc + 1] as usize; + state.regs[i] = state.regs[i] + 1; + pc += 2; + } + // Ingredient 2 alone: computed index in lvalue position, plain + // assignment, no read of the slot being written. + OP_PLAIN_COMPUTEDIDX => { + state.regs[program[pc + 1] as usize] = 1; + pc += 2; + } + OP_COMPOUND_COMPUTEDIDX => { + state.regs[program[pc + 1] as usize] += 1; + pc += 2; + } + _ => break, + } + } + 0 +} + +fn install() -> JitCode { + let mut asm = Assembler::new(); + asm.set_canonical_liveness_triple(vec![0], vec![], vec![0]); + __prebuild_jitcode_liveness_dispatch_compound_assign(&mut asm); + let _ = asm.ensure_canonical_liveness_offset(); + __dispatch_jitcode_dispatch_compound_assign(&mut asm, 0i64) + .expect("dispatch lower must succeed for fixture") +} + +/// A grouping module. It is no longer a requirement: every item `#[jit_interp]` +/// emits is now suffixed with the annotated function's name, so machines over +/// DIFFERENT state types co-reside (`jit_interp_two_machines_one_module.rs`). +/// Two machines over the SAME state type still collide on `impl JitState`. +mod float_control { + use super::Bytecode; + use majit_metainterp::{Assembler, JitCode, JitDriver}; + + const OP_FADD_ASSIGN: u8 = 1; + const OP_FREM_ASSIGN: u8 = 2; + + struct FloatCompoundState { + fregs: Vec, + } + + /// Float compound assignments supported by `opcode_for_assign_binop_f` + /// lower normally; remainder is deliberately unsupported and supplies + /// the positive control for the degraded-arm recorder. + #[majit_macros::jit_interp( + state = FloatCompoundState, + env = Bytecode, + greens = [pc, program], + state_fields = { + fregs: [float; virt], + }, +)] + #[allow(unused_assignments, unused_variables)] + fn dispatch_float_compound(program: &Bytecode, threshold: u32) -> i64 { + let mut driver: JitDriver = JitDriver::new(threshold); + let mut pc: usize = 0; + let mut state = FloatCompoundState { + fregs: vec![0.0f64; 4], + }; + { + use majit_metainterp::JitState as _; + state + .build_meta(0, program) + .install_canonical_liveness(&mut driver); + } + loop { + jit_merge_point!(); + let opcode = program[pc]; + match opcode { + OP_FADD_ASSIGN => { + let i = program[pc + 1] as usize; + state.fregs[i] += 1.0; + pc += 2; + } + OP_FREM_ASSIGN => { + let i = program[pc + 1] as usize; + state.fregs[i] %= 1.0; + pc += 2; + } + _ => break, + } + } + 0 + } + + pub(super) fn install_float() -> JitCode { + let mut asm = Assembler::new(); + asm.set_canonical_liveness_triple(vec![0], vec![], vec![0]); + __prebuild_jitcode_liveness_dispatch_float_compound(&mut asm); + let _ = asm.ensure_canonical_liveness_offset(); + __dispatch_jitcode_dispatch_float_compound(&mut asm, 0i64) + .expect("dispatch lower must succeed for fixture") + } +} + +use float_control::install_float; + +fn recorded_arms() -> Vec { + majit_metainterp::degraded_dispatch_arms() + .into_iter() + .filter(|entry| entry.interp == "CompoundAssignState") + .map(|entry| entry.arm.to_string()) + .collect() +} + +/// Post-fix: every spelling lowers, including both compound-assign forms. +/// +/// Before `lower_vable_array_update` existed, this same fixture recorded +/// `["OP_COMPOUND_COMPUTEDIDX", "OP_COMPOUND_LETIDX"]` — the two arms carrying +/// the compound operator, and only those. That measurement is what identified +/// the operator as the blocker and cleared the computed index and the +/// read-modify-write; the assertion below is its post-fix half. +#[test] +fn every_compound_assign_spelling_lowers() { + let _ = install(); + let mut arms = recorded_arms(); + arms.sort(); + + assert!( + arms.is_empty(), + "no arm in this fixture may degrade; recorded={arms:?}", + ); +} + +/// Guard the guard: `every_compound_assign_spelling_lowers` asserts an empty +/// set, which a channel that records nothing at all would also satisfy. This +/// pins that the recorder is live in this binary by driving it with an arm +/// that genuinely cannot lower: float remainder assignment is not in +/// `opcode_for_assign_binop_f`, while float addition assignment must lower. +#[test] +fn the_recorder_is_live_in_this_binary() { + let _ = install_float(); + let arms: Vec = majit_metainterp::degraded_dispatch_arms() + .into_iter() + .filter(|e| e.interp == "FloatCompoundState") + .map(|e| e.arm.to_string()) + .collect(); + assert!( + arms.iter().any(|a| a == "OP_FREM_ASSIGN"), + "unsupported float remainder-assign must be recorded, \ + otherwise the empty set above asserts nothing; recorded={arms:?}", + ); + assert!( + !arms.iter().any(|a| a == "OP_FADD_ASSIGN"), + "supported float addition-assign must lower; recorded={arms:?}", + ); +} diff --git a/majit/majit-metainterp/tests/jit_interp_degraded_arm_accumulates_refusals.rs b/majit/majit-metainterp/tests/jit_interp_degraded_arm_accumulates_refusals.rs new file mode 100644 index 00000000000..8106fdb9b65 --- /dev/null +++ b/majit/majit-metainterp/tests/jit_interp_degraded_arm_accumulates_refusals.rs @@ -0,0 +1,165 @@ +//! Verifies that an arm with multiple lowering blockers records every refusal +//! in encounter order and that refusal classification handles each member. + +use majit_metainterp::{ + Assembler, JitCode, JitDriver, REFUSAL_SEPARATOR, RefusalKind, refusal_kind, refusal_kinds, +}; + +pub type Bytecode = [u8]; + +/// `regs[0] -= 1`, advance. +const OP_DEC: u8 = 1; +/// Back edge: jump to 0 while `regs[0] != 0`, else fall past. +const OP_BACK: u8 = 2; +/// The arm under test. Two blockers, deliberately ordered so the FIRST one's +/// family sits LATER in `refusal_kind_of_one`'s `contains` chain than the +/// SECOND one's. +const OP_REALLOC_THEN_BREAK: u8 = 3; + +struct AccumState { + regs: Vec, +} + +#[majit_macros::jit_interp( + state = AccumState, + env = Bytecode, + greens = [pc, program], + state_fields = { + regs: [int; virt], + }, +)] +#[allow(unused_assignments, unused_variables)] +fn dispatch_accum(program: &Bytecode, threshold: u32) -> i64 { + let mut driver: JitDriver = JitDriver::new(threshold); + let mut pc: usize = 0; + let mut state = AccumState { + regs: vec![0i64; 2], + }; + state.regs[0] = program[program.len() - 1] as i64; + { + use majit_metainterp::JitState as _; + state + .build_meta(0, program) + .install_canonical_liveness(&mut driver); + } + loop { + jit_merge_point!(); + let opcode = program[pc]; + match opcode { + OP_DEC => { + state.regs[0] = state.regs[0] - 1; + pc = pc + 1; + } + OP_BACK => { + if state.regs[0] != 0 { + pc = 0; + continue; + } else { + pc = pc + 1; + } + } + OP_REALLOC_THEN_BREAK => { + // Blocker 1 — whole-array reallocation has no lowering. + // `UnlowerableStmt`, the chain's 4th test. + state.regs = vec![0i64; 2]; + // Blocker 2 — an enclosed `break` cannot be lowered in place. + // `EnclosedBreakContinue`, the chain's 2nd test. + if state.regs[0] == 0 { + break; + } + pc = pc + 1; + } + _ => break, + } + } + state.regs[1] +} + +/// `[OP_DEC, OP_BACK, OP_REALLOC_THEN_BREAK, n]` — the trailing byte seeds +/// `regs[0]`, so the loop runs `n` times before reaching the subject arm. +fn program_for(n: u8) -> Vec { + vec![OP_DEC, OP_BACK, OP_REALLOC_THEN_BREAK, n] +} + +fn install() -> JitCode { + let mut asm = Assembler::new(); + asm.set_canonical_liveness_triple(vec![0], vec![], vec![0]); + __prebuild_jitcode_liveness_dispatch_accum(&mut asm); + let _ = asm.ensure_canonical_liveness_offset(); + __dispatch_jitcode_dispatch_accum(&mut asm, 0i64) + .expect("dispatch lower must succeed for fixture") +} + +/// The subject arm's recorded `reason`, taken live from the registry rather +/// than copied into a literal, so it cannot go stale against the macro. +fn subject_reason() -> String { + let _ = install(); + majit_metainterp::degraded_dispatch_arms() + .into_iter() + .find(|e| e.interp == "AccumState" && e.arm == "OP_REALLOC_THEN_BREAK") + .map(|e| e.reason.to_string()) + .expect("OP_REALLOC_THEN_BREAK must be recorded as degraded") +} + +/// The recorded reason contains both blockers in the order lowering encounters +/// them. +#[test] +fn both_blockers_are_reported() { + let reason = subject_reason(); + let kinds = refusal_kinds(&reason); + assert_eq!( + kinds, + vec![ + RefusalKind::UnlowerableStmt, + RefusalKind::EnclosedBreakContinue, + RefusalKind::EnclosedBreakContinue + ], + "the arm's blockers must all be reported, reallocation first \ + (statement order), then the enclosing `if` and the `break` inside it; \ + reason={reason:?}" + ); + assert!( + reason.contains("state.regs = vec!") && reason.contains("break"), + "each member must still name its own offending statement; reason={reason:?}" + ); +} + +/// Property 2: `refusal_kind` reports the OUTERMOST refusal, which it can only +/// do by splitting first. +/// +/// The negative control is executable rather than asserted: collapsing the +/// separator turns the accumulated reason into a single segment, which is +/// exactly what an un-split classifier would see. If the two answers agreed, +/// this subject could not discriminate and the test would be worthless — so the +/// disagreement is asserted too. +#[test] +fn classifying_the_head_requires_splitting_first() { + let reason = subject_reason(); + let unsplit = reason.replace(REFUSAL_SEPARATOR, " "); + assert_eq!( + refusal_kind(&unsplit), + RefusalKind::EnclosedBreakContinue, + "control: seen as one segment, the ordered `contains` chain reaches \ + `encloses a `break`` (2nd test) before `cannot express` (4th), so it \ + answers with the arm's SECOND blocker; unsplit={unsplit:?}" + ); + assert_eq!( + refusal_kind(&reason), + RefusalKind::UnlowerableStmt, + "subject: split first, the head is the reallocation — the blocker \ + lowering actually stopped at; reason={reason:?}" + ); + assert_ne!( + refusal_kind(&unsplit), + refusal_kind(&reason), + "this subject exists to make the two disagree; if they ever agree it \ + has stopped discriminating and must be re-chosen, not relaxed" + ); +} + +/// The degraded arm runs interpreted. Liveness only — see the header: this is +/// not an oracle for the refusal and must not be cited as one. +#[test] +fn the_degraded_arm_runs_without_panicking() { + let _ = dispatch_accum(&program_for(3), 4); +} diff --git a/majit/majit-metainterp/tests/jit_interp_degraded_stub_abort_resume.rs b/majit/majit-metainterp/tests/jit_interp_degraded_stub_abort_resume.rs new file mode 100644 index 00000000000..f98ad495223 --- /dev/null +++ b/majit/majit-metainterp/tests/jit_interp_degraded_stub_abort_resume.rs @@ -0,0 +1,160 @@ +//! Verifies that a degraded-stub abort resumes at the aborting opcode's own +//! boundary. +//! +//! The subject uses a multi-byte degraded opcode after a shared prologue +//! advance, so resuming at the following byte would decode an operand as a new +//! opcode and produce a wrong result. + +use majit_metainterp::{Assembler, JitCode, JitDriver}; + +pub type Bytecode = [u8]; + +/// `regs[0] -= 1`, advance one byte. +const OP_DEC: u8 = 1; +/// `[OP_BUMP, k]` — `regs[1] += k` through a helper the lowerer cannot express, +/// so this arm degrades to an abort stub. Two bytes wide, and its operand is +/// spelled `1` so a late resume decodes it as [`OP_DEC`]. +const OP_BUMP: u8 = 2; +/// `[OP_BACK, target]` — jump to `target` while `regs[0] > 0`, else fall past. +const OP_BACK: u8 = 3; +/// Bare `break` arm, the spelling that classifies as `ArmPattern::Halt`. +const OP_END: u8 = 4; + +/// Mutates the register array through its raw base pointer, a shape the lowerer +/// cannot express and therefore emits as a degraded stub. +/// +/// # Safety +/// `base` points to the two-element register array owned by this fixture. +fn bump_via_ptr(base: usize, idx: i64, by: i64) { + let p = base as *mut i64; + unsafe { + *p.add(idx as usize) += by; + } +} + +struct StubResumeState { + regs: Vec, +} + +#[majit_macros::jit_interp( + state = StubResumeState, + env = Bytecode, + greens = [pc, program], + state_fields = { + regs: [int; virt], + }, +)] +#[allow(unused_assignments, unused_variables)] +fn dispatch_stub_resume(program: &Bytecode, threshold: u32, n: i64) -> i64 { + let mut driver: JitDriver = JitDriver::new(threshold); + let mut pc: usize = 0; + let mut state = StubResumeState { + regs: vec![0i64; 2], + }; + state.regs[0] = n; + { + use majit_metainterp::JitState as _; + state + .build_meta(0, program) + .install_canonical_liveness(&mut driver); + } + while pc < program.len() { + jit_merge_point!(driver, program, pc; state); + // The shared prologue advance. It is what makes the abort resume + // position wrong: `i0` is advanced HERE, before the arm runs, so a + // stub abort inside the arm leaves `i0` one past an opcode that + // applied nothing. Each arm then advances again over its own operands. + let opcode = program[pc]; + pc += 1; + match opcode { + OP_DEC => { + state.regs[0] = state.regs[0] - 1; + } + OP_BUMP => { + let k = program[pc] as i64; + pc += 1; + bump_via_ptr(state.regs.as_mut_ptr() as usize, 1, k); + } + OP_BACK => { + let target = program[pc] as usize; + pc += 1; + if state.regs[0] > 0 { + if target < pc { + can_enter_jit!(driver, target, &mut state, program, || {}); + } + pc = target; + continue; + } + } + _ => break, + } + } + state.regs[1] +} + +/// `[OP_BUMP, 1, OP_DEC, OP_BACK, 0, OP_END]`. +/// +/// Each iteration bumps `regs[1]` by one and decrements `regs[0]` by one, so a +/// clean run of `n` iterations answers exactly `n`. +fn program() -> Vec { + vec![OP_BUMP, 1, OP_DEC, OP_BACK, 0, OP_END] +} + +/// What the same program computes with no JIT tier involved at all. +fn interpret(n: i64) -> i64 { + let (mut a, mut b) = (n, 0i64); + while a > 0 { + b += 1; + a -= 1; + } + b +} + +fn install() -> JitCode { + let mut asm = Assembler::new(); + asm.set_canonical_liveness_triple(vec![0], vec![], vec![0]); + __prebuild_jitcode_liveness_dispatch_stub_resume(&mut asm); + let _ = asm.ensure_canonical_liveness_offset(); + __dispatch_jitcode_dispatch_stub_resume(&mut asm, 0i64) + .expect("dispatch lower must succeed for fixture") +} + +/// The fixture is only meaningful while `OP_BUMP` actually degrades. +/// +/// If a later lowering change teaches the macro to express the helper call, +/// this arm stops being a stub, the abort under test never happens, and the +/// resume assertion below silently stops testing anything. Fail here instead, +/// loudly, so the fixture cannot rot into an oracle that cannot fail. +#[test] +fn op_bump_is_a_degraded_arm() { + let _ = install(); + let arms: Vec = majit_metainterp::degraded_dispatch_arms() + .into_iter() + .filter(|e| e.interp == "StubResumeState") + .map(|e| e.arm.to_string()) + .collect(); + assert!( + arms.iter().any(|a| a == "OP_BUMP"), + "OP_BUMP must lower to an abort stub for this fixture to exercise the \ + degraded-stub resume path at all; recorded={arms:?}" + ); +} + +/// The opcode the stub aborted in must run exactly once, not zero times. +/// +/// A threshold low enough to trace, over enough iterations to reach the abort +/// and continue past it. The answer is compared against the JIT-free +/// computation rather than a literal, so the fixture states the property +/// (`the JIT must not change the answer`) rather than a constant. +#[test] +fn a_degraded_stub_abort_reruns_its_own_opcode() { + for n in [12i64, 20i64] { + let got = dispatch_stub_resume(&program(), 4, n); + assert_eq!( + got, + interpret(n), + "n={n}: a degraded-stub abort resumed one byte past OP_BUMP, so the \ + bump was skipped and its operand byte was decoded as OP_DEC" + ); + } +} diff --git a/majit/majit-metainterp/tests/jit_interp_dispatch_ir_shape.rs b/majit/majit-metainterp/tests/jit_interp_dispatch_ir_shape.rs index 5ce54a83601..527a465ed99 100644 --- a/majit/majit-metainterp/tests/jit_interp_dispatch_ir_shape.rs +++ b/majit/majit-metainterp/tests/jit_interp_dispatch_ir_shape.rs @@ -36,6 +36,7 @@ impl BytecodeExt for [u8] { state = DispatchTestState, env = Bytecode, state_fields = { a: int }, + greens = [], )] #[allow(unused_assignments, unused_variables)] fn dispatch_minimal(program: &Bytecode, threshold: u32) -> i64 { @@ -153,6 +154,7 @@ mod literal_for_unroll { state = LiteralForState, env = Bytecode, state_fields = { acc: int }, + greens = [], )] #[allow(unused_assignments, unused_variables)] fn dispatch_literal_for_unroll(program: &Bytecode, threshold: u32) -> i64 { @@ -235,6 +237,7 @@ mod float_state_field_shape { state = FloatDispatchState, env = Bytecode, state_fields = { f: float }, + greens = [], )] #[allow(unused_assignments, unused_variables)] fn dispatch_float_field(program: &Bytecode, threshold: u32) -> i64 { @@ -299,6 +302,7 @@ mod or_pattern { state = OrDispatchState, env = Bytecode, state_fields = { a: int }, + greens = [], )] #[allow(unused_assignments, unused_variables)] fn dispatch_or_pattern(program: &Bytecode, threshold: u32) -> i64 { @@ -370,6 +374,7 @@ mod switch_dispatch { env = Bytecode, state_fields = { a: int }, switch_dispatch = true, + greens = [], )] #[allow(unused_assignments, unused_variables)] fn dispatch_switch_pattern(program: &Bytecode, threshold: u32) -> i64 { @@ -534,6 +539,7 @@ mod pre_promote { state = PromoteState, env = Bytecode, state_fields = { stackpos: int }, + greens = [], )] #[allow(unused_assignments, unused_variables)] fn dispatch_with_pre_promote(program: &Bytecode, threshold: u32) -> i64 { @@ -2589,6 +2595,7 @@ mod residual_call_not_dropped { state = CallState, env = Bytecode, state_fields = { acc: int }, + greens = [], )] #[allow(unused_assignments, unused_variables)] fn dispatch_residual_call(program: &Bytecode, threshold: u32) -> i64 { @@ -2650,9 +2657,8 @@ mod residual_call_not_dropped { } } -/// A float comparison lowers in value form (materialized to an int) but a -/// float comparison feeding a conditional guard must abort lowering — the -/// guard bridge hangs the compiled trace (a4e191f71b5). +/// A float comparison can lower as an integer value, but using it directly as +/// a conditional guard must degrade until guard-bridge lowering is supported. mod float_compare_branch_gate { use super::Bytecode; use majit_metainterp::jitcode::insns::BC_FLOAT_GE; @@ -2671,6 +2677,7 @@ mod float_compare_branch_gate { state = FloatCmpState, env = Bytecode, state_fields = { fa: float, fb: float, acc: int }, + greens = [], )] #[allow(unused_assignments, unused_variables)] fn dispatch_float_cmp(program: &Bytecode, threshold: u32) -> i64 { @@ -2752,6 +2759,7 @@ mod huge_range_for_loop_falls_back { state = HugeState, env = Bytecode, state_fields = { acc: int }, + greens = [], )] #[allow(unused_assignments, unused_variables)] fn dispatch_huge_range(program: &Bytecode, threshold: u32) -> i64 { @@ -2809,3 +2817,210 @@ mod huge_range_for_loop_falls_back { ); } } + +/// Pairs one genuinely unlowerable float-remainder assignment with healthy +/// controls, ensuring installation reports the degraded arm without producing +/// false positives for arms the macro can lower. +mod degraded_arm_is_named_at_install { + use super::Bytecode; + use majit_metainterp::jitcode::insns::{ + BC_ABORT, BC_CONVERT_FLOAT_BYTES_TO_LONGLONG, BC_CONVERT_LONGLONG_BYTES_TO_FLOAT, + }; + use majit_metainterp::{Assembler, JitCode, JitDriver}; + + const OP_ADD: u8 = 1; + const OP_RETURN: u8 = 2; + const OP_RETURN_F: u8 = 3; + const OP_BUMP: u8 = 4; + const OP_LOAD_CONST_F: u8 = 5; + const OP_FBUMP: u8 = 6; + const OP_FREM_ASSIGN: u8 = 7; + + struct DegradedArmState { + regs: Vec, + fregs: Vec, + } + + #[majit_macros::jit_interp( + state = DegradedArmState, + env = Bytecode, + greens = [pc, program], + state_fields = { + regs: [int; virt], + fregs: [float; virt], + }, + )] + #[allow(unused_assignments, unused_variables)] + fn dispatch_degraded_arm(program: &Bytecode, threshold: u32) -> i64 { + let mut driver: JitDriver = JitDriver::new(threshold); + let mut pc: usize = 0; + let mut state = DegradedArmState { + regs: vec![0i64; 4], + fregs: vec![0.0f64; 4], + }; + { + use majit_metainterp::JitState as _; + state + .build_meta(0, program) + .install_canonical_liveness(&mut driver); + } + loop { + jit_merge_point!(); + let opcode = program[pc]; + match opcode { + OP_ADD => { + let a = program[pc + 1] as usize; + let b = program[pc + 2] as usize; + let d = program[pc + 3] as usize; + state.regs[d] = state.regs[a] + state.regs[b]; + pc += 4; + } + OP_RETURN => { + return state.regs[program[pc + 1] as usize]; + } + OP_RETURN_F => { + return state.fregs[program[pc + 1] as usize].to_bits() as i64; + } + OP_BUMP => { + state.regs[program[pc + 1] as usize] += 1; + pc += 2; + } + OP_FBUMP => { + state.fregs[program[pc + 1] as usize] += 1.0; + pc += 2; + } + OP_FREM_ASSIGN => { + state.fregs[program[pc + 1] as usize] %= 1.0; + pc += 2; + } + OP_LOAD_CONST_F => { + state.fregs[program[pc + 2] as usize] = f64::from_bits(program[pc + 1] as u64); + pc += 3; + } + _ => break, + } + } + 0 + } + + /// Build the dispatch JitCode so the install-time recorder runs. + fn install() -> JitCode { + let mut asm = Assembler::new(); + asm.set_canonical_liveness_triple(vec![0], vec![], vec![0]); + __prebuild_jitcode_liveness_dispatch_degraded_arm(&mut asm); + let _ = asm.ensure_canonical_liveness_offset(); + __dispatch_jitcode_dispatch_degraded_arm(&mut asm, 0i64) + .expect("dispatch lower must succeed for fixture") + } + + /// Entries this fixture contributed, isolated from every other `#[jit_interp]` + /// machine in the binary by the `state = T` name. + fn recorded_arms() -> Vec { + majit_metainterp::degraded_dispatch_arms() + .into_iter() + .filter(|entry| entry.interp == "DegradedArmState") + .map(|entry| entry.arm.to_string()) + .collect() + } + + #[test] + fn degraded_arm_is_named_and_healthy_arms_are_not() { + let _ = install(); + let arms = recorded_arms(); + + // Positive: the arm that actually degrades is named. + assert!( + arms.iter().any(|arm| arm == "OP_FREM_ASSIGN"), + "the unsupported float remainder-assign arm must be recorded as degraded; recorded={arms:?}" + ); + + // Negative controls. `OP_ADD` is `OP_BUMP` written out and lowers; + // `OP_RETURN_F` reads a float register through `f64::to_bits`, which + // the macro recognizes, and `OP_RETURN` is its int twin; `_ => break` + // is a DECLARED halt rather than a degradation. A channel that named + // any of these would not be telling anyone which arm to fix. + for silent in [ + "OP_ADD", + "OP_RETURN", + "OP_RETURN_F", + "OP_LOAD_CONST_F", + "OP_BUMP", + "OP_FBUMP", + "_", + ] { + assert!( + !arms.iter().any(|arm| arm == silent), + "`{silent}` lowers (or is declared) and must not be recorded; recorded={arms:?}" + ); + } + } + + /// The inherent float bitcast spellings must reach the bitcast resops, not + /// merely avoid the abort stub: `.to_bits()` is + /// `convert_float_bytes_to_longlong` and `f64::from_bits(..)` is its + /// inverse, the same two ops the free `majit_f64_to_bits` / + /// `majit_bits_to_f64` intrinsics emit. + #[test] + fn inherent_float_bitcast_spellings_lower_to_the_convert_ops() { + let dispatch_jc = install(); + let count = |op: u8| -> usize { + let in_dispatch = dispatch_jc.code.iter().filter(|&&b| b == op).count(); + let in_subs: usize = dispatch_jc + .exec + .descrs + .iter() + .filter_map(|descr| descr.as_jitcode()) + .map(|sub| sub.code.iter().filter(|&&b| b == op).count()) + .sum(); + in_dispatch + in_subs + }; + let to_bits = count(BC_CONVERT_FLOAT_BYTES_TO_LONGLONG); + let from_bits = count(BC_CONVERT_LONGLONG_BYTES_TO_FLOAT); + assert_eq!( + (to_bits, from_bits), + (1, 1), + "`.to_bits()` and `f64::from_bits(..)` must each lower to one bitcast op" + ); + } + + /// The registry must agree with the artifact it describes; checking it + /// against itself would prove nothing. Every arm the macro named is one + /// abort stub in the built dispatch JitCode, and there are no others. + #[test] + fn the_named_arms_are_exactly_the_abort_stubs_in_the_ir() { + let dispatch_jc = install(); + let abort_stubs = dispatch_jc + .exec + .descrs + .iter() + .filter_map(|descr| descr.as_jitcode()) + .filter(|sub| sub.code.iter().any(|&b| b == BC_ABORT)) + .count(); + let arms = recorded_arms(); + assert_eq!( + abort_stubs, + arms.len(), + "abort stubs in the dispatch JitCode must match the named arms; recorded={arms:?}" + ); + assert_eq!(abort_stubs, 1, "fixture must degrade exactly one arm"); + } + + #[test] + fn install_time_record_carries_a_reason_and_dedups() { + let _ = install(); + let _ = install(); + let entries: Vec<_> = majit_metainterp::degraded_dispatch_arms() + .into_iter() + .filter(|entry| entry.interp == "DegradedArmState" && entry.arm == "OP_FREM_ASSIGN") + .collect(); + assert_eq!( + entries.len(), + 1, + "a second dispatch build must not duplicate the entry; entries={entries:?}" + ); + assert!( + !entries[0].reason.is_empty(), + "the recorded arm must carry a staged reason, not an empty bucket" + ); + } +} diff --git a/majit/majit-metainterp/tests/jit_interp_fixed_array_identity_slot.rs b/majit/majit-metainterp/tests/jit_interp_fixed_array_identity_slot.rs new file mode 100644 index 00000000000..6d453704b1a --- /dev/null +++ b/majit/majit-metainterp/tests/jit_interp_fixed_array_identity_slot.rs @@ -0,0 +1,159 @@ +//! Verifies virtualizable identity handling when a fixed integer array appears +//! before a virtualizable array in the flattened input layout. +//! +//! Runtime-sized fixed arrays prevent the macro from declaring a constant +//! identity slot. The optimizer must decline identity tracking instead of +//! falling back to slot zero, which contains an integer in this fixture. + +use core::sync::atomic::{AtomicU32, Ordering}; + +pub type Bytecode = [u8]; + +const OP_ACC: u8 = 1; // iregs[2] += cells[0] (the fixed array, read-only) +const OP_INC: u8 = 2; // iregs[0] += 1 +const OP_JUMP_BACK: u8 = 3; // [OP_JUMP_BACK, target]: loop while iregs[0] < iregs[1] +const OP_RETURN: u8 = 4; // yield iregs[0] + +/// `iregs = [counter, limit, accumulator]`. +fn count_program() -> Vec { + vec![OP_ACC, OP_INC, OP_JUMP_BACK, 0, OP_RETURN] +} + +/// What every machine here must return for a given limit. +fn expected(limit: i64) -> i64 { + limit +} + +/// A fixed `[int]` array between the loop-dead `int` scalar and the +/// `[int; virt]` array. `extract_live` emits `[ret, cells[0], cells[1], +/// identity]`, so the identity is at flat slot 3 — not at `num_scalars` (1), +/// and not at 0. +mod fixed_array_before_virt_array { + use super::{AtomicU32, Bytecode, OP_ACC, OP_INC, OP_JUMP_BACK, OP_RETURN, Ordering}; + use majit_metainterp::JitState as _; + + static COMPILES: AtomicU32 = AtomicU32::new(0); + + struct FixedAndVirt { + ret: i64, + cells: Vec, + iregs: Vec, + } + + #[majit_macros::jit_interp( + state = FixedAndVirt, + env = Bytecode, + greens = [pc, program], + state_fields = { + ret: int, + cells: [int], + iregs: [int; virt], + }, + )] + #[allow(unused_assignments, unused_variables)] + fn mainloop(program: &Bytecode, limit: i64, threshold: u32) -> i64 { + let mut driver: majit_metainterp::JitDriver = + majit_metainterp::JitDriver::new(threshold); + driver.set_on_compile_loop(|_gk, _b, _a, _opcodes| { + COMPILES.fetch_add(1, Ordering::Relaxed); + }); + let mut pc: usize = 0; + let mut state = FixedAndVirt { + ret: 0, + cells: vec![0i64, 0i64], + iregs: vec![0i64, limit, 0i64], + }; + { + use majit_metainterp::JitState as _; + state + .build_meta(0, program) + .install_canonical_liveness(&mut driver); + } + + while pc < program.len() { + jit_merge_point!(driver, program, pc; state); + + let opcode = program[pc]; + pc += 1; + + match opcode { + // Read-only: a MUTATED plain `[int]` is refused outright (it is + // not restored on deopt), so the fixed array is loop-carried but + // never written. + OP_ACC => state.iregs[2] = state.iregs[2] + state.cells[0], + OP_INC => state.iregs[0] = state.iregs[0] + 1, + OP_JUMP_BACK => { + let target = program[pc] as usize; + pc += 1; + if state.iregs[0] < state.iregs[1] { + if target < pc { + can_enter_jit!(driver, target, &mut state, program, || {}); + } + pc = target; + continue; + } + } + OP_RETURN => { + state.ret = state.iregs[0]; + return state.ret; + } + _ => panic!("bad opcode {opcode}"), + } + } + state.ret + } + + /// Ground truth for the slot the identity occupies, in the same shape + /// `jit_interp_float_state_field.rs` + /// `the_entry_contract_carries_no_element_in_its_red_block` pins: int + /// scalars, then every fixed array's cells, then the identity. No `iregs` + /// element belongs in the red block. + #[test] + fn the_identity_sits_past_the_fixed_array_cells() { + let state = FixedAndVirt { + ret: 0, + cells: vec![0i64, 0i64], + iregs: vec![0i64, 4, 0i64], + }; + let program: &Bytecode = &super::count_program(); + let meta = state.build_meta(0, program); + + assert_eq!( + state.live_value_types(&meta), + vec![ + majit_ir::Type::Int, // ret + majit_ir::Type::Int, // cells[0] + majit_ir::Type::Int, // cells[1] + majit_ir::Type::Ref, // __vable_identity + ], + "the identity must follow the fixed array's cells, so its flat slot \ + is `num_scalars + cells.len()` = 3 — neither `num_scalars` (1) nor \ + the legacy frame-first 0", + ); + assert_eq!(state.extract_live(&meta).len(), 4); + } + + #[test] + fn a_fixed_array_beside_a_virt_array_compiles_the_hot_loop() { + let program = super::count_program(); + let limit = 50i64; + COMPILES.store(0, Ordering::Relaxed); + + let got = mainloop(&program, limit, 3); + + assert_eq!( + got, + super::expected(limit), + "scalar carried the wrong result out" + ); + assert!( + COMPILES.load(Ordering::Relaxed) >= 1, + "declaring a fixed `[int]` array beside a `[int; virt]` one stopped \ + the hot loop from compiling at all: the macro emits no \ + `identity_live_index` for that combination and the optimizer fell \ + back to flat slot 0, which here is the loop-dead `ret` int scalar, \ + so every trace aborted with VirtualStatesCantMatch (expected Ref, \ + got Int) on slot 0" + ); + } +} diff --git a/majit/majit-metainterp/tests/jit_interp_float_state_field.rs b/majit/majit-metainterp/tests/jit_interp_float_state_field.rs index 565bbbb1b54..48c6b5cada6 100644 --- a/majit/majit-metainterp/tests/jit_interp_float_state_field.rs +++ b/majit/majit-metainterp/tests/jit_interp_float_state_field.rs @@ -32,6 +32,7 @@ mod scalar { state = FloatScalarState, env = Bytecode, state_fields = { a: int, f: float }, + greens = [], )] #[allow(unused_assignments, unused_variables)] fn float_scalar_minimal(program: &Bytecode, threshold: u32) -> i64 { @@ -97,8 +98,9 @@ mod scalar { // `lower_dispatch_body`'s `stmt_modifies_jit_state` skip gate. Until float // scalars were added to `expr_is_jit_state_place`, that predicate returned // false for `state.f`, so the write was silently dropped from the dispatch -// JitCode and compiled execution left the field stale. Its own module -// because `#[jit_interp]` emits per-module `__JitSym` / `__JitMeta` types. +// JitCode and compiled execution left the field stale. The module groups the +// fixture; it is no longer forced, because `__JitSym_` / `__JitMeta_` +// now carry the annotated function's name. mod scalar_toplevel { use super::{Bytecode, all_jitcode_bodies}; use majit_metainterp::jitcode::insns::BC_STORE_STATE_FIELD_FLOAT; @@ -115,6 +117,7 @@ mod scalar_toplevel { state = FloatToplevelState, env = Bytecode, state_fields = { a: int, f: float }, + greens = [], )] #[allow(unused_assignments, unused_variables)] fn float_scalar_toplevel_write(program: &Bytecode, threshold: u32) -> i64 { @@ -176,6 +179,7 @@ mod virt_array { state = FloatArrayState, env = Bytecode, state_fields = { regs: [float; virt] }, + greens = [], )] #[allow(unused_assignments, unused_variables)] fn float_array_minimal(program: &Bytecode, threshold: u32) -> i64 { @@ -249,6 +253,7 @@ mod scalar_f32 { state = F32State, env = Bytecode, state_fields = { a: int, f: float(f32) }, + greens = [], )] #[allow(unused_assignments, unused_variables)] fn f32_scalar_minimal(program: &Bytecode, threshold: u32) -> i64 { @@ -313,6 +318,7 @@ mod scalar_float_slot_reserve { state = TwoFloatState, env = Bytecode, state_fields = { a: float, b: float }, + greens = [], )] #[allow(unused_assignments, unused_variables)] fn two_float_minimal(program: &Bytecode, threshold: u32) -> i64 { @@ -412,6 +418,7 @@ mod virt_array_with_float_scalar { // by the recursive-portal fresh-allocation gate, so the ordering bug // could only be hit alongside a fixed array or a ref scalar. state_fields = { sp: int, cells: [int], acc: float, stack: [int; virt] }, + greens = [], )] #[allow(unused_assignments, unused_variables)] fn mixed_virt_and_float(program: &Bytecode, threshold: u32) -> i64 { @@ -522,4 +529,45 @@ mod virt_array_with_float_scalar { ); assert_eq!(state.extract_live(&meta).len(), 5); } + + #[test] + fn clearing_the_sym_bindings_drops_positions_and_keeps_values() { + let state = MixedState { + sp: 7, + cells: vec![11, 13], + acc: 1.5, + stack: vec![0; 2], + }; + let program: &Bytecode = &[OP_NOP, OP_STEP]; + let meta = state.build_meta(0, program); + let mut sym = ::create_sym(&meta, 0); + state.initialize_sym(&mut sym, &meta); + + // The mints, in the flat order `live_value_types` above declares. + // Spelled out rather than asserted `is_some` so the test also states + // WHICH position each field claims. + assert_eq!(sym.sp, majit_ir::OpRef::input_arg_int(0)); + assert_eq!(sym.cells[0], majit_ir::OpRef::input_arg_int(1)); + assert_eq!(sym.cells[1], majit_ir::OpRef::input_arg_int(2)); + assert_eq!(sym.__vable_identity, majit_ir::OpRef::input_arg_ref(3)); + assert_eq!(sym.acc, majit_ir::OpRef::input_arg_float(4)); + + ::clear_sym_inputarg_bindings(&mut sym); + + assert!(sym.sp.is_none(), "int scalar kept its position"); + assert!(sym.cells[0].is_none(), "array cell 0 kept its position"); + assert!(sym.cells[1].is_none(), "array cell 1 kept its position"); + assert!( + sym.__vable_identity.is_none(), + "the virtualizable identity kept its position" + ); + assert!(sym.acc.is_none(), "float scalar kept its position"); + + // The concrete mirrors are runtime data, not positions: clearing must + // not touch them, or the bridge loses the values `initialize_sym` read + // off the live state. + assert_eq!(sym.sp_value, 7); + assert_eq!(sym.cells_values, vec![11, 13]); + assert_eq!(sym.acc_value, 1.5f64.to_bits() as i64); + } } diff --git a/majit/majit-metainterp/tests/jit_interp_halt_arm_post_loop_expression.rs b/majit/majit-metainterp/tests/jit_interp_halt_arm_post_loop_expression.rs new file mode 100644 index 00000000000..aaf263b770a --- /dev/null +++ b/majit/majit-metainterp/tests/jit_interp_halt_arm_post_loop_expression.rs @@ -0,0 +1,184 @@ +//! Ensures a `break` arm leaves post-loop work to native execution. +//! +//! The fixture gives the trailing expression an observable state mutation, so +//! routing a halt through that expression during tracing would apply the +//! epilogue twice. + +use std::sync::atomic::{AtomicUsize, Ordering}; + +use majit_metainterp::{Assembler, JitCode, JitDriver}; + +pub type Bytecode = [u8]; + +/// `acc += cnt`. +const OP_ADD: u8 = 1; +/// `cnt -= 1`. +const OP_DEC: u8 = 2; +/// `[OP_BACK, target]` — jump to `target` while `cnt > 0`, else fall past. +const OP_BACK: u8 = 3; +/// Bare `break` arm, the spelling that classifies as `ArmPattern::Halt`. +const OP_END: u8 = 4; + +/// What the post-loop expression adds. A round number, so a doubled +/// application is unmistakable in the failure message rather than reading like +/// an off-by-one somewhere else. +const EPILOGUE_BONUS: i64 = 100; + +/// Loops majit compiled during the most recent [`run`] — evidence the JIT tier +/// was alive for it. Process-global, so every read is bracketed by +/// [`PROBE_LOCK`]; a load taken outside that window observes another test's +/// compile. +static COMPILES: AtomicUsize = AtomicUsize::new(0); + +static PROBE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + +struct HaltPostLoopState { + acc: i64, + cnt: i64, +} + +#[majit_macros::jit_interp( + state = HaltPostLoopState, + env = Bytecode, + greens = [pc, program], + state_fields = { + acc: int, + cnt: int, + }, +)] +#[allow(unused_assignments, unused_variables)] +fn dispatch_halt_post_loop(program: &Bytecode, threshold: u32, n: i64) -> i64 { + let mut driver: JitDriver = JitDriver::new(threshold); + driver.set_on_compile_loop(|_, _, _, _| { + COMPILES.fetch_add(1, Ordering::Relaxed); + }); + let mut pc: usize = 0; + let mut state = HaltPostLoopState { acc: 0, cnt: n }; + { + use majit_metainterp::JitState as _; + state + .build_meta(0, program) + .install_canonical_liveness(&mut driver); + } + while pc < program.len() { + jit_merge_point!(driver, program, pc; state); + let opcode = program[pc]; + pc += 1; + match opcode { + OP_ADD => { + state.acc = state.acc + state.cnt; + } + OP_DEC => { + state.cnt = state.cnt - 1; + } + OP_BACK => { + let target = program[pc] as usize; + pc += 1; + if state.cnt > 0 { + if target < pc { + can_enter_jit!(driver, target, &mut state, program, || {}); + } + pc = target; + continue; + } + } + _ => break, + } + } + // The post-loop expression, shaped like tlc's: a trailing `if` whose taken + // branch STORES to a scalar state field before yielding the value. Running + // it twice adds `EPILOGUE_BONUS` twice. + if state.cnt < 0 { + 0 + } else { + state.acc = state.acc + EPILOGUE_BONUS; + state.acc + } +} + +/// `[OP_ADD, OP_DEC, OP_BACK, 0, OP_END]`. +fn program() -> Vec { + vec![OP_ADD, OP_DEC, OP_BACK, 0, OP_END] +} + +/// What the same program computes with no JIT tier involved at all, post-loop +/// expression applied exactly once. +fn interpret(n: i64) -> i64 { + let (mut acc, mut cnt) = (0i64, n); + loop { + acc += cnt; + cnt -= 1; + if cnt > 0 { + continue; + } + break; + } + if cnt < 0 { 0 } else { acc + EPILOGUE_BONUS } +} + +/// Run one `(threshold, n)` pair, returning the answer and the number of loops +/// compiled during it. The counter is reset *and* read under [`PROBE_LOCK`]; +/// reading after the guard drops would reintroduce exactly the race the lock +/// exists to remove. +fn run(threshold: u32, n: i64) -> (i64, usize) { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + COMPILES.store(0, Ordering::Relaxed); + let got = dispatch_halt_post_loop(&program(), threshold, n); + (got, COMPILES.load(Ordering::Relaxed)) +} + +fn install() -> JitCode { + let mut asm = Assembler::new(); + asm.set_canonical_liveness_triple(vec![0], vec![], vec![0]); + __prebuild_jitcode_liveness_dispatch_halt_post_loop(&mut asm); + let _ = asm.ensure_canonical_liveness_offset(); + __dispatch_jitcode_dispatch_halt_post_loop(&mut asm, 0i64) + .expect("dispatch lower must succeed for fixture") +} + +/// The fixture is only meaningful while the dispatch actually lowers. +#[test] +fn dispatch_lowers() { + let _ = install(); +} + +/// The post-loop expression must be applied exactly once. +/// +/// Each pair satisfies `n == threshold + 1`, the only relation under which the +/// walk is still recording when the loop exits (see the header). The compile +/// assertion is not decoration: at any other `n` this program answers correctly +/// even with the defect present, so a fixture that quietly stopped tracing +/// would pass while testing nothing. +#[test] +fn a_halt_arm_does_not_run_the_post_loop_expression() { + for (threshold, n) in [(2u32, 3i64), (4, 5), (8, 9)] { + let (got, compiles) = run(threshold, n); + assert!( + compiles >= 1, + "threshold={threshold} n={n}: no loop compiled, so the walk never \ + reached the `break` and this case tests nothing" + ); + assert_eq!( + got, + interpret(n), + "threshold={threshold} n={n}: the `break` arm diverted through \ + `default_label`, so the walk ran the post-loop expression and the \ + write-back carried its store into native state, which then ran it \ + a second time" + ); + } +} + +/// The answer must be right at every trip count, not only at the three the +/// test above singles out. This sweep spans both regimes — the `CloseLoop` +/// walks that the whole example corpus exercises, and the three `Finish` walks +/// — so it also guards against a fix that traded one regime for the other. +#[test] +fn every_threshold_and_trip_count_answers_correctly() { + for threshold in [2u32, 4, 8] { + for n in 2i64..30 { + let (got, _) = run(threshold, n); + assert_eq!(got, interpret(n), "threshold={threshold} n={n}"); + } + } +} diff --git a/majit/majit-metainterp/tests/jit_interp_label_entry_deopt_resume.rs b/majit/majit-metainterp/tests/jit_interp_label_entry_deopt_resume.rs new file mode 100644 index 00000000000..b7232ca4513 --- /dev/null +++ b/majit/majit-metainterp/tests/jit_interp_label_entry_deopt_resume.rs @@ -0,0 +1,195 @@ +//! Ensures a label-entered compiled loop deoptimizes to the program counter +//! reached by the blackhole interpreter. +//! +//! Resuming at the loop header would repeat non-idempotent instructions between +//! the header and the blackhole's green program counter. + +use core::sync::atomic::{AtomicU32, Ordering}; + +pub type Bytecode = [i64]; + +const OP_LOAD: i64 = 1; // [LOAD, imm, dst] pc += 3 +const OP_ADD: i64 = 2; // [ADD, a, b, dst] regs[dst] = a + b pc += 4 +const OP_JIA: i64 = 3; // [JIA, a, b, target] jump while a > b pc += 4 +const OP_RETURN: i64 = 4; // [RETURN, reg] terminal + +const R_I: i64 = 0; // trip counter +const R_N: i64 = 1; // trip limit +const R_ACC: i64 = 2; // accumulator, advanced once per trip +const R_ONE: i64 = 3; +const NUM_REGS: usize = 4; + +/// `[prologue] [header: acc += 1; i += 1; jump while n > i] return acc`. +/// +/// The prologue is four `LOAD`s, so the loop header sits at word 12 and the +/// terminal `RETURN` at word 24 — the two pcs a deopt could resume at. +fn count_program(n: i64) -> Vec { + let mut p = vec![ + OP_LOAD, 0, R_I, // + OP_LOAD, n, R_N, // + OP_LOAD, 0, R_ACC, // + OP_LOAD, 1, R_ONE, // + ]; + let header = p.len() as i64; + assert_eq!(header, 12); + p.extend_from_slice(&[ + OP_ADD, R_ACC, R_ONE, R_ACC, // + OP_ADD, R_I, R_ONE, R_I, // + OP_JIA, R_N, R_I, header, // + OP_RETURN, R_ACC, // + ]); + p +} + +static COMPILES: AtomicU32 = AtomicU32::new(0); + +struct VmState { + regs: Vec, + ret: i64, +} + +#[majit_macros::jit_interp( + state = VmState, + env = Bytecode, + greens = [pc, program], + state_fields = { + regs: [int; virt], + ret: int, + }, +)] +#[allow(unused_assignments, unused_variables)] +fn mainloop(program: &Bytecode, threshold: u32) -> i64 { + let mut driver: majit_metainterp::JitDriver = + majit_metainterp::JitDriver::new(threshold); + driver.set_on_compile_loop(|_gk, _b, _a, _opcodes| { + COMPILES.fetch_add(1, Ordering::Relaxed); + }); + let mut pc: usize = 0; + let mut state = VmState { + regs: vec![0; NUM_REGS], + ret: 0, + }; + { + use majit_metainterp::JitState as _; + state + .build_meta(0, program) + .install_canonical_liveness(&mut driver); + } + + loop { + jit_merge_point!(driver, program, pc; state); + + let opcode = program[pc]; + match opcode { + OP_LOAD => { + state.regs[program[pc + 2] as usize] = program[pc + 1]; + pc += 3; + } + OP_ADD => { + let a = program[pc + 1] as usize; + let b = program[pc + 2] as usize; + let d = program[pc + 3] as usize; + state.regs[d] = state.regs[a] + state.regs[b]; + pc += 4; + } + OP_JIA => { + let a = program[pc + 1] as usize; + let b = program[pc + 2] as usize; + let tgt = program[pc + 3] as usize; + if state.regs[a] > state.regs[b] { + if tgt < pc { + can_enter_jit!(driver, tgt, &mut state, program, || {}); + } + pc = tgt; + continue; + } + pc += 4; + } + // Store then in-arm `return`, never `{ store; break }`: `classify.rs` + // `is_break_expr` requires the arm body to be exactly `break`, so a + // composite body classifies `Lowerable` and its tail `break` reaches + // `lower_stmt_fallback`, which guards an enclosed `return` but not an + // enclosed `break` — the statement is inert and silently dropped, + // leaving the lowered arm to fall through to the dispatch back-edge. + OP_RETURN => { + state.ret = state.regs[program[pc + 1] as usize]; + return state.ret; + } + _ => panic!("bad opcode {opcode}"), + } + } + state.ret +} + +/// The same bytecode with no driver, no merge point and no `can_enter_jit`. +fn clean_interp(program: &Bytecode) -> i64 { + let mut regs = vec![0i64; NUM_REGS]; + let mut pc = 0usize; + loop { + match program[pc] { + OP_LOAD => { + regs[program[pc + 2] as usize] = program[pc + 1]; + pc += 3; + } + OP_ADD => { + regs[program[pc + 3] as usize] = + regs[program[pc + 1] as usize] + regs[program[pc + 2] as usize]; + pc += 4; + } + OP_JIA => { + if regs[program[pc + 1] as usize] > regs[program[pc + 2] as usize] { + pc = program[pc + 3] as usize; + continue; + } + pc += 4; + } + OP_RETURN => return regs[program[pc + 1] as usize], + op => panic!("bad opcode {op}"), + } + } +} + +#[test] +fn a_label_entered_deopt_resumes_at_the_green_pc() { + for n in [1_000i64, 1_001] { + let program = count_program(n); + COMPILES.store(0, Ordering::Relaxed); + + let clean = clean_interp(&program); + assert_eq!(clean, n, "fixture: the clean interpreter must run n trips"); + + let got = mainloop(&program, 3); + + assert!( + COMPILES.load(Ordering::Relaxed) >= 1, + "n={n}: nothing compiled, so the machine never reached the \ + label-entered deopt this test exists to exercise" + ); + assert_eq!( + got, clean, + "n={n}: the JIT tier answered {got} where the clean interpreter \ + answered {clean}. A result of n+1 means the deopt resumed at the \ + loop header and re-ran the header..green-pc span on state that had \ + already passed it" + ); + } +} + +/// The same machine with the JIT effectively off, so a failure above can be +/// attributed to the compiled tier rather than to the bytecode or the fixture. +#[test] +fn the_same_machine_without_tracing_answers_n() { + for n in [1_000i64, 1_001] { + let program = count_program(n); + COMPILES.store(0, Ordering::Relaxed); + + let got = mainloop(&program, u32::MAX); + + assert_eq!( + COMPILES.load(Ordering::Relaxed), + 0, + "n={n}: an unreachable threshold must not compile anything" + ); + assert_eq!(got, n, "n={n}: the untraced dispatch loop answered {got}"); + } +} diff --git a/majit/majit-metainterp/tests/jit_interp_polymorphic_liveness.rs b/majit/majit-metainterp/tests/jit_interp_polymorphic_liveness.rs index bf6e9a54a10..74c4ef89396 100644 --- a/majit/majit-metainterp/tests/jit_interp_polymorphic_liveness.rs +++ b/majit/majit-metainterp/tests/jit_interp_polymorphic_liveness.rs @@ -36,8 +36,6 @@ use majit_metainterp::jitcode::insns::BC_LIVE; use majit_metainterp::{Assembler, JitCode, JitDriver, JitState as _}; -// ── Synthetic state ──────────────────────────────────────────────── - struct Polymorphic4State { a: i64, b: i64, @@ -90,6 +88,7 @@ impl BytecodeExt for [u8] { c: int, d: int, }, + greens = [], )] #[allow(unused_assignments, unused_variables)] fn polymorphic_mainloop(program: &Bytecode, threshold: u32) -> i64 { @@ -139,8 +138,6 @@ fn polymorphic_mainloop(program: &Bytecode, threshold: u32) -> i64 { state.a } -// ── Helpers ──────────────────────────────────────────────────────── - /// Walk a JitCode body and collect every BC_LIVE marker's 2-byte offset. fn collect_bc_live_offsets(jitcode: &JitCode) -> Vec { let code = &jitcode.code; @@ -163,26 +160,8 @@ fn collect_bc_live_offsets(jitcode: &JitCode) -> Vec { offsets } -// ── Tests ────────────────────────────────────────────────────────── - -/// Mirror the production install ordering from -/// `codegen_state.rs::install_canonical_liveness`: -/// 1. `set_canonical_liveness_triple` stages the canonical triple lazily. -/// 2. `__prebuild_jitcode_liveness_*` writes the per-marker triples first -/// (so they occupy the head of `all_liveness`). -/// 3. `ensure_canonical_liveness_offset` registers the canonical triple -/// at the tail — matching RPython `assembler.assemble` where per-marker -/// `-live-` entries occupy the early offsets and pyre's canonical entry -/// is a leading-dummy affordance bound after the IR walk. -/// -/// The earlier shape here ran `_register_liveness_offset(canonical, …)` -/// up front, which (a) forced canonical to offset 0 — the very layout the -/// deferred-canonical patcher was introduced to remove — and (b) skipped -/// the `set_canonical_liveness_triple` staging step entirely, so the -/// `finalize_liveness` path the test was meant to validate ran against an -/// uninitialised triple slot. The new helper drives the same call sequence -/// production goes through, so each test below validates the actual install -/// order rather than a synthesised one. +/// Mirrors production ordering: stage canonical liveness, prebuild per-marker +/// entries, then bind the canonical entry after the IR walk. fn install_canonical_for_test(asm: &mut Assembler, canonical: &[u8]) { asm.set_canonical_liveness_triple(canonical.to_vec(), Vec::new(), Vec::new()); __prebuild_jitcode_liveness_polymorphic_mainloop(asm); @@ -289,11 +268,8 @@ fn distinct_arms_emit_distinct_bc_live_offsets() { // only). Skip — not a lowerable arm sub-JitCode. continue; } - // Strip the leading canonical marker emitted by - // `live_placeholder()` at the head of every lowerable arm - // body. The remaining offsets are per-pc markers from - // `live_placeholder_with_triple` inside the arm body - // (compute_per_marker_liveness output). + // The first offset is the canonical marker; the rest are per-pc + // liveness markers emitted inside the arm body. let per_pc: Vec = offs.into_iter().skip(1).collect(); assert!( !per_pc.is_empty(), diff --git a/majit/majit-metainterp/tests/jit_interp_ref_state_field.rs b/majit/majit-metainterp/tests/jit_interp_ref_state_field.rs index c1f96777b43..9b8038ec80c 100644 --- a/majit/majit-metainterp/tests/jit_interp_ref_state_field.rs +++ b/majit/majit-metainterp/tests/jit_interp_ref_state_field.rs @@ -39,6 +39,7 @@ impl BytecodeExt for [u8] { state = RefTestState, env = Bytecode, state_fields = { a: int, sel: ref(Stack) }, + greens = [], )] #[allow(clippy::self_assignment)] #[allow(unused_assignments, unused_variables)] diff --git a/majit/majit-metainterp/tests/jit_interp_two_machines_one_module.rs b/majit/majit-metainterp/tests/jit_interp_two_machines_one_module.rs new file mode 100644 index 00000000000..af902d2f41a --- /dev/null +++ b/majit/majit-metainterp/tests/jit_interp_two_machines_one_module.rs @@ -0,0 +1,246 @@ +//! Two `#[jit_interp]` machines must be able to share one module. +//! +//! Every item the macro emits lands in the CALLER's module. Five of them used +//! to carry fixed names (`__JitMeta`, `__JitSym`, `__jit_loop_carried_boxes`, +//! `__majit_recursive_fresh_alloc`, `__majit_recursive_fresh_free`) while five +//! others were already suffixed with the annotated function's name, so a second +//! machine in the same module failed with E0428 "defined multiple times". Every +//! machine in the tree worked around it by living in its own `mod`. +//! +//! THIS FIXTURE NEEDS BOTH SHAPES, and a green run with only one proves +//! nothing about the other two idents. `__majit_recursive_fresh_alloc` / +//! `_free` are emitted ONLY when `supports_fresh_alloc` holds — zero ref +//! scalars, no opaque carriers, no fixed arrays, and exactly one `[int; virt]` +//! array (`codegen_state.rs` `let supports_fresh_alloc`). So: +//! * `CounterState` is scalars-only -> supports_fresh_alloc = FALSE (3 idents) +//! * `StackState` has one virt array -> supports_fresh_alloc = TRUE (5 idents) +//! Adding a machine here without checking which side of that predicate it falls +//! on re-opens the hole this fixture exists to close. +//! +//! CEILING, deliberately not tested because it cannot be fixed by naming: +//! two machines over the SAME state type still collide on +//! `impl JitState for #state_type` (E0119). Unique idents buy co-residence only +//! for machines whose `state` types differ. + +pub type Bytecode = [u8]; + +const C_ADD: u8 = 1; // acc += n; n -= 1 +const C_BR_COND: u8 = 2; // [C_BR_COND, off]: if n != 0 jump +const C_RETURN: u8 = 3; + +const PUSH: u8 = 2; +const POP: u8 = 3; +const SWAP: u8 = 4; +const PICK: u8 = 6; +const ADD: u8 = 8; +const SUB: u8 = 9; +const BR_COND: u8 = 18; +const RETURN: u8 = 21; +const PUSHARG: u8 = 22; + +// Scalars only: supports_fresh_alloc is FALSE, so this machine alone would +// leave `__majit_recursive_fresh_alloc` / `_free` unexercised. +struct CounterState { + acc: i64, + n: i64, +} + +#[majit_macros::jit_interp( + state = CounterState, + env = Bytecode, + auto_calls = true, + greens = [pc, program], + state_fields = { + acc: int, + n: int, + }, +)] +#[allow(unused_assignments, unused_variables)] +pub fn mainloop_counter(program: &Bytecode, inputarg: i64, threshold: u32) -> i64 { + let mut driver: majit_metainterp::JitDriver = + majit_metainterp::JitDriver::new(threshold); + let mut pc: usize = 0; + let mut state = CounterState { + acc: 0, + n: inputarg, + }; + { + use majit_metainterp::JitState as _; + state + .build_meta(0, program) + .install_canonical_liveness(&mut driver); + } + + while pc < program.len() { + jit_merge_point!(driver, program, pc; state); + + let opcode = program[pc]; + pc += 1; + + match opcode { + C_ADD => { + state.acc = state.acc + state.n; + state.n = state.n - 1; + } + C_BR_COND => { + let offset = program[pc] as i8 as i64; + let target = ((pc as i64) + offset + 1) as usize; + pc += 1; + if state.n != 0 { + if target <= pc { + can_enter_jit!(driver, target, &mut state, program, || {}); + } + pc = target; + continue; + } + } + C_RETURN => break, + _ => {} + } + } + + state.acc +} + +/// `acc = n + (n-1) + ... + 1`, a hot loop over two int scalars. +fn counter_program() -> Vec { + vec![ + C_ADD, // 0 + C_BR_COND, 253, // 1 (-3 -> back to 0) + C_RETURN, // 3 + ] +} + +// One `[int; virt]` array and no ref scalars/opaques/fixed arrays: this is the +// shape that turns `supports_fresh_alloc` ON, so the two extern "C" idents are +// emitted here and nowhere else in this file. +struct StackState { + stackpos: i64, + stack: Vec, +} + +#[majit_macros::jit_interp( + state = StackState, + env = Bytecode, + auto_calls = true, + greens = [pc, program], + state_fields = { + stackpos: int, + stack: [int; virt], + }, +)] +#[allow(unused_assignments, unused_variables)] +pub fn mainloop_stack(program: &Bytecode, inputarg: i64, threshold: u32) -> i64 { + let mut driver: majit_metainterp::JitDriver = + majit_metainterp::JitDriver::new(threshold); + let mut pc: usize = 0; + let mut state = StackState { + stackpos: 0, + stack: vec![0i64; program.len()], + }; + { + use majit_metainterp::JitState as _; + state + .build_meta(0, program) + .install_canonical_liveness(&mut driver); + } + + while pc < program.len() { + jit_merge_point!(driver, program, pc; state); + + let opcode = program[pc]; + pc += 1; + + match opcode { + PUSH => { + let value = program[pc] as i8 as i64; + pc += 1; + state.stack[state.stackpos as usize] = value; + state.stackpos = state.stackpos + 1; + } + POP => { + state.stackpos = state.stackpos - 1; + } + SWAP => { + let a = state.stack[(state.stackpos - 1) as usize]; + let b = state.stack[(state.stackpos - 2) as usize]; + state.stack[(state.stackpos - 1) as usize] = b; + state.stack[(state.stackpos - 2) as usize] = a; + } + PICK => { + let i = program[pc] as usize; + pc += 1; + let v = state.stack[(state.stackpos as usize) - i - 1]; + state.stack[state.stackpos as usize] = v; + state.stackpos = state.stackpos + 1; + } + ADD => { + let a = state.stack[(state.stackpos - 1) as usize]; + let b = state.stack[(state.stackpos - 2) as usize]; + state.stack[(state.stackpos - 2) as usize] = b + a; + state.stackpos = state.stackpos - 1; + } + SUB => { + let a = state.stack[(state.stackpos - 1) as usize]; + let b = state.stack[(state.stackpos - 2) as usize]; + state.stack[(state.stackpos - 2) as usize] = b - a; + state.stackpos = state.stackpos - 1; + } + BR_COND => { + let offset = program[pc] as i8 as i64; + let target = ((pc as i64) + offset + 1) as usize; + pc += 1; + state.stackpos = state.stackpos - 1; + let jump = state.stack[state.stackpos as usize] != 0; + if jump { + if target <= pc { + can_enter_jit!(driver, target, &mut state, program, || {}); + } + pc = target; + continue; + } + } + RETURN => break, + PUSHARG => { + state.stack[state.stackpos as usize] = inputarg; + state.stackpos = state.stackpos + 1; + } + _ => {} + } + } + + state.stackpos = state.stackpos - 1; + state.stack[state.stackpos as usize] +} + +/// `sum(N) = N + (N-1) + ... + 1`, a hot loop over the virt stack. +fn sum_program() -> Vec { + vec![ + PUSH, 0, // 0 + PUSHARG, // 2 + PICK, 0, // 3 (loop header) + BR_COND, 2, // 5 -> body @9 + POP, // 7 + RETURN, // 8 + SWAP, // 9 + PICK, 1, // 10 + ADD, // 12 + SWAP, // 13 + PUSH, 1, SUB, // 14 + PUSH, 1, // 17 + BR_COND, 238, // 19 -> back to the header @3 + ] +} + +/// The compile-time assertion is that this file builds at all: before the five +/// idents were suffixed, two machines in one module were an E0428. The runtime +/// assertions confirm each machine still computes its own answer, i.e. that +/// renaming did not cross the two expansions' wires. +#[test] +fn two_machines_share_one_module() { + let n = 10i64; + let expected = n * (n + 1) / 2; // 55 + + assert_eq!(mainloop_counter(&counter_program(), n, 4), expected); + assert_eq!(mainloop_stack(&sum_program(), n, 4), expected); +} diff --git a/majit/majit-metainterp/tests/jit_interp_two_virt_arrays_with_scalar.rs b/majit/majit-metainterp/tests/jit_interp_two_virt_arrays_with_scalar.rs new file mode 100644 index 00000000000..9fd9d237fd2 --- /dev/null +++ b/majit/majit-metainterp/tests/jit_interp_two_virt_arrays_with_scalar.rs @@ -0,0 +1,589 @@ +//! Verifies that virtualizable identity lookup uses its declared input slot +//! rather than assuming slot zero. +//! +//! State-field layouts place integer scalars before the virtualizable identity. +//! The fixture varies array count and scalar liveness to ensure a scalar is not +//! misclassified as the identity reference. + +use core::sync::atomic::{AtomicU32, Ordering}; + +pub type Bytecode = [u8]; + +const OP_ACC: u8 = 1; // advance the machine's accumulator +const OP_INC: u8 = 2; // iregs[0] += 1 +const OP_JUMP_BACK: u8 = 3; // [OP_JUMP_BACK, target]: loop while iregs[0] < iregs[1] +const OP_RETURN: u8 = 4; // yield iregs[0] + +/// `iregs = [counter, limit]`; the accumulator is one element or one scalar. +fn count_program() -> Vec { + vec![OP_ACC, OP_INC, OP_JUMP_BACK, 0, OP_RETURN] +} + +/// What every machine here must return for a given limit. +fn expected(limit: i64) -> i64 { + limit +} + +/// Control with two virtualizable arrays and no scalar preceding the identity. +mod two_arrays_no_scalar { + use super::{AtomicU32, Bytecode, OP_ACC, OP_INC, OP_JUMP_BACK, OP_RETURN, Ordering}; + + static COMPILES: AtomicU32 = AtomicU32::new(0); + + struct TwoArrays { + iregs: Vec, + fregs: Vec, + } + + #[majit_macros::jit_interp( + state = TwoArrays, + env = Bytecode, + greens = [pc, program], + state_fields = { + iregs: [int; virt], + fregs: [float; virt], + }, + )] + #[allow(unused_assignments, unused_variables)] + fn mainloop(program: &Bytecode, limit: i64, threshold: u32) -> i64 { + let mut driver: majit_metainterp::JitDriver = + majit_metainterp::JitDriver::new(threshold); + driver.set_on_compile_loop(|_gk, _b, _a, _opcodes| { + COMPILES.fetch_add(1, Ordering::Relaxed); + }); + let mut pc: usize = 0; + let mut state = TwoArrays { + iregs: vec![0i64, limit], + fregs: vec![0.0f64], + }; + { + use majit_metainterp::JitState as _; + state + .build_meta(0, program) + .install_canonical_liveness(&mut driver); + } + + while pc < program.len() { + jit_merge_point!(driver, program, pc; state); + + let opcode = program[pc]; + pc += 1; + + match opcode { + OP_ACC => state.fregs[0] = state.fregs[0] + 1.25, + OP_INC => state.iregs[0] = state.iregs[0] + 1, + OP_JUMP_BACK => { + let target = program[pc] as usize; + pc += 1; + if state.iregs[0] < state.iregs[1] { + if target < pc { + can_enter_jit!(driver, target, &mut state, program, || {}); + } + pc = target; + continue; + } + } + OP_RETURN => break, + _ => panic!("bad opcode {opcode}"), + } + } + // A constant index into a transferred virt array is readable after the + // loop; the walk-final element value arrives via + // `writeback_virt_array_state_fields`. + state.iregs[0] + } + + #[test] + fn two_virt_arrays_without_a_scalar_compile_the_hot_loop() { + let program = super::count_program(); + let limit = 50i64; + COMPILES.store(0, Ordering::Relaxed); + + let got = mainloop(&program, limit, 3); + + assert_eq!( + got, + super::expected(limit), + "two-array machine answered wrong" + ); + assert!( + COMPILES.load(Ordering::Relaxed) >= 1, + "the two-array control never compiled its hot loop, so the \ + experiments below have no baseline to be compared against" + ); + } +} + +/// One virt array and two scalars. `colscalar` in `majit/examples/cel` is this +/// shape and runs today; it is repeated here so the failing machine below +/// differs from a passing one in exactly one declaration. +mod one_array_with_scalar { + use super::{AtomicU32, Bytecode, OP_ACC, OP_INC, OP_JUMP_BACK, OP_RETURN, Ordering}; + + static COMPILES: AtomicU32 = AtomicU32::new(0); + + struct OneArrayScalar { + iregs: Vec, + acc: i64, + ret: i64, + } + + #[majit_macros::jit_interp( + state = OneArrayScalar, + env = Bytecode, + greens = [pc, program], + state_fields = { + iregs: [int; virt], + acc: int, + ret: int, + }, + )] + #[allow(unused_assignments, unused_variables)] + fn mainloop(program: &Bytecode, limit: i64, threshold: u32) -> i64 { + let mut driver: majit_metainterp::JitDriver = + majit_metainterp::JitDriver::new(threshold); + driver.set_on_compile_loop(|_gk, _b, _a, _opcodes| { + COMPILES.fetch_add(1, Ordering::Relaxed); + }); + let mut pc: usize = 0; + let mut state = OneArrayScalar { + iregs: vec![0i64, limit], + acc: 0, + ret: 0, + }; + { + use majit_metainterp::JitState as _; + state + .build_meta(0, program) + .install_canonical_liveness(&mut driver); + } + + while pc < program.len() { + jit_merge_point!(driver, program, pc; state); + + let opcode = program[pc]; + pc += 1; + + match opcode { + OP_ACC => state.acc = state.acc + 2, + OP_INC => state.iregs[0] = state.iregs[0] + 1, + OP_JUMP_BACK => { + let target = program[pc] as usize; + pc += 1; + if state.iregs[0] < state.iregs[1] { + if target < pc { + can_enter_jit!(driver, target, &mut state, program, || {}); + } + pc = target; + continue; + } + } + OP_RETURN => { + state.ret = state.iregs[0]; + return state.ret; + } + _ => panic!("bad opcode {opcode}"), + } + } + state.ret + } + + #[test] + fn one_virt_array_with_scalars_compiles_the_hot_loop() { + let program = super::count_program(); + let limit = 50i64; + COMPILES.store(0, Ordering::Relaxed); + + let got = mainloop(&program, limit, 3); + + assert_eq!( + got, + super::expected(limit), + "scalar carried the wrong result out" + ); + assert!( + COMPILES.load(Ordering::Relaxed) >= 1, + "one virt array beside two `int` scalars did not compile — that \ + combination has shipping consumers, so a failure here means the \ + fixture is wrong, not the combination" + ); + } +} + +/// Two virt arrays AND a scalar — the combination with no consumer. Identical to +/// `two_arrays_no_scalar` except that the result leaves the loop in a declared +/// `int` scalar instead of in element 0 of the int array. +mod two_arrays_with_scalar { + use super::{AtomicU32, Bytecode, OP_ACC, OP_INC, OP_JUMP_BACK, OP_RETURN, Ordering}; + + static COMPILES: AtomicU32 = AtomicU32::new(0); + + struct TwoArraysScalar { + iregs: Vec, + fregs: Vec, + ret: i64, + } + + #[majit_macros::jit_interp( + state = TwoArraysScalar, + env = Bytecode, + greens = [pc, program], + state_fields = { + iregs: [int; virt], + fregs: [float; virt], + ret: int, + }, + )] + #[allow(unused_assignments, unused_variables)] + fn mainloop(program: &Bytecode, limit: i64, threshold: u32) -> i64 { + let mut driver: majit_metainterp::JitDriver = + majit_metainterp::JitDriver::new(threshold); + driver.set_on_compile_loop(|_gk, _b, _a, _opcodes| { + COMPILES.fetch_add(1, Ordering::Relaxed); + }); + let mut pc: usize = 0; + let mut state = TwoArraysScalar { + iregs: vec![0i64, limit], + fregs: vec![0.0f64], + ret: 0, + }; + { + use majit_metainterp::JitState as _; + state + .build_meta(0, program) + .install_canonical_liveness(&mut driver); + } + + while pc < program.len() { + jit_merge_point!(driver, program, pc; state); + + let opcode = program[pc]; + pc += 1; + + match opcode { + OP_ACC => state.fregs[0] = state.fregs[0] + 1.25, + OP_INC => state.iregs[0] = state.iregs[0] + 1, + OP_JUMP_BACK => { + let target = program[pc] as usize; + pc += 1; + if state.iregs[0] < state.iregs[1] { + if target < pc { + can_enter_jit!(driver, target, &mut state, program, || {}); + } + pc = target; + continue; + } + } + OP_RETURN => { + state.ret = state.iregs[0]; + return state.ret; + } + _ => panic!("bad opcode {opcode}"), + } + } + state.ret + } + + #[test] + fn two_virt_arrays_with_a_scalar_compile_the_hot_loop() { + let program = super::count_program(); + let limit = 50i64; + COMPILES.store(0, Ordering::Relaxed); + + let got = mainloop(&program, limit, 3); + + assert_eq!( + got, + super::expected(limit), + "scalar carried the wrong result out" + ); + assert!( + COMPILES.load(Ordering::Relaxed) >= 1, + "declaring one `int` scalar beside two `[.. ; virt]` arrays stopped \ + the hot loop from compiling at all; every trace aborts with \ + VirtualStatesCantMatch on not-virtual slot 0 (expected Ref, got Int)" + ); + } +} + +/// Two virt arrays of the SAME item type, plus a scalar. `dualtape` +/// (`majit/examples/dualtape/src/jit_interp.rs:28-33`) declares this shape +/// (`pa: int, a: [int; virt], pb: int, b: [int; virt]`) and its tests pass — but +/// they assert result equality only, and `MAJIT_LOG=1 cargo test -p dualtape` +/// emits no `[jit]` line at all, so that suite never reaches tracing and says +/// nothing about whether the shape compiles. This machine asks the question it +/// leaves open, and separates "two arrays" from "two arrays of mixed types". +mod two_int_arrays_with_scalar { + use super::{AtomicU32, Bytecode, OP_ACC, OP_INC, OP_JUMP_BACK, OP_RETURN, Ordering}; + + static COMPILES: AtomicU32 = AtomicU32::new(0); + + struct TwoIntArraysScalar { + iregs: Vec, + aregs: Vec, + ret: i64, + } + + #[majit_macros::jit_interp( + state = TwoIntArraysScalar, + env = Bytecode, + greens = [pc, program], + state_fields = { + iregs: [int; virt], + aregs: [int; virt], + ret: int, + }, + )] + #[allow(unused_assignments, unused_variables)] + fn mainloop(program: &Bytecode, limit: i64, threshold: u32) -> i64 { + let mut driver: majit_metainterp::JitDriver = + majit_metainterp::JitDriver::new(threshold); + driver.set_on_compile_loop(|_gk, _b, _a, _opcodes| { + COMPILES.fetch_add(1, Ordering::Relaxed); + }); + let mut pc: usize = 0; + let mut state = TwoIntArraysScalar { + iregs: vec![0i64, limit], + aregs: vec![0i64], + ret: 0, + }; + { + use majit_metainterp::JitState as _; + state + .build_meta(0, program) + .install_canonical_liveness(&mut driver); + } + + while pc < program.len() { + jit_merge_point!(driver, program, pc; state); + + let opcode = program[pc]; + pc += 1; + + match opcode { + OP_ACC => state.aregs[0] = state.aregs[0] + 2, + OP_INC => state.iregs[0] = state.iregs[0] + 1, + OP_JUMP_BACK => { + let target = program[pc] as usize; + pc += 1; + if state.iregs[0] < state.iregs[1] { + if target < pc { + can_enter_jit!(driver, target, &mut state, program, || {}); + } + pc = target; + continue; + } + } + OP_RETURN => { + state.ret = state.iregs[0]; + return state.ret; + } + _ => panic!("bad opcode {opcode}"), + } + } + state.ret + } + + #[test] + fn two_int_virt_arrays_with_a_scalar_compile_the_hot_loop() { + let program = super::count_program(); + let limit = 50i64; + COMPILES.store(0, Ordering::Relaxed); + + let got = mainloop(&program, limit, 3); + + assert_eq!( + got, + super::expected(limit), + "scalar carried the wrong result out" + ); + assert!( + COMPILES.load(Ordering::Relaxed) >= 1, + "two same-typed `[int; virt]` arrays beside one `int` scalar did not \ + compile the hot loop" + ); + } +} + +/// PREDICTION (a): ONE virt array plus a single LOOP-DEAD scalar. If the array +/// count were the discriminator this would compile; if the discriminator is +/// "flat slot 0 holds a passthrough scalar", it fails. +mod one_array_dead_scalar { + use super::{AtomicU32, Bytecode, OP_ACC, OP_INC, OP_JUMP_BACK, OP_RETURN, Ordering}; + + static COMPILES: AtomicU32 = AtomicU32::new(0); + + struct OneArrayDeadScalar { + iregs: Vec, + ret: i64, + } + + #[majit_macros::jit_interp( + state = OneArrayDeadScalar, + env = Bytecode, + greens = [pc, program], + state_fields = { + iregs: [int; virt], + ret: int, + }, + )] + #[allow(unused_assignments, unused_variables)] + fn mainloop(program: &Bytecode, limit: i64, threshold: u32) -> i64 { + let mut driver: majit_metainterp::JitDriver = + majit_metainterp::JitDriver::new(threshold); + driver.set_on_compile_loop(|_gk, _b, _a, _opcodes| { + COMPILES.fetch_add(1, Ordering::Relaxed); + }); + let mut pc: usize = 0; + let mut state = OneArrayDeadScalar { + iregs: vec![0i64, limit, 0i64], + ret: 0, + }; + { + use majit_metainterp::JitState as _; + state + .build_meta(0, program) + .install_canonical_liveness(&mut driver); + } + + while pc < program.len() { + jit_merge_point!(driver, program, pc; state); + + let opcode = program[pc]; + pc += 1; + + match opcode { + OP_ACC => state.iregs[2] = state.iregs[2] + 2, + OP_INC => state.iregs[0] = state.iregs[0] + 1, + OP_JUMP_BACK => { + let target = program[pc] as usize; + pc += 1; + if state.iregs[0] < state.iregs[1] { + if target < pc { + can_enter_jit!(driver, target, &mut state, program, || {}); + } + pc = target; + continue; + } + } + OP_RETURN => { + state.ret = state.iregs[0]; + return state.ret; + } + _ => panic!("bad opcode {opcode}"), + } + } + state.ret + } + + #[test] + fn one_virt_array_with_one_loop_dead_scalar_compiles_the_hot_loop() { + let program = super::count_program(); + let limit = 50i64; + COMPILES.store(0, Ordering::Relaxed); + + let got = mainloop(&program, limit, 3); + + assert_eq!( + got, + super::expected(limit), + "scalar carried the wrong result out" + ); + assert!( + COMPILES.load(Ordering::Relaxed) >= 1, + "one virt array beside ONE loop-dead scalar did not compile" + ); + } +} + +/// PREDICTION (b): TWO virt arrays, and the only scalar is reassigned on every +/// iteration, so it does not reach the loop-close Jump as its own inputarg. If +/// the array count were the discriminator this would fail; if the discriminator +/// is the passthrough scalar at slot 0, it compiles. +mod two_arrays_live_scalar { + use super::{AtomicU32, Bytecode, OP_ACC, OP_INC, OP_JUMP_BACK, OP_RETURN, Ordering}; + + static COMPILES: AtomicU32 = AtomicU32::new(0); + + struct TwoArraysLiveScalar { + acc: i64, + iregs: Vec, + fregs: Vec, + } + + #[majit_macros::jit_interp( + state = TwoArraysLiveScalar, + env = Bytecode, + greens = [pc, program], + state_fields = { + acc: int, + iregs: [int; virt], + fregs: [float; virt], + }, + )] + #[allow(unused_assignments, unused_variables)] + fn mainloop(program: &Bytecode, limit: i64, threshold: u32) -> i64 { + let mut driver: majit_metainterp::JitDriver = + majit_metainterp::JitDriver::new(threshold); + driver.set_on_compile_loop(|_gk, _b, _a, _opcodes| { + COMPILES.fetch_add(1, Ordering::Relaxed); + }); + let mut pc: usize = 0; + let mut state = TwoArraysLiveScalar { + acc: 0, + iregs: vec![0i64, limit], + fregs: vec![0.0f64], + }; + { + use majit_metainterp::JitState as _; + state + .build_meta(0, program) + .install_canonical_liveness(&mut driver); + } + + while pc < program.len() { + jit_merge_point!(driver, program, pc; state); + + let opcode = program[pc]; + pc += 1; + + match opcode { + OP_ACC => state.acc = state.acc + 2, + OP_INC => state.iregs[0] = state.iregs[0] + 1, + OP_JUMP_BACK => { + let target = program[pc] as usize; + pc += 1; + if state.iregs[0] < state.iregs[1] { + if target < pc { + can_enter_jit!(driver, target, &mut state, program, || {}); + } + pc = target; + continue; + } + } + OP_RETURN => break, + _ => panic!("bad opcode {opcode}"), + } + } + state.iregs[0] + } + + #[test] + fn two_virt_arrays_with_a_loop_live_scalar_compile_the_hot_loop() { + let program = super::count_program(); + let limit = 50i64; + COMPILES.store(0, Ordering::Relaxed); + + let got = mainloop(&program, limit, 3); + + assert_eq!( + got, + super::expected(limit), + "two-array machine answered wrong" + ); + assert!( + COMPILES.load(Ordering::Relaxed) >= 1, + "two virt arrays beside ONE loop-live scalar did not compile" + ); + } +} diff --git a/majit/majit-metainterp/tests/mc_diag_mirror.rs b/majit/majit-metainterp/tests/mc_diag_mirror.rs new file mode 100644 index 00000000000..9e35a7e73f9 --- /dev/null +++ b/majit/majit-metainterp/tests/mc_diag_mirror.rs @@ -0,0 +1,263 @@ +//! Keeps `pyre-wasm-runner`'s positional diagnostic labels synchronized with +//! `MC_DIAG_LABELS`. +//! +//! The runner intentionally does not depend on the metainterpreter crate, so +//! this test compares the two source declarations and validates the parser +//! against `MC_DIAG_SLOTS`. + +use std::path::{Path, PathBuf}; + +fn repo_root() -> PathBuf { + // `/majit/majit-metainterp` -> `` + Path::new(env!("CARGO_MANIFEST_DIR")) + .ancestors() + .nth(2) + .expect("CARGO_MANIFEST_DIR has fewer than two ancestors") + .to_path_buf() +} + +fn read(path: &Path) -> String { + // A missing file must FAIL rather than skip. This test's whole job is to + // compare two files; one that cannot find them and passes anyway is the + // failure mode it exists to prevent, one level up. + std::fs::read_to_string(path).unwrap_or_else(|e| panic!("cannot read {} — {e}", path.display())) +} + +/// The string literals of the first array literal following `anchor`. +/// +/// The anchor is load-bearing on BOTH sides, and getting it wrong is silent: +/// +/// * `main.rs` has THREE `let labels = [` arrays. A bare `let labels = ` match +/// takes the first (abort reasons, 6 entries) and reports 64 spurious +/// mismatches — a total-drift verdict manufactured entirely by the selector. +/// * `MC_DIAG_LABELS: [&str; MC_DIAG_SLOTS] = [...]` has a bracket in its TYPE. +/// Scanning for the first `[` after the name lands there and yields 0 +/// entries. +/// +/// So: anchor on something unique, then step to the `= [` that opens the value. +fn array_after(text: &str, anchor: &str, what: &str) -> Vec { + let at = text + .find(anchor) + .unwrap_or_else(|| panic!("{what}: anchor {anchor:?} not found")); + let eq = text[at..] + .find("= [") + .unwrap_or_else(|| panic!("{what}: no `= [` after anchor {anchor:?}")) + + at; + let open = eq + text[eq..].find('[').expect("`= [` contains a `[`"); + + let bytes = text.as_bytes(); + let mut depth = 0usize; + let mut end = open; + for (i, b) in bytes.iter().enumerate().skip(open) { + match b { + b'[' => depth += 1, + b']' => { + depth -= 1; + if depth == 0 { + end = i; + break; + } + } + _ => {} + } + } + assert!(depth == 0 && end > open, "{what}: unbalanced array literal"); + + let mut out = Vec::new(); + for line in text[open + 1..end].lines() { + // Whole-line comments are dropped so a `"` inside prose cannot be + // mistaken for an entry. Entries are one per line in both files. + let line = line.trim(); + if line.starts_with("//") { + continue; + } + let mut rest = line; + while let Some(s) = rest.find('"') { + let after = &rest[s + 1..]; + let Some(e) = after.find('"') else { break }; + out.push(after[..e].to_string()); + rest = &after[e + 1..]; + } + } + out +} + +fn declared_slots(majit_src: &str) -> usize { + let anchor = "pub const MC_DIAG_SLOTS: usize = "; + let at = majit_src + .find(anchor) + .expect("majit: `MC_DIAG_SLOTS` declaration not found"); + let tail = &majit_src[at + anchor.len()..]; + let digits: String = tail.chars().take_while(|c| c.is_ascii_digit()).collect(); + digits + .parse() + .unwrap_or_else(|e| panic!("majit: MC_DIAG_SLOTS is not a number ({digits:?}) — {e}")) +} + +fn sources() -> (String, String) { + let root = repo_root(); + ( + read(&root.join("majit/majit-metainterp/src/lib.rs")), + read(&root.join("pyre/pyre-wasm-runner/src/main.rs")), + ) +} + +fn extract(majit_src: &str, runner_src: &str) -> (Vec, Vec, usize) { + let slots = declared_slots(majit_src); + let majit = array_after(majit_src, "pub const MC_DIAG_LABELS", "majit"); + let runner = array_after(runner_src, "\"pyre_jit_mc_diag\"", "runner"); + (majit, runner, slots) +} + +/// The slots whose two spellings disagree, over the shorter of the two. +fn divergences(majit: &[String], runner: &[String]) -> Vec { + (0..majit.len().min(runner.len())) + .filter(|&i| majit[i] != runner[i]) + .map(|i| format!(" slot {i}: majit={:?} runner={:?}", majit[i], runner[i])) + .collect() +} + +/// Validates the source parser against the compiler-enforced slot count before +/// using the parser to diagnose mirror drift. +#[test] +fn slots_agree_with_the_declared_count() { + let (majit_src, runner_src) = sources(); + let (majit, _runner, slots) = extract(&majit_src, &runner_src); + assert_eq!( + majit.len(), + slots, + "the majit-side extraction found {} labels but MC_DIAG_SLOTS is {slots}. \ + rustc already enforces that array's length, so THE PARSER IN THIS FILE \ + IS WRONG — do not touch MC_DIAG_LABELS. Most likely the anchor now \ + matches a different array, or an entry is not one-per-line.", + majit.len(), + ); +} + +#[test] +fn the_runner_mirror_matches_mc_diag_labels() { + let (majit_src, runner_src) = sources(); + let (majit, runner, slots) = extract(&majit_src, &runner_src); + + assert_eq!( + runner.len(), + slots, + "pyre-wasm-runner's mc_diag label array has {} entries but \ + MC_DIAG_SLOTS is {slots}. A new slot was added to \ + majit_metainterp::MC_DIAG_LABELS without being appended to the mirror \ + in pyre-wasm-runner/src/main.rs (or vice versa). APPEND it — inserting \ + in the middle silently renames every tally after the insertion point.\n\ + BUT FIRST: a count that is far off rather than off by a few means the \ + `\"pyre_jit_mc_diag\"` anchor matched a different array, and the \ + extraction is at fault, not the mirror. main.rs has three \ + `let labels = [` literals; the first is a 6-entry abort-reason list.", + runner.len(), + ); + + let divergent = divergences(&majit, &runner); + assert!( + divergent.is_empty(), + "the pyre-wasm-runner mc_diag mirror no longer matches \ + majit_metainterp::MC_DIAG_LABELS.\n{}\n\ + The runner zips these labels against `mc_diag(i)` BY INDEX, so every \ + tally from the first divergent slot onward is printed under the wrong \ + name. Fix the mirror in pyre-wasm-runner/src/main.rs to match majit — \ + majit is the authority, since its array is length-checked against \ + MC_DIAG itself.", + divergent.join("\n"), + ); +} + +/// POSITIVE CONTROL — proves the two tests above can fail. +/// +/// A guard that has never been observed failing asserts nothing; `assert!(x +/// == x)` also passes on every tree. So both drift shapes are injected into +/// the REAL runner source, in memory, and the checks are required to catch +/// them. Perturbing the real text rather than a hand-written fixture is what +/// makes this a control over the ACTUAL extraction: a fixture would only +/// exercise `divergences`, leaving the anchors — the part that has already +/// been wrong twice — untested. +/// +/// Nothing is written to disk; the test perturbs an in-memory copy. +#[test] +fn a_perturbed_mirror_is_detected() { + let (majit_src, runner_src) = sources(); + let (majit, runner, slots) = extract(&majit_src, &runner_src); + let last = runner.last().expect("runner mirror is empty").clone(); + + // `replacen(.., 1)` takes the FIRST occurrence in the whole file, not the + // first inside the mirror. Today the last label occurs exactly once, but a + // future label colliding with a string in one of the three earlier + // `let labels = [` arrays would put the perturbation OUTSIDE the extracted + // region — and this test would then fail saying the element-wise comparison + // missed a rename, accusing `divergences`, which would be innocent. Assert + // the target is unique so a broken control reports itself as broken. + let needle = format!("\"{last}\","); + assert_eq!( + runner_src.matches(&needle).count(), + 1, + "the perturbation target {needle:?} occurs more than once in main.rs, so \ + `replacen` may patch text outside the mc_diag array and this control \ + would be measuring the wrong region. Pick a unique target — do NOT \ + read a failure below as mirror drift until this passes.", + ); + + // Shape 1 — RENAMED in place. The count still matches, so only the + // element-wise comparison can see it. + const INJECTED: &str = "XX_perturbed_by_the_positive_control"; + let renamed = runner_src.replacen(&needle, &format!("\"{INJECTED}\","), 1); + assert_ne!(renamed, runner_src, "the perturbation did not apply"); + let (_, perturbed, _) = extract(&majit_src, &renamed); + assert_eq!( + perturbed.len(), + slots, + "a rename must not change the count — otherwise this control proves \ + the length check, not the comparison", + ); + let caught = divergences(&majit, &perturbed); + assert_eq!( + caught.len(), + 1, + "the element-wise comparison did not catch a single renamed label; \ + it reported {caught:?}", + ); + + // A COUNT is not an identity. Exactly one mismatch is also what a + // MISALIGNED extraction produces — shift the runner array by one and + // some other single slot disagrees, giving `len() == 1` while the + // perturbation was never seen. Since positional agreement is the whole + // property under guard, pin WHICH slot fired and WHAT it read: the + // divergence must name the last slot and carry the injected string. + assert!( + caught[0].starts_with(&format!(" slot {}: ", slots - 1)) && caught[0].contains(INJECTED), + "the comparison reported one divergence, but not the one that was \ + injected — expected slot {} carrying {INJECTED:?}, got {:?}. The \ + count matched by coincidence, so this control was passing without \ + observing the perturbation: suspect the extraction anchors, not \ + `divergences`.", + slots - 1, + caught[0], + ); + + // Shape 2 — DELETED. The remaining labels still agree pairwise up to the + // deletion point, so only the length check can see it. + let deleted = runner_src.replacen(&needle, "", 1); + assert_ne!( + deleted, runner_src, + "the deletion did not apply. Asserted separately from the length \ + check below so a no-op substitution reports itself as such, rather \ + than as `divergences` failing to shorten", + ); + let (_, shortened, _) = extract(&majit_src, &deleted); + assert_eq!( + shortened.len(), + slots - 1, + "removing one label did not shorten the extracted mirror by one", + ); + assert!( + divergences(&majit, &shortened).is_empty(), + "a trailing deletion is invisible to the element-wise comparison by \ + construction — if this fires, `divergences` is no longer bounded by \ + the shorter side and shape 2 is being caught for the wrong reason", + ); +} diff --git a/majit/majit-trace/src/heapcache.rs b/majit/majit-trace/src/heapcache.rs index 72b7ff7b641..070cc050038 100644 --- a/majit/majit-trace/src/heapcache.rs +++ b/majit/majit-trace/src/heapcache.rs @@ -355,7 +355,7 @@ pub struct HeapCache { /// RPython: FrontendOp flag. BitSet indexed by OpRef.0. seen_allocation: BitSet, - /// RPython: FrontendOp flag. Vec indexed by OpRef.0. + /// RPython: FrontendOp flag. `Vec` indexed by OpRef.0. /// 0 = unknown, 1 = non-null, 2 = null. known_nullity: Vec, @@ -400,7 +400,7 @@ pub struct HeapCache { head_version: u32, likely_virtual_version: u32, - /// RPython: FrontendOp flags. Vec indexed by OpRef.0. + /// RPython: FrontendOp flags. `Vec` indexed by OpRef.0. heapc_flags: Vec, } diff --git a/majit/majit-translate/docs/design-346-foreign-lib-cluster-epic.md b/majit/majit-translate/docs/design-346-foreign-lib-cluster-epic.md index fba0c5788d0..2a62289d190 100644 --- a/majit/majit-translate/docs/design-346-foreign-lib-cluster-epic.md +++ b/majit/majit-translate/docs/design-346-foreign-lib-cluster-epic.md @@ -29,9 +29,16 @@ three readings of one unchanged input set: rg --no-config 'PREPASS phaseA fail' "$stderr" | rg --no-config 'try_from' | sort -u | wc -l ``` -- **276 total post-Slice-A phaseA failures:** commit - `bb6ee8d179c70f170ee4e3a3cb9b4dce99d96d9b`, release profile, default - features, and `PYRE_RTYPER_VERBOSE=1`. Slice A changed `pyre-object`, so this +- **276 total post-Slice-A phaseA failures** (the *post-Slice-A baseline + commit*, named that way everywhere below): release profile, default + features, and `PYRE_RTYPER_VERBOSE=1`. + + That commit existed only on a feature branch and was later replaced by the + squash merge in PR #606 (`7944966b4dd`, "jit: retire foreign-lib cluster + walls (gh#346 Slice A + B1a + B2 + B3a)"). The squash tree contains the + whole PR, not the exact tree measured here, so the 276 reading cannot be + reproduced from that merge. Re-run the command below at a named commit + before comparing a new result to 276. Slice A changed `pyre-object`, so this run first re-extracted the default LLBC set with the repository's pinned Charon, then rebuilt and counted: @@ -43,7 +50,8 @@ three readings of one unchanged input set: rg --no-config 'PREPASS phaseA fail' "$stderr" | sort -u | wc -l ``` -The 276 run is **not** a direct 268→276 Slice-A delta: `bb6ee8d179c` descends +The 276 run is **not** a direct 268→276 Slice-A delta: the post-Slice-A +baseline commit descends from `eca75827fe4` through intervening changes and uses re-extracted LLBC, while 268 used the older frozen snapshot. The 268 figure is therefore the historical pre-Slice-A planning baseline only; the separately configured 276 run is the @@ -165,7 +173,7 @@ Err arm — MUST still raise IndexError/ValueError, not panic). ### Slice B — String / Wtf8 / IndexMap / slice / iter-adapter residuals (task #22, after A) Scoped 7/17 on the separate post-Slice-A census run -(`bb6ee8d179c70f170ee4e3a3cb9b4dce99d96d9b`, 276 total phaseA; exact +(the post-Slice-A baseline commit, 276 total phaseA; exact configuration and command above): **46 distinct unregistered residual paths**, saved to `/tmp/sliceB_residual_ranking.txt` (de-escape `\"`→`"` before counting). The three walls that gate the hot dispatcher heads (innermost per record): @@ -221,7 +229,7 @@ co-land):** ### Slice C — vec!/NewList recognizer + repr-generic rtype_newlist (task #20, LAST, capstone) -- **Ca** Re-add the front/mir recognizer (verbatim from reverted `f41cb0496dc`): match +- **Ca** Re-add the front/mir recognizer (verbatim from the reverted work in PR #310): match `box_assume_init_into_vec_unsafe(box [e0..eN])` → `OpKind::NewList{args}`. Helpers `read_array_literal_elements` (mir.rs:13581) + `fmt_path_ends_with` (mir.rs:13673) still in tree. - **Cb** `remove_dead_aggregates` (model.rs:2469) already sweeps the dead `Box::new_uninit`. No work. @@ -248,8 +256,8 @@ The metric is the distinct `[PREPASS phaseA fail]` count produced by the exact commands and configurations recorded above. Use set-diff only between runs made from the same source commit ancestry and the same extracted LLBC snapshot. In particular, 268 at `eca75827fe4` is the historical pre-Slice-A planning baseline, -16 at `ccdc1a52be2` is only the filtered `try_from` subset, and 276 at -`bb6ee8d179c` is a separately configured post-Slice-A baseline; do not treat +16 at `ccdc1a52be2` is only the filtered `try_from` subset, and 276 at the +post-Slice-A baseline commit is separately configured; do not treat 268→276 as a Slice-A regression. GOTCHA: the census **stderr** logs only phaseA *reasons* — the emitted `newlist/r>r` opname lives in `insns.bin` (build OUT_DIR `target/debug/build/pyre-jit-trace-/out/insns.bin`; diff --git a/majit/majit-translate/src/annotator/bookkeeper.rs b/majit/majit-translate/src/annotator/bookkeeper.rs index 3ec6d8f52f2..98cd1186609 100644 --- a/majit/majit-translate/src/annotator/bookkeeper.rs +++ b/majit/majit-translate/src/annotator/bookkeeper.rs @@ -382,7 +382,7 @@ pub struct Bookkeeper { pub pyre_trait_unique_impls: RefCell>, /// Trait qualified-path (`name_path()`) → base `HostObject` for a /// receiver-driven method-dispatch family registered through - /// [`Self::register_trait_family`] (issue #346). + /// [`Self::register_trait_family`] (receiver-dispatch configuration). /// `derive_subject_inputcells` seeds a `dyn Trait` receiver whose /// `class_root` is a key here with the base `ClassDef`, so /// `getattr(receiver, method)` resolves the impl-subclass @@ -3960,9 +3960,7 @@ fn lookup_zero_arg_method( /// (`ConstValue::Tuple([Int(cnt), Tuple([Str(k0), …]), Bool(star)])`, /// see `flowcontext::build_call_shape_constant`) and reconstructs a /// CallShape + tail `args_s[1..]`. -// ===================================================================== // check_no_flags_on_instances walker (bookkeeper.py:124-147) -// ===================================================================== /// Entry point for the recursive sanity walk — inspects a `ClassDef` /// and recurses through its attributes. `seen_classdefs` / @@ -4640,8 +4638,26 @@ mod tests { // the leaf → module origin so `canonical_struct_name(leaf)` lands // on the same `module::Enum` spelling. Without that origin the // bare leaf passes through and the two sides mint sibling - // classdefs. Uses a unique leaf so replacing the process-global - // `STRUCT_ORIGIN_REGISTRY` cannot perturb a concurrent test. + // classdefs. + // + // The unique leaf below does NOT make the write below safe against a + // concurrent test, and an earlier version of this comment claimed it + // did. A unique KEY protects a concurrent reader looking up a + // DIFFERENT key; `register_struct_origins` is `*guard = origins`, so it + // discards the whole table, and what it destroys is whatever some other + // test registered — a direction key uniqueness cannot reach. + // + // What actually holds today is narrower and needs re-checking before it + // is relied on: this is the ONLY `#[test]` in the `majit-translate` + // lib-test binary that writes `STRUCT_ORIGIN_REGISTRY`, so there is no + // second writer to race. Adding one re-opens the hazard. + // + // Not hypothetical: the sibling registry `STRUCT_ID_BY_NAME` gained a + // second lib-binary `#[test]` writer and the pair then failed 9 runs in + // 12, while passing in isolation and under `--test-threads=1`. The + // remedy used there is `register_struct_ids_serialized` + // (`codewriter/assembler.rs`, in its `mod tests`); mirror it here + // rather than reasoning about key collisions. use crate::front::StructFieldRegistry; let bk = bk(); let mut reg = StructFieldRegistry::default(); @@ -6544,7 +6560,7 @@ mod tests { } /// Fixture for the methods-on-classdef capability that `dyn Trait` - /// receiver-driven dispatch (issue #346, aheui LinkedList) needs. + /// receiver-driven dispatch (receiver-dispatch configuration, aheui LinkedList) needs. /// /// A ≥2-impl trait family registered through `register_trait_family` /// must (a) link a base ClassDef to its impl subclasses, (b) carry the diff --git a/majit/majit-translate/src/annotator/classdesc.rs b/majit/majit-translate/src/annotator/classdesc.rs index 9becce7dba0..8939ed5229f 100644 --- a/majit/majit-translate/src/annotator/classdesc.rs +++ b/majit/majit-translate/src/annotator/classdesc.rs @@ -86,9 +86,7 @@ thread_local! { FlattenRecursion::new(); } -// --------------------------------------------------------------------------- // ClassDictEntry (classdesc.py:506 `{attr: Constant-or-Desc}`). -// --------------------------------------------------------------------------- /// RPython `classdict[attr]` value type — upstream comment /// (classdesc.py:506) states `{attr: Constant-or-Desc}`. Upstream relies @@ -115,9 +113,7 @@ impl ClassDictEntry { } } -// --------------------------------------------------------------------------- // Error marker (classdesc.py:466-468). -// --------------------------------------------------------------------------- /// RPython `class NoSuchAttrError(AnnotatorError)` (classdesc.py:466-468). /// @@ -146,9 +142,7 @@ impl std::fmt::Display for NoSuchAttrError { impl std::error::Error for NoSuchAttrError {} -// --------------------------------------------------------------------------- // BuiltinTypeDesc (classdesc.py:479-485). -// --------------------------------------------------------------------------- /// RPython `class BuiltinTypeDesc(object)` (classdesc.py:479-485). /// @@ -171,9 +165,7 @@ impl BuiltinTypeDesc { } } -// --------------------------------------------------------------------------- // is_mixin / is_primitive_type helpers (classdesc.py:471-476). -// --------------------------------------------------------------------------- /// RPython `is_mixin(cls)` (classdesc.py:471-472). /// @@ -214,9 +206,7 @@ pub fn is_primitive_type(cls: &HostObject) -> bool { ) } -// --------------------------------------------------------------------------- // FORCE_ATTRIBUTES_INTO_CLASSES (classdesc.py:957-961). -// --------------------------------------------------------------------------- thread_local! { /// RPython `FORCE_ATTRIBUTES_INTO_CLASSES` (classdesc.py:957-968). @@ -339,10 +329,8 @@ fn forced_attributes_for(qualname: &str) -> Option String { @@ -2510,9 +2484,7 @@ impl ClassDef { )) } - // ----------------------------------------------------------------------- // Mutable structural methods. - // ----------------------------------------------------------------------- /// RPython `ClassDef.setup(self, sources)` (classdesc.py:161-166). fn setup( @@ -3078,9 +3050,7 @@ impl ClassDef { } } -// --------------------------------------------------------------------------- // Tests. -// --------------------------------------------------------------------------- #[cfg(test)] mod tests { @@ -3170,18 +3140,33 @@ mod tests { let result = ClassDef::read_attr__class__(&parent_cd); match &result { SomeValue::PBC(pbc) => { - let names: Vec = pbc + // `descriptions` is a `BTreeMap` keyed by pointer identity, so + // its iteration order is an allocation artefact rather than a + // property of this fixture — sort before comparing. + // + // Mapping every entry, rather than `filter_map`ping to the + // classes, is what lets one assertion carry the kind check too: + // a non-class description used to be dropped here and caught + // only by a separate `.all()`, and now reads as + // `` in the diff. + let mut entries: Vec<(String, DescKind)> = pbc .descriptions .values() - .filter_map(|d| d.as_class()) - .map(|cd| cd.borrow().name.clone()) + .map(|d| { + let name = d + .as_class() + .map(|cd| cd.borrow().name.clone()) + .unwrap_or_else(|| String::from("")); + (name, d.kind()) + }) .collect(); - assert!(names.iter().any(|n| n == "pkg.Parent")); - assert!(names.iter().any(|n| n == "pkg.Child")); - assert!( - pbc.descriptions - .values() - .all(|d| d.kind() == DescKind::Class) + entries.sort(); + assert_eq!( + entries, + [ + (String::from("pkg.Child"), DescKind::Class), + (String::from("pkg.Parent"), DescKind::Class), + ] ); } _ => panic!("read_attr__class__ must return a SomePBC"), @@ -3260,10 +3245,14 @@ mod tests { let obj = HostObject::new_instance(cls, vec![]); obj.instance_set("dyn", ConstValue::Int(1)); let src = InstanceSource::new(&bk, obj); - let attrs = src.all_instance_attributes().unwrap(); - assert!(attrs.iter().any(|attr| attr == "dyn")); - assert!(attrs.iter().any(|attr| attr == "slot_a")); - assert!(attrs.iter().any(|attr| attr == "base_slot")); + // `all_instance_attributes` starts from `instance_dict_keys()`, which + // reads a `HashMap`, so the order is not stable across runs — sort + // before comparing. What the three membership checks could not see is + // an *extra* attribute: the instance dict, this class's `__slots__` + // and the base's `__slots__` contribute exactly one entry each. + let mut attrs = src.all_instance_attributes().unwrap(); + attrs.sort(); + assert_eq!(attrs, ["base_slot", "dyn", "slot_a"]); } #[test] diff --git a/majit/majit-translate/src/annotator/exception.rs b/majit/majit-translate/src/annotator/exception.rs index e9b399d82b1..4ff912706a8 100644 --- a/majit/majit-translate/src/annotator/exception.rs +++ b/majit/majit-translate/src/annotator/exception.rs @@ -144,19 +144,81 @@ pub(crate) fn standard_exception_classdefs( mod tests { use super::*; + /// The whole resolved sequence, in order. + /// + /// These names are **measured**, not derived from + /// [`STANDARD_EXCEPTION_NAMES`]. `standard_exception_classes` transforms + /// that constant twice — `resolve` maps a name to whatever `HOST_ENV` + /// bootstraps, and the `identity_id()` dedup then drops repeats — so the + /// output is not the input, and two entries differ: + /// + /// - **16 names in, 15 classes out.** `IOError` has no distinct host class; + /// it resolves to the `OSError` object and is deduped. It is the `IOError` + /// slot that survives (it comes first), so the list has one `OSError` + /// entry, not two, and no `IOError` entry at all. + /// - **`_StackOverflow` reads back as `StackOverflow`.** The bootstrap + /// lookup key keeps the underscore; the qualname does not. + /// + /// The order is a property of the producer, not an accident: `classes` is a + /// `Vec` pushed in `STANDARD_EXCEPTION_NAMES` order, and the `HashSet` is + /// consulted for membership only. Pinning it ordered therefore also pins + /// that a reorder of the constant is a visible change. + const RESOLVED_EXCEPTION_NAMES: [&str; 15] = [ + "TypeError", + "OverflowError", + "ValueError", + "ZeroDivisionError", + "MemoryError", + "OSError", + "StopIteration", + "KeyError", + "IndexError", + "AssertionError", + "RuntimeError", + "UnicodeDecodeError", + "UnicodeEncodeError", + "NotImplementedError", + "StackOverflow", + ]; + #[test] fn standard_exception_classes_have_expected_names() { let excs = standard_exception_classes(); - // Spot-check a few entries so rename-away regressions fire. let names: Vec = excs.iter().map(|c| c.qualname().to_string()).collect(); - assert!(names.iter().any(|n| n == "TypeError")); - assert!(names.iter().any(|n| n == "OverflowError")); - assert!(names.iter().any(|n| n == "MemoryError")); - assert!(names.iter().any(|n| n == "UnicodeDecodeError")); - assert!(names.iter().any(|n| n == "UnicodeEncodeError")); + assert_eq!(names, RESOLVED_EXCEPTION_NAMES); + } + + /// The dedup is the reason the set is 15 and not 16, so pin it at its cause + /// rather than only at the length: `IOError` and `OSError` must still name + /// one object. + /// + /// Without this, a bootstrap that grew a distinct `IOError` class would fail + /// `standard_exception_classes_have_expected_names` with a diff that looks + /// like a naming change, and the obvious repair — add `"IOError"` to the + /// expected list — would restore green while silently changing what the + /// annotator treats as one exception. + #[test] + fn io_error_and_os_error_resolve_to_one_class() { + STANDARD_EXCEPTION_OBJECTS.with(|objects| { + assert_eq!( + objects.resolve("IOError").identity_id(), + objects.resolve("OSError").identity_id(), + "IOError is no longer an OSError alias, so the standard set is \ + 16 classes and the dedup no longer fires" + ); + }); + } + + /// `_StackOverflow` is bootstrapped under a name the qualname does not + /// carry, so a name-keyed check alone cannot show the two spellings are one + /// object. + #[test] + fn stack_overflow_entry_is_the_bootstrapped_builtin() { + let excs = standard_exception_classes(); let stack_overflow = HOST_ENV .lookup_builtin("_StackOverflow") .expect("HOST_ENV _StackOverflow bootstrap"); + assert_eq!(stack_overflow.qualname(), "StackOverflow"); assert!( excs.iter() .any(|cls| cls.identity_id() == stack_overflow.identity_id()) diff --git a/majit/majit-translate/src/codewriter/assembler.rs b/majit/majit-translate/src/codewriter/assembler.rs index f7ff841a37a..076f45c343a 100644 --- a/majit/majit-translate/src/codewriter/assembler.rs +++ b/majit/majit-translate/src/codewriter/assembler.rs @@ -266,7 +266,7 @@ pub struct Assembler { /// RPython: Assembler.num_liveness_ops (assembler.py:32). pub num_liveness_ops: usize, /// State-field JIT canonical "all-live" liveness triple, set once at - /// `__JitMeta::install_canonical_liveness` time (RPython + /// `__JitMeta_::install_canonical_liveness` time (RPython /// `assembler.py:218-231 get_liveness_info` flat-state adaptation). /// `JitCodeBuilder::live_placeholder` defers patching of the leading /// `BC_LIVE` slot at the start of every per-opcode JitCode until @@ -321,7 +321,7 @@ impl Assembler { /// Stage the state-field JIT canonical "all-live" triple for lazy /// registration by `ensure_canonical_liveness_offset`. Called once - /// per `__JitMeta::install_canonical_liveness` invocation, before any + /// per `__JitMeta_::install_canonical_liveness` invocation, before any /// per-pc JitCode is built. pub fn set_canonical_liveness_triple( &mut self, @@ -1796,7 +1796,7 @@ impl Assembler { state.code.push((descr_idx & 0xFF) as u8); state.code.push((descr_idx >> 8) as u8); argcodes.push('d'); - // The allocation result is ALWAYS a fresh GC ref ('r'). #314: + // The allocation result is ALWAYS a fresh GC ref ('r'). fresh-reference: // assert the regalloc result kind structurally (mirror the // getarrayitem invariant), so an Int-banked result fails loud at // emit time instead of silently miscompiling. @@ -3811,7 +3811,14 @@ fn fielddescrof( }; let mut is_immutable = false; let mut is_quasi_immutable = false; - let mut index_in_parent = 0usize; + // `None` until a lookup arm actually resolves a slot. The minted descr still + // carries `0` for the unresolved case (`unwrap_or(0)` at the bottom), so the + // emitted `BhDescr::Field` is unchanged; what the `Option` adds is that the + // two states stop being the same bytes while they are still separable. + // Written as a literal `0`, an unresolved mint and a mint that resolved to + // slot 0 are indistinguishable the moment this function returns, and every + // census downstream inherits that. + let mut index_in_parent: Option = None; let mut parent = None; let field_key = if let Some(owner) = field.owner_root.as_deref() { let prefix = format!("{owner}."); @@ -3853,7 +3860,7 @@ fn fielddescrof( is_field_signed = spec.is_field_signed; is_immutable = spec.is_immutable; is_quasi_immutable = spec.is_quasi_immutable; - index_in_parent = spec.index_in_parent; + index_in_parent = Some(spec.index_in_parent); } BhFieldLookup::Layout(layout_field) => { offset = layout_field.offset; @@ -3908,10 +3915,16 @@ fn fielddescrof( && let Some(parent_spec) = parent.as_ref() && let Some(pos) = unique_slot_at_offset(&parent_spec.all_fielddescrs, offset) { - index_in_parent = pos; + index_in_parent = Some(pos); } } + // Outside every `parent` check above, deliberately. This is the only census + // that fires when the mint never had a usable parent at all — the case where + // the placeholder survives unrepaired and both of the existing counters are + // silent by construction. See `census_mint_index_provenance`. + majit_ir::descr::census_mint_index_provenance(index_in_parent.is_some()); + // The descr's two halves, counted where they are produced rather than at a // reader that would repair them (`GcCache::derive_index_in_parent` // re-derives by name and overwrites, so it can only report repairs it @@ -3942,6 +3955,9 @@ fn fielddescrof( is_field_signed, is_immutable, is_quasi_immutable, + // Carried as-is: the `None` is the whole point. Collapsing it to `0` + // here is what made the split underivable at the reader, and this is + // the last place that still knows the difference. index_in_parent, parent, name: field_key, @@ -4677,6 +4693,82 @@ impl Assembler { .collect() } + /// descriptor census: how much of the descr pool is content-duplicate. + /// + /// `emit_descr` dedups on [`AssemblerDescrKey`], whose `Call` arms carry an + /// [`EffectInfoKey`] keyed on `Arc::as_ptr` ptr-ids. Upstream can key on + /// `id(descr)` (`effectinfo.py:152-164`) because one gccache per process + /// canonicalises every descr, so `id()` *is* content identity. Pyre can + /// mint the same logical descr more than once, so two EffectInfos that + /// agree on content can disagree on ptr-id and take separate pool slots. + /// + /// **That over-split is a real unsoundness but it is NOT the cause of + /// descriptor census's byte non-determinism, and this census is what refuted it.** + /// Measured across two generations: `structurally_distinct` equals + /// `comparable` (229 == 229), so every effect-keyed entry is already + /// pairwise distinct under the structural key — re-keying on + /// `descr_set_keys` would yield the same entries. And `total` was + /// identical (4537) in both generations while `descrs.bin` still moved + /// −2,226 bytes. The pool population does not move; entry *lengths* do. + /// See [`crate::codewriter::jitcode::descr_pool_content`], which measures + /// the channel that does. + /// + /// Retained because a *stable* over-split is still worth knowing about, + /// and because this function is the control proving the population is not + /// the mover. Measurable in ONE run. + pub fn descr_pool_duplication(&self) -> DescrPoolDuplication { + let mut out = DescrPoolDuplication { + total: self.descrs.len(), + ..Default::default() + }; + let mut seen: std::collections::HashSet<(String, EffectInfoStructuralKey)> = + std::collections::HashSet::new(); + for descr in &self.descrs { + let (shape, effect) = match descr { + AssemblerDescr::Ready(crate::jitcode::BhDescr::Call { calldescr }) => ( + format!( + "call|{}|{}|{}|{}|{:?}", + calldescr.arg_classes, + calldescr.result_type, + calldescr.result_signed, + calldescr.result_size, + calldescr.result_erased + ), + &calldescr.extra_info, + ), + AssemblerDescr::Ready(crate::jitcode::BhDescr::JitCode { + jitcode_index, + fnaddr, + calldescr, + }) => ( + format!( + "jitcode|{jitcode_index}|{fnaddr}|{}|{}|{}|{}|{:?}", + calldescr.arg_classes, + calldescr.result_type, + calldescr.result_signed, + calldescr.result_size, + calldescr.result_erased + ), + &calldescr.extra_info, + ), + _ => continue, + }; + out.effect_keyed += 1; + match DescrSetShape::of(effect) { + DescrSetShape::InvariantViolation => { + out.invariant_violations += 1; + continue; + } + DescrSetShape::Concrete => out.concrete += 1, + DescrSetShape::Wildcard => out.wildcard += 1, + } + out.comparable += 1; + seen.insert((shape, EffectInfoStructuralKey::from_effect_info(effect))); + } + out.structurally_distinct = seen.len(); + out + } + pub fn finished(&mut self, callinfocollection: &CallInfoCollection) { for func_addr in callinfocollection.all_function_addresses_as_int() { // RPython: see_raw_object(func.ptr) @@ -4719,6 +4811,129 @@ struct EffectInfoKey { call_release_gil_target: (u64, i32), } +/// [`EffectInfoKey`] re-spelled structurally, for the descriptor census census only. +/// +/// Identical except that the six raw descr sets are read from +/// `EffectInfo::descr_set_keys` — the `DescrSetMember` projection that already +/// crosses the build/runtime boundary — instead of `Arc::as_ptr` ptr-ids. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +struct EffectInfoStructuralKey { + extraeffect: majit_ir::descr::ExtraEffect, + oopspecindex: majit_ir::descr::OopSpecIndex, + descr_set_keys: Option, + can_invalidate: bool, + can_collect: bool, + call_release_gil_target: (u64, i32), +} + +impl EffectInfoStructuralKey { + fn from_effect_info(effect: &majit_ir::descr::EffectInfo) -> Self { + Self { + extraeffect: effect.extraeffect, + oopspecindex: effect.oopspecindex, + descr_set_keys: effect.descr_set_keys.clone(), + can_invalidate: effect.can_invalidate, + can_collect: effect.can_collect, + call_release_gil_target: effect.call_release_gil_target, + } + } +} + +/// Which shape an `EffectInfo`'s `descr_set_keys` takes (descriptor census). +/// +/// `effectinfo.rs:149-161` states the rule and names its upstream source: +/// `effectinfo.py:149-162` makes the six raw sets `None` **iff** the EI is +/// `EF_RANDOM_EFFECTS`. So the population partitions in two, and the third +/// class below is unrepresentable rather than merely rare. +/// +/// An earlier version of this file folded [`Self::Wildcard`] and +/// [`Self::Concrete`] into one `is_informative` predicate and excluded only +/// [`Self::InvariantViolation`]. Because those two disjuncts *partition* the +/// population, that predicate was identically `true` and its exclusion count +/// identically zero — a counter that cannot fire, reported as if it were the +/// census's control. Splitting the two shapes is what makes the control real: +/// the census is only trustworthy if it can be shown to have seen both. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum DescrSetShape { + /// `descr_set_keys: Some(_)` — the structural projection is present. The + /// only shape where two `Arc`s for one logical descr can split a key. + Concrete, + /// `descr_set_keys: None` with all six raw sets `None` — the + /// `EF_RANDOM_EFFECTS` wildcard. Its ptr-id projection is `None` too, so + /// it is already canonical under the ptr-keyed dict and cannot move. + Wildcard, + /// Keys absent while a raw set is present. **The invariant above forbids + /// this**; counted so the census *asserts* the invariant rather than + /// assuming it. Expected zero — and a zero here means only that, not that + /// the census works. + InvariantViolation, +} + +impl DescrSetShape { + fn of(effect: &majit_ir::descr::EffectInfo) -> Self { + if effect.descr_set_keys.is_some() { + return Self::Concrete; + } + let raw_sets_absent = effect._readonly_descrs_fields.is_none() + && effect._write_descrs_fields.is_none() + && effect._readonly_descrs_arrays.is_none() + && effect._write_descrs_arrays.is_none() + && effect._readonly_descrs_interiorfields.is_none() + && effect._write_descrs_interiorfields.is_none(); + if raw_sets_absent { + Self::Wildcard + } else { + Self::InvariantViolation + } + } +} + +/// Output of [`Assembler::descr_pool_duplication`] (descriptor census). +#[derive(Debug, Default)] +pub struct DescrPoolDuplication { + /// `descrs.len()` — the bincode `Vec` length that opens `descrs.bin`. + pub total: usize, + /// Entries whose `_descr_dict` key carries an `EffectInfoKey`, i.e. the + /// ones keyed partly on `Arc::as_ptr`. + pub effect_keyed: usize, + /// Of those, [`DescrSetShape::Concrete`] — the only sub-population the + /// ptr-keyed dict can over-split, and the only one the fix can move. + pub concrete: usize, + /// Of those, [`DescrSetShape::Wildcard`] — already canonical under both + /// keys, so inert for the defect signal and load-bearing as the control. + pub wildcard: usize, + /// `concrete + wildcard`. Entries carrying a well-defined structural key. + pub comparable: usize, + /// Distinct structural keys among `comparable`. **Lower than `comparable` + /// means the ptr-keyed dict over-split: the pool holds entries that agree + /// on content and disagree only on which `Arc` instance they reached.** + pub structurally_distinct: usize, + /// [`DescrSetShape::InvariantViolation`] — expected zero *by construction*. + /// It asserts the `effectinfo.py:149-162` invariant; it does **not** + /// certify the census. + pub invariant_violations: usize, +} + +impl DescrPoolDuplication { + /// Whether the census observed both shapes, so `structurally_distinct` can + /// be read at all. + /// + /// This is the control the earlier `ambiguous == 0` was mistaken for. A + /// census that saw no `Concrete` entry cannot have seen an over-split + /// whatever it reports, and one that saw no `Wildcard` entry is walking a + /// population that does not match the enumerated construction sites. + pub fn saw_both_shapes(&self) -> bool { + self.concrete > 0 && self.wildcard > 0 + } + + /// The partition identity. False means the classification lost entries and + /// every other field is suspect. + pub fn counts_reconcile(&self) -> bool { + self.concrete + self.wildcard + self.invariant_violations == self.effect_keyed + && self.comparable == self.concrete + self.wildcard + } +} + impl EffectInfoKey { fn from_effect_info(effect: &majit_ir::descr::EffectInfo) -> Self { Self { @@ -4763,7 +4978,12 @@ enum AssemblerDescrKey { is_field_signed: bool, is_immutable: bool, is_quasi_immutable: bool, - index_in_parent: usize, + /// `Option`, matching `BhDescr::Field`, so `None` and `Some(0)` keep + /// separate pool slots. Two mints that agree on every other component + /// while one resolved a slot and the other did not are not the same + /// descr, and collapsing them here would hand the runtime whichever + /// provenance happened to be minted first. + index_in_parent: Option, parent: Option, name: String, owner: String, @@ -5064,6 +5284,45 @@ mod tests { ); } + /// Publish `table` into the process-global name → `StructId` map and hold + /// every other registering test out until the returned guard drops. + /// + /// `register_struct_ids` REPLACES the whole table (`majit-ir/src/descr.rs`, + /// `*guard = table`), which is right for production — the front end + /// populates it once per program, and merging would leak one program's + /// names into the next — and hostile to `cargo test`'s default + /// parallelism, where a second registering test wipes the first one's only + /// entry while that test is still running. The victim does not fail where + /// it registered: its `fielddescrof` silently takes a different arm and + /// returns a wrong `offset`. + /// + /// Measured, not inferred: the two tests below, run as a pair with default + /// threads, failed 9 of 12 runs; in the full 3141-test binary the same race + /// surfaced once in 6, because the scheduler rarely puts them adjacent. + /// Both pass in isolation and under `--test-threads=1`, so neither the + /// isolated run nor the serial run can see this. + /// + /// Scope is one test BINARY, which is the whole racing population: other + /// crates' tests run in their own processes and cannot reach this table. + /// + /// The lock is poison-tolerant. A test that panics mid-body would + /// otherwise convert one real failure into a cascade of unrelated ones. + /// + /// Bind the result to a NAMED local (`let _registry = …`). A bare + /// `let _ = …` drops the guard at once and restores exactly the race this + /// exists to remove — while still compiling, and still passing in + /// isolation. + #[must_use = "the returned guard holds the registry lock for the rest of \ + the test; dropping it immediately re-opens the race"] + fn register_struct_ids_serialized( + table: HashMap>, + ) -> std::sync::MutexGuard<'static, ()> { + static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + let guard = LOCK.lock().unwrap_or_else(|e| e.into_inner()); + majit_ir::descr::register_struct_ids(table); + guard + } + #[test] fn fielddescrof_resolves_the_slot_when_the_offset_comes_from_the_layout_registry() { use crate::call::{CallControl, StructFieldLayout, StructLayout}; @@ -5071,7 +5330,8 @@ mod tests { let owner = "assembler_fielddescrof_layout_registry_test::Owner"; let owner_id = majit_ir::descr::StructId::from_canonical(owner); - majit_ir::descr::register_struct_ids(HashMap::from([(owner.to_string(), Some(owner_id))])); + let _registry = + register_struct_ids_serialized(HashMap::from([(owner.to_string(), Some(owner_id))])); let mut cc = CallControl::new(); let mut struct_fields = crate::front::StructFieldRegistry::default(); @@ -5134,13 +5394,127 @@ mod tests { }; assert_eq!(offset, 8); - assert_eq!(index_in_parent, 1); + assert_eq!(index_in_parent, Some(1)); assert_eq!( - parent.as_ref().unwrap().all_fielddescrs[index_in_parent].offset, + parent.as_ref().unwrap().all_fielddescrs[index_in_parent.unwrap()].offset, offset ); } + /// The pair the mint census exists for: two `fielddescrof` calls whose + /// `index_in_parent` is the SAME NUMBER and means opposite things. + /// + /// The first half is the load-bearing one. Both descrs carry the INTEGER + /// `0` — one because no lookup arm ever ran, one because the field really + /// is at slot 0 — so any counter that reads only the value + /// (`census_attached_index`, `derive_index_in_parent`, + /// `census_spec_positions`) is blind to the difference by construction, not + /// by oversight. Asserting that the integers agree is what makes the rest + /// mean anything: a census that splits + /// two already-distinguishable cases would prove nothing. + #[test] + fn mint_census_splits_a_placeholder_index_from_a_real_slot_zero() { + use crate::call::{CallControl, StructFieldLayout, StructLayout}; + use crate::model::FieldDescriptor; + + let owner = "assembler_mint_census_test::Owner"; + let owner_id = majit_ir::descr::StructId::from_canonical(owner); + let _registry = + register_struct_ids_serialized(HashMap::from([(owner.to_string(), Some(owner_id))])); + + let mut cc = CallControl::new(); + let mut struct_fields = crate::front::StructFieldRegistry::default(); + struct_fields.fields.insert( + owner.to_string(), + vec![("visible_zero".to_string(), "i64".to_string())], + ); + cc.set_struct_fields(struct_fields); + cc.set_struct_layout( + owner_id, + StructLayout { + size: 8, + fields: vec![StructFieldLayout { + name: "visible_zero".to_string(), + offset: 0, + size: 8, + flag: majit_ir::descr::ArrayFlag::Signed, + field_type: majit_ir::value::Type::Int, + rank: None, + }], + }, + ); + + let index_of = |descr: crate::jitcode::BhDescr| { + let crate::jitcode::BhDescr::Field { + index_in_parent, .. + } = descr + else { + panic!("fielddescrof must produce a field descriptor"); + }; + index_in_parent + }; + + // A real slot-0 claim: the name is in the flattened list at position 0, + // so the `Parent` arm writes the index. + let before = majit_ir::descr::GcCache::mint_index_census(); + let claimed_index = index_of(fielddescrof( + &FieldDescriptor::new("visible_zero", Some(owner.to_string())), + &crate::model::ValueType::Int, + Some(&cc), + )); + let after_claimed = majit_ir::descr::GcCache::mint_index_census(); + + // A placeholder: with no `CallControl` the whole lookup block is + // skipped, so nothing ever writes the index and the initialiser rides + // out on the descr. + let placeholder_index = index_of(fielddescrof( + &FieldDescriptor::new("visible_zero", Some(owner.to_string())), + &crate::model::ValueType::Int, + None, + )); + let after_placeholder = majit_ir::descr::GcCache::mint_index_census(); + + // The two mints must land on the SAME integer and still be separable, + // or neither half of this test is testing anything: identical integers + // are what made the population underivable, and the `Option` is what + // makes them tellable apart. Assert both, in that order. + assert_eq!( + claimed_index.unwrap_or(0), + placeholder_index.unwrap_or(0), + "the two mints must agree on the integer, or this pair is splitting \ + something that was already visible without the provenance bit" + ); + assert_eq!(claimed_index, Some(0), "the lookup arm resolved slot 0"); + assert_eq!( + placeholder_index, None, + "a mint whose lookup block never ran must carry no claim; if this \ + is `Some(0)` the provenance was flattened somewhere on the way out" + ); + + // And separable on the census: each call moved its own arm. + // + // `>=`, not `==`, and the reason is measured rather than defensive — + // the counters are process-global and other tests in this binary mint + // field descrs concurrently, so an exact delta read `[3, 0]` where the + // call itself contributed `[1, 0]`. Concurrency can only ADD, so a + // lower bound on the arm the call must move stays sound: an instrument + // wired to one constant arm, or wired to the wrong one, leaves the + // expected arm at zero and fails here. + // + // What this cannot assert, for the same reason: that the call did + // NOT also move the other arm. No assertion over a shared counter can, + // while the suite runs in parallel. The deterministic half above is the + // one that does not depend on it. + assert!( + after_claimed[0] - before[0] >= 1, + "a resolved slot must count as claimed" + ); + assert!( + after_placeholder[1] - after_claimed[1] >= 1, + "a mint that resolved nothing must count as a placeholder" + ); + } + #[test] fn explicit_result_variant_size_inherits_discriminant_slot() { use crate::call::CallControl; @@ -5474,7 +5848,7 @@ mod tests { is_field_signed: false, is_immutable: false, is_quasi_immutable: false, - index_in_parent: 0, + index_in_parent: Some(0), parent: None, name: "value".into(), owner: "Cell".into(), @@ -5487,7 +5861,7 @@ mod tests { is_field_signed: false, is_immutable: false, is_quasi_immutable: false, - index_in_parent: 0, + index_in_parent: Some(0), parent: None, name: "value".into(), owner: "Cell".into(), @@ -5500,7 +5874,7 @@ mod tests { is_field_signed: false, is_immutable: false, is_quasi_immutable: false, - index_in_parent: 1, + index_in_parent: Some(1), parent: None, name: "mutate_value".into(), owner: "Cell".into(), @@ -5670,7 +6044,7 @@ mod tests { #[test] fn assemble_fused_goto_if_not_routes_to_reserved_opcode() { - // gh #37: a `FlatOp::GotoIfNotOp` assembles through the + // the GotoIfNotOp lowering: a `FlatOp::GotoIfNotOp` assembles through the // registered `goto_if_not_/` jitcode key — the // per-arg register kinds form the argcodes, and the key must // resolve to the reserved opcode the metainterp blackhole diff --git a/majit/majit-translate/src/codewriter/call.rs b/majit/majit-translate/src/codewriter/call.rs index 96b7f686a9c..6188e22819a 100644 --- a/majit/majit-translate/src/codewriter/call.rs +++ b/majit/majit-translate/src/codewriter/call.rs @@ -6,7 +6,7 @@ //! which should remain as opaque calls ("residual"). Also handles builtin //! (oopspec) and recursive (portal) call classification. -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeSet, HashMap, HashSet}; use majit_ir::descr::{DescrRef, EffectInfo, ExtraEffect, OopSpecIndex}; use majit_ir::value::Type; @@ -42,6 +42,51 @@ pub enum CanRaise { Yes, } +/// Charon spells the receiver type of every closure `closure`, so a +/// `Method { receiver_root: Some("closure") }` names a *kind*, not a type: +/// it cannot say which closure is being invoked. Test against +/// [`is_closure_receiver`], never against this literal — Charon appends a +/// `#N` disambiguator to all but one of them. +const CLOSURE_RECEIVER_ROOT: &str = "closure"; + +/// Whether `receiver` names the closure *kind* rather than a type. +/// +/// Charon appends a `#N` disambiguator when one scope defines several +/// closures, so the production spelling is `closure`, `closure#1`, +/// `closure#12`, … — the bare form is the exception, not the rule. A +/// pyre-jit-trace codegen over the three pyre artefacts reaches the +/// receiver-agnostic fallback with 11 closure receivers, of which exactly +/// **one** is spelled bare; matching the literal alone therefore let ten of +/// them through to bind `Fn::call` to whichever graph happens to be the +/// table's only `call`. +/// +/// `#` cannot occur in a Rust path segment, so the disambiguator is +/// unambiguous to strip and this cannot widen onto a real type name. +fn is_closure_receiver(receiver: &str) -> bool { + match receiver.split_once('#') { + Some((base, disambiguator)) => { + base == CLOSURE_RECEIVER_ROOT + && !disambiguator.is_empty() + && disambiguator.bytes().all(|b| b.is_ascii_digit()) + } + None => receiver == CLOSURE_RECEIVER_ROOT, + } +} + +/// Which analyzer a witness callstack is being recovered for. +/// +/// RPython gets one `explain_analyze_slowly` per `GraphAnalyzer` subclass +/// (graphanalyze.py:79-91); `_raise_effect_error` consults exactly two of +/// them (call.py:191-194), and those two differ only in their leaf +/// predicates, so the choice is a value here rather than two walks. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum EffectWitness { + /// `RandomEffectsAnalyzer` — effectinfo.py:410-418. + RandomEffects, + /// `VirtualizableAnalyzer` — effectinfo.py:401-404. + ForcesVirtualizable, +} + /// Operation-level raise classification for `_canraise()`. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] enum RaiseClass { @@ -641,7 +686,7 @@ impl GraphStore { } } -/// Opt-in receiver-driven method-dispatch family (issue #346). A +/// Opt-in receiver-driven method-dispatch family (receiver-dispatch configuration). A /// consumer names a `>=2`-impl trait whose `dyn Trait` receivers should /// annotate to a base `ClassDef` linking the impl subclasses, so a /// method getattr on the receiver resolves the impl `MethodDesc` family @@ -668,6 +713,369 @@ pub struct TraitFamilyRegistration { /// (free functions via `collect_function_graphs` and trait impl methods /// via `extract_trait_impls`). +/// Output of [`CallControl::unknown_callee_census`] (callee census). +/// +/// Each direct-call bucket maps a callee spelling to the number of static +/// call sites naming it, so both populations are readable: how many distinct +/// callees fall in the bucket, and how many references ride on them. +/// +/// The bucket key is the callee's SEGMENTS (`["core","ptr","null"]`), never +/// the `::`-joined path. `CallPath` equality keys on the split, and so does +/// the only consumer this census exists to feed: a `call_spec.rs` +/// `FunctionPath(&[...])` override matches through +/// `call_target_matches_loose`'s `_ => pattern == target` arm, which is +/// exact structural equality with no leaf-suffix tolerance. Two callees that +/// differ only in where the owner is split — the pair owner-segmentation records — +/// render to one string and are two different keys to that matcher, so a +/// joined key both merges their counts and leaves the entry untranscribable. +/// `segmentations_by_spelling` is the control that says whether the merge is +/// happening. +#[derive(Default, Debug)] +pub struct UnknownCalleeCensus { + /// Resolved and registered — the analyzers walk the body. Upstream's + /// `funcobj.graph` arm. + pub with_graph: HashMap, + /// No graph, but named in `external_funcobjs`. Upstream's + /// `analyze_external_call` arm (`graphanalyze.py:104-108`), reached for + /// upstream's reason — with the caveat that membership here means a + /// `mark_*` setter named the path (`func_effects_mut`), which is an + /// assertion about the callee rather than upstream's `external` + /// annotation on the funcobj. It is the closest declaration this side + /// owns, and the only one. + pub declared_external: HashMap, + /// No graph and no declaration. Upstream's `AttributeError` arm + /// (`graphanalyze.py:109-112`) takes `top_result()` here; this side + /// answers it as declared-external, which is bottom in five of the six + /// analyzers. + pub unknown: HashMap, + /// `target_to_path` declined, keyed by `CallTarget` variant. Not one + /// answer: `analyze_can_raise_impl` takes top, `analyze_random_effects` + /// takes bottom. + pub unresolvable_by_variant: HashMap, + /// `graphs: None` — the faithfully ported indirect arm + /// (`graphanalyze.py:117-121`), already top. The control for the rows + /// above. + pub indirect_unknown_family: usize, + /// `graphs: Some([])` — folds to bottom for the opposite reason (empty-graph). + pub indirect_empty_family: usize, + pub indirect_named_family: usize, + /// `external_funcobjs.len()` — the control for a `declared_external` of + /// zero, which otherwise cannot distinguish "the declaration channel is + /// empty" from "it has members no call site names". Two different + /// findings with the same count. + pub external_funcobjs_len: usize, + /// `function_graphs.len()` — the same control for `with_graph`. + pub function_graphs_len: usize, + /// Every `external_funcobjs` key, with the marks it carries and the + /// registered graphs whose path ends with it. + /// + /// This is what tells a declaration nothing calls from a declaration + /// spelled so that nothing *can* call it. `func_effects_mut` creates the + /// entry under whatever path the `mark_*` setter used, and + /// `insert_function_graph_indexed` only folds it into the graph when the + /// graph registers under that same key — so a mark written against a + /// shorter spelling than the one the graph carries stays here forever, + /// having annotated nothing. + pub declared_external_keys: Vec, + /// Every `::`-joined callee spelling seen in a direct-call bucket, mapped + /// to the distinct segmentations that rendered to it. + /// + /// The control for keying the buckets by segments. A spelling with one + /// entry is transcribable into a `FunctionPath(&[...])` override without + /// a choice; a spelling with two says the older joined keying was summing + /// two callees into one row, and that picking either split for an + /// override silently declines on the other — with no diagnostic, because + /// `call_target_matches_loose` reports a non-match by returning `false`. + /// + /// `BTreeSet` so two runs of one program print the same bytes. + pub segmentations_by_spelling: HashMap>, + /// Every `CallTarget::Method` call site, keyed by the three fields + /// `call_target_matches_loose`'s `Method`/`Method` arm actually reads: + /// `name`, `receiver_root`, and the `impl_type_prefix()` of + /// `resolved_path` (the only fallback it consults). + /// + /// The buckets above cannot answer why a `Method` override is inert, + /// because they key on the resolved `CallPath` and so record neither the + /// variant nor the receiver. Worse, they cannot see a `Method` site at + /// all when `target_to_path` declines — 80,254 of them on this tree — so + /// a reader concluding "no such call site exists" from the buckets would + /// be reading a population the instrument excludes. This map is therefore + /// filled *before* the resolution check, and covers every `Method` site + /// whether or not it resolves. + pub method_shapes: HashMap, +} + +/// One `external_funcobjs` entry, as read by the callee census census. +#[derive(Default, Debug)] +pub struct DeclaredExternalKey { + /// `path.segments.join("::")`. + pub spelling: String, + /// The non-default [`FuncEffects`](crate::model::FuncEffects) fields this + /// entry carries. Empty means the entry exists but asserts nothing, so + /// nothing is lost by its being unreachable. + pub marks: Vec, + /// Registered graph paths ending with this key's segments — the same + /// function under a longer spelling. Non-empty means the mark was + /// orphaned: the graph exists, and this assertion never reached it. + pub graph_suffix_matches: Vec, + /// Registered graph paths sharing this key's leaf segment, and one + /// example. The control for `graph_suffix_matches` being empty, which + /// alone cannot separate "no graph anywhere names this function" from + /// "a graph names it under a spelling a suffix match cannot reach" — a + /// prefix *replacement* (`crate::jit::x` vs `majit_metainterp::jit::x`) + /// is not a suffix relation in either direction. + pub leaf_candidates: usize, + pub leaf_example: Option, + /// Marks carried by `leaf_example`'s own graph. This is what separates a + /// LOST mark from a harmless duplicate: the same hint is written twice, + /// once against the graph's path (`lib.rs:1705`) and once against the + /// 2-segment owner spelling (`lib.rs:1743-1748`). If the graph already + /// carries the mark, the `external_funcobjs` entry is redundant; if it + /// does not, the assertion was lost. + pub leaf_example_marks: Vec, +} + +/// The non-default fields of a [`FuncEffects`](crate::model::FuncEffects), +/// named. An empty result means the record asserts nothing. +fn func_effects_marks(effects: &crate::model::FuncEffects) -> Vec { + let mut marks = Vec::new(); + if let Some(spec) = &effects.oopspec { + marks.push(format!("oopspec={spec}")); + } + for (flag, name) in [ + (effects.cannot_collect, "cannot_collect"), + (effects.random_effects_on_gcobjs, "random_effects_on_gcobjs"), + (effects.cannot_raise_assertion, "cannot_raise_assertion"), + (effects.memerror_only_assertion, "memerror_only_assertion"), + (effects.elidable, "elidable"), + (effects.loop_invariant, "loop_invariant"), + (effects.close_stack, "close_stack"), + ] { + if flag { + marks.push(name.to_string()); + } + } + marks +} + +impl UnknownCalleeCensus { + /// Bucket totals as `(distinct callees, call sites)`. + fn totals(bucket: &HashMap) -> (usize, usize) { + (bucket.len(), bucket.values().sum()) + } + + /// The `limit` heaviest entries, most call sites first, ties broken by + /// spelling — an order that reproduces across processes, unlike the + /// map's own. + fn top(bucket: &HashMap, limit: usize) -> Vec<(&str, usize)> { + let mut rows: Vec<(&str, usize)> = bucket.iter().map(|(k, v)| (k.as_str(), *v)).collect(); + rows.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(b.0))); + rows.truncate(limit); + rows + } + + /// Rows per table, from `PYRE_CALLEE_CENSUS_ROWS` (`all` for no cap). + /// + /// A knob rather than a constant because the question this census is + /// usually asked — *what spelling does a call site actually carry?* — + /// cannot be answered from a 25-row prefix of a 14,834-entry population, + /// and widening a constant costs a whole analysis pass to rebuild. + /// + /// An unparseable value is reported beside the table rather than silently + /// replaced by the default: a cap that quietly ignored what was asked for + /// would make the header's own `limit=` a lie. + fn row_limit() -> (usize, Option) { + const DEFAULT: usize = 25; + match std::env::var("PYRE_CALLEE_CENSUS_ROWS") { + Err(_) => (DEFAULT, None), + Ok(raw) if raw == "all" => (usize::MAX, None), + Ok(raw) => match raw.parse::() { + Ok(n) => (n, None), + Err(_) => (DEFAULT, Some(raw)), + }, + } + } + + /// One bucket's heaviest rows, with the cap and the denominator on the + /// header line. + /// + /// `distinct` is the population, `shown` is what this table lists and + /// `limit` is what was asked for. All three ride on the header because a + /// reader holding only the rows cannot tell a complete table from a + /// truncated one — and a truncated census does not fail, it reports a + /// smaller true number, which is the failure mode that reads as a result. + fn write_bucket( + f: &mut std::fmt::Formatter<'_>, + label: &str, + bucket: &HashMap, + limit: usize, + ) -> std::fmt::Result { + let rows = Self::top(bucket, limit); + let limit_shown = if limit == usize::MAX { + "all".to_string() + } else { + limit.to_string() + }; + writeln!( + f, + "[callee census] {label}_rows distinct={} shown={} limit={limit_shown} \ + order=count-desc,key-asc", + bucket.len(), + rows.len() + )?; + for (name, count) in rows { + writeln!(f, "[callee census] {label} {count:>6} {name}")?; + } + Ok(()) + } +} + +impl std::fmt::Display for UnknownCalleeCensus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for (label, bucket) in [ + ("with_graph", &self.with_graph), + ("declared_external", &self.declared_external), + ("unknown", &self.unknown), + ("unresolvable", &self.unresolvable_by_variant), + ] { + let (distinct, sites) = Self::totals(bucket); + writeln!( + f, + "[callee census] {label}: {distinct} distinct, {sites} call sites" + )?; + } + writeln!( + f, + "[callee census] indirect: {} unknown-family (top), {} empty-family (bottom), \ + {} named-family", + self.indirect_unknown_family, self.indirect_empty_family, self.indirect_named_family + )?; + writeln!( + f, + "[callee census] registry sizes: function_graphs {}, external_funcobjs {}", + self.function_graphs_len, self.external_funcobjs_len + )?; + // The keying control. Both denominators ride on the line so an empty + // list below is distinguishable from a run that recorded nothing: + // `joined 0` means the walk found no direct calls at all, `split 0` + // out of a non-zero `joined` means the two keyings agree here. + let mut split: Vec<(&str, &BTreeSet)> = self + .segmentations_by_spelling + .iter() + .filter(|(_, splits)| splits.len() > 1) + .map(|(spelling, splits)| (spelling.as_str(), splits)) + .collect(); + split.sort(); + let segmented_keys: usize = self + .segmentations_by_spelling + .values() + .map(BTreeSet::len) + .sum(); + writeln!( + f, + "[callee census] segmentation: {} joined spellings carry >1 segmentation \ + (joined {}, segmented {})", + split.len(), + self.segmentations_by_spelling.len(), + segmented_keys + )?; + for (spelling, splits) in &split { + writeln!( + f, + "[callee census] split-spelling {spelling} -> {}", + splits.iter().cloned().collect::>().join(" | ") + )?; + } + let (limit, rejected) = Self::row_limit(); + if let Some(raw) = rejected { + writeln!( + f, + "[callee census] row_limit_env_ignored value={raw:?} is neither a number nor \ + `all`; the tables below use the default" + )?; + } + // `with_graph` is printed here for the first time. Its callees are the + // only place a call site's ACTUAL resolved spelling is readable, and + // the census used to publish just its count -- so the one question an + // override author must answer (what do I have to match?) could not be + // answered from the census at all. + Self::write_bucket(f, "with_graph", &self.with_graph, limit)?; + // Zero-row on every run so far. Printed anyway: an absent table and a + // table that is genuinely empty read identically, and telling those + // two apart is the whole open question about this bucket. + Self::write_bucket(f, "declared_external", &self.declared_external, limit)?; + Self::write_bucket(f, "unknown", &self.unknown, limit)?; + Self::write_bucket(f, "unresolvable", &self.unresolvable_by_variant, limit)?; + // Keyed by the fields the Method/Method arm reads, not by resolved + // path, and populated before the resolution check — so this is the + // only table in which a `Method` override's non-match is falsifiable. + Self::write_bucket(f, "method_shapes", &self.method_shapes, limit)?; + let orphaned = self + .declared_external_keys + .iter() + .filter(|decl| !decl.graph_suffix_matches.is_empty()) + .count(); + let carrying = self + .declared_external_keys + .iter() + .filter(|decl| !decl.marks.is_empty()) + .count(); + writeln!( + f, + "[callee census] declarations: {} total, {orphaned} ORPHANED (a registered graph's \ + path ends with the key, so the mark never reached it), {carrying} carrying marks", + self.declared_external_keys.len() + )?; + for decl in &self.declared_external_keys { + let marks = if decl.marks.is_empty() { + "(no marks)".to_string() + } else { + decl.marks.join(",") + }; + write!(f, "[callee census] decl {} [{marks}]", decl.spelling)?; + match decl.graph_suffix_matches.split_first() { + Some((first, rest)) if rest.is_empty() => writeln!(f, " ORPHANED-> {first}")?, + Some((first, rest)) => writeln!(f, " ORPHANED-> {first} (+{} more)", rest.len())?, + // No suffix match. The leaf control says whether that means + // no graph names this function at all, or one names it under + // a spelling the suffix test cannot reach. + None => match &decl.leaf_example { + None => writeln!( + f, + " no-graph (leaf absent, {} candidates)", + decl.leaf_candidates + )?, + Some(example) => { + let graph_marks = if decl.leaf_example_marks.is_empty() { + "MARK-LOST".to_string() + } else { + format!("graph-has[{}]", decl.leaf_example_marks.join(",")) + }; + writeln!( + f, + " NO-SUFFIX-MATCH but leaf has {} graph(s), e.g. {example} {graph_marks}", + decl.leaf_candidates + )? + } + }, + } + } + Ok(()) + } +} + +/// `CallTarget`'s variant name, for bucketing a target no `CallPath` +/// resolution reached. +fn call_target_variant_name(target: &CallTarget) -> &'static str { + match target { + CallTarget::Method { .. } => "Method", + CallTarget::FunctionPath { .. } => "FunctionPath", + CallTarget::SyntheticTransparentCtor { .. } => "SyntheticTransparentCtor", + CallTarget::Indirect { .. } => "Indirect", + CallTarget::UnsupportedExpr => "UnsupportedExpr", + } +} + pub struct CallControl { /// Registered graphs, keyed by call path through a funcobj-identity /// indirection so alias spellings share one `FunctionGraph` (and thus @@ -1717,7 +2125,7 @@ impl CallControl { // Same rule as the named-element path (`get_type_flag`) and // the codewriter-less fallback in `assembler.rs`: a pointer // element strides by the TARGET word, an int/float bank by 8. - // The list/tuple items-block ops the #171 append fold emits + // The list/tuple items-block ops the list-append append fold emits // carry no `array_type_id`, so a flat 8 here would stride a // `GcArray(OBJECTPTR)` at 8 bytes on a 32-bit target while // the runtime block holds 4-byte items. @@ -1916,27 +2324,19 @@ impl CallControl { /// element type come from `get_type_flag(field_type_str)` (same /// mechanism `arraydescrof` uses for primitive item sizing). /// - /// `descr.py:218-239 get_field_descr` cache-or-mint: PyPy - /// `cpu.fielddescrof` 는 `(STRUCT, fieldname)` identity 로 cache 하여 - /// 분석기와 런타임이 동일한 `FieldDescr` Arc 에 도달한다. Pyre 는 - /// `path_hash(STRUCT)` 를 surrogate identity 로 쓰는데, 런타임은 - /// `__majit_type_id` = `path_hash(concat!(module_path!(), "::", - /// stringify!(Struct)))` (`jit_struct.rs:92`) 로 def-path 를 해시 - /// 한다. 분석기 `field.owner_root` 는 `qualify_type_name(type_root, - /// ctx.module_prefix)` 로 use-site 모듈 qualifier 를 받으므로 - /// `canonical_struct_name` (Followup 2, `5fcab5ddc8`, - /// `STRUCT_ORIGIN_REGISTRY` 컨설팅) 를 거쳐 정의-모듈 qualifier 로 - /// 표준화해야 publish 슬롯과 일치한다. `fielddescrof_concrete` 의 - /// `path_hash` boundary 에서 canonicalise (이 모듈 fix), 그리고 - /// `interiorfielddescrof`/`all_interiorfielddescrs` 와 동일 패턴 - /// (`call.rs:1492` + `:5067`). + /// PyPy's `descr.py:218-239 get_field_descr` caches by `(STRUCT, + /// fieldname)`, so analyzer and runtime users reach one descriptor. Pyre + /// uses `path_hash(STRUCT)` for that identity. Runtime publication hashes + /// the definition path through `__majit_type_id` (`jit_struct.rs:92`), + /// while analyzer fields can initially carry a use-site-qualified owner. + /// `canonical_struct_name` consults `STRUCT_ORIGIN_REGISTRY` to normalize + /// that owner to its definition path before hashing. The same rule is used + /// by `interiorfielddescrof` and `all_interiorfielddescrs`. /// - /// `ei_index` consequence (`effectinfo.py:465-538 compute_bitstrings`): - /// `EI_INDEX_TABLE` side-table 폐기 (Round 4) 이후 - /// `descr.get_ei_index()` 만 readback (`heap.rs:866`). canonicalise - /// 가 분석기와 런타임을 같은 `register_keyed_field` Arc 로 모으므로 - /// `set_ei_index` 가 단일 슬롯에 도달, cross-module 호출 사이트도 - /// effectinfo bitstring 을 정상 소비한다. + /// `effectinfo.py:465-538 compute_bitstrings` stores the effect-info index + /// on the descriptor itself. Canonicalization therefore also ensures that + /// cross-module callers read the index written by `set_ei_index` from the + /// same `register_keyed_field` descriptor. /// /// `None` when the struct is not registered in `self.struct_fields` /// (unanalyzable callee — caller silently skips the raw-set push). @@ -2055,19 +2455,10 @@ impl CallControl { // `is_quasi_immutable=true` (the `record_quasiimmut_field` // path `jtransform.py:895-903`). Missing entry retains // the mutable default. - // `descr.py:108-118 cache[STRUCT]` 단일 identity 와 - // 정렬: 분석기측 `owner_root` 가 use-site 모듈 qualifier - // (`front::mir` 이 Charon `name_path()` 에서 기록) 인 - // 동안 런타임 publish 는 - // `path_hash(strip_crate(module_path!())::Name)` (def-path). - // `canonical_struct_name` 가 `STRUCT_ORIGIN_REGISTRY` - // (Followup 2, `5fcab5ddc8`) 를 통해 bare-name 입력에 - // 정의-모듈 qualifier 를 붙여 `path_hash` 가 publish 슬롯 - // 과 일치하게 한다. Cross-module use-site 도 같은 - // `register_keyed_field` Arc 에 도달하므로 `set_ei_index` - // 가 런타임 reader 와 단일 슬롯에서 만난다. - // `interiorfielddescrof`/`all_interiorfielddescrs` 와 동일 - // 패턴 (`call.rs:1492` + `:5067`). + // Runtime publication hashes a struct's definition path, + // whereas analyzer input can carry a use-site-qualified owner. + // Normalize through `STRUCT_ORIGIN_REGISTRY` so both paths use + // the same keyed descriptor and its attached effect-info index. // Prefer the source-attached identity token (collision-free // even when `owner_root` is a bare leaf two modules share); // fall back to canonicalising the name when the descriptor @@ -2186,7 +2577,9 @@ impl CallControl { flag, u32::MAX, false, - index_in_parent, + // Always a claim: `field_pos_in` walks the owner's layout and + // panics rather than hand back an unnumbered field. + Some(index_in_parent), ); descr.set_index(idx); // Same arguments this `get_field_descr` miss just used, kept so @@ -2355,7 +2748,9 @@ impl CallControl { flag, u32::MAX, false, - index_in_parent, + // Always a claim: `field_pos_in` walks the owner's layout and + // panics rather than hand back an unnumbered field. + Some(index_in_parent), ); found = Some(mint as std::sync::Arc); } @@ -3497,7 +3892,15 @@ impl CallControl { /// Size of the post-`find_all_graphs` candidate set — the graphs the /// portal closure actually reaches, against which the registered graph - /// count is the eagerly-lowered universe. + /// count (`function_graphs.len()`) is the eagerly-lowered universe. + /// + /// `function_graphs.len()` and `SemanticProgram::functions.len()` are + /// *different* populations — the registry is roughly 4x the program's + /// function list — so a ratio taken against one is not comparable to a + /// ratio taken against the other. Whenever this count is published as a + /// proportion, name the denominator alongside it; the pipeline profile + /// line (`lib.rs`, `PYRE_PROFILE_PIPELINE`) prints both absolutes rather + /// than a quotient for exactly that reason. pub fn candidate_graph_count(&self) -> usize { self.candidate_graphs.len() } @@ -4503,51 +4906,33 @@ impl CallControl { if receiver.contains("::") || canonical != receiver { return None; } + // A closure receiver names the kind, not a type, so it can + // never identify a callee. Subsumed by the retirement of the + // receiver-agnostic fallback below — every receiver now + // declines — and kept as an explicit statement of the case, + // since resolving these for real needs the closure bodies in + // `function_graphs`, which the front end does not lower. + if is_closure_receiver(receiver) { + return None; + } } - // TODO(parity): retire when annotator wiring publishes - // classdef hints before BFS runs. - // TODO: receiver-agnostic "unique concrete impl" fallback. - // Upstream `call.py:175-187 - // getfunctionptr(graph)` keys on graph identity, never on - // method name, so this branch has no RPython counterpart. - // Pyre keeps it as a BFS-coverage adaptation: the codewriter - // producer (`codewriter.rs::stamp_classdef_hints_on_graph`) - // runs per-graph during transform, which is AFTER - // `find_all_graphs_bfs` (`call.rs:2398`) has already chosen - // candidates. When BFS walks a Call site whose receiver is a - // generic trait variable (``) with no - // annotator-derived classdef hint and no receiver-name match, - // collapsing to the unique concrete impl is the only way to - // include the impl's body in `candidate_graphs`. Retired once - // the annotator publishes classdef hints - // before BFS runs; the - // `find_all_graphs_closure_reaches_handler_graphs_from_dispatch_portal` - // and `all_jitcodes_registry_contains_inherent_impl_methods` - // oracles pin the BFS coverage this branch enables. - let concrete_impls: Vec<&String> = impls - .iter() - .copied() - .filter(|t| !t.starts_with("`) that reach `find_all_graphs_bfs` + // before `stamp_classdef_hints_on_graph` has published a classdef + // hint. It is retired because uniqueness could only ever be + // checked over the impls this artifact happens to contain: a + // second lowered impl turns the bind into a decline, but an impl + // in a crate outside the artifact never registers, the count stays + // one, and the wrong bind persists with nothing to detect it. + // That is a rule over a partial universe, and the direction it + // fails in is silent. None } @@ -4596,28 +4981,14 @@ impl CallControl { if majit_ir::descr::canonical_struct_name(receiver) != receiver { return None; } + // Closure receiver: same decline as [`Self::resolve_method`]. + if is_closure_receiver(receiver) { + return None; + } } - // TODO: receiver-agnostic "unique concrete impl" fallback — see - // the matching branch in [`Self::resolve_method`] for the - // rationale. Retired - // alongside it once the annotator publishes classdef hints - // before BFS. - let concrete_impls: Vec<&String> = impls - .iter() - .copied() - .filter(|t| !t.starts_with(" UnknownCalleeCensus { + let mut census = UnknownCalleeCensus { + external_funcobjs_len: self.external_funcobjs.len(), + function_graphs_len: self.function_graphs.len(), + ..Default::default() + }; + for (_, graph) in self.function_graphs.iter() { + for block in &graph.blocks { + for op in &block.operations { + match &op.kind { + OpKind::Call { target, .. } => { + // Before the resolution check: an unresolvable + // `Method` never reaches the buckets, and that is + // exactly where a missing override match could be + // hiding. + if let CallTarget::Method { + name, + receiver_root, + resolved_path, + } = target + { + let prefix = + resolved_path.as_ref().map(|path| path.impl_type_prefix()); + *census + .method_shapes + .entry(format!( + "name={name:?} receiver_root={receiver_root:?} impl_type_prefix={prefix:?}" + )) + .or_default() += 1; + } + let Some(path) = self.target_to_path(target) else { + // `target_to_path` declined. The analyzers + // disagree about this one: `can_raise` takes + // top (:5037), `random_effects` takes bottom + // (:5170). + *census + .unresolvable_by_variant + .entry(call_target_variant_name(target).to_string()) + .or_default() += 1; + continue; + }; + // Key by the SEGMENTS: that is what `CallPath` + // equality compares, and what an override in + // `call_spec.rs` has to reproduce exactly. The + // joined spelling is recorded beside it, as the + // control for whether the two keyings differ at + // all on this tree. + let segmented = format!("{:?}", path.segments); + census + .segmentations_by_spelling + .entry(path.segments.join("::")) + .or_default() + .insert(segmented.clone()); + let bucket = if self.function_graphs.contains_key(&path) { + &mut census.with_graph + } else if self.external_funcobjs.contains_key(&path) { + &mut census.declared_external + } else { + &mut census.unknown + }; + *bucket.entry(segmented).or_default() += 1; + } + // The indirect arm is ported faithfully + // (`graphanalyze.py:117-121`), so it is the control: + // `None` here already takes top. + OpKind::IndirectCall { graphs, .. } => match graphs.as_deref() { + None => census.indirect_unknown_family += 1, + Some([]) => census.indirect_empty_family += 1, + Some(_) => census.indirect_named_family += 1, + }, + _ => {} + } + } + } + } + // Index every registered graph path by its leaf segment once, rather + // than rescanning `function_graphs` per declaration. + let mut graphs_by_leaf: HashMap<&str, Vec<(&CallPath, &FunctionGraph)>> = HashMap::new(); + for (path, graph) in self.function_graphs.iter() { + if let Some(leaf) = path.segments.last() { + graphs_by_leaf + .entry(leaf.as_str()) + .or_default() + .push((path, graph)); + } + } + let mut declared: Vec = self + .external_funcobjs + .iter() + .map(|(path, effects)| { + let leaf = path.segments.last().map(String::as_str).unwrap_or(""); + let candidates = graphs_by_leaf.get(leaf).map(Vec::as_slice).unwrap_or(&[]); + // A graph naming the same function under a longer path: its + // segments end with every segment of the declaration. + let mut graph_suffix_matches: Vec = candidates + .iter() + .filter(|(candidate, _)| candidate.segments.ends_with(&path.segments)) + .map(|(candidate, _)| candidate.segments.join("::")) + .collect(); + graph_suffix_matches.sort(); + // Render the candidate's SEGMENTS, not its joined path: when + // the two agree on the joined string and disagree on the + // split, only the segmentation shows it — and the split is + // what `CallPath` equality keys on. + let mut leaf_rows: Vec<(String, Vec)> = candidates + .iter() + .map(|(candidate, graph)| { + ( + format!("{:?}", candidate.segments), + func_effects_marks(&graph.func), + ) + }) + .collect(); + leaf_rows.sort(); + let (leaf_example, leaf_example_marks) = match leaf_rows.into_iter().next() { + Some((spelling, graph_marks)) => (Some(spelling), graph_marks), + None => (None, Vec::new()), + }; + DeclaredExternalKey { + spelling: format!("{:?}", path.segments), + marks: func_effects_marks(effects), + graph_suffix_matches, + leaf_candidates: candidates.len(), + leaf_example, + leaf_example_marks, + } + }) + .collect(); + // By spelling: the map's own order is a hash order, and this is read + // by a human comparing two runs. + declared.sort_by(|a, b| a.spelling.cmp(&b.spelling)); + census.declared_external_keys = declared; + census + } + // // The five `analyze_*` methods below walk // `crate::model::FunctionGraph` (the flat codewriter graph), inlining @@ -5176,6 +5701,126 @@ impl CallControl { false } + /// RPython: `GraphAnalyzer.explain_analyze_slowly` (graphanalyze.py:79-91) + /// — re-run the analysis with `verbose` set and collect the callstack that + /// reached the top result, for `_raise_effect_error` to print + /// (call.py:189-208). The fast analyzers memoize a bare bool, so the + /// witness has to be recomputed on the failure path rather than recorded + /// on the hot one; upstream re-`__init__`s the analyzer for the same + /// reason. + /// + /// Returned outermost-first, matching upstream's `explanation.reverse()` + /// (graphanalyze.py:90) so the offending edge sits nearest the error. + fn explain_effect_witness(&self, path: &CallPath, witness: EffectWitness) -> Vec { + let mut chain = Vec::new(); + let mut seen = HashSet::new(); + self.walk_effect_witness(path, witness, &mut seen, &mut chain); + chain.reverse(); + chain + } + + /// One walk for both witnesses: `RandomEffectsAnalyzer` and + /// `VirtualizableAnalyzer` differ only in their leaf predicates, so the + /// differing value is selected first and the traversal runs once. + fn walk_effect_witness( + &self, + path: &CallPath, + witness: EffectWitness, + seen: &mut HashSet, + chain: &mut Vec, + ) -> bool { + if !seen.insert(path.clone()) { + return false; // cycle → bottom_result + } + let name = path.segments.join("::"); + let graph = match self.function_graphs.get(path) { + Some(graph) => graph, + None => { + // Mirrors the leaf arms of `analyze_random_effects` / + // `analyze_forces_virtualizable`: only a + // `random_effects_on_gcobjs` external is a witness. + let reached = witness == EffectWitness::RandomEffects + && self + .external_funcobjs + .get(path) + .is_some_and(|funcobj| funcobj.random_effects_on_gcobjs); + if reached { + chain.push(format!("{name} is external with random_effects_on_gcobjs")); + } + return reached; + } + }; + for block in &graph.blocks { + for op in &block.operations { + let reason = match &op.kind { + OpKind::VableForce { .. } if witness == EffectWitness::ForcesVirtualizable => { + Some("forces a virtualizable".to_string()) + } + OpKind::IndirectCall { + graphs: None, + funcptr, + .. + } => Some(format!( + "calls the function pointer {funcptr} indirectly, and its \ + family is unknown" + )), + OpKind::Call { target, .. } => self.target_to_path(target).and_then(|callee| { + self.walk_effect_witness(&callee, witness, seen, chain) + .then(|| format!("calls {}", callee.segments.join("::"))) + }), + OpKind::IndirectCall { + graphs: Some(graphs), + .. + } => graphs + .iter() + .find(|callee| self.walk_effect_witness(callee, witness, seen, chain)) + .map(|callee| format!("indirectly calls {}", callee.segments.join("::"))), + _ => None, + }; + if let Some(reason) = reason { + chain.push(format!("{name} {reason}")); + return true; + } + } + } + false + } + + /// RPython: `_raise_effect_error` (call.py:189-208). A failed + /// `elidable` / `_jit_loop_invariant_` post-condition prints the + /// callstack that produced the contradicting effect before the error + /// itself, so the offending edge is named instead of searched for. + fn raise_effect_error( + &self, + target: &CallTarget, + extraeffect: ExtraEffect, + functype: &str, + ) -> ! { + // call.py:191-194 — only these two effects have an explainable + // witness; anything else falls back to the bare error. + let witness = match extraeffect { + ExtraEffect::RandomEffects => Some(EffectWitness::RandomEffects), + ExtraEffect::ForcesVirtualOrVirtualizable => Some(EffectWitness::ForcesVirtualizable), + _ => None, + }; + let explanation = witness + .zip(self.target_to_path(target)) + .map(|(witness, path)| self.explain_effect_witness(&path, witness)) + .unwrap_or_default(); + let mut msg = Vec::new(); + if !explanation.is_empty() { + msg.push("_______ ERROR AT BOTTOM ______".to_string()); + msg.push("callstack leading to problem:".to_string()); + msg.extend(explanation); + msg.push("_______ ERROR: ______".to_string()); + } + msg.push(format!( + "getcalldescr: {target} is marked {functype} but got \ + extraeffect={extraeffect:?}" + )); + panic!("{}", msg.join("\n")); + } + /// RPython: QuasiImmutAnalyzer.analyze() (effectinfo.py). /// /// analyze_simple_operation: op.opname == 'jit_force_quasi_immutable'. @@ -5298,8 +5943,6 @@ impl CallControl { false } - // ── Cached analyzer wrappers ──────────────────────────────────── - /// Cached version of _canraise for a CallTarget. /// /// RPython call.py:337-355 — `_canraise()` returns the tri-state @@ -5488,8 +6131,6 @@ impl CallControl { .any(|path| self.cached_can_collect_path(path, cache)) } - // ── _canraise + getcalldescr (call.py:210-355) ────────────────── - /// RPython: CallControl._canraise(op) (call.py:337-355). /// /// ```python @@ -5513,8 +6154,17 @@ impl CallControl { method_name, } = target { + // Same fold as `rpbc.rs:404-421` and as `getcalldescr`'s + // `CallShape::Indirect` above: `all_impls_for_indirect` returns + // an empty vector both when the family is genuinely empty and + // when its impls live outside the analyzed sources, and this + // side cannot tell those apart. `Some(&[])` would reach + // `cached_can_raise_family`'s `No` initialiser without the loop + // body ever running, i.e. "an unregistered family cannot + // raise". `None` is the honest answer and gives `Yes`. let graphs = self.all_impls_for_indirect(trait_root, method_name); - return self.cached_can_raise_family(Some(&graphs), cache); + let graphs = (!graphs.is_empty()).then_some(graphs); + return self.cached_can_raise_family(graphs.as_deref(), cache); } self.cached_can_raise(target, cache) } @@ -5552,7 +6202,28 @@ impl CallControl { } let shape = match &op.kind { OpKind::Call { target, .. } => CallShape::Direct(target), - OpKind::IndirectCall { graphs, .. } => CallShape::Indirect(graphs.as_deref()), + // An empty family is folded to `None` HERE, once, rather than + // left for each analyzer to meet. `graphs` distinguishes "the + // family is unknown" (`None` → top) from "the family is known + // and has no members" (`Some([])`), but every family analyzer + // below unwraps the `Option` and then iterates, so `Some([])` + // reaches each one's bottom result — `CanRaise::No`, + // `forces_virtualizable = false`, no random effects — i.e. it + // silently asserts that a callee nobody enumerated has no + // effects. Nothing in this pipeline ever proves a family + // closed and empty: `Some([])` is only ever the deferred + // `BuiltinWrapper` marker (`front/mir.rs:13056-13073`), and it + // survives to here exactly when the fill at `rpbc.rs:335-343` + // had no registered wrappers to fill it with. "No members" + // therefore means "unknown", which is `None`. + // + // `rpbc.rs:404-421` already makes this fold at the other site + // that answers the same question, with the same reasoning. + OpKind::IndirectCall { graphs, .. } => CallShape::Indirect( + graphs + .as_deref() + .filter(|candidates| !candidates.is_empty()), + ), other => panic!("getcalldescr called on non-call op: {other:?}"), }; @@ -5830,10 +6501,7 @@ impl CallControl { CallShape::Direct(t) => t, _ => unreachable!(), }; - panic!( - "getcalldescr: {target} is marked loop-invariant but got \ - extraeffect={extraeffect:?}" - ); + self.raise_effect_error(target, extraeffect, "loop-invariant"); } if elidable { let target = match shape { @@ -5846,10 +6514,7 @@ impl CallControl { | ExtraEffect::ElidableOrMemoryError | ExtraEffect::ElidableCanRaise ) { - panic!( - "getcalldescr: {target} is marked elidable but got \ - extraeffect={extraeffect:?}" - ); + self.raise_effect_error(target, extraeffect, "elidable"); } // call.py:315-318: elidable function must have a result if result_type == Type::Void { @@ -6146,8 +6811,19 @@ fn canonicalize_keyed_descrs( descrs.push(descr); keys.push(key?); } - // The raw set stays in pointer order for identity dedup and lookup, while - // the member order crossing into the artifact is determined by content. + // `descrs` keeps the `Arc::as_ptr` order every raw-set consumer expects + // (`descr_set_eq`, `descr_set_hash`, `compute_bitstrings`' ptr-id + // `binary_search`); it is `#[serde(skip)]` and never leaves this process, + // so its address order is harmless. + // + // `keys` does leave: it is the `descr_set_keys` that lands in `descrs.bin` + // and `jit_metadata.json`, and inheriting the address order would make + // those artifacts a function of this process's heap layout rather than of + // the analyzed source. Order it by the member instead — structural, and + // identical in any process. The two Vecs are consequently NOT positionally + // paired; the only consumer of `keys` (`rehydrate_effect_info`) rebuilds + // each member independently and re-canonicalizes, so it never indexes one + // by the other's position. keys.sort(); Some((descrs, keys)) } @@ -6304,21 +6980,13 @@ pub fn effectinfo_from_writeanalyze( // `UnsupportedFieldExc` filters at `effectinfo.py:380-397` + // `:316-324`. // - // Identity convergence (Followup 2 + Reviewer fix-1): - // `__majit_type_id` 는 `path_hash(concat!(module_path!(), "::", - // stringify!(Struct)))` (`jit_struct.rs:92`) 로 def-path 해시. - // 분석기 `field.owner_root` 는 `qualify_type_name(type_root, - // ctx.module_prefix)` 로 use-site qualifier 를 받는다. `canonical_ - // struct_name` (`STRUCT_ORIGIN_REGISTRY`, `5fcab5ddc8`) 가 - // bare-name 입력을 정의-모듈 qualifier 로 표준화하므로 - // `cc.fielddescrof()` / `cc.interiorfielddescrof()` / `all_interior - // fielddescrs` 가 `gc_cache.get_field_descr(LLType::Struct( - // path_hash(canonical)), field_name, ...)` 로 분기 통합 — - // 분석기와 런타임이 같은 `register_keyed_field` Arc 에 도달 - // (`descr.py:218-239 get_field_descr` per-tuple identity). - // - // `compute_bitstrings` 도 `descr.get_ei_index()` 단일 Arc 슬롯으로 - // 수렴하므로 heap-invalidation 이 cross-module 호출 사이트에서도 + // Runtime `__majit_type_id` values hash the struct's definition path, but + // analyzer fields can carry a use-site-qualified owner. Normalize with + // `canonical_struct_name` before descriptor lookup so `fielddescrof`, + // `interiorfielddescrof`, and `all_interiorfielddescrs` converge on the + // same `register_keyed_field` allocation. This also makes + // `compute_bitstrings` and cross-module heap invalidation share the + // descriptor's single effect-info index. // populated bitstring 을 정상 소비한다. // PyPy `effectinfo.py:345-360` `readonly` rule: // elif tup[0] == "readstruct": @@ -7200,7 +7868,9 @@ fn all_interiorfielddescrs( *flag, u32::MAX, false, - index_in_parent, + // Always a claim: the index is this field's position in + // `entries`, straight off `.iter().enumerate()`. + Some(index_in_parent), ) } }; @@ -7333,7 +8003,9 @@ fn all_interiorfielddescrs( *flag, u32::MAX, false, - index_in_parent, + // Always a claim: the index is this field's position in + // `entries`, straight off `.iter().enumerate()`. + Some(index_in_parent), ) } }; @@ -7566,7 +8238,6 @@ fn op_can_raise(op: &OpKind) -> RaiseClass { // (MemoryError,) -> MemoryErrorOnly // anything else truthy -> Yes match op { - // ── Known non-raising ops (canraise = ()) ───────────────── // RPython LL: getfield_gc, setfield_gc → cannot raise OpKind::FieldRead { .. } | OpKind::FieldWrite { .. } => RaiseClass::No, // `malloc` / `malloc_varsize` can only raise MemoryError; with @@ -7642,7 +8313,6 @@ fn op_can_raise(op: &OpKind) -> RaiseClass { | OpKind::ConditionalCall { .. } | OpKind::ConditionalCallValue { .. } => RaiseClass::No, - // ── Known raising ops ───────────────────────────────────── // RPython LL: jit_force_virtualizable has `canrun=True`, not // `canraise`; effect classification handles its special meaning. OpKind::VableForce { .. } => RaiseClass::No, @@ -7655,7 +8325,6 @@ fn op_can_raise(op: &OpKind) -> RaiseClass { // RPython LL: int_neg_ovf → canraise = (OverflowError,) OpKind::UnaryOp { .. } => RaiseClass::Yes, // ovf (others matched above) - // ── Calls handled by analyze() dispatch, not here ───────── // RPython: Call ops dispatch to analyze_direct_call/analyze_external_call. // op_can_raise is only for "simple operations" (non-call). // But if we see a Call here (shouldn't happen in normal flow), @@ -7761,7 +8430,6 @@ fn value_type_discriminant(ty: &crate::model::ValueType) -> u8 { } } -// ── Builtin call effect tables ────────────────────────────────── // // RPython equivalent: effect classification in `call.py::getcalldescr()` // combined with the builtin function tables. @@ -8447,8 +9115,14 @@ mod tests { ); } + /// A sole registered impl does NOT make its method name resolvable + /// from an unrelated receiver. `call.py:175-187 getfunctionptr(graph)` + /// keys on graph identity; the uniqueness this used to lean on could + /// only be observed over the impls this artifact contains, so an impl + /// living outside it left the count at one and the bind wrong, with + /// nothing able to detect it. #[test] - fn resolve_method_unique_impl() { + fn resolve_method_declines_receiver_agnostic_unique_impl() { let mut cc = CallControl::new(); let graph = FunctionGraph::new("PyFrame::load_local_value"); cc.register_trait_method( @@ -8458,19 +9132,24 @@ mod tests { graph, ); - // TODO: receiver-agnostic unique-impl fallback in - // `resolve_method` enables BFS to monomorphise - // generic-receiver call sites at trait dispatch. Retired - // when the annotator publishes classdef hints before BFS. + // Generic-parameter receivers and an absent receiver name no impl. assert!( cc.resolve_method("load_local_value", Some("handler"), None) - .is_some() + .is_none() ); assert!( cc.resolve_method("load_local_value", Some("H"), None) - .is_some() + .is_none() + ); + assert!(cc.resolve_method("load_local_value", None, None).is_none()); + + // Non-vacuity: the receiver that DOES name the impl still resolves, + // so this cannot be satisfied by declining everything. + let hit = cc.resolve_method("load_local_value", Some("PyFrame"), None); + assert_eq!( + hit.expect("named receiver resolves").name, + "PyFrame::load_local_value" ); - assert!(cc.resolve_method("load_local_value", None, None).is_some()); } #[test] @@ -8532,8 +9211,13 @@ mod tests { assert_eq!(miframe.unwrap().name, "MIFrame::push_value"); } + /// A `resolved_path` that names no registered graph declines outright. + /// It used to fall through to the receiver-agnostic fallback, which + /// answered with whichever impl happened to be the table's only owner + /// of the method name — a different graph than the one the call site + /// asked for. #[test] - fn resolve_method_falls_back_when_resolved_path_miss() { + fn resolve_method_declines_when_resolved_path_misses() { let mut cc = CallControl::new(); cc.register_trait_method( "load_local_value", @@ -8545,11 +9229,18 @@ mod tests { let unknown_path = CallPath::for_impl_method("Unknown", "load_local_value"); assert!( cc.resolve_method("load_local_value", Some("handler"), Some(&unknown_path)) + .is_none() + ); + + // Non-vacuity: the `resolved_path` that DOES name a registered + // graph still resolves through the same call. + let known_path = CallPath::for_impl_method("PyFrame", "load_local_value"); + assert!( + cc.resolve_method("load_local_value", Some("handler"), Some(&known_path)) .is_some() ); } - // ── getcalldescr tests ───────────────────────────���────────────── /// Helper: create a FunctionGraph with just a return. fn simple_graph(name: &str) -> FunctionGraph { let mut g = FunctionGraph::new(name); @@ -9905,8 +10596,6 @@ mod tests { assert_eq!(cc.jitdrivers_sd[0].index_of_virtualizable, 0); } - // ── RPython indirect_call family tests ────────────────────────── - /// `guess_call_kind` for `OpKind::IndirectCall`: /// ≥1 candidate impl is a regular candidate → `Regular` /// graphs `None` (unknown family) → `Residual` @@ -10012,6 +10701,121 @@ mod tests { ); } + /// `getcalldescr` must answer an indirect call whose family it cannot + /// enumerate with the analyzers' TOP result on every counter, matching + /// `graphanalyze.py:117-121` (`graphs is None` → `top_result()`). + /// + /// A family with no members carries no more information than a family + /// that was never enumerated — nothing in this pipeline ever proves a + /// family closed and empty — so the two must give the same answer. The + /// registered family is the non-vacuity control: it shows the analyzers + /// really do run and really can reach their bottom result here, so a + /// top answer above is the lattice speaking and not an inert descriptor. + #[test] + fn unknown_indirect_family_analyzes_to_top() { + let mut cc = CallControl::new(); + cc.register_trait_method("run", Some("Handler"), "A", simple_graph("A::run")); + cc.register_trait_method("run", Some("Handler"), "B", simple_graph("B::run")); + cc.find_all_graphs_for_tests(); + + let family = cc.all_impls_for_indirect("Handler", "run"); + assert_eq!( + family.len(), + 2, + "control needs an enumerable family: {family:?}" + ); + + let descr_of = |graphs: Option>| { + let mut cache = AnalysisCache::default(); + cc.getcalldescr( + &indirect_call_op(graphs), + Vec::new(), + Type::Void, + OopSpecIndex::None, + None, + &mut cache, + None, + ) + }; + + // Control — the mechanism is engaged: two enumerable, effect-free + // members drive every family analyzer to its bottom result. + let known = descr_of(Some(family)); + assert_eq!(known.extra_info.extraeffect, ExtraEffect::CannotRaise); + assert!(!known.extra_info.can_invalidate); + assert!(!known.extra_info.can_collect); + assert!( + known.extra_info._write_descrs_fields.is_some(), + "an enumerated family has a concrete write set" + ); + + // Invariant — an unenumerable family is top on every counter. + let unknown = descr_of(None); + assert_eq!(unknown.extra_info.extraeffect, ExtraEffect::RandomEffects); + assert!(unknown.extra_info.can_invalidate); + assert!(unknown.extra_info.can_collect); + assert!( + unknown.extra_info._write_descrs_fields.is_none(), + "an unenumerable family's write set is the wildcard" + ); + + // Invariant — "no members" carries no information "not enumerated" + // does not, so it must reach the same top. + let no_members = descr_of(Some(Vec::new())); + assert_eq!( + no_members.extra_info.extraeffect, + unknown.extra_info.extraeffect + ); + assert_eq!( + no_members.extra_info.can_invalidate, + unknown.extra_info.can_invalidate + ); + assert_eq!( + no_members.extra_info.can_collect, + unknown.extra_info.can_collect + ); + assert!(no_members.extra_info._write_descrs_fields.is_none()); + } + + /// `_canraise`'s `CallTarget::Indirect` arm resolves the family itself + /// through `all_impls_for_indirect`, so it meets the same question one + /// step earlier than `getcalldescr` does — and must answer it the same + /// way: a family it cannot enumerate can raise. + /// + /// The two registered families are the non-vacuity control: they show + /// `cached_can_raise_family` reaching both `No` and `Yes` off the + /// members it was handed, so the unregistered family's `Yes` is the + /// unknown-family rule and not a constant. + #[test] + fn unenumerable_indirect_family_canraise_is_top() { + let mut cc = CallControl::new(); + cc.register_trait_method("run", Some("Quiet"), "A", simple_graph("A::run")); + cc.register_trait_method("run", Some("Loud"), "B", raising_graph("B::run")); + cc.find_all_graphs_for_tests(); + + let mut cache = AnalysisCache::default(); + assert_eq!( + cc._canraise(&CallTarget::indirect("Quiet", "run"), &mut cache), + CanRaise::No, + "control: an enumerated non-raising family reaches bottom" + ); + assert_eq!( + cc._canraise(&CallTarget::indirect("Loud", "run"), &mut cache), + CanRaise::Yes, + "control: an enumerated raising member is seen" + ); + + assert!( + cc.all_impls_for_indirect("Unheard", "run").is_empty(), + "the invariant below needs a family with no registered impls" + ); + assert_eq!( + cc._canraise(&CallTarget::indirect("Unheard", "run"), &mut cache), + CanRaise::Yes, + "a family with no enumerable members is unknown, not proven empty" + ); + } + /// Inherent impl (no `impl Trait for Type`) continues to resolve /// via `function_graphs` and classify as `Regular`, without /// populating `trait_method_impls`. @@ -10313,4 +11117,91 @@ mod tests { assert_eq!(cc.fnaddr_for_target(&target), 0x1234); assert_eq!(cc.target_to_path(&target), None); } + + /// A closure's `Fn::call` must not be resolved by method name. + /// + /// A closure receiver names the *kind*, so the receiver-agnostic + /// "unique impl owning this method name" fallback — a BFS-coverage + /// adaptation for generic *trait* receivers with no RPython + /// counterpart — would bind every closure invocation to whichever + /// unrelated graph happens to be the only registered `call`. Observed + /// in pyre: `longobject::jit_bigint_is_zero`'s `($body)(value)` bound + /// to `::call`, grafting the + /// whole opcode dispatcher onto a function that only reads a bigint. + /// + /// **Every disambiguated spelling has to be a case here.** The + /// guard originally compared against the bare literal, and this test + /// exercised only that literal, so both were green while a codegen + /// over the three pyre artefacts declined **1** receiver and let + /// **10** — `closure#1`, `closure#12`, `closure#39`, … — through to + /// the bad bind. The disambiguated forms are the production majority; + /// the bare one is the exception. + #[test] + fn closure_receiver_does_not_resolve_by_method_name() { + let mut cc = CallControl::new(); + // The sole registered `call` — exactly the shape that made the + // fallback fire, a trait default-method shim with no concrete impl. + cc.register_trait_method( + "call", + Some("OpcodeStepExecutor"), + "", + FunctionGraph::new("opcode_step_executor_call"), + ); + + // Both spellings Charon produces. `closure#1` / `closure#39` are + // verbatim from the measured population, not invented. + for receiver in [CLOSURE_RECEIVER_ROOT, "closure#1", "closure#39"] { + let closure_call = CallTarget::Method { + name: "call".to_string(), + receiver_root: Some(receiver.to_string()), + resolved_path: None, + }; + assert_eq!( + cc.target_to_path(&closure_call), + None, + "receiver {receiver:?} must not bind to an unrelated same-named graph" + ); + } + + // No receiver-agnostic fallback survives, so every receiver that + // does not name the registration declines — the near-miss spellings + // the class match must not widen onto, and the generic-parameter + // receiver the fallback used to exist for, alike. + for receiver in [ + "closures", + "closure_env", + "closure#", + "closure#a", + "handler", + "H", + ] { + let other = CallTarget::Method { + name: "call".to_string(), + receiver_root: Some(receiver.to_string()), + resolved_path: None, + }; + assert_eq!( + cc.target_to_path(&other), + None, + "receiver {receiver:?} names no registered impl and must decline" + ); + } + + // Non-vacuity: the receiver that DOES name the registration still + // resolves, so the assertions above cannot be satisfied by a + // `target_to_path` that answers `None` for everything. + let named_call = CallTarget::Method { + name: "call".to_string(), + receiver_root: Some("".to_string()), + resolved_path: None, + }; + assert_eq!( + cc.target_to_path(&named_call), + Some(CallPath::for_impl_method( + "", + "call" + )), + "a receiver that names the registration must still resolve" + ); + } } diff --git a/majit/majit-translate/src/codewriter/jitcode.rs b/majit/majit-translate/src/codewriter/jitcode.rs index b4b02771c51..1b139b47cf8 100644 --- a/majit/majit-translate/src/codewriter/jitcode.rs +++ b/majit/majit-translate/src/codewriter/jitcode.rs @@ -1253,7 +1253,23 @@ pub enum BhDescr { is_field_signed: bool, is_immutable: bool, is_quasi_immutable: bool, - index_in_parent: usize, + /// The producer's `descr.py:228` slot claim, `None` when it never + /// resolved one. + /// + /// `Option` rather than `usize` because the two states have to survive + /// the crossing to the runtime and a literal `0` cannot carry them: + /// pyre mints in the build process and resolves in another + /// (`pyre-jit-trace/build.rs`), so `descrs.bin` is the only channel, + /// and an unwritten mint and a real slot-0 claim serialize to the same + /// bytes the moment this is a plain integer. `derive_index_in_parent` + /// then reports both as `caller_index=0` and the `unresolved` table + /// cannot say which rows are slot claims at all. + /// + /// It is deliberately NOT a separate `index_resolved: bool` beside a + /// `usize`: that pair can represent `resolved = false` next to a + /// nonzero index, a state with no meaning, and nothing would stop a + /// later edit from writing it. + index_in_parent: Option, parent: Option, name: String, owner: String, @@ -1408,6 +1424,263 @@ pub enum BhDescr { }, } +/// descriptor census: the length-bearing content of the serialized descr pool. +/// +/// C0 measured the entry *count* stable across two generations (4537 both) +/// while `descrs.bin` moved −2,226 bytes, first difference at offset 1182. +/// Same number of entries, different total bytes ⇒ at least one entry +/// serialized to a different **length**. In a bincode encoding only the +/// variable-length components can do that: `String` payloads, `Vec` +/// membership, and enum discriminants selecting differently-sized arms. +/// +/// Each channel is summed separately so a second generation names *which* +/// one moved rather than only that the file did. The pool-population +/// counters ([`crate::codewriter::assembler::DescrPoolDuplication`]) cannot +/// see any of this — they measure how many entries there are, not how long +/// each one is. +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct DescrPoolContent { + /// Must equal the pool census `total`. A mismatch means this walk and + /// the pool walk disagree about the population, and nothing below is + /// interpretable. + pub entries: usize, + /// Entries per variant. A `BTreeMap` rather than a `HashMap` so the + /// printed order is deterministic — a hash-ordered instrument would be + /// a source of the very non-determinism it is measuring. + pub kind_counts: std::collections::BTreeMap<&'static str, usize>, + /// The string channel: count and total bytes of every `String` / + /// `Option` payload reachable from an entry. + pub string_count: usize, + pub string_bytes: usize, + /// The `Vec` channel, excluding descr-set membership below. + pub vec_members: usize, + /// `descr_set_keys` membership summed over all six sets of every + /// concrete `EffectInfo`. This is what separates "a set gained or lost + /// a member" from "a string changed length". + pub descr_set_members: usize, + /// Entries whose `descr_set_keys` is the `EF_RANDOM_EFFECTS` wildcard, + /// which contributes no membership. Reported so `descr_set_members` + /// has a denominator and a zero cannot be read as "no sets present". + pub wildcard_effects: usize, + /// `Field` pool slots that exist ONLY because `index_in_parent` is an + /// `Option` — entries that would merge with another if `None` collapsed + /// back to `Some(0)`. + /// + /// This is the cost of carrying provenance in the pool key, measured + /// rather than argued, and in one build rather than an A/B: the `Option` + /// adds exactly one distinction over the old `usize` key (`None` vs + /// `Some(0)`), so the entries a collapse would remove ARE the difference + /// the old key would have shown. **Zero means the key change split + /// nothing** — no entry count moved, so no downstream count keyed on the + /// pool population moved either. + /// + /// It is an UPPER BOUND, not an exact count, and the asymmetry is what + /// makes it usable. The collapsed key spells the parent through + /// `all_fielddescrs.len()` rather than the list itself, while + /// `AssemblerDescrKey::Field` carries the whole `BhSizeSpec` — so two + /// entries under same-shaped but differently-populated parents merge HERE + /// and not in the real pool. That can only inflate the number. **Zero is + /// therefore exact**: nothing merged under a coarser key means nothing + /// would merge under the finer one either. A nonzero reading is a ceiling + /// on the split and needs `field_dupe_report` to say what actually varied. + pub field_index_provenance_splits: usize, + /// Order-independent digest of every `path_hash`-derived `type_id`. + /// + /// Addition is commutative, so this moves only if the *set* of hashed + /// struct paths changes — not if the same paths are visited in a + /// different order. That is exactly the discriminator the suspect + /// needs: `path_hash` uses fixed SipHash keys and cannot vary per + /// process, so the open question is *which* path gets hashed. + pub type_id_sum: u64, + pub type_id_count: usize, + /// Logical fields `(owner, name)` holding more than one pool entry. + /// + /// The pool key is injective over `BhDescr::Field`, so a group of size + /// two is one logical field emitted under two different payloads — which + /// is exactly the +1 that makes `descrs.len()` differ between runs. + /// Expected 0 in a run that reproduces the smaller pool. + pub field_dupe_groups: usize, + pub field_dupe_entries: usize, + /// The groups themselves, one header line per group followed by the + /// indented spellings. This is the payload that names the varying + /// component; without it a nonzero `field_dupe_groups` says only that + /// something split. + pub field_dupe_report: Vec, +} + +fn account_effect(effect: &majit_ir::descr::EffectInfo, out: &mut DescrPoolContent) { + match &effect.descr_set_keys { + None => out.wildcard_effects += 1, + Some(keys) => { + out.descr_set_members += keys.readonly_fields.len() + + keys.write_fields.len() + + keys.readonly_arrays.len() + + keys.write_arrays.len() + + keys.readonly_interiorfields.len() + + keys.write_interiorfields.len(); + } + } +} + +/// Census the length-bearing content of exactly the slice that gets +/// serialized to `descrs.bin` — see [`DescrPoolContent`]. +pub fn descr_pool_content(descrs: &[BhDescr]) -> DescrPoolContent { + let mut out = DescrPoolContent { + entries: descrs.len(), + ..Default::default() + }; + // `AssemblerDescrKey::Field` carries every component of `BhDescr::Field`, + // so the pool key is injective over the variant: two entries can only + // coexist by differing somewhere. Grouping on the logical identity + // `(owner, name)` therefore turns a cross-run count difference into a + // SINGLE-run observation — the run with the extra entry has a group of + // size two, and the two spellings name the component that varied. + let mut fields_by_identity: std::collections::BTreeMap<(&str, &str), Vec> = + std::collections::BTreeMap::new(); + // Keyed by the pre-`Option` spelling of the same entry; see + // `DescrPoolContent::field_index_provenance_splits`. + let mut collapsed_field_keys: std::collections::BTreeMap = + std::collections::BTreeMap::new(); + for descr in descrs { + let kind = match descr { + BhDescr::Field { + offset, + field_size, + field_type, + field_flag, + is_field_signed, + is_immutable, + is_quasi_immutable, + index_in_parent, + name, + owner, + parent, + } => { + out.string_count += 2; + out.string_bytes += name.len() + owner.len(); + if let Some(spec) = parent { + out.type_id_sum = out.type_id_sum.wrapping_add(spec.type_id); + out.type_id_count += 1; + } + let parent_spelling = match parent { + None => "none".to_string(), + Some(spec) => format!( + "size={} type_id={:#x} vtable={:#x} gc={} headerless={} nfields={}", + spec.size, + spec.type_id, + spec.vtable, + spec.is_gc_managed, + spec.headerless, + spec.all_fielddescrs.len(), + ), + }; + // `none` and `0` are printed distinctly on purpose: a spelling + // that rendered both as `0` would hide exactly the distinction + // this field was widened to carry. + let idx_spelling = match index_in_parent { + None => "none".to_string(), + Some(i) => i.to_string(), + }; + // The same entry with the `Option` collapsed back to the old + // `usize` key. Two entries sharing this string are two pool + // slots the pre-`Option` key would have merged. + collapsed_field_keys + .entry(format!( + "{owner}.{name} off={offset} sz={field_size} ty={field_type:?} \ + flag={field_flag:?} signed={is_field_signed} imm={is_immutable} \ + qi={is_quasi_immutable} idx={} parent[{parent_spelling}]", + index_in_parent.unwrap_or(0) + )) + .and_modify(|n| *n += 1) + .or_insert(1usize); + fields_by_identity + .entry((owner.as_str(), name.as_str())) + .or_default() + .push(format!( + "off={offset} sz={field_size} ty={field_type:?} flag={field_flag:?} \ + signed={is_field_signed} imm={is_immutable} qi={is_quasi_immutable} \ + idx={idx_spelling} parent[{parent_spelling}]" + )); + "field" + } + BhDescr::Array { + type_id, + array_type_id, + interior_fields, + .. + } => { + out.type_id_sum = out.type_id_sum.wrapping_add(*type_id); + out.type_id_count += 1; + if let Some(spelling) = array_type_id { + out.string_count += 1; + out.string_bytes += spelling.len(); + } + out.vec_members += interior_fields.len(); + "array" + } + BhDescr::Size { + type_id, + owner, + all_fielddescrs, + .. + } => { + out.type_id_sum = out.type_id_sum.wrapping_add(*type_id); + out.type_id_count += 1; + out.string_count += 1; + out.string_bytes += owner.len(); + out.vec_members += all_fielddescrs.len(); + "size" + } + BhDescr::Switch { + const_keys_in_order, + .. + } => { + out.vec_members += const_keys_in_order.len(); + "switch" + } + BhDescr::VtableMethod { + trait_root, + method_name, + } => { + out.string_count += 2; + out.string_bytes += trait_root.len() + method_name.len(); + "vtable_method" + } + BhDescr::Call { calldescr } => { + account_effect(&calldescr.extra_info, &mut out); + "call" + } + BhDescr::JitCode { calldescr, .. } => { + account_effect(&calldescr.extra_info, &mut out); + "jitcode" + } + BhDescr::InteriorField { .. } => "interior_field", + BhDescr::VableField { .. } => "vable_field", + BhDescr::VableArray { .. } => "vable_array", + }; + *out.kind_counts.entry(kind).or_default() += 1; + } + for ((owner, name), mut spellings) in fields_by_identity { + if spellings.len() < 2 { + continue; + } + out.field_dupe_groups += 1; + out.field_dupe_entries += spellings.len(); + spellings.sort(); + out.field_dupe_report + .push(format!("{owner}::{name} ×{}", spellings.len())); + out.field_dupe_report + .extend(spellings.into_iter().map(|s| format!(" {s}"))); + } + // Slots beyond the first in each collapsed group are the ones the + // pre-`Option` key would not have minted. + out.field_index_provenance_splits = collapsed_field_keys + .into_values() + .map(|n| n.saturating_sub(1)) + .sum(); + out +} + impl BhDescr { /// Extract byte offset for field/array operations (FieldDescr/ArrayDescr). /// Panics on VableField/VableArray — those must use `as_vable_field_index`. @@ -1659,7 +1932,13 @@ impl BhDescr { is_field_signed: spec.is_field_signed, is_immutable: spec.is_immutable, is_quasi_immutable: spec.is_quasi_immutable, - index_in_parent: spec.index_in_parent, + // `Some`, not a carried provenance: the source here is a LIVE + // `FieldDescr`, whose index `get_field_descr` already reconciled + // against its parent. Whatever the original mint claimed, this + // number is the reader's answer and is resolved by construction. + // The unresolved state exists only between `fielddescrof` and that + // reconciliation. + index_in_parent: Some(spec.index_in_parent), // Resume/blackhole reconstruct identity from structural // fields only; the parent SizeDescr backref is not surfaced // by the live `FieldDescr` trait. diff --git a/majit/majit-translate/src/codewriter/jtransform.rs b/majit/majit-translate/src/codewriter/jtransform.rs index 741832ff4f7..45d8a7839cb 100644 --- a/majit/majit-translate/src/codewriter/jtransform.rs +++ b/majit/majit-translate/src/codewriter/jtransform.rs @@ -9,6 +9,9 @@ //! - ArrayRead on virtualizable arrays → VableArrayRead marker //! - Call classification → elidable/residual/may_force tagging +use std::collections::BTreeMap; +use std::sync::Mutex; + use serde::{Deserialize, Serialize}; use crate::call::CallDescriptor; @@ -141,12 +144,83 @@ pub enum CallEffectKind { Elidable, Residual, MayForce, + /// A callee whose effects row is stated by the override table rather than + /// derived from a graph (callee census) — upstream's `analyze_external_call` answer + /// (`graphanalyze.py:104-108`). + /// + /// The three variants above are shorthands for three particular rows; + /// this one carries the row. + Declared(DeclaredCallEffects), +} + +/// `effectinfo.py:17-24` `EF_*`, minus `EF_RANDOM_EFFECTS`. +/// +/// The omission is the enforcement. `EF_RANDOM_EFFECTS` is top — the answer +/// for a callee nobody can describe — so it is not something a declaration +/// can say, and leaving it out makes upstream's +/// `assert not (elidable_function and random_effects_on_gcobjs)` +/// (`rpython/rtyper/lltypesystem/rffi.py:160`) hold by construction. The +/// pairing that assert forbids cannot be spelled. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum DeclaredExtraEffect { + ElidableCannotRaise, + LoopInvariant, + CannotRaise, + ElidableOrMemoryError, + ElidableCanRaise, + CanRaise, + ForcesVirtualOrVirtualizable, +} + +impl From for ExtraEffect { + fn from(declared: DeclaredExtraEffect) -> Self { + match declared { + DeclaredExtraEffect::ElidableCannotRaise => ExtraEffect::ElidableCannotRaise, + DeclaredExtraEffect::LoopInvariant => ExtraEffect::LoopInvariant, + DeclaredExtraEffect::CannotRaise => ExtraEffect::CannotRaise, + DeclaredExtraEffect::ElidableOrMemoryError => ExtraEffect::ElidableOrMemoryError, + DeclaredExtraEffect::ElidableCanRaise => ExtraEffect::ElidableCanRaise, + DeclaredExtraEffect::CanRaise => ExtraEffect::CanRaise, + DeclaredExtraEffect::ForcesVirtualOrVirtualizable => { + ExtraEffect::ForcesVirtualOrVirtualizable + } + } + } +} + +/// One declared effects row. +/// +/// The six read/write descr sets stay at `EffectInfo`'s default (empty). +/// The override table is built before the descr universe exists, so it has no +/// way to name a descr. Empty is the right row for a callee that touches no +/// field or array and the wrong row for anything else. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct DeclaredCallEffects { + pub extra: DeclaredExtraEffect, + /// `effectinfo.py:125 can_collect`. + pub can_collect: bool, + pub can_invalidate: bool, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct CallEffectOverride { /// `op.args[0]`-equivalent funcptr identity used to match the /// override against a call site. + /// + /// Spell this the way the **resolver** spells it at the call site, which + /// is not necessarily a name the source artifact declares. Reading the + /// `.ullbc` cannot tell you: it registers `opcode_binary_op` under both a + /// bare and a module-qualified name, and every one of the 38 opcode call + /// sites resolves to the qualified `["pyre_interpreter", "pyopcode", + /// "opcode_binary_op"]`. A row written with the bare name is legitimate + /// against the artifact and still matches nothing, because non-`Method` + /// patterns are compared by full structural equality. + /// + /// The spelling a call site actually carries is what `PYRE_CALLEE_CENSUS=1` + /// prints in its `with_graph` table (set `PYRE_CALLEE_CENSUS_ROWS=all`; + /// the default cap is 25 rows). Confirm a new row against the per-entry + /// match counter in the same output — a row that reads `0x INERT` is not + /// installed, whatever it looks like here. pub target: CallTarget, /// `calldescr`-equivalent EffectInfo wrapper attached to the call. pub descriptor: CallDescriptor, @@ -171,6 +245,11 @@ fn effect_info_for_kind(effect: CallEffectKind) -> EffectInfo { ExtraEffect::ForcesVirtualOrVirtualizable, OopSpecIndex::None, ), + CallEffectKind::Declared(declared) => EffectInfo { + can_collect: declared.can_collect, + can_invalidate: declared.can_invalidate, + ..EffectInfo::new(declared.extra.into(), OopSpecIndex::None) + }, } } @@ -2337,8 +2416,6 @@ impl<'a> Transformer<'a> { } } - // ── helpers ────────────────────────────────────────────── - /// RPython: `Transformer.make_three_lists(vars)` (jtransform.py:437-445). /// Split args into three lists by kind (int, ref, float) keyed on /// the backing [`crate::flowspace::model::Variable`] (orthodox per @@ -2571,8 +2648,6 @@ impl<'a> Transformer<'a> { ) } - // ── rewrite_op_* methods ────────────────────────────────── - /// RPython: `Transformer.rewrite_op_hint(op)`. /// Dispatches based on the hint kind (access_directly, force_virtualizable, /// fresh_virtualizable, promote, etc.) @@ -5860,7 +5935,7 @@ impl<'a> Transformer<'a> { /// `Constant::with_concretetype(ConstValue::Bool(b), lltype.Bool)`, /// carrying the `lltype.Bool` concretetype upstream stamps. // -// gh #37: called from `optimize_block` (jtransform.py:123); the fused +// the GotoIfNotOp lowering: called from `optimize_block` (jtransform.py:123); the fused // `ExitSwitch::Fused` is lowered to `FlatOp::GotoIfNotOp` in flatten. fn optimize_goto_if_not(graph: &mut FunctionGraph, block_idx: usize) -> bool { use crate::flowspace::model::{ConstValue, Constant}; @@ -6832,6 +6907,27 @@ fn classify_hint_target(target: &CallTarget) -> Option { /// `resolved_path` — by comparing the pattern against the path's /// `impl_type_prefix()`, directly or via leaf-suffix `canonical_leaf` /// (the `::`-joined path's trailing segment). +/// +/// # Do not relax this into a leaf-name comparison +/// +/// An override whose pattern never matches is **inert**: the call keeps the +/// effects the analysis derived for it. An override that matches the *wrong* +/// callee installs **another function's declared effects** on the call, which +/// the analysis then trusts. Inert is safe; wrong is a miscompile. The two are +/// one relaxation apart, and the relaxation is the obvious-looking repair when +/// a row reads 0 matches. +/// +/// Measured against the registered graphs, matching on the trailing segment +/// alone would make an `RBigInt::sub` row also capture `time::Instant::sub`, +/// `core::ops::arith::::sub` and `buffer::Buffer::sub`, and an +/// `RBigInt::hash` row capture 16 unrelated `hash` impls. Same failure class as +/// registering a callee family that cannot be proven closed. +/// +/// A row that reads 0 is telling you its *spelling* is wrong, not that the +/// predicate is too strict. Every non-`Method` pattern is compared by full +/// structural equality below, so the pattern must carry the same segmentation +/// the resolver produces at the call site — see `CallEffectOverride`'s own docs +/// for how to obtain it. fn call_target_matches_loose(pattern: &CallTarget, target: &CallTarget) -> bool { match (pattern, target) { ( @@ -7038,6 +7134,164 @@ fn map_user_oopspec_to_index(spec: &str) -> majit_ir::descr::OopSpecIndex { } } +/// Call sites matched, per `call_effects` override entry. +/// +/// `call_target_matches_loose` reports a non-match by returning `false`, and +/// `classify_call` then falls through to the ordinary `describe_call` path. +/// So an entry that matches nothing behaves exactly like an entry that is +/// absent: the build is green, no test fails, and the effects row it was +/// written to publish is simply never attached. This counter is the only +/// channel that separates the two. +/// +/// Keyed by the target's `Debug` rendering, never its `Display`: `Display` +/// joins a `FunctionPath`'s segments with `::`, and the segmentation is what +/// the match keys on — see +/// `function_path_override_matches_only_its_own_segmentation`. +/// +/// `BTreeMap` so two runs of one program print the same bytes. +static OVERRIDE_MATCHES: Mutex> = Mutex::new(BTreeMap::new()); + +/// The key `OVERRIDE_MATCHES` files a target under. +fn override_key(target: &CallTarget) -> String { + format!("{target:?}") +} + +/// Every `Method` target `classify_call` was actually invoked on, and the +/// total number of invocations. +/// +/// `OVERRIDE_MATCHES` counts *successful* matches, so a zero there has two +/// causes it cannot tell apart: the predicate was consulted and said no, or +/// the call site never reached the predicate at all. The census's own +/// `method_shapes` table cannot settle it either — it walks every registered +/// graph, which is a different and much larger population than the one +/// `transform_graph` visits. This pair is the consumer-side denominator, so +/// "no such call site was ever classified" becomes falsifiable. +/// +/// Gated on the census env var: `classify_call` is hot, and an unconditional +/// lock here would tax every build to answer a question nobody is asking. +static CLASSIFY_SEEN_METHODS: Mutex> = Mutex::new(BTreeMap::new()); +/// The same denominator for `FunctionPath`, keyed by segments. +/// +/// Needed for the same reason: knowing an override's spelling differs from the +/// call site's is only half an explanation if that call site is never +/// classified either. Without this, a spelling fix reads as unblocking a row +/// that would stay inert regardless. +static CLASSIFY_SEEN_PATHS: Mutex> = Mutex::new(BTreeMap::new()); +static CLASSIFY_SEEN_TOTAL: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); + +fn record_classify_seen(target: &CallTarget) { + static ON: std::sync::OnceLock = std::sync::OnceLock::new(); + if !*ON.get_or_init(|| std::env::var_os("PYRE_CALLEE_CENSUS").is_some_and(|v| v == "1")) { + return; + } + CLASSIFY_SEEN_TOTAL.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + match target { + CallTarget::Method { + name, + receiver_root, + .. + } => { + *CLASSIFY_SEEN_METHODS + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()) + .entry(format!("name={name:?} receiver_root={receiver_root:?}")) + .or_insert(0) += 1; + } + CallTarget::FunctionPath { segments, .. } => { + *CLASSIFY_SEEN_PATHS + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()) + .entry(format!("{segments:?}")) + .or_insert(0) += 1; + } + _ => {} + } +} + +fn record_override_match(pattern: &CallTarget) { + *OVERRIDE_MATCHES + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()) + .entry(override_key(pattern)) + .or_insert(0) += 1; +} + +/// Every entry of `overrides` with the number of call sites it matched, +/// heaviest first, zero-match entries last. +/// +/// The table is passed in rather than remembered, because the finding this +/// exists to surface is an entry with **no** matches — and an entry that +/// matched nothing leaves no trace in the counter to enumerate. Reading the +/// rows off the caller's table is what makes a `0` printable. +/// +/// The header carries `entries` beside `matched`, so an empty list cannot be +/// read as a clean table when it is really a run where no override table was +/// installed at all. +pub fn call_effect_override_census(overrides: &[CallEffectOverride]) -> Vec { + let matches = OVERRIDE_MATCHES + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()); + let mut rows: Vec<(String, usize)> = overrides + .iter() + .map(|override_| { + let key = override_key(&override_.target); + let hits = matches.get(&key).copied().unwrap_or(0); + (key, hits) + }) + .collect(); + rows.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0))); + // A key counted but absent from this table means two different override + // tables were installed in one process. That makes every count below a + // partial figure, so it is reported rather than dropped. + let listed: std::collections::BTreeSet<&String> = rows.iter().map(|(key, _)| key).collect(); + let unlisted: Vec<&String> = matches.keys().filter(|key| !listed.contains(key)).collect(); + let seen_methods = CLASSIFY_SEEN_METHODS + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()); + let mut lines = vec![ + format!( + "call_effect_overrides entries={} matched={} inert={} sites={} unlisted={}", + overrides.len(), + rows.iter().filter(|(_, hits)| *hits > 0).count(), + rows.iter().filter(|(_, hits)| *hits == 0).count(), + matches.values().sum::(), + unlisted.len(), + ), + // The denominator for every zero above: without it, "no entry + // matched" and "the predicate was never consulted" print identically. + format!( + "classify_call_seen total={} method_sites={} method_shapes={}", + CLASSIFY_SEEN_TOTAL.load(std::sync::atomic::Ordering::Relaxed), + seen_methods.values().sum::(), + seen_methods.len(), + ), + ]; + for (key, hits) in seen_methods.iter() { + lines.push(format!("classify_call_method {hits:>6} {key}")); + } + let seen_paths = CLASSIFY_SEEN_PATHS + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()); + lines.push(format!( + "classify_call_seen_paths sites={} shapes={}", + seen_paths.values().sum::(), + seen_paths.len(), + )); + for (key, hits) in seen_paths.iter() { + lines.push(format!("classify_call_path {hits:>6} {key}")); + } + lines.extend(rows.iter().map(|(key, hits)| { + let verdict = if *hits == 0 { " INERT" } else { "" }; + format!("call_effect_override {hits:>7}x{verdict} {key}") + })); + lines.extend( + unlisted + .iter() + .map(|key| format!("call_effect_override UNLISTED {key}")), + ); + lines +} + /// Classify a call's side-effect level. /// /// RPython equivalent: jtransform.py effect classification @@ -7056,11 +7310,13 @@ fn classify_call( } } - if let Some(descriptor) = overrides + record_classify_seen(target); + if let Some(override_) = overrides .iter() .find(|override_| call_target_matches_loose(&override_.target, target)) - .map(|override_| override_.descriptor.clone()) { + record_override_match(&override_.target); + let descriptor = override_.descriptor.clone(); let effect = classify_effect_info(&descriptor.get_extra_info()); return Some((descriptor, effect)); } @@ -7093,7 +7349,100 @@ mod tests { assert_eq!(keep_operation_unchanged(&transformer, &op), op); } - /// gh #37 Stage 1: `int_lt(a, b); exitswitch = t` fuses into a + /// A `FunctionPath` override matches its own segmentation and nothing + /// else, and a non-match is silent. + /// + /// `call_target_matches_loose` is loose only on the `Method` arm; a + /// `FunctionPath` pattern falls through to `_ => pattern == target`, + /// structural equality with no leaf-suffix tolerance. Both spellings + /// below `Display` as `core::ptr::null`, and owner-segmentation records that pyre's + /// two registration writers really do produce owner-split and + /// owner-unsplit paths for one function. So an override table written + /// from a `::`-joined listing is a guess about which split the call site + /// carries, and guessing wrong costs a `false` and no diagnostic — the + /// row is simply never consulted. + #[test] + fn function_path_override_matches_only_its_own_segmentation() { + let split = CallTarget::FunctionPath { + segments: vec!["core".into(), "ptr".into(), "null".into()], + }; + let unsplit = CallTarget::FunctionPath { + segments: vec!["core::ptr".into(), "null".into()], + }; + assert_eq!(split.to_string(), unsplit.to_string()); + assert!(call_target_matches_loose(&split, &split)); + assert!(!call_target_matches_loose(&split, &unsplit)); + assert!(!call_target_matches_loose(&unsplit, &split)); + } + + /// A declared row reaches `EffectInfo` intact, and the `RandomEffects` + /// pairing upstream forbids cannot be written. + /// + /// The row below is the one a primitive like `core::ptr::null` wants: + /// elidable, cannot raise, allocates nothing. Upstream's constraint is + /// `assert not (elidable_function and random_effects_on_gcobjs)` + /// (`rffi.py:160`), and `random_effects_on_gcobjs` derives from + /// "releases the GIL or runs a callback" — neither of which this does. So + /// elidable-with-a-concrete-row is the orthodox state, not a liberty, and + /// the type must keep it expressible while `DeclaredExtraEffect` having + /// no `RandomEffects` member makes the forbidden pairing unspellable. + #[test] + fn declared_effects_reach_the_effect_info_intact() { + let info = effect_info_for_kind(CallEffectKind::Declared(DeclaredCallEffects { + extra: DeclaredExtraEffect::ElidableCannotRaise, + can_collect: false, + can_invalidate: false, + })); + assert_eq!(info.extraeffect, ExtraEffect::ElidableCannotRaise); + assert!(info.check_is_elidable()); + assert!(!info.can_collect); + assert!(!info.can_invalidate); + // The default row says `can_collect: true`, so the assertion above is + // reading the declaration and not the default. + assert!(EffectInfo::default().can_collect); + } + + /// The override census names an entry that matched nothing. + /// + /// Both entries below are well-formed and differ only in segmentation, so + /// the build cannot tell them apart and neither can any existing test: + /// one attaches its effects row, the other is consulted, declined, and + /// forgotten. The census has to print the second as `INERT`, which is the + /// whole reason it exists. + /// + /// Asserts on this test's own rows rather than the header totals — + /// `OVERRIDE_MATCHES` is process-global and the lib tests run in + /// parallel, so a total is not this test's to own. The segment names are + /// deliberately unique so no other test can touch these two keys. + #[test] + fn override_census_names_an_entry_that_matched_nothing() { + let live = CallTarget::FunctionPath { + segments: vec!["__probe135".into(), "live".into(), "leaf".into()], + }; + let inert = CallTarget::FunctionPath { + segments: vec!["__probe135::inert".into(), "leaf".into()], + }; + let overrides = vec![ + CallEffectOverride::new(live.clone(), CallEffectKind::Elidable), + CallEffectOverride::new(inert.clone(), CallEffectKind::Elidable), + ]; + assert!(classify_call(&live, &overrides).is_some()); + + let lines = call_effect_override_census(&overrides); + let row = |target: &CallTarget| { + let key = override_key(target); + lines + .iter() + .find(|line| line.ends_with(&key)) + .unwrap_or_else(|| panic!("census has no row for {key}")) + .clone() + }; + assert!(row(&live).contains("1x"), "{}", row(&live)); + assert!(!row(&live).contains("INERT"), "{}", row(&live)); + assert!(row(&inert).contains("0x INERT"), "{}", row(&inert)); + } + + /// the GotoIfNotOp lowering Stage 1: `int_lt(a, b); exitswitch = t` fuses into a /// `Fused { opname: "int_lt", args: [a, b] }` switch, the `int_lt` /// op is removed, and the `t` riding a link's args is replaced by /// that link's bool constant (`jtransform.py:196-234`). @@ -7175,7 +7524,7 @@ mod tests { ); } - /// gh #37 Stage 1: a non-supported result op (`int_add`) is NOT + /// the GotoIfNotOp lowering Stage 1: a non-supported result op (`int_add`) is NOT /// fusable — `optimize_goto_if_not` returns false and leaves the /// block untouched (`jtransform.py:206-209` opname gate). #[test] @@ -11042,7 +11391,6 @@ mod tests { ); } - // ── RPython indirect_call plumbing tests — parity guard ────────── // // RPython upstream: `jtransform.py:538-553 handle_regular_indirect_ // call` emits `[-live-, int_guard_value, residual_call + diff --git a/majit/majit-translate/src/config/config.rs b/majit/majit-translate/src/config/config.rs index e1dea60372f..ef803b8f212 100644 --- a/majit/majit-translate/src/config/config.rs +++ b/majit/majit-translate/src/config/config.rs @@ -1809,8 +1809,24 @@ mod tests { fn getpaths_include_groups_lists_descriptions_too() { let c = Config::new(translation_descr(), HashMap::new()).expect("config"); let paths = c.getpaths(true); - assert!(paths.iter().any(|p| p == "translation")); - assert!(paths.iter().any(|p| p == "translation.verbose")); + // The whole list, in the description tree's declaration order — not + // alphabetical: `verbose` precedes `gc` because that is the order + // `translation_descr()` declares them in. + // + // The two membership checks this replaces covered 2 of the 5. `backend`, + // `type_system` and `gc` were never asserted, and nothing pinned that + // asking for groups adds exactly one entry (the bare "translation") + // rather than some other number. + assert_eq!( + paths, + [ + "translation", + "translation.backend", + "translation.type_system", + "translation.verbose", + "translation.gc", + ] + ); } #[test] diff --git a/majit/majit-translate/src/flowspace/model.rs b/majit/majit-translate/src/flowspace/model.rs index a778ca370ba..f2ad637fb07 100644 --- a/majit/majit-translate/src/flowspace/model.rs +++ b/majit/majit-translate/src/flowspace/model.rs @@ -5914,17 +5914,43 @@ mod tests { end.borrow_mut().closeblock(vec![link_es.clone()]); let g = FunctionGraph::new("f", start.clone()); + // Name the identities so a wrong walk fails as a readable sequence diff + // rather than as a bare `false`. Anything the walk yields that is not + // one of the three constructed nodes reads as `?` instead of being + // silently absorbed. + let name = |b: &Rc>| { + for (n, r) in [("start", &start), ("mid", &mid), ("end", &end)] { + if Rc::ptr_eq(b, r) { + return n; + } + } + "?" + }; + let lname = |l: &Rc>| { + for (n, r) in [("sm", &link_sm), ("me", &link_me), ("es", &link_es)] { + if Rc::ptr_eq(l, r) { + return n; + } + } + "?" + }; + + // The graph is the cycle start -> mid -> end -> start. Both helpers + // yield each node once, in forward-exit order; `end`'s exit back to + // `start` is the revisit the "once" in this test's name is about. + // Measured, and pinned as a sequence: membership alone cannot see a + // walk that reaches the same three nodes in a different order. let blocks = safe_iterblocks(&g); - assert_eq!(blocks.len(), 3); - assert!(Rc::ptr_eq(&blocks[0], &start)); - assert!(blocks.iter().any(|block| Rc::ptr_eq(block, &mid))); - assert!(blocks.iter().any(|block| Rc::ptr_eq(block, &end))); + assert_eq!( + blocks.iter().map(name).collect::>(), + ["start", "mid", "end"] + ); let links = safe_iterlinks(&g); - assert_eq!(links.len(), 3); - assert!(links.iter().any(|link| Rc::ptr_eq(link, &link_sm))); - assert!(links.iter().any(|link| Rc::ptr_eq(link, &link_me))); - assert!(links.iter().any(|link| Rc::ptr_eq(link, &link_es))); + assert_eq!( + links.iter().map(lname).collect::>(), + ["sm", "me", "es"] + ); } #[test] diff --git a/majit/majit-translate/src/front/mir.rs b/majit/majit-translate/src/front/mir.rs index b929a4d3802..334d87913ce 100644 --- a/majit/majit-translate/src/front/mir.rs +++ b/majit/majit-translate/src/front/mir.rs @@ -63,8 +63,9 @@ //! - `Switch { discr, targets }` — `ExitSwitch::Value` + per-arm //! `Link` with `ExitCase::Bool` / `ExitCase::Const`. //! - `Call` — Direct / Trait → `Call(FunctionPath)`; Dynamic vtable calls -//! use the trait-family indirect pipeline, plain function pointers become -//! `IndirectCall`, and the remaining closure shims use synthetic +//! use the trait-family indirect pipeline, plain safe function pointers +//! become `IndirectCall`, and the residue — `unsafe fn` pointers and +//! `dyn` receivers the vtable arm could not resolve — uses synthetic //! `Call(__dyn_call)`. //! - `Drop` — pass-through `Goto` (JIT does not model destructor //! semantics). @@ -2413,7 +2414,7 @@ fn lower_unstructured_with_static_addrs_and_attrs( crate::model::clear_unreachable_blocks(&mut lo.graph); } simplify_lowered_graph(&mut lo.graph, struct_field_attrs, true); - // `format!`-chain expansion (#131): rewrite the recognized + // `format!`-chain expansion (descriptor census): rewrite the recognized // `Argument::new_display`/`Arguments::new`/`alloc::fmt::format` // chain into native `str` + `ll_strconcat` ops so the graph-less // fmt externs stop blocking the rtyper. All emitted ops are ones @@ -5114,7 +5115,7 @@ impl<'a> Lowering<'a> { /// annotation a pointee-less raw pointer already carries here, so /// the erased callers and the callers that pass an untyped `*mut u8` /// straight through agree on one annotation. - /// * **narrow** — `obj as *const RegisteredStruct` (#298); see + /// * **narrow** — `obj as *const RegisteredStruct`; see /// `__pyre_cast_instance` below. /// /// Both are pointer-to-pointer only: a `Ref` source is required, since @@ -5629,16 +5630,40 @@ impl<'a> Lowering<'a> { return Ok(res); } let segments = self.global_segments(mir_bb, id)?; - // A `PyType` singleton static (`&SLICE_TYPE`): narrow the - // raw address through `__pyre_cast_instance["PyType"]` so - // the read types `SomeInstance("PyType")`, matching the - // `(*obj).ob_type` field-read. The bare `ConstInt` address - // would pair `IntegerRepr` against that field's - // `InstanceRepr` and block `rtype_is_` on the - // `ob_type == &TYPE` pointer-identity chain — the same - // narrow `obj as *const RegisteredStruct` already uses - // (#298). - if let Some(addr) = self.pytype_static_addr(&segments) { + // A class-singleton static (`&SLICE_TYPE`, `&CEL_INT_CLASS`): + // narrow the raw address through + // `__pyre_cast_instance[]` so the read types + // `SomeInstance()`, matching the `(*obj).ob_type` + // field-read. The bare `ConstInt` address would pair + // `IntegerRepr` against that field's `InstanceRepr` and + // block `rtype_is_` on the `ob_type == &TYPE` + // pointer-identity chain — the same narrow + // `obj as *const RegisteredStruct` already uses. + // + // The root is the static's own declared type, read off the + // `Global` place, not a fixed name: a prebuilt instance is + // annotated with the class of the object itself + // (`rpython/annotator/bookkeeper.py:339-345` + // `SomeInstance(self.getuniqueclassdef(x.__class__))`). + // pyre's whole `pytypes` bucket is declared `PyType` + // (`pyre-object/src/pyobject.rs:42`, and the + // `#[pyre_class]`-minted statics at + // `pyre-macros/src/lib.rs:1330`), so it derives `"PyType"` + // exactly as the fixed name did; a host crate with its own + // class root (`charon-corpus` `CelClass`) derives that root + // instead of being stamped with pyre's. + // + // A `pytypes` row whose static is not a named ADT has no + // class root to narrow to and is not a class singleton at + // all — a mis-bucketed key. Declining here drops it to the + // ordinary global handling below (`refs` / `int_values` / + // initialiser fold / residual call), which is what any other + // static of that shape gets. Measured empty for pyre: all + // 7362 `pyre-object` local functions lower with an identical + // `__pyre_cast_instance` root histogram either way. + if let Some(addr) = self.pytype_static_addr(&segments) + && let Some(root) = tyref_class_root(&place_ty, self.llbc) + { let bb_id = self.block_id[mir_bb]; let raw = self .graph @@ -5654,13 +5679,10 @@ impl<'a> Lowering<'a> { result: Some(res.clone()), kind: OpKind::Call { target: CallTarget::FunctionPath { - segments: vec![ - "__pyre_cast_instance".to_string(), - "PyType".to_string(), - ], + segments: vec!["__pyre_cast_instance".to_string(), root.clone()], }, args: vec![raw], - result_ty: ValueType::Ref(Some("PyType".to_string())), + result_ty: ValueType::Ref(Some(root)), }, }); return Ok(res); @@ -6360,17 +6382,21 @@ impl<'a> Lowering<'a> { None } - /// Address of a `pytypes`-bucket host static — a `PyType` singleton + /// Address of a `pytypes`-bucket host static — a class singleton /// (`&SLICE_TYPE`, `&INT_TYPE`, …). The `Global` reader lowers these - /// to a `__pyre_cast_instance["PyType"]` narrow of the raw address + /// to a `__pyre_cast_instance[]` narrow of the raw address /// (a typed instance pointer) rather than the bare `ConstInt` the - /// `refs` siblings avoid: a `PyType` static is the same kind of value + /// `refs` siblings avoid: a class static is the same kind of value /// as the `(*obj).ob_type` field-read it is compared against, so it - /// must type `SomeInstance("PyType")` for `rtype_is_` (pointer + /// must type `SomeInstance()` for `rtype_is_` (pointer /// identity) to lower the `ob_type == &TYPE` chain - /// (`is_slice` / `is_cell` / `is_range`). `jit_static_pytype_addrs` - /// puts only `PyType` statics in this bucket, so the root is always - /// `"PyType"`. + /// (`is_slice` / `is_cell` / `is_range`). + /// + /// The bucket carries only the address; the root comes from the + /// static's own declared type at the read site ([`tyref_class_root`]), + /// so a host crate whose class root is not pyre's `PyType` — the + /// `charon-corpus` `CelClass` — narrows to its own root rather than + /// being stamped with pyre's. fn pytype_static_addr(&self, segments: &[String]) -> Option { let full = segments.join("::"); let stripped = strip_crate_prefix(&full); @@ -7788,10 +7814,9 @@ impl<'a> Lowering<'a> { // `newlist/r>r` — an opname with no blackhole handler — // breaking `default_bh_builder_unwired_set_matches_task_85_snapshot`. // - // Measured (base 4d3d6e290f6, assembler - // `[ASM newlist DIAG]`): EXACTLY TWO graphs carry a vec! and - // still drop to the legacy walker, each behind an INDEPENDENT - // non-vec! wall the recognizer does not touch — + // Two graphs carry a vec! and still drop to the legacy walker, + // each behind an independent non-vec! wall the recognizer does + // not touch: // • `_pypy_generic_alias::make_generic_alias`: `collect_parameters` // → `push_unique` → `slice::iter::Iter::next` (unregistered), // plus a `collect_parameters` `UnionError: r_uint ∪ int`. @@ -8182,12 +8207,18 @@ impl<'a> Lowering<'a> { // receiver slot `IndirectCall` expects, so the arg list // passes through unchanged. // - // Any other operand shape — the one-hop `Option` - // deref and `call_once` closure shims — is a stored - // fn-ptr / `FnOnce`, not a vtable slot; it keeps the - // synthetic `__dyn_call` path (the fat-pointer receiver - // threaded into `args[0]`). - let is_fn_ptr = operand_is_fn_ptr(&dyn_operand, self.llbc); + // A stored fn-ptr — the one-hop `Option` deref, a + // host-registered callback table, a settable hook — is not a + // vtable slot, so `dyn_indirect_target` does not name it. It + // is still a PBC: RPython's rtyped call through a + // `Ptr(FuncType)` value is `indirect_call(funcptr, *args, + // c_graphs)`, not a synthetic opaque helper, and the family + // it carries decides whether the codewriter looks inside the + // callees or emits a residual call. Only a genuinely + // non-pointer operand, such as a `dyn` fat pointer the vtable + // arm could not resolve, keeps the synthetic `__dyn_call` + // path with the receiver threaded into `args[0]`. + let fn_ptr_family = operand_fn_ptr_family(&dyn_operand, self.llbc); let indirect = self.dyn_indirect_target(&dyn_operand); if let Some((trait_root, method_name)) = indirect { OpKind::Call { @@ -8195,20 +8226,16 @@ impl<'a> Lowering<'a> { args, result_ty, } - } else if is_fn_ptr { - // RPython `BuiltinCode.func` (and any other plain ll - // function-pointer field) is a PBC. Its rtyped call is - // `indirect_call(funcptr, *args, c_graphs)`, not a - // synthetic opaque helper. `Some([])` is the - // pre-CallControl marker for the generated builtin - // wrapper family; `rpbc::lower_indirect_calls` fills the - // candidate list once all registered graphs/fnaddrs are - // available. + } else if let Some(family) = fn_ptr_family { let funcptr = self.resolve_operand(mir_bb, dyn_operand)?; + let graphs = match family { + FnPtrFamily::BuiltinWrapper => Some(Vec::new()), + FnPtrFamily::Unknown => None, + }; OpKind::IndirectCall { funcptr, args, - graphs: Some(Vec::new()), + graphs, result_ty, } } else { @@ -13297,31 +13324,89 @@ impl<'a> Lowering<'a> { } } -fn operand_is_fn_ptr(operand: &Operand, llbc: &Llbc) -> bool { +/// Which PBC family an `OpKind::IndirectCall` through a bare function +/// pointer carries — the `c_graphs` argument `FunctionReprBase.call` +/// appends at `rpbc.py:216`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum FnPtrFamily { + /// The generated builtin-wrapper family. `Some([])` is the + /// pre-CallControl marker `rpbc::lower_indirect_calls` fills once the + /// registered graphs and the linker-resolved wrapper addresses are + /// available. + BuiltinWrapper, + /// The candidate set is not recoverable from the LLBC: the pointer is + /// installed at runtime (a host-registered callback, a settable hook), + /// so the addresses that reach the site are not a closed set a static + /// walk of the artifact can enumerate. Carries `None` — + /// `graphs_from` then answers `None` (`call.py:105`) and + /// `guess_call_kind` answers `residual` (`call.py:137-138`), so + /// `rewrite_op_indirect_call` emits `handle_residual_call` + /// (`jtransform.py:410-412`). `None` is also what keeps the family + /// analyzers conservative: `Some([])` reads as "empty family" and + /// collapses canraise / can_invalidate / forces_virtualizable to their + /// bottom result, which is unsound for a callee this side cannot see. + Unknown, +} + +/// Classify a `CallFunc::Dynamic` operand that resolves to a bare +/// function pointer. `None` means the operand is not a plain fn pointer +/// (a `dyn` fat pointer), or is one this side declines to name a family +/// for, and leaves the caller on its `__dyn_call` fallback. +fn operand_fn_ptr_family(operand: &Operand, llbc: &Llbc) -> Option { let ty = match operand { Operand::Copy(place) | Operand::Move(place) => &place.ty, - Operand::Const(_) => return false, + Operand::Const(_) => return None, }; - let Some(fnptr) = tyref_node(ty, llbc) + let signature = tyref_node(ty, llbc) .and_then(|node| strip_ty_wrappers(node, llbc)) - .and_then(|node| node.get("FnPtr")) - .and_then(serde_json::Value::as_object) - else { - return false; - }; - let Some(signature) = fnptr + .and_then(|node| node.get("FnPtr"))? .get("skip_binder") - .and_then(serde_json::Value::as_object) - else { - return false; - }; - if signature + .and_then(serde_json::Value::as_object)?; + // A missing or non-boolean `is_unsafe` reads as unsafe. + let is_safe = signature .get("is_unsafe") .and_then(serde_json::Value::as_bool) - != Some(false) - { - return false; - } + == Some(false); + fn_ptr_family_for( + is_safe, + fn_ptr_signature_is_builtin_code_fn(signature, llbc), + fnptr_indirect_enabled(), + ) +} + +/// The family decision itself, separated from the LLBC digging above so it +/// can be exercised without an extracted artefact — the fixture corpus +/// declares exactly one fn-pointer alias and it is safe, so a corpus-driven +/// test cannot reach the `unsafe` rows at all. +/// +/// `is_builtin_shape` is [`fn_ptr_signature_is_builtin_code_fn`], which +/// matches on signature SHAPE. An `unsafe fn` of that shape would otherwise +/// be handed the `__pyre_wrap_*` family: a positive claim about a callee set +/// it is not a member of. Only a safe pointer may name that family. +/// +/// An `unsafe fn` pointer is otherwise the same value with the same ABI as a +/// safe one, so it still carries the unknown family — `None` graphs is an +/// honest residual, not a claim about the callee. +fn fn_ptr_family_for( + is_safe: bool, + is_builtin_shape: bool, + fnptr_indirect: bool, +) -> Option { + if is_builtin_shape { + return is_safe.then_some(FnPtrFamily::BuiltinWrapper); + } + fnptr_indirect.then_some(FnPtrFamily::Unknown) +} + +/// Whether an fn-pointer signature is the exact source signature of +/// `gateway::BuiltinCodeFn`: `fn(&[PyObjectRef]) -> Result`. That family's members are the `__pyre_wrap_*` graphs +/// `CallControl::builtin_wrapper_indirect_graphs` enumerates, so a call +/// through it carries the deferred-family marker instead of `None`. +fn fn_ptr_signature_is_builtin_code_fn( + signature: &serde_json::Map, + llbc: &Llbc, +) -> bool { let Some(inputs) = signature .get("inputs") .and_then(serde_json::Value::as_array) @@ -13334,9 +13419,6 @@ fn operand_is_fn_ptr(operand: &Operand, llbc: &Llbc) -> bool { .get("output") .map(|value| charon_type_value_to_ast_string(value, llbc, 0)) .unwrap_or_default(); - // Exact source signature of `gateway::BuiltinCodeFn`: - // `fn(&[PyObjectRef]) -> Result`. Keep other - // one-argument callbacks in their existing residual `__dyn_call` family. input.starts_with('[') && input.contains("PyObject") && output.starts_with("Result<") @@ -15168,6 +15250,19 @@ fn tyref_to_value_type(ty: &TyRef, llbc: &Llbc) -> ValueType { if tyref_is_fieldless_enum_free(ty, llbc) { return ValueType::Int; } + // "At every site" above includes a BORROW of such an enum: `Rvalue::Ref` + // is the identity on a value that is already its own tag, so `&E` carries + // no representation of its own and a `fn f(&self)` method on `E` receives + // the int. Without this arm the enum is `Int` by value and `Ref(None)` + // through a borrow, and the `Rvalue::Discriminant` fold above — which + // tests the place TYPE (the pointee, a fieldless enum) and then aliases + // the place VALUE (which `resolve_place` collapses a `Deref` out of) — + // hands a ref-kinded operand to a `SwitchInt`, which + // `codewriter/flatten.rs` rejects outright (`switch exitswitch must be + // int`). `match self { .. }` on a C-like enum is the shape that hits it. + if tyref_is_borrowed_fieldless_enum_free(ty, llbc) { + return ValueType::Int; + } // A `str`/`String`/`Wtf8`/`Wtf8Buf` value is the single immutable // rpy_string in the value model (`tyref_is_string_value`), matching // upstream's one string type (`rstr.py`). A bare string-family value @@ -15205,6 +15300,65 @@ fn tyref_is_fieldless_enum_free(ty: &TyRef, llbc: &Llbc) -> bool { tyref_fieldless_enum_def(ty, llbc).is_some() } +/// `ty` is a **borrow** of a fieldless (C-like) enum — `&E` / `&mut E` for an +/// `E` that [`tyref_is_fieldless_enum_free`] accepts. +/// +/// A fieldless enum is modelled by-value as its discriminant integer, and +/// `Rvalue::Ref` is the identity on it, so the borrow carries no +/// representation of its own (the same statement +/// [`Lowerer::tyref_is_borrowed_fieldless_enum`] documents). A `&self` +/// method on such an enum therefore receives the tag, not a pointer to it. +/// +/// Peels `Ref` ONLY, deliberately — unlike +/// [`Lowerer::tyref_ref_adt_def_id`], which also peels `RawPtr`. A +/// `*const E` / `*mut E` is a genuine pointer value that some other code is +/// free to compare, offset or null-check; folding it to the tag would be a +/// wrong answer rather than a missed optimization. A borrow is not. +fn tyref_is_borrowed_fieldless_enum_free(ty: &TyRef, llbc: &Llbc) -> bool { + let mut v: &serde_json::Value = match ty { + TyRef::Inline { value: (_, v) } | TyRef::Other(v) => v, + TyRef::Dedup { id } => match llbc.dedup_body(*id) { + Some(v) => v, + None => return false, + }, + }; + let mut peeled_a_ref = false; + loop { + let Some(obj) = v.as_object() else { + return false; + }; + if let Some(id) = obj.get("Deduplicated").and_then(serde_json::Value::as_u64) { + match llbc.dedup_body(id) { + Some(next) => v = next, + None => return false, + } + continue; + } + if let Some(arr) = obj + .get("HashConsedValue") + .and_then(serde_json::Value::as_array) + && arr.len() == 2 + { + v = &arr[1]; + continue; + } + if let Some(arr) = obj.get("Ref").and_then(serde_json::Value::as_array) { + let Some(next) = arr.get(1) else { + return false; + }; + v = next; + peeled_a_ref = true; + continue; + } + // Reached a non-indirection node. Only a genuine borrow qualifies; + // the by-value shape is the caller's other arm. + return peeled_a_ref + && inline_adt_def_id(v) + .and_then(|def_id| llbc.type_by_id(def_id)) + .is_some_and(type_decl_is_fieldless_enum); + } +} + /// The `TypeDecl` behind `ty` when it resolves to a fieldless (C-like) /// enum (at least one variant, every variant carrying zero payload /// fields); `None` otherwise. Shared by [`tyref_is_fieldless_enum_free`] @@ -16785,6 +16939,72 @@ fn render_adt_type_args( /// | off | 243 | `__dyn_call` 48 | /// | on | **224** | `Wtf8::as_str` 37 | /// +/// Lower a call through a plain function pointer whose PBC family is not +/// recoverable from the LLBC — a host-registered callback table, a settable +/// hook — as `OpKind::IndirectCall` with `graphs: None` instead of the +/// synthetic `__dyn_call` residual. +/// +/// `graphs is None` is upstream's own spelling for "cannot follow the +/// indirect call": `call.py:105` skips the candidate filter, `call.py:137` +/// answers `residual`, and `jtransform.py:410-412` emits +/// `handle_residual_call`. `__dyn_call` has no such continuation — it is an +/// unregistered synthetic path that stops the graph reaching it. Before this +/// arm existed only the +/// `BuiltinCodeFn` signature reached `IndirectCall`, so every other +/// fn-pointer call site was a wall; census at the flip point, both states, +/// same artifacts: +/// +/// | artifact | `__dyn_call` off→on | `IndirectCall(graphs=None)` off→on | +/// |---|---:|---:| +/// | `cel.ullbc` | 6 → **0** | 0 → 6 | +/// | `pyre-object.ullbc` | 33 → 17 | 0 → 16 | +/// | `pyre-jit.ullbc` | 1 → **0** | 0 → 1 | +/// | `pyre-interpreter.ullbc` | 38 → 12 | 0 → 26 | +/// +/// The residue there was exactly the `unsafe fn` pointers (pyre's GC and +/// slot hooks), which the classifier declined outright when that census was +/// taken. [`operand_fn_ptr_family`] now admits them as +/// `FnPtrFamily::Unknown` — an `unsafe fn` pointer is the same value with +/// the same ABI as a safe one, so `graphs: None` is an honest residual for +/// it; only `BuiltinWrapper` stays closed to it, because that family is +/// matched on signature *shape* and an `unsafe fn` of that shape is not one +/// of its members. **The `on` column has not been re-measured since**; the +/// `off` column is unaffected by construction, since this arm emits nothing +/// with the switch off. Nothing else moved in the original census: the +/// vtable-slot calls already on `CallTarget::Indirect` held at 47 / 24, and +/// `pyre-interpreter`'s 203 `BuiltinCodeFn` sites kept the `Some([])` +/// marker in both states. +/// +/// OFF by default, and the reason is a pyre-side consequence of being +/// right rather than a defect in the lowering. `__dyn_call` is an +/// unregistered path, so every effect analyzer classified it as an +/// *external* call and took its bottom result; a `graphs: None` +/// `IndirectCall` is the top result instead +/// (`analyze_random_effects` → `BoolGraphAnalyzer.top_result()`, +/// `graphanalyze.py:117`). Turning the arm on therefore propagates +/// `EF_RANDOM_EFFECTS` up through every caller that transitively reaches +/// one of pyre's hook pointers, and `getcalldescr`'s elidable +/// post-condition (`call.py:326-332`) then rejects the first +/// `#[elidable_cannot_raise]` helper that does: +/// +/// ```text +/// getcalldescr: pyre_object::longobject::jit_bigint_is_zero is marked +/// elidable but got extraeffect=RandomEffects +/// ``` +/// +/// `cargo build -p pyre-jit-trace --no-default-features --features +/// cranelift` reproduces it with the switch on and is green with it off. +/// Clearing that needs pyre's elidable helpers to stop reaching an +/// unanalyzable callee — or those hooks to carry a real family — which is +/// its own change; until then this stays opt-in with +/// `PYRE_FNPTR_INDIRECT=1`. The `BuiltinCodeFn` family is unaffected by +/// the switch: it reached `IndirectCall` before this arm and still does. +pub(crate) fn fnptr_indirect_enabled() -> bool { + matches!( + std::env::var("PYRE_FNPTR_INDIRECT").as_deref(), + Ok("1") | Ok("true") + ) +} /// Narrow a `dont_look_inside` residual call whose destination is /// `Option<*mut PyObject>` from the classdef-less `ref` GCREF token to the /// per-instantiation `Option` enum root, so the caller's `if let Some(x) = @@ -20197,9 +20417,9 @@ fn collapse_panic_message_chains(graph: &mut FunctionGraph) -> usize { mod tests { use super::harden_duplicate_leaf_metadata; use super::{ - DecodedConst, cast_kind_is_raw_ptr, cast_pointer_marker_op, charon_const_generic_to_string, - charon_type_value_to_ast_string, decode_literal, simplify_lowered_graph, - tyref_is_raw_byte_ptr, + DecodedConst, FnPtrFamily, cast_kind_is_raw_ptr, cast_pointer_marker_op, + charon_const_generic_to_string, charon_type_value_to_ast_string, decode_literal, + fn_ptr_family_for, simplify_lowered_graph, tyref_is_raw_byte_ptr, }; use crate::model::{CallTarget, FunctionGraph, LinkArg, OpKind, ValueType}; use majit_charon_reader::{Llbc, ullbc::TyRef}; @@ -20285,6 +20505,40 @@ mod tests { ); } + /// Every row of the fn-pointer family decision, including the two the + /// fixture corpus structurally cannot reach: it declares one fn-pointer + /// alias (`HostCallback`) and that alias is safe, so no corpus-driven + /// test distinguishes `is_safe` either way. + /// + /// The load-bearing row is `(unsafe, builtin shape) -> None`. Its + /// positive control is the row above it: drop the safety gate and + /// `(safe, builtin shape)` still passes while that row flips to + /// `Some(BuiltinWrapper)`, so the pair fails in exactly one direction. + #[test] + fn fn_ptr_family_names_the_wrapper_set_only_for_a_safe_pointer() { + // (is_safe, is_builtin_shape, fnptr_indirect) -> family + let rows = [ + (true, true, false, Some(FnPtrFamily::BuiltinWrapper)), + (true, true, true, Some(FnPtrFamily::BuiltinWrapper)), + (false, true, false, None), + (false, true, true, None), + (true, false, true, Some(FnPtrFamily::Unknown)), + // An `unsafe fn` outside the wrapper shape keeps the unknown + // family: `None` graphs claims nothing about the callee. + (false, false, true, Some(FnPtrFamily::Unknown)), + (true, false, false, None), + (false, false, false, None), + ]; + for (is_safe, is_builtin_shape, fnptr_indirect, expected) in rows { + assert_eq!( + fn_ptr_family_for(is_safe, is_builtin_shape, fnptr_indirect), + expected, + "is_safe={is_safe} is_builtin_shape={is_builtin_shape} \ + fnptr_indirect={fnptr_indirect}" + ); + } + } + #[test] fn decode_scalar_i128_and_u128_preserves_full_width() { let signed = serde_json::json!({ @@ -22129,6 +22383,18 @@ mod tests { /// interpreter LLBC. `constant_at` must project the wrapper's sole field /// and index that list; `code_getdocstring` must project the same field /// for its slice view. Neither accessor may survive as a residual call. + /// + /// `#[ignore]` is deliberate and has a precondition, not a verdict: this + /// loads a 667 MB artefact that only exists after `extract-llbc.py` has run, + /// and takes ~11 s. Run it explicitly after a fresh extraction: + /// + /// ```text + /// cargo test -p majit-translate --lib \ + /// constants_wrapper_access_matches_real_interpreter_llbc -- --ignored + /// ``` + /// + /// An ignored test states a belief until someone runs it, so this must be + /// exercised explicitly whenever the extracted interpreter LLBC changes. #[test] #[ignore] fn constants_wrapper_access_matches_real_interpreter_llbc() { @@ -22177,8 +22443,14 @@ mod tests { ("im".to_string(), "f64".to_string()), ]) ); + // `attrs` is keyed by the crate-stripped path, and `strip_crate_prefix` + // splits on the FIRST `::` — so `num_complex::Complex` strips to the bare + // leaf `Complex` while `compiler_core::bytecode::Constants` above keeps its + // module qualifier as `bytecode::Constants`. The two assertions read as a + // symmetric pair and are not: path depth decides whether the key retains a + // qualifier. assert_eq!( - attrs.get("num_complex::Complex"), + attrs.get("Complex"), Some(&vec![ ("re".to_string(), ValueType::Float), ("im".to_string(), ValueType::Float), @@ -22192,30 +22464,49 @@ mod tests { .iter() .flat_map(|block| block.operations.iter()) .collect(); - assert!( - constant_ops.iter().any(|op| { - matches!( - op.kind, - OpKind::Input { - ty: ValueType::Int, - .. - } - ) - }), + // `ConstIdx` is a `#[repr(transparent)] u32` oparg wrapper, so the + // front-end types it `Unsigned` — whose register class *is* `'int'` + // (`getkind(Unsigned) == 'int'`, `model.rs:16-30`), signedness being an + // rtyper-level distinction only. `Int | Unsigned` is the pairing every + // bank-blind consumer already uses; naming the input as well keeps this + // from being satisfied by some unrelated integer argument. + assert_eq!( + constant_ops + .iter() + .filter(|op| { + matches!( + &op.kind, + OpKind::Input { + name, + ty: ValueType::Int | ValueType::Unsigned, + .. + } if name == "index" + ) + }) + .count(), + 1, "ConstIdx input must use RPython's integer oparg representation" ); - assert!(constant_ops.iter().any(|op| { - matches!( - &op.kind, - OpKind::FieldRead { field, .. } - if field.name == "__pos_0" - && field.owner_root.as_deref() == Some("Constants") - ) - })); - assert!( + assert_eq!( + constant_ops + .iter() + .filter(|op| { + matches!( + &op.kind, + OpKind::FieldRead { field, .. } + if field.name == "__pos_0" + && field.owner_root.as_deref() == Some("Constants") + ) + }) + .count(), + 1 + ); + assert_eq!( constant_ops .iter() - .any(|op| matches!(op.kind, OpKind::ArrayRead { .. })) + .filter(|op| matches!(op.kind, OpKind::ArrayRead { .. })) + .count(), + 1 ); assert!(!constant_ops.iter().any(|op| { matches!( @@ -22234,14 +22525,20 @@ mod tests { .iter() .flat_map(|block| block.operations.iter()) .collect(); - assert!(doc_ops.iter().any(|op| { - matches!( - &op.kind, - OpKind::FieldRead { field, .. } - if field.name == "__pos_0" - && field.owner_root.as_deref() == Some("Constants") - ) - })); + assert_eq!( + doc_ops + .iter() + .filter(|op| { + matches!( + &op.kind, + OpKind::FieldRead { field, .. } + if field.name == "__pos_0" + && field.owner_root.as_deref() == Some("Constants") + ) + }) + .count(), + 1 + ); assert!(!doc_ops.iter().any(|op| { matches!( &op.kind, @@ -22259,16 +22556,22 @@ mod tests { .iter() .flat_map(|block| block.operations.iter()) .collect(); - assert!(load_ops.iter().any(|op| { - matches!( - &op.kind, - OpKind::Input { - name, - class_root: Some(root), - .. - } if name == "constant" && root == "ConstantData" - ) - })); + assert_eq!( + load_ops + .iter() + .filter(|op| { + matches!( + &op.kind, + OpKind::Input { + name, + class_root: Some(root), + .. + } if name == "constant" && root == "ConstantData" + ) + }) + .count(), + 1 + ); // `PyFrame::bigint_constant(&RBigInt)` is the LOAD_CONST consumer // that boxes the already-translated payload. Anchor its ABI rewrite @@ -22310,14 +22613,23 @@ mod tests { })); } } - assert!( - boxed_handlers >= 1, + // The loop above filters to bodies (`unstructured().is_some()`), which + // drops the trait declaration and leaves the single impl. `== 1` reds if + // a second body-carrying impl appears: the inner ABI check runs only + // inside the `w_long_from_raw` arm, so a duplicate that reverted to the + // by-value ABI would fall out of that arm and go ungraded. + assert_eq!( + boxed_handlers, 1, "real LOAD_CONST bigint handler must use the RBigInt pointer boxing ABI" ); - assert!( + // Pins the whole ArrayRead population of `load_const_value`, not just + // its presence — so an added or removed indexed read reds here. + assert_eq!( load_ops .iter() - .any(|op| matches!(op.kind, OpKind::ArrayRead { .. })) + .filter(|op| matches!(op.kind, OpKind::ArrayRead { .. })) + .count(), + 3 ); assert!(!load_ops.iter().any(|op| { matches!( @@ -22342,24 +22654,37 @@ mod tests { ) })); for residual in ["jit_bigint_to_i64_value_or_zero", "jit_bigint_to_i64_fits"] { - assert!(load_ops.iter().any(|op| { - matches!( - &op.kind, - OpKind::Call { - target: CallTarget::FunctionPath { segments }, - .. - } if segments.last().map(String::as_str) == Some(residual) - ) - })); + assert_eq!( + load_ops + .iter() + .filter(|op| { + matches!( + &op.kind, + OpKind::Call { + target: CallTarget::FunctionPath { segments }, + .. + } if segments.last().map(String::as_str) == Some(residual) + ) + }) + .count(), + 1, + "{residual} is called exactly once" + ); } - assert!(load_ops.iter().any(|op| { - matches!( - &op.kind, - OpKind::FieldWrite { field, .. } - if field.name == "__discriminant" - && field.owner_root.as_deref().is_some_and(|root| root.contains("Result")) - ) - })); + assert_eq!( + load_ops + .iter() + .filter(|op| { + matches!( + &op.kind, + OpKind::FieldWrite { field, .. } + if field.name == "__discriminant" + && field.owner_root.as_deref().is_some_and(|root| root.contains("Result")) + ) + }) + .count(), + 1 + ); } #[test] diff --git a/majit/majit-translate/src/generated.rs b/majit/majit-translate/src/generated.rs index 6bd2274cca1..914630691bc 100644 --- a/majit/majit-translate/src/generated.rs +++ b/majit/majit-translate/src/generated.rs @@ -92,8 +92,12 @@ //! - NOT a new key schema. The canonical key is `CallPath` (matching //! `CallControl.jitcodes`, which is `rpython/jit/codewriter/call.py:87 //! self.jitcodes` keyed by graph identity). -//! - NOT a variant-keyed map. No `HashMap` exists under -//! `majit/majit-translate/src/` (`rg "HashMap bool { static ENABLED: std::sync::OnceLock = std::sync::OnceLock::new(); *ENABLED.get_or_init(|| { @@ -1502,7 +1502,7 @@ fn analyze_pipeline_from_module_paths( *struct_leaf_counts.entry(leaf).or_default() += 1; } } - // Opt-in receiver-driven dispatch families (issue #346): a consumer + // Opt-in receiver-driven dispatch families (receiver-dispatch configuration): a consumer // (e.g. the aheui census) names `>=2`-impl trait qualified paths // whose `dyn Trait` receivers should annotate to a base ClassDef // linking the impl subclasses, so a method getattr on the receiver @@ -1569,7 +1569,7 @@ fn analyze_pipeline_from_module_paths( }, ) .collect(); - // Auto-population (issue #346): register EVERY `>=2`-impl trait so its + // Auto-population (receiver-dispatch configuration): register EVERY `>=2`-impl trait so its // inline `dyn Trait` receiver (lowered to `CallTarget::Indirect`) narrows // to the family base ClassDef. Union with the config list (dedup by // base_root), never replacing it, to stay forward-safe with the aheui @@ -1865,10 +1865,21 @@ fn analyze_pipeline_from_module_paths( call_control.find_all_graphs(&mut policy); prof.mark(" find_all_graphs"); prof.note(|| { + // Two different populations are each legitimately "the universe" the + // closure could be read against, and they differ by ~4x: + // `program.functions` is what was lowered into this program, while + // `function_graphs` is the eagerly-lowered registry that + // `candidate_graph_count`'s own doc names. Print both absolutes with + // the field each comes from — a bare quotient leaves the reader to + // supply the denominator, and picking the other one yields a number + // that is arithmetically correct and means something else entirely. format!( - " portal closure: {} candidate graphs out of {} lowered functions", + " portal closure: {} candidate graphs; denominators: \ + {} lowered functions (program.functions), \ + {} registered graphs (function_graphs)", call_control.candidate_graph_count(), program.functions.len(), + call_control.function_graphs().len(), ) }); @@ -1893,6 +1904,23 @@ fn analyze_pipeline_from_module_paths( let (jitcodes, indirectcalltarget_indices, insns, descrs, all_liveness) = make_jitcodes(&config.pipeline, &mut call_control, &mut prof); mark_phase!("make_jitcodes"); + // callee census: how many callees the six `getcalldescr` analyzers answer as + // upstream's declared-external arm without a declaration behind them. + // Off by default — it is a whole extra walk of the registered universe, + // and it reports a population, not a defect. + if std::env::var_os("PYRE_CALLEE_CENSUS").is_some_and(|v| v == "1") { + eprint!("{}", call_control.unknown_callee_census()); + // the unused-override check's falsifier. `call_target_matches_loose` reports a + // non-match by returning `false`, so an override entry that names a + // spelling no call site carries is silently never consulted. Printed + // beside the census because that is where the spellings it must + // reproduce are read from. + for line in codewriter::jtransform::call_effect_override_census( + &config.pipeline.transform.call_effects, + ) { + eprintln!("[callee census] {line}"); + } + } pipeline.jit_drivers = call_control .jitdrivers_sd() .iter() @@ -2090,6 +2118,104 @@ fn make_jitcodes( // `insns`, mirroring RPython's single-store model. let descrs: Vec = codewriter.assembler.snapshot_descrs(); + if std::env::var_os("PYRE_DESCR_POOL_CENSUS").is_some_and(|v| v == "1") { + let dup = codewriter.assembler.descr_pool_duplication(); + // C1: the mint universe, beside the pool, in the same generation. + // `all_descrs` stable while the pool moves is a SELECTION defect; + // both moving is a POOL defect. No artefact diff can tell them apart. + let all_descrs = majit_ir::descr::gc_cache() + .lock() + .unwrap_or_else(|e| e.into_inner()) + .all_descrs_len(); + // C2: one cache slot answering for two different logical fields. + let (field_hits, field_collisions) = majit_ir::descr::field_descr_cache_collisions(); + eprintln!( + "[descriptor pool] total {} | all_descrs {} | effect-keyed {} | \ + concrete {} | wildcard {} | comparable {} | structurally distinct {} | \ + invariant violations {} | field-descr cache hits {} collisions {}", + dup.total, + all_descrs, + dup.effect_keyed, + dup.concrete, + dup.wildcard, + dup.comparable, + dup.structurally_distinct, + dup.invariant_violations, + field_hits, + field_collisions, + ); + // The three counters above all measure the pool POPULATION, and all + // three came back stable while `descrs.bin` still changed size. The + // bytes therefore moved inside entries, which no population counter + // can see. This walks exactly the slice that gets serialized and + // sums each length-bearing channel separately, so the next A/B names + // WHICH channel moved instead of only that the file did. + let content = jitcode::descr_pool_content(&descrs); + eprintln!( + "[descriptor content] entries {} | strings {} ({} bytes) | \ + vec members {} | descr-set members {} | wildcard effects {} | \ + type_ids {} (sum {:#018x}) | kinds {:?}", + content.entries, + content.string_count, + content.string_bytes, + content.vec_members, + content.descr_set_members, + content.wildcard_effects, + content.type_id_count, + content.type_id_sum, + content.kind_counts, + ); + // The pool key carries every component of `BhDescr::Field`, so it is + // injective and two entries for one logical field must differ + // somewhere. Printing the differing spellings turns the run-to-run + // count difference into a single-run observation naming the component + // that varied — the count is intermittent, so a cross-run diff can + // easily sample two runs that agree and show nothing. + // M1. Printed on the same line as the split totals because it is a + // SUBSET of them and reading it alone invites the wrong denominator. + // Zero says the `Option` key added no pool entry, so nothing + // downstream that counts pool entries moved — the claim the widening + // has to make good on, taken here rather than argued from the key's + // shape. It must be READ OUT, not merely computed: a number nobody + // prints is the same instrument as no number at all, which is the + // defect this whole change exists to repair. + eprintln!( + "[field split] groups {} | entries {} | index-provenance splits {}", + content.field_dupe_groups, + content.field_dupe_entries, + content.field_index_provenance_splits, + ); + for line in &content.field_dupe_report { + eprintln!("[field split] {line}"); + } + // Two walks of the same population that disagree on its size make + // every per-channel sum above unattributable. + if content.entries != dup.total { + eprintln!( + "[descriptor content] content walk saw {} entries, pool \ + census saw {} — the two walks disagree and every channel \ + sum above is suspect", + content.entries, dup.total + ); + } + // The census asserts nothing until it is shown to have seen both + // shapes: a run with no `concrete` entry cannot have seen an + // over-split whatever `structurally_distinct` says. Reported rather + // than asserted so a build still completes and says why. + if !dup.counts_reconcile() { + eprintln!( + "[descriptor pool] counts do not reconcile — the shape \ + classification lost entries; every field above is suspect" + ); + } else if !dup.saw_both_shapes() { + eprintln!( + "[descriptor pool] census saw only one shape (concrete {}, \ + wildcard {}) — `structurally distinct` is NOT interpretable", + dup.concrete, dup.wildcard + ); + } + } + // RPython `pyjitpl.py:2264 self.liveness_info = "".join(asm.all_liveness)`. // Snapshot the assembler's shared `all_liveness` byte stream so the runtime // can resolve the `BC_LIVE` offsets baked into `JitCode.code`. diff --git a/majit/majit-translate/src/model.rs b/majit/majit-translate/src/model.rs index 6b911c22eca..ad7958dbc7c 100644 --- a/majit/majit-translate/src/model.rs +++ b/majit/majit-translate/src/model.rs @@ -111,9 +111,10 @@ pub enum UnknownKind { MacroStmt, /// `syn::Expr::Lit` with a kind pyre cannot yet model. The `variant` /// tag names the specific syn literal kind (`Str`, `Float`, - /// `ByteStr`, `Verbatim`) so downstream diagnostics and - /// `MAJIT_UNKNOWN_DUMP` logs show the exact failure category - /// without re-walking the syn AST. + /// `ByteStr`, `Verbatim`) so downstream diagnostics show the exact + /// failure category without re-walking the syn AST. (An earlier + /// `MAJIT_UNKNOWN_DUMP` dump consumed this too; nothing reads that + /// gate now.) UnsupportedLiteral { variant: UnsupportedLiteralKind }, /// `syn::Expr::*` variants pyre cannot yet lower. Tag names the /// specific expression kind so diagnostics and the `Unknown` @@ -899,7 +900,6 @@ pub enum OpKind { cond: crate::flowspace::model::Variable, }, - // ── JIT-specific ops (generated by jtransform pass) ────────── /// Guard that a value equals a compile-time constant. /// /// RPython upstream emits three opnames in the guard_value family @@ -1140,7 +1140,6 @@ pub enum OpKind { result_kind: char, }, - // ── JIT builtin ops (jtransform.py:1731-1743) ──────────── // // These correspond to RPython's `_handle_jit_call()` in jtransform.py. // The codewriter converts calls to `jit.*` oopspec functions into @@ -6006,8 +6005,6 @@ impl FunctionGraph { self.block_mut(block).inputargs.push(var); } - // ── RPython FunctionGraph iteration methods ────────────────── - /// Iterate all blocks. RPython: `graph.iterblocks()`. pub fn iter_blocks(&self) -> impl Iterator { self.blocks.iter() @@ -7283,202 +7280,6 @@ mod tests { } } - #[test] - fn fuse_boxing_alloc_resolves_a_split_cluster_only_when_the_links_agree() { - // A boxing cluster's header stores sit before its `malloc_typed`, and - // each call ends a block, so `ob_type` / `w_class` reach the ctor as - // `Block.inputargs` while the `ConstRefAddr` producing them stays in a - // predecessor. `resolve_addr` steps through the links to read it, and - // takes an answer only when every predecessor agrees: a phi merging two - // type pointers is not a constant, and stamping either one onto the - // allocation would name the wrong type. Every other cluster here is - // built in one block, so neither half of that walk is otherwise reached. - type Var = crate::flowspace::model::Variable; - const FLOAT_TYPE_ADDR: i64 = 4357049520; - const OTHER_TYPE_ADDR: i64 = 4357049600; - - fn call(graph: &mut FunctionGraph, blk: BlockId, path: &[&str], args: Vec) -> Var { - graph - .push_op_var( - blk, - OpKind::Call { - target: CallTarget::FunctionPath { - segments: path.iter().map(|s| (*s).to_string()).collect(), - }, - args, - result_ty: ValueType::Ref(Some("object".into())), - }, - true, - ) - .unwrap() - } - /// The `(ob_type, w_class)` pair a `w_float_new` header stores, both - /// read off a `&FLOAT_TYPE`-shaped constant at `addr`. - fn header_pair(graph: &mut FunctionGraph, blk: BlockId, addr: i64) -> Vec { - let cast = |graph: &mut FunctionGraph| { - let ty = graph - .push_op_var(blk, OpKind::ConstRefAddr(addr), true) - .unwrap(); - call(graph, blk, &["__pyre_cast_instance", "PyType"], vec![ty]) - }; - let ob_type = cast(graph); - let w_class_cast = cast(graph); - let w_class = call( - graph, - blk, - &["pyre_object", "pyobject", "get_instantiate"], - vec![w_class_cast], - ); - vec![ob_type, w_class] - } - /// The rest of the cluster, in `blk`: the nested `PyObject` header - /// taking the two values `blk` was handed, the `W_FloatObject` ctor and - /// its payload store, and the `malloc_typed` the fusion rewrites. - fn cluster_in(graph: &mut FunctionGraph, blk: BlockId, ob_type: &Var, w_class: &Var) { - let field = |base: &Var, name: &str, owner: &str, value: &Var| OpKind::FieldWrite { - base: base.clone(), - field: FieldDescriptor { - name: name.into(), - owner_root: Some(owner.into()), - owner_id: None, - base_is_deref: None, - taken_by_address: false, - }, - value: LinkArg::Value(value.clone()), - ty: ValueType::Ref(None), - }; - let ctor = |graph: &mut FunctionGraph, name: &str| { - graph - .push_op_var( - blk, - OpKind::Call { - target: CallTarget::synthetic_transparent_ctor(name), - args: vec![], - result_ty: ValueType::Ref(Some(name.into())), - }, - true, - ) - .unwrap() - }; - let payload = graph - .push_op_var(blk, OpKind::ConstFloat(0.0f64.to_bits()), true) - .unwrap(); - let header = ctor(graph, "PyObject"); - graph.push_op_var(blk, field(&header, "ob_type", "PyObject", ob_type), false); - graph.push_op_var(blk, field(&header, "w_class", "PyObject", w_class), false); - let agg = ctor(graph, "W_FloatObject"); - graph.push_op_var( - blk, - field(&agg, "ob_header", "W_FloatObject", &header), - false, - ); - graph.push_op_var( - blk, - field(&agg, "floatval", "W_FloatObject", &payload), - false, - ); - let ret = call( - graph, - blk, - &["pyre_object", "lltype", "malloc_typed"], - vec![agg], - ); - graph.set_return(blk, Some(ret)); - } - - /// One producer of the header pair, `crossings` blocks of pure relay, - /// then the cluster — the shape a run of calls before the allocation - /// leaves behind. - fn chain(crossings: usize) -> FunctionGraph { - let mut graph = FunctionGraph::new("test"); - let entry = graph.startblock; - let mut carried = header_pair(&mut graph, entry, FLOAT_TYPE_ADDR); - let mut from = entry; - for _ in 0..crossings { - let (next, args) = graph.create_block_with_arg_vars(2); - graph.set_goto(from, next, carried); - carried = args; - from = next; - } - cluster_in(&mut graph, from, &carried[0], &carried[1]); - graph - } - /// Two predecessors of one merge block, each building its own header - /// pair off the address it is given, and the cluster in the merge. - fn merge(left_addr: i64, right_addr: i64) -> FunctionGraph { - let mut graph = FunctionGraph::new("test"); - let entry = graph.startblock; - let cond = graph.push_op_var(entry, OpKind::ConstInt(0), true).unwrap(); - let (join, carried) = graph.create_block_with_arg_vars(2); - let arms: Vec = [(true, left_addr), (false, right_addr)] - .into_iter() - .map(|(case, addr)| { - let arm = graph.create_block(); - let pair = header_pair(&mut graph, arm, addr); - graph.set_goto(arm, join, pair); - Link::from_variables(&graph, vec![], arm, Some(ExitCase::Bool(case))) - }) - .collect(); - graph.block_mut(entry).exitswitch = Some(ExitSwitch::Value(cond)); - graph.closeblock(entry, arms); - cluster_in(&mut graph, join, &carried[0], &carried[1]); - graph - } - - let rows: [(&str, &dyn Fn() -> FunctionGraph, usize); 4] = [ - ("one link crossing", &|| chain(1), 1), - ("two link crossings", &|| chain(2), 1), - ( - "predecessors naming one type", - &|| merge(FLOAT_TYPE_ADDR, FLOAT_TYPE_ADDR), - 1, - ), - ( - "predecessors naming two types", - &|| merge(FLOAT_TYPE_ADDR, OTHER_TYPE_ADDR), - 0, - ), - ]; - for (shape, build, expected) in rows { - let mut graph = build(); - assert_eq!( - fuse_boxing_alloc(&mut graph, &numeric_boxing_attrs()), - expected, - "{shape}: wrong number of fused clusters" - ); - let vtables: Vec = graph - .blocks - .iter() - .flat_map(|b| &b.operations) - .filter_map(|op| match &op.kind { - OpKind::NewWithVtable { vtable, .. } => Some(*vtable), - _ => None, - }) - .collect(); - // Naming the address rather than counting the op is what separates - // a walk that read the predecessor from one that read some other - // constant in the graph. - let expected_vtables = if expected == 0 { - Vec::new() - } else { - vec![FLOAT_TYPE_ADDR] - }; - assert_eq!(vtables, expected_vtables, "{shape}: wrong vtable stamped"); - let residual = graph.blocks.iter().flat_map(|b| &b.operations).any(|op| { - matches!( - &op.kind, - OpKind::Call { target: CallTarget::FunctionPath { segments }, .. } - if segments.last().map(String::as_str) == Some("malloc_typed") - ) - }); - assert_eq!( - residual, - expected == 0, - "{shape}: malloc_typed residual must survive exactly when the cluster declines" - ); - } - } - #[test] fn fuse_boxing_alloc_sweeps_nested_header_chain() { // Faithful `w_float_new` shape: the boxing struct's header is a nested diff --git a/majit/majit-translate/src/test_support.rs b/majit/majit-translate/src/test_support.rs index 43d12b0ce7d..90be4df40d9 100644 --- a/majit/majit-translate/src/test_support.rs +++ b/majit/majit-translate/src/test_support.rs @@ -3,6 +3,24 @@ mod call_spec; #[path = "../../../pyre/pyre-jit-trace/src/virtualizable_spec.rs"] mod virtualizable_spec; +/// Carry one declared `EF_*` from the pyre-owned spec to the translator's +/// own enum. Mirrors `pyre-jit-trace/build.rs`, which does the same walk for +/// the production pipeline; both enumerate the arms so a new member is a +/// compile error rather than a silent re-mapping. +fn declared_extra_effect(extra: call_spec::DeclaredExtraEffect) -> crate::DeclaredExtraEffect { + use crate::DeclaredExtraEffect as Translate; + use call_spec::DeclaredExtraEffect as Spec; + match extra { + Spec::ElidableCannotRaise => Translate::ElidableCannotRaise, + Spec::LoopInvariant => Translate::LoopInvariant, + Spec::CannotRaise => Translate::CannotRaise, + Spec::ElidableOrMemoryError => Translate::ElidableOrMemoryError, + Spec::ElidableCanRaise => Translate::ElidableCanRaise, + Spec::CanRaise => Translate::CanRaise, + Spec::ForcesVirtualOrVirtualizable => Translate::ForcesVirtualOrVirtualizable, + } +} + pub(crate) fn pyre_pipeline_config() -> crate::PipelineConfig { crate::PipelineConfig { transform: crate::GraphTransformConfig { @@ -41,6 +59,13 @@ pub(crate) fn pyre_pipeline_config() -> crate::PipelineConfig { let effect = match spec.effect { call_spec::CallEffectKind::Elidable => crate::CallEffectKind::Elidable, call_spec::CallEffectKind::Residual => crate::CallEffectKind::Residual, + call_spec::CallEffectKind::Declared(declared) => { + crate::CallEffectKind::Declared(crate::DeclaredCallEffects { + extra: declared_extra_effect(declared.extra), + can_collect: declared.can_collect, + can_invalidate: declared.can_invalidate, + }) + } }; crate::CallEffectOverride::new(target, effect) }) diff --git a/majit/majit-translate/src/translator/rtyper/lltypesystem/rstr.rs b/majit/majit-translate/src/translator/rtyper/lltypesystem/rstr.rs index cf14e608240..391239d340f 100644 --- a/majit/majit-translate/src/translator/rtyper/lltypesystem/rstr.rs +++ b/majit/majit-translate/src/translator/rtyper/lltypesystem/rstr.rs @@ -469,7 +469,6 @@ pub fn ll_getnextindex() -> Result<(), TyperError> { Err(rstr_deferred("ll_getnextindex")) } -// ____________________________________________________________ // LLHelpers — `lltypesystem/rstr.py:307` `class LLHelpers(AbstractLLHelpers)`. // // Each helper is synthesised as a single-block low-level graph the @@ -9720,23 +9719,40 @@ mod tests { .collect::>() }) .collect(); - for opname in [ - "getsubstruct", - "getarraysize", - "cast_char_to_int", - "int_ge", - "int_le", - "int_lt", - "int_eq", - "int_sub", - "int_mul", - "int_add", - ] { - assert!( - all_ops.iter().any(|op| op == opname), - "ll_int graph must contain {opname}" + // Counts, not memberships. `.any()` pinned each opname as PRESENT and + // discarded multiplicity, so deleting nine of the ten `int_add`s passed. + // + // `getarrayitem` is new to this list. It is emitted five times and was + // asserted nowhere — the only one-directional gap in the set, and the + // reason the length check below could not have been written before. + let op_counts: [(&str, usize); 11] = [ + ("int_add", 10), + ("int_eq", 6), + ("cast_char_to_int", 5), + ("getarrayitem", 5), + ("int_ge", 5), + ("int_le", 4), + ("int_lt", 4), + ("int_sub", 3), + ("int_mul", 2), + ("getarraysize", 1), + ("getsubstruct", 1), + ]; + for (opname, expected) in op_counts { + assert_eq!( + all_ops.iter().filter(|op| op.as_str() == opname).count(), + expected, + "ll_int graph must contain {expected} {opname}" ); } + // Derived from the table rather than restated, so a count that moves + // cannot leave a stale total beside it. This is what makes the set + // two-directional: a twelfth opname reds here instead of passing. + assert_eq!( + all_ops.len(), + op_counts.iter().map(|(_, n)| n).sum::(), + "the pinned opnames must account for every operation in the graph" + ); let raises_value_error = blocks.iter().any(|block| { block.borrow().exits.iter().any(|link| { diff --git a/majit/majit-translate/src/translator/rtyper/rpbc.rs b/majit/majit-translate/src/translator/rtyper/rpbc.rs index 4ddf936e3ca..94edc448faa 100644 --- a/majit/majit-translate/src/translator/rtyper/rpbc.rs +++ b/majit/majit-translate/src/translator/rtyper/rpbc.rs @@ -332,13 +332,19 @@ pub fn lower_indirect_calls(graph: &mut JitFunctionGraph, call_control: &CallCon let builtin_wrappers = call_control.builtin_wrapper_indirect_graphs(); for block in &mut graph.blocks { for op in &mut block.operations { - if let OpKind::IndirectCall { - graphs: Some(graphs), - .. - } = &mut op.kind - && graphs.is_empty() + if let OpKind::IndirectCall { graphs, .. } = &mut op.kind + && graphs.as_deref().is_some_and(<[_]>::is_empty) { - *graphs = builtin_wrappers.to_vec(); + // Filling the marker with an empty wrapper set would leave + // `Some([])`, which every family analyzer reads as "the + // family is known and has no members" and folds to its + // bottom result — silently asserting that a callee this + // side never enumerated has no effects. Nothing here can + // tell "no wrappers registered" from "wrappers exist but + // were not registered", so an unfilled marker is an unknown + // family: `None`, the same fold this function makes for an + // empty `(trait, method)` family below. + *graphs = (!builtin_wrappers.is_empty()).then(|| builtin_wrappers.to_vec()); } } } @@ -937,9 +943,107 @@ pub(crate) mod tests { "inherent call must not produce IndirectCall" ); } + + /// Build a graph holding one deferred builtin-wrapper marker — + /// `IndirectCall { graphs: Some([]) }`, the shape `front/mir.rs` + /// emits for a gateway-wrapper call it cannot resolve yet. + fn graph_with_deferred_wrapper_marker() -> JitFunctionGraph { + let mut graph = JitFunctionGraph::new("outer"); + let funcptr_var = graph + .push_op_var( + graph.startblock, + OpKind::Input { + name: "fp".to_string(), + ty: ValueType::Int, + class_root: None, + }, + true, + ) + .unwrap(); + graph.push_op_var( + graph.startblock, + OpKind::IndirectCall { + funcptr: funcptr_var, + args: vec![], + graphs: Some(Vec::new()), + result_ty: ValueType::Void, + }, + true, + ); + graph.set_return(graph.startblock, None); + graph + } + + fn sole_indirect_call_family(graph: &JitFunctionGraph) -> Option> { + let mut found = graph + .blocks + .iter() + .flat_map(|b| &b.operations) + .filter_map(|op| match &op.kind { + OpKind::IndirectCall { graphs, .. } => Some(graphs.clone()), + _ => None, + }); + let family = found.next().expect("marker op must survive lowering"); + assert!(found.next().is_none(), "expected exactly one IndirectCall"); + family + } + + /// The deferred builtin-wrapper marker is a request to enumerate a + /// family, not a claim that the family is empty. When `CallControl` + /// has no wrappers to fill it with, the request went unanswered, and + /// the op must say so — `graphs: None`, the same "unknown family" + /// spelling `lower_indirect_calls` gives an unregistered + /// `(trait, method)` pair. Leaving `Some([])` would instead assert a + /// closed, empty family, which every downstream analyzer folds to its + /// bottom result (`graphanalyze.py:117-121` takes `None` to top). + /// + /// The registered-wrapper case is the non-vacuity control: it shows + /// the fill still runs and still reaches the marker, so the `None` + /// above is this rule and not a skipped pass. + #[test] + fn unfillable_wrapper_marker_lowers_to_unknown_family() { + use crate::call::CallControl; + use crate::parse::CallPath; + + // Control — the fill is engaged and rewrites the marker. + let wrapper = CallPath::from_segments(["helpers", "__pyre_wrap_add"]); + let mut with_wrappers = CallControl::new(); + with_wrappers + .register_function_graph(wrapper.clone(), JitFunctionGraph::new("__pyre_wrap_add")); + with_wrappers.register_function_fnaddr(wrapper.clone(), 0x1234); + assert_eq!( + with_wrappers.builtin_wrapper_indirect_graphs().to_vec(), + vec![wrapper.clone()], + "control needs exactly one registered wrapper" + ); + + let mut filled = graph_with_deferred_wrapper_marker(); + lower_indirect_calls(&mut filled, &with_wrappers); + assert_eq!( + sole_indirect_call_family(&filled), + Some(vec![wrapper]), + "a fillable marker must be filled with the wrapper family" + ); + + // Invariant — with nothing to enumerate, the family is unknown. + let without_wrappers = CallControl::new(); + assert!( + without_wrappers + .builtin_wrapper_indirect_graphs() + .is_empty(), + "the invariant below needs an empty wrapper registry" + ); + + let mut unfilled = graph_with_deferred_wrapper_marker(); + lower_indirect_calls(&mut unfilled, &without_wrappers); + assert_eq!( + sole_indirect_call_family(&unfilled), + None, + "an unfillable marker is an unknown family, not an empty one" + ); + } } -// ===================================================================== // rpbc.py:177-994 — full PBC Repr hierarchy // // Concrete classes ported below mirror upstream's hierarchy: @@ -959,7 +1063,6 @@ pub(crate) mod tests { // field and overrides the lowleveltype + dispatch body. Pair-type arms // (rpbc.py:373-633 `__extend__(pairtype(...))`) live alongside their // owning concrete reprs below. -// ===================================================================== use crate::flowspace::model::{ConstValue, Constant, GraphKey, Hlvalue}; use crate::translator::rtyper::lltypesystem::lltype::LowLevelType; diff --git a/majit/majit-translate/src/translator/rtyper/rtyper.rs b/majit/majit-translate/src/translator/rtyper/rtyper.rs index 633863ac298..3100ac546b2 100644 --- a/majit/majit-translate/src/translator/rtyper/rtyper.rs +++ b/majit/majit-translate/src/translator/rtyper/rtyper.rs @@ -2338,7 +2338,6 @@ fn is_primitive_lowleveltype(lltype: &LowLevelType) -> bool { ) } -// ____________________________________________________________ // HighLevelOp — `rtyper.py:617-779`. /// RPython `HighLevelOp.inputarg(converted_to, arg)` accepts either a @@ -2893,7 +2892,6 @@ pub enum SliceKind { StartStop, } -// ____________________________________________________________ // LowLevelOpList — `rtyper.py:783-871+`. /// RPython `class LowLevelOpList(list)` (rtyper.py:783-809) — mutable @@ -7386,22 +7384,39 @@ mod tests { .. }) if *n == upper_bound )); - assert!(upper.exits.iter().any(|link| { - bool_exitcase(link) == Some(true) - && link - .borrow() - .target - .as_ref() - .is_some_and(|target| BlockKey::of(target) == BlockKey::of(&returnblock)) - })); - assert!(upper.exits.iter().any(|link| { - bool_exitcase(link) == Some(false) - && link - .borrow() - .target - .as_ref() - .is_some_and(|target| BlockKey::of(target) == BlockKey::of(&exceptblock)) - })); + // One equality, not two membership checks. Two `.any()`s pinned + // that both expected exits are PRESENT and said nothing about how + // many there are — a spurious third exit, a duplicate, or the two + // swapped all passed. The sibling startblock assertion above + // already pins its length for exactly that reason; this block had + // no length pin at all. + // + // The ORDER is contractual rather than incidental: + // `lowlevel_range_check_helper_graph` closes this block with a + // single vec literal — `closeblock(vec![Link(Bool(true) → + // returnblock), Link(Bool(false) → exceptblock)])` — so the + // producer's own container decides the equality, and it is an + // ordered one. + let upper_exits: Vec<(Option, Option)> = upper + .exits + .iter() + .map(|link| { + ( + bool_exitcase(link), + link.borrow().target.as_ref().map(BlockKey::of), + ) + }) + .collect(); + assert_eq!( + upper_exits, + vec![ + (Some(true), Some(BlockKey::of(&returnblock))), + (Some(false), Some(BlockKey::of(&exceptblock))), + ], + "the upper-bound check must have exactly two exits, in the \ + order the producer writes them: true → returnblock, \ + false → exceptblock" + ); } } @@ -7455,14 +7470,39 @@ mod tests { .map(|op| op.opname.as_str()) .collect(); assert_eq!(opnames, vec!["int_eq", "int_eq", "int_and"]); - assert!(start.exits.iter().any(|link| { - bool_exitcase(link) == Some(true) - && link + // One equality, not a membership check. `.any()` pinned that the + // overflow exit is PRESENT and said nothing about how many exits there + // are, so a spurious third exit or a duplicate passed — and the `find` + // below takes the FIRST `false` exit, which made a second one invisible. + // + // The ORDER is contractual rather than incidental: + // `lowlevel_overflow_check_wrapper_graph` closes this block with a + // single vec literal — `closeblock(vec![Link(Bool(true) → exceptblock), + // Link(Bool(false) → callblock)])` — so the producer's own container + // decides the equality. Note the true-target is the OPPOSITE of the + // sibling assertion above, which reads true → returnblock. + // + // The second element is pinned as "does not target the exceptblock" + // rather than by name: `callblock` is OBTAINED from these exits just + // below, so naming it here would assert nothing. + let start_exits: Vec<(Option, bool)> = start + .exits + .iter() + .map(|link| { + let targets_exceptblock = link .borrow() .target .as_ref() - .is_some_and(|target| BlockKey::of(target) == BlockKey::of(&exceptblock)) - })); + .is_some_and(|target| BlockKey::of(target) == BlockKey::of(&exceptblock)); + (bool_exitcase(link), targets_exceptblock) + }) + .collect(); + assert_eq!( + start_exits, + vec![(Some(true), true), (Some(false), false)], + "the overflow check must have exactly two exits, in the order the \ + producer writes them: true → exceptblock, false → the call path" + ); let callblock = start .exits .iter() diff --git a/majit/majit-translate/tests/test_epic_acceptance_greps.rs b/majit/majit-translate/tests/test_epic_acceptance_greps.rs index f409745b215..57714b7d108 100644 --- a/majit/majit-translate/tests/test_epic_acceptance_greps.rs +++ b/majit/majit-translate/tests/test_epic_acceptance_greps.rs @@ -1,129 +1,331 @@ -//! Acceptance invariants — the "what must NOT be introduced" -//! side of the parity contract in `docs/plans/lucky-growing-puzzle.md`. +//! Source-level acceptance gates for structural RPython parity. //! -//! The plan's verification protocol lists three grep invariants the -//! codebase must uphold. Each invariant maps to a -//! specific RPython structural decision that pyre must NOT diverge from: -//! -//! | Invariant | RPython anchor | Why | -//! |---|---|---| -//! | `HashMap` = 0 | `rpython/jit/codewriter/call.py:87 self.jitcodes = {}` | upstream keys jitcodes by `graph`, not by opcode variant. A variant-keyed map in pyre would mean the codewriter lowers per-opcode-arm instead of per-graph — that is the CPython-3.13-bytecode coupling that was removed. | -//! | `_may_raise \| TryOp \| OpKind::Try` = 0 | `rpython/jit/codewriter/jtransform.py:456 rewrite_op_direct_call` + `rpython/translator/exceptiontransform.py` | upstream routes raising calls through the existing `residual_call_*` opname family and lowers `?`/`PyResult` to exceptional successor edges on the existing `Terminator`. A fresh opname family would be a deviation. | -//! | `compile_pyre_interpreter` = 0 | (pyre-specific legacy name) | The pyre-specific opcode-walking entry point is replaced with `CodeWriter::make_jitcodes`. A lingering `compile_pyre_interpreter` definition means the legacy path survives in parallel. | -//! -//! The tests walk `majit-translate/src/` and strip line comments before -//! matching; a mention inside `//` / `//!` is acceptable (typically this -//! very file's anchor comment referencing the forbidden pattern). Any -//! non-comment occurrence is a hard failure. +//! The tests reject variant-keyed JitCode maps, a separate exception opname +//! family, and the legacy `compile_pyre_interpreter` entry point. They scan +//! every MAJIT crate's `src` tree after stripping comments and normalizing +//! whitespace, with positive controls that prove each selector still matches. +use std::ffi::OsStr; use std::fs; use std::path::{Path, PathBuf}; use std::sync::OnceLock; use walkdir::WalkDir; -/// Walk production source tree. Returns (relative_path, source) pairs. -fn production_rs_files(root: &Path) -> Vec<(PathBuf, String)> { - WalkDir::new(root) - .into_iter() - .filter_map(Result::ok) - .filter(|e| { - e.path() - .extension() - .and_then(|s| s.to_str()) - .map(|ext| ext == "rs") - .unwrap_or(false) - }) - .filter_map(|e| { - let path = e.into_path(); - let rel = path.strip_prefix(root).ok()?.to_path_buf(); - let src = fs::read_to_string(&path).ok()?; - Some((rel, src)) - }) - .collect() -} - -/// Strip `//` line comments (including `//!` doc lines and `///` -/// rustdoc lines). Matches rustc lexer semantics for single-line comments. -/// Block comments are left in place — forbidden strings inside `/* */` -/// are rare enough that stripping them is not worth the parser. -fn strip_line_comments(line: &str) -> &str { - match line.find("//") { - Some(idx) => &line[..idx], - None => line, - } -} - -/// Read the production source tree exactly once and share it across -/// every invariant test. The tree is ~180 files; without the memo each -/// of the three grep tests below would re-`read_to_string` the whole -/// `src/` subtree. -fn all_src_files() -> &'static Vec<(PathBuf, String)> { - static FILES: OnceLock> = OnceLock::new(); - FILES.get_or_init(|| production_rs_files(&src_root())) -} - -fn scan_forbidden( - label: &str, - is_forbidden: impl Fn(&str) -> bool, -) -> Vec<(PathBuf, usize, String)> { +// Forbidden selectors + +/// Spellings that would introduce a variant-keyed JitCode map. Source is +/// whitespace-normalized before matching, so ordinary and turbofish forms of +/// both hash and ordered maps are listed explicitly. +const VARIANT_KEYED_MAP: &[&str] = &[ + "HashMap Option<(usize, usize)> { + // A raw string may not be the tail of an identifier (`foo_r"x"` is not one). + if i > 0 && (b[i - 1].is_ascii_alphanumeric() || b[i - 1] == b'_') { + return None; + } + let mut p = i; + if b.get(p) == Some(&b'b') { + p += 1; + } + if b.get(p) != Some(&b'r') { + return None; + } + p += 1; + let mut hashes = 0; + while b.get(p) == Some(&b'#') { + hashes += 1; + p += 1; + } + if b.get(p) != Some(&b'"') { + return None; + } + Some((p + 1, hashes)) +} + +fn raw_string_end(b: &[u8], body_start: usize, hashes: usize) -> usize { + let mut p = body_start; + while p < b.len() { + if b[p] == b'"' && (0..hashes).all(|k| b.get(p + 1 + k) == Some(&b'#')) { + return p + 1 + hashes; + } + p += 1; + } + b.len() +} + +fn string_end(b: &[u8], i: usize) -> usize { + let mut p = i + 1; + while p < b.len() { + match b[p] { + b'\\' => p += 2, + b'"' => return p + 1, + _ => p += 1, + } + } + b.len() +} + +/// `'a'` / `'\n'` are char literals; `'a` in `&'a T` is a lifetime and must not +/// swallow what follows. The two are told apart by where the closing quote sits. +fn char_literal_end(b: &[u8], i: usize) -> usize { + match b.get(i + 1) { + Some(&b'\\') => { + let limit = (i + 8).min(b.len()); + (i + 2..limit) + .find(|&p| b[p] == b'\'') + .map_or(i + 1, |p| p + 1) + } + Some(&c) if c.is_ascii() && b.get(i + 2) == Some(&b'\'') => i + 3, + // A multi-byte char: the quote follows its continuation bytes. + Some(&c) if !c.is_ascii() => { + let limit = (i + 6).min(b.len()); + (i + 2..limit) + .find(|&p| b[p] == b'\'') + .map_or(i + 1, |p| p + 1) + } + _ => i + 1, + } +} + +/// Rust block comments nest, so this counts depth rather than scanning for the +/// first `*/`. +fn block_comment_end(b: &[u8], i: usize) -> usize { + let mut depth = 1usize; + let mut p = i + 2; + while p < b.len() { + if b[p] == b'/' && b.get(p + 1) == Some(&b'*') { + depth += 1; + p += 2; + } else if b[p] == b'*' && b.get(p + 1) == Some(&b'/') { + depth -= 1; + p += 2; + if depth == 0 { + return p; + } + } else { + p += 1; + } + } + b.len() +} + +/// Blank every comment, preserving length, byte offsets and line breaks. +/// +/// String contents are KEPT. A string is not a comment, and keeping it is the +/// over-reporting direction — the safe one for a gate. It is also the whole +/// point: the previous selector cut each line at its first `//`, so a line +/// carrying `"scheme://host"` lost everything after it. +fn strip_comments(src: &str) -> String { + let b = src.as_bytes(); + let mut out = b.to_vec(); + let mut i = 0usize; + while i < b.len() { + if let Some((body, hashes)) = raw_string_open(b, i) { + i = raw_string_end(b, body, hashes); + continue; + } + match b[i] { + b'"' => i = string_end(b, i), + b'\'' => i = char_literal_end(b, i), + b'/' if b.get(i + 1) == Some(&b'/') => { + let end = b[i..] + .iter() + .position(|&c| c == b'\n') + .map_or(b.len(), |off| i + off); + blank(&mut out, i, end); + i = end; + } + b'/' if b.get(i + 1) == Some(&b'*') => { + let end = block_comment_end(b, i); + blank(&mut out, i, end); + i = end; + } + _ => i += 1, + } + } + String::from_utf8(out).expect("comment bytes are replaced with ASCII spaces") +} + +/// Drop every whitespace byte, recording the source line each surviving byte +/// came from. Dropping newlines too is what makes a generic wrapped across +/// lines matchable; the forbidden spellings contain no whitespace, so nothing +/// about them is lost. +fn normalise(src: &str) -> (String, Vec) { + let mut dense = String::with_capacity(src.len()); + let mut lines = Vec::with_capacity(src.len()); + let mut line = 1u32; + for ch in src.chars() { + if ch == '\n' { + line += 1; + continue; + } + if ch.is_whitespace() { + continue; + } + dense.push(ch); + // One entry per UTF-8 byte: `str::find` returns byte offsets, so the + // line map has to be indexed the same way. + for _ in 0..ch.len_utf8() { + lines.push(line); + } + } + (dense, lines) +} + +// Source population + +struct Scanned { + rel: PathBuf, + dense: String, + lines: Vec, +} + +/// `.../majit`, the parent of every crate directory. +fn majit_root() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .expect("the crate directory has a parent") + .to_path_buf() +} + +/// True for `/src/**.rs` and nothing else — in particular not for +/// `/tests/**`, which is what stops this gate reading its own body. +fn is_crate_source(rel: &Path) -> bool { + let mut parts = rel.components(); + let _crate_dir = parts.next(); + parts.next().is_some_and(|c| c.as_os_str() == "src") + && rel.extension().and_then(|s| s.to_str()) == Some("rs") +} + +fn population() -> &'static Vec { + static FILES: OnceLock> = OnceLock::new(); + FILES.get_or_init(|| { + let root = majit_root(); + WalkDir::new(&root) + .into_iter() + // Descend only `majit//src/**`. Pruning at depth 2 is not + // cosmetic: `majit/charon-corpus/target` exists on a built tree, + // and an unpruned walk would descend the whole build directory. + .filter_entry(|e| e.depth() != 2 || e.file_name() == OsStr::new("src")) + .filter_map(Result::ok) + .filter_map(|entry| { + let path = entry.into_path(); + let rel = path.strip_prefix(&root).ok()?.to_path_buf(); + if !is_crate_source(&rel) { + return None; + } + let src = fs::read_to_string(&path).ok()?; + let (dense, lines) = normalise(&strip_comments(&src)); + Some(Scanned { rel, dense, lines }) + }) + .collect() + }) +} + +fn scan_forbidden(label: &str, patterns: &[&str]) -> Vec { + let files = population(); + let crates: std::collections::BTreeSet<_> = files + .iter() + .filter_map(|f| f.rel.components().next()) + .collect(); + + // The denominator, printed on every run: a zero below is only readable + // beside the size of the population that produced it. + eprintln!( + "[{label}] scanned {} files across {} crates under majit//src", + files.len(), + crates.len() + ); + assert!( + files.len() >= MIN_POPULATION && crates.len() >= MIN_CRATES, + "acceptance-grep population collapsed to {} files / {} crates — the \ + walk is broken, so a green here would mean nothing. Expected at \ + least {MIN_POPULATION} files across {MIN_CRATES} crates.", + files.len(), + crates.len() + ); + let mut hits = Vec::new(); - for (rel, src) in all_src_files() { - for (lineno, line) in src.lines().enumerate() { - let stripped = strip_line_comments(line); - if is_forbidden(stripped) { - hits.push((rel.clone(), lineno + 1, line.to_string())); + for file in files { + for &pattern in patterns { + let mut from = 0; + while let Some(at) = file.dense[from..].find(pattern) { + let at = from + at; + hits.push(format!( + "{}:{}: {}", + file.rel.display(), + file.lines[at], + pattern + )); + from = at + 1; } } } if !hits.is_empty() { - eprintln!( - "[{label}] {} forbidden occurrence(s) in majit-translate/src/:", - hits.len() - ); - for (rel, lineno, line) in &hits { - eprintln!(" {}:{}: {}", rel.display(), lineno, line.trim()); + eprintln!("[{label}] {} forbidden occurrence(s):", hits.len()); + for hit in &hits { + eprintln!(" {hit}"); } } hits } -fn src_root() -> PathBuf { - PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src") -} +// Acceptance gates -/// Plan verification protocol item #5: `rg "HashMap` instead — see \ + reintroduces the CPython-3.13-bytecode coupling that was removed. \ + Use `HashMap` instead — see \ `rpython/jit/codewriter/call.py:87 self.jitcodes = {{}}`." ); } -/// Plan verification protocol item #4: `rg "_may_raise|TryOp|OpKind::Try" -/// majit/` = 0. -/// /// Anchor: `rpython/jit/codewriter/jtransform.py:456 rewrite_op_direct_call` /// handles raising calls through the existing `residual_call_{r,v,f}_*` -/// family; `rpython/translator/exceptiontransform.py` lowers exception -/// edges to additional successors on the existing `Terminator`. No new -/// opname family, no new `OpKind` variant. +/// family; `rpython/translator/exceptiontransform.py` lowers exception edges +/// to additional successors on the existing `Terminator`. No new opname +/// family, no new `OpKind` variant. #[test] fn no_new_opname_family_for_exceptions() { - let hits = scan_forbidden("no_new_opname_family_for_exceptions", |line| { - line.contains("_may_raise") || line.contains("OpKind::Try") || line.contains("TryOp") - }); + let hits = scan_forbidden("no_new_opname_family_for_exceptions", NEW_OPNAME_FAMILY); assert!( hits.is_empty(), "Plan acceptance violated: exception-carrying operations must be \ @@ -136,16 +338,13 @@ fn no_new_opname_family_for_exceptions() { ); } -/// Plan verification protocol item: the legacy `compile_pyre_interpreter` -/// entry point must not re-emerge. It is replaced with -/// `CodeWriter::make_jitcodes` (upstream `codewriter.py:74`). A surviving -/// definition means the pyre-bytecode-walking path lives in parallel with -/// the graph-keyed one. +/// The legacy `compile_pyre_interpreter` entry point must not re-emerge. It +/// is replaced with `CodeWriter::make_jitcodes` (upstream `codewriter.py:74`). +/// A surviving definition means the pyre-bytecode-walking path lives in +/// parallel with the graph-keyed one. #[test] fn no_legacy_compile_pyre_interpreter() { - let hits = scan_forbidden("no_legacy_compile_pyre_interpreter", |line| { - line.contains("fn compile_pyre_interpreter") - }); + let hits = scan_forbidden("no_legacy_compile_pyre_interpreter", LEGACY_ENTRY_POINT); assert!( hits.is_empty(), "Plan acceptance violated: `compile_pyre_interpreter` resurfaced. \ @@ -154,3 +353,64 @@ fn no_legacy_compile_pyre_interpreter() { definition." ); } + +/// Positive control for the three absence gates. It runs their real +/// strip/normalize/find pipeline over awkward spellings and confirms that code +/// is found while commented mentions are ignored. +#[test] +fn selector_detects_every_forbidden_spelling() { + const SYNTHETIC: &str = r###" +// a comment naming HashMap; +} +type M2 = HashMap< + Instruction, + u32, +>; +fn turbofish() { let _ = HashMap::::new(); } +fn ordered() -> BTreeMap { todo!() } +fn raising(x: u8) -> bool { x._may_raise() } +struct S { t: TryOp, k: OpKind::Try } +fn compile_pyre_interpreter() {} +"###; + + let (dense, _lines) = normalise(&strip_comments(SYNTHETIC)); + + // (pattern, expected count) — every count is deliberate, not a floor. + let expected: &[(&str, usize)] = &[ + ("HashMap` deref. +const FIXTURES: [&str; 2] = ["host_registry_dispatch", "host_registry_dispatch_optional"]; + +#[derive(Debug, Default, PartialEq, Eq)] +struct Shape { + /// `OpKind::IndirectCall { graphs: None }` — `indirect_call` with an + /// unknown PBC family. + indirect_unknown_family: usize, + /// `Call(FunctionPath["__dyn_call"])` — the placeholder. + dyn_call: usize, +} + +fn shape_of(llbc: &Llbc, name: &str) -> Shape { + let graph = lower_function(llbc, name).expect("lowering"); + let mut shape = Shape::default(); + for b in &graph.blocks { + for op in &b.operations { + match &op.kind { + OpKind::IndirectCall { graphs, args, .. } => { + // `None`, not `Some([])`, is load-bearing and asserted + // here rather than left to `is_some()`: `graphs is None` + // is upstream's "cannot follow the indirect call" + // (`call.py:105`/`137`, `jtransform.py:410-412`) and the + // one value that keeps every family analyzer on its top + // result. `Some([])` reads as an *empty* family and + // collapses canraise / can_invalidate / + // forces_virtualizable to their bottom result. + assert_eq!( + graphs.as_deref(), + None, + "{name}: a runtime-installed callback has no \ + statically recoverable PBC family", + ); + // The callee moves out of the argument list into the op's + // own `funcptr` slot, so the arity is one below the + // `__dyn_call` spelling of the same call. + assert_eq!( + args.len(), + 1, + "{name}: the callee lives in `funcptr`, not in args[0]", + ); + shape.indirect_unknown_family += 1; + } + OpKind::Call { + target: CallTarget::FunctionPath { segments }, + args, + .. + } if segments.last().map(String::as_str) == Some("__dyn_call") => { + assert_eq!( + args.len(), + 2, + "{name}: __dyn_call threads the callee as args[0]", + ); + shape.dyn_call += 1; + } + _ => {} + } + } + } + shape +} + +/// Off (the default) the call reaches the `__dyn_call` placeholder; on, it +/// reaches `IndirectCall` with no family. +/// +/// The switch is opt-in rather than on-by-default for a reason recorded at +/// `front::mir::fnptr_indirect_enabled`: turning it on propagates +/// `EF_RANDOM_EFFECTS` out of the newly visible indirect calls and trips +/// `getcalldescr`'s elidable post-condition on pyre's +/// `#[elidable_cannot_raise]` bigint helpers. This test pins the lowering +/// itself so that stays a pyre-side blocker and not a silent regression in +/// the lowering. +#[test] +fn fnptr_indirect_switch_moves_the_lowering_in_both_directions() { + let llbc = Llbc::load(CORPUS).expect("load corpus.ullbc"); + + // SAFETY: `set_var` is unsafe in Rust 2024 because concurrent + // environment mutation races. This file's test binary holds exactly + // one test, so nothing else in the process reads or writes the + // environment while it runs. + unsafe { std::env::remove_var("PYRE_FNPTR_INDIRECT") }; + for name in FIXTURES { + assert_eq!( + shape_of(&llbc, name), + Shape { + indirect_unknown_family: 0, + dyn_call: 1, + }, + "{name}: default (switch unset) keeps the placeholder", + ); + } + + unsafe { std::env::set_var("PYRE_FNPTR_INDIRECT", "1") }; + for name in FIXTURES { + assert_eq!( + shape_of(&llbc, name), + Shape { + indirect_unknown_family: 1, + dyn_call: 0, + }, + "{name}: switch on lowers the callback to indirect_call", + ); + } + + unsafe { std::env::remove_var("PYRE_FNPTR_INDIRECT") }; +} diff --git a/pyre/gate-triage.md b/pyre/gate-triage.md index 596a1912f73..dd2b984aca8 100644 --- a/pyre/gate-triage.md +++ b/pyre/gate-triage.md @@ -978,6 +978,21 @@ already-ON criterion. They are listed so they cannot be missed again. `PYRE_WASM_GUARD_CENSUS`, `PYRE_WASM_JIT_STATS`, `PYRE_WASM_NO_CACHE`, `PYRE_WASM_STARTUP_TRACE`. +## §7 — General MAJIT gates + +These translator and metainterpreter controls are inert unless explicitly set, +except for the function-pointer lowering switch, which is a build configuration +input. + +| gate | default polarity | what it gates / retirement condition | +|---|---|---| +| `PYRE_CALLEE_CENSUS` | OFF | build-time census of resolved and unresolved callees; retire when all supported callees are classified without this diagnostic | +| `PYRE_CALLEE_CENSUS_ROWS` | OFF | row cap for that census; retire with `PYRE_CALLEE_CENSUS` | +| `PYRE_DESCR_POOL_CENSUS` | OFF | reports descriptor interning and duplication; retire when descriptor identity is covered by ordinary tests | +| `PYRE_FNPTR_INDIRECT` | OFF | enables indirect function-pointer lowering; retained as a build configuration switch | +| `PYRE_PROBE14` | OFF | reports discarded reference-constant relocations; retire when relocation preservation is covered by ordinary tests | +| `PYRE_VABLE_IDX_PROBE` | OFF | reports whether virtualizable array indices are constant; retire when all supported index shapes are covered by ordinary tests | + ## Summary | bucket | count | diff --git a/pyre/pyre-jit-trace/build.rs b/pyre/pyre-jit-trace/build.rs index b076abcaec4..45529e70b2d 100644 --- a/pyre/pyre-jit-trace/build.rs +++ b/pyre/pyre-jit-trace/build.rs @@ -33,6 +33,17 @@ const CODEGEN_OUTPUTS: &[&str] = &[ "static_ref_bindings.bin", ]; +/// Lowering switches read by `majit-translate` while this build script runs. +/// They affect generated graphs, so Cargo and the content cache must both see +/// their values; otherwise an A/B can silently restore the opposite setting. +const LOWERING_GATE_ENV: [&str; 5] = [ + "PYRE_DYN_INDIRECT", + "PYRE_FNPTR_INDIRECT", + "PYRE_MIR_FRAMESTATE", + "PYRE_OPTION_RESIDUAL_NARROW", + "PYRE_TUPLE_PER_SHAPE_CLASSDEF", +]; + /// Build script for pyre-jit: runs majit-translate on the active pyre /// interpreter to auto-generate tracing code. This is the Rust /// equivalent of RPython's translation pipeline. @@ -655,7 +666,10 @@ fn real_main() { // `PYRE_RTYPER_VERBOSE=1` workflow. The generated artifacts themselves do // not depend on verbosity, so an ordinary build may still reuse the cache. let verbose_prepass = std::env::var_os("PYRE_RTYPER_VERBOSE").is_some_and(|value| value == "1"); - if !verbose_prepass && restore_codegen_cache(&cache_dir, &out_dir) { + // The callee census is emitted from the analysis itself. A cache restore + // would print no rows, which is indistinguishable from an empty census. + let callee_census = std::env::var_os("PYRE_CALLEE_CENSUS").is_some_and(|value| value == "1"); + if !verbose_prepass && !callee_census && restore_codegen_cache(&cache_dir, &out_dir) { eprintln!( "[pyre-jit-trace build.rs] restored generated JIT trace artifacts from cache {}", cache_key @@ -864,12 +878,43 @@ fn build_call_effect_overrides() -> Vec { let effect = match spec.effect { call_spec::CallEffectKind::Elidable => majit_translate::CallEffectKind::Elidable, call_spec::CallEffectKind::Residual => majit_translate::CallEffectKind::Residual, + call_spec::CallEffectKind::Declared(declared) => { + majit_translate::CallEffectKind::Declared( + majit_translate::DeclaredCallEffects { + extra: declared_extra_effect(declared.extra), + can_collect: declared.can_collect, + can_invalidate: declared.can_invalidate, + }, + ) + } }; majit_translate::CallEffectOverride::new(target, effect) }) .collect() } +/// Carry one declared `EF_*` across the crate boundary. +/// +/// Two enums rather than one shared type because `call_spec.rs` is the +/// data-only contract pyre owns and `majit-translate` is the consumer; the +/// arms are enumerated so adding a member on either side is a compile error +/// here rather than a silent re-mapping. +fn declared_extra_effect( + extra: call_spec::DeclaredExtraEffect, +) -> majit_translate::DeclaredExtraEffect { + use call_spec::DeclaredExtraEffect as Spec; + use majit_translate::DeclaredExtraEffect as Translate; + match extra { + Spec::ElidableCannotRaise => Translate::ElidableCannotRaise, + Spec::LoopInvariant => Translate::LoopInvariant, + Spec::CannotRaise => Translate::CannotRaise, + Spec::ElidableOrMemoryError => Translate::ElidableOrMemoryError, + Spec::ElidableCanRaise => Translate::ElidableCanRaise, + Spec::CanRaise => Translate::CanRaise, + Spec::ForcesVirtualOrVirtualizable => Translate::ForcesVirtualOrVirtualizable, + } +} + fn emit_rerun_directives(repo_root: &str, source_paths: &[String]) { for path in source_paths { println!("cargo::rerun-if-changed={path}"); @@ -878,6 +923,11 @@ fn emit_rerun_directives(repo_root: &str, source_paths: &[String]) { println!("cargo::rerun-if-changed=src/virtualizable_spec.rs"); println!("cargo::rerun-if-changed=src/call_spec.rs"); println!("cargo::rerun-if-env-changed=PYRE_RTYPER_VERBOSE"); + println!("cargo::rerun-if-env-changed=PYRE_CALLEE_CENSUS"); + println!("cargo::rerun-if-env-changed=PYRE_CALLEE_CENSUS_ROWS"); + for key in LOWERING_GATE_ENV { + println!("cargo::rerun-if-env-changed={key}"); + } // The mir-frontend analysis derives `jit_trace_gen.rs` from // the workspace LLBC artefacts or the `PYRE_MIR_FRONTEND_LLBC` // override. Track both so re-extracting LLBC or repointing the override @@ -1072,6 +1122,10 @@ fn codegen_cache_key(manifest_dir: &str, repo_root: &str, source_paths: &[String h.write_str(&value); } h.write_os(std::env::var_os("PYRE_MIR_FRONTEND_LLBC")); + for key in LOWERING_GATE_ENV { + h.write_str(key); + h.write_os(std::env::var_os(key)); + } // The codegen output also depends on every crate linked into this // build-script binary — `majit-translate`'s own dependencies diff --git a/pyre/pyre-jit-trace/src/call_spec.rs b/pyre/pyre-jit-trace/src/call_spec.rs index 54db8214638..04dbeb1b6cf 100644 --- a/pyre/pyre-jit-trace/src/call_spec.rs +++ b/pyre/pyre-jit-trace/src/call_spec.rs @@ -10,6 +10,89 @@ pub const PYFRAME_CALL_OWNER_ROOT: &str = "PyFrame"; pub enum CallEffectKind { Elidable, Residual, + /// A callee whose effects are stated here rather than derived from a + /// graph (callee census). + /// + /// `Elidable` and `Residual` are shorthands for two whole rows; this arm + /// carries the row itself, which is what a callee with **no graph** needs. + /// It is upstream's `analyze_external_call` answer — the middle arm of + /// `graphanalyze.py:93-130` — reached through the only channel this side + /// has for it: a spelling-keyed table, not a source attribute. + /// + /// `#[allow(dead_code)]` because **no row uses this arm yet.** The rows + /// are blocked on callee census item 6: a census re-run on a fresh LLBC set names + /// the callees and, decisively, their SEGMENTATION — and a + /// `FunctionPath` override matches on the split, so a row written from a + /// `::`-joined listing is a guess that fails silently. Delete the + /// attribute along with the first row. + #[allow(dead_code)] + Declared(DeclaredEffects), +} + +/// `effectinfo.py:17-24` `EF_*`, minus `EF_RANDOM_EFFECTS`. +/// +/// A table entry states what a callee **does**. `EF_RANDOM_EFFECTS` is the +/// answer for a callee nobody can describe — it is the absence of a +/// declaration, so it has no member here. +/// +/// Omitting it is load-bearing, not tidiness: it is what makes upstream's +/// +/// ```text +/// assert not (elidable_function and random_effects_on_gcobjs) +/// ``` +/// +/// (`rpython/rtyper/lltypesystem/rffi.py:160`, inside `llexternal`) hold **by +/// construction** — the illegal pairing cannot be written. Upstream enforces +/// it at the declaration site for externals, which is exactly what this table +/// is, so the constraint belongs here and not downstream in an analyzer. +/// +/// The converse must stay writable. `elidable` **plus a concrete benign +/// row** is legal upstream and is the whole point: `random_effects_on_gcobjs` +/// derives from `invoke_around_handlers or has_callback` (`rffi.py:130-175`), +/// i.e. "can release the GIL or can run a callback". A primitive like +/// `core::ptr::null` does neither. A type that forbade elidable-with-a-row +/// would delete the population this arm exists to serve, at compile time, +/// while looking like a hardening. +/// +/// `#[allow(dead_code)]` for the same reason as `CallEffectKind::Declared`: +/// every member is *matched* by the translators, none is *constructed*, and +/// none will be until the first row lands. Delete it with that row. +#[allow(dead_code)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum DeclaredExtraEffect { + /// `EF_ELIDABLE_CANNOT_RAISE` — the row a pure primitive wants. + ElidableCannotRaise, + /// `EF_LOOPINVARIANT` + LoopInvariant, + /// `EF_CANNOT_RAISE` + CannotRaise, + /// `EF_ELIDABLE_OR_MEMORYERROR` + ElidableOrMemoryError, + /// `EF_ELIDABLE_CAN_RAISE` + ElidableCanRaise, + /// `EF_CAN_RAISE` + CanRaise, + /// `EF_FORCES_VIRTUAL_OR_VIRTUALIZABLE` + ForcesVirtualOrVirtualizable, +} + +/// One declared effects row. +/// +/// The six read/write descr sets are **not** expressible here and are left +/// at `EffectInfo`'s default (empty, i.e. "touches no field or array"). A +/// `const` table has no way to name a descr — the descr universe does not +/// exist until the codewriter has run. Empty is the correct row for the +/// primitives this arm targets (`core::ptr::null` reads and writes nothing), +/// and it is **wrong** for anything that touches memory, so an entry for such +/// a callee must not be written until that channel exists. +#[derive(Clone, Copy)] +pub struct DeclaredEffects { + pub extra: DeclaredExtraEffect, + /// `effectinfo.py:125 can_collect` — upstream defaults it to `True`. + /// `false` states the callee cannot trigger a collection. + pub can_collect: bool, + /// Whether the callee can invalidate a quasi-immutable. + pub can_invalidate: bool, } #[derive(Clone, Copy)] @@ -27,6 +110,19 @@ pub struct CallEffectSpec { pub effect: CallEffectKind, } +/// Every row below is currently **inert** — the per-entry match counter reads +/// 0 for all 88. The `FunctionPath` rows carry a bare leaf name while the call +/// sites resolve to the module-qualified path (`["opcode_binary_op"]` vs +/// `["pyre_interpreter", "pyopcode", "opcode_binary_op"]`), and non-`Method` +/// patterns are compared by full structural equality. +/// +/// Do **not** repair that by relaxing the comparison to the leaf name. An +/// unmatched row is inert and safe; a wrongly-matched row installs another +/// function's declared effects on the call, and matching on the leaf alone +/// would make an `RBigInt::sub` row also capture `time::Instant::sub` and +/// `core::ops::arith::::sub`. Fix the spelling in the row, not the +/// predicate — see `CallEffectOverride::target` for how to read the spelling a +/// call site actually carries, and verify against the census match counter. pub const PYFRAME_CALL_EFFECTS: &[CallEffectSpec] = &[ CallEffectSpec { target: CallTargetSpec::Method { diff --git a/pyre/pyre-jit-trace/src/descr.rs b/pyre/pyre-jit-trace/src/descr.rs index e9fb4f3f5f0..4b28b48936f 100644 --- a/pyre/pyre-jit-trace/src/descr.rs +++ b/pyre/pyre-jit-trace/src/descr.rs @@ -2390,7 +2390,7 @@ fn new_w_class_field_descr() -> Arc { // the first value field, e.g. `W_IntObject.intval`). Arc::new(PyreFieldDescr { offset: pyre_object::pyobject::W_CLASS_OFFSET, - // ⚠️`WORD` on paper — the field is a `*mut PyObject`, so 4 bytes on + // `WORD` on paper — the field is a `*mut PyObject`, so 4 bytes on // wasm32, and the build-time descr pool already sizes it that way // (`call.rs get_type_flag` → `layout::target_word_size()`). Deriving it // here to match makes `synth/exception_traceback_loop_forms` lose one @@ -4712,7 +4712,7 @@ mod tests { is_field_signed: true, is_immutable: true, is_quasi_immutable: false, - index_in_parent: 1, + index_in_parent: Some(1), parent: Some(parent), name: "value".into(), owner: "Cell".into(), @@ -4769,7 +4769,7 @@ mod tests { is_field_signed: capacity.is_field_signed, is_immutable: capacity.is_immutable, is_quasi_immutable: capacity.is_quasi_immutable, - index_in_parent: capacity.index_in_parent, + index_in_parent: Some(capacity.index_in_parent), parent: Some(parent), name: "capacity".into(), owner: "ItemsBlock".into(), @@ -4805,7 +4805,7 @@ mod tests { is_field_signed: false, is_immutable: false, is_quasi_immutable: false, - index_in_parent: 0, + index_in_parent: Some(0), parent: None, name: name.into(), owner: "W_ListObject".into(), @@ -4842,7 +4842,7 @@ mod tests { is_field_signed: false, is_immutable: false, is_quasi_immutable: false, - index_in_parent: 0, + index_in_parent: Some(0), parent: None, name: name.into(), owner: "W_ListObject".into(), @@ -4883,7 +4883,7 @@ mod tests { is_field_signed: false, is_immutable: false, is_quasi_immutable: false, - index_in_parent: 0, + index_in_parent: Some(0), parent: None, name: name.into(), owner: "W_ListObject".into(), @@ -4949,7 +4949,7 @@ mod tests { is_field_signed: true, is_immutable: false, is_quasi_immutable: false, - index_in_parent: 2, + index_in_parent: Some(2), parent: None, name: name.into(), owner: owner.into(), @@ -4976,7 +4976,7 @@ mod tests { is_immutable: false, is_quasi_immutable: false, // slot 0 is `ob_type`; `w_class` is slot 1 of the header. - index_in_parent: 1, + index_in_parent: Some(1), parent: None, name: "w_class".into(), owner: owner.into(), @@ -5246,9 +5246,16 @@ fn simple_descr_group_from_bh_size( ) } +/// `claimed_index` is the descr's own `index_in_parent` claim, passed beside +/// `field` rather than read off it: `BhFieldSpec` is the shape of a PARENT's +/// field-list entry, where every index is a real position, so its field is a +/// plain `usize` and cannot carry the "never resolved a slot" state that a +/// standalone `BhDescr::Field` can be in. Flattening it here is what made the +/// unresolved-mint table unsplittable. fn field_descr_from_bh_field( field: &majit_translate::jitcode::BhFieldSpec, parent: Option<&majit_translate::jitcode::BhSizeSpec>, + claimed_index: Option, ) -> DescrRef { if let Some(parent) = parent { // `descr.py:218-239 get_field_descr` cache-hit: when the parent @@ -5291,7 +5298,7 @@ fn field_descr_from_bh_field( field.field_flag, field.index, false, - field.index_in_parent, + claimed_index, ); return fd as DescrRef; } @@ -5689,7 +5696,7 @@ pub fn make_descr_from_bh(bh: &majit_translate::jitcode::BhDescr) -> DescrRef { .. } => { let field_key = bh_field_cache_key(owner, name); - // #171 codewriter descr-bridge: `_handle_list_call` + // list-append codewriter descr-bridge: `_handle_list_call` // (codewriter/jtransform.rs) lowers Integer-strategy list // ops to fields on the dotted nested names // `int_items.{len,block}` (owner `W_ListObject`). @@ -5781,7 +5788,7 @@ pub fn make_descr_from_bh(bh: &majit_translate::jitcode::BhDescr) -> DescrRef { _ => {} } } - // #171 object-strategy capacity read: `list.obj_capacity` lowers + // list-append object-strategy capacity read: `list.obj_capacity` lowers // to getfield_gc_r(items) + getfield_gc_i(block.capacity). The // block's offset-0 GcArray length header IS the allocated // capacity (immutable for the block's lifetime). @@ -5830,7 +5837,7 @@ pub fn make_descr_from_bh(bh: &majit_translate::jitcode::BhDescr) -> DescrRef { } } } - // #171 codewriter descr-bridge: a codewriter-lowered body reads a + // list-append codewriter descr-bridge: a codewriter-lowered body reads a // box payload (`W_IntObject.intval` / `W_BoolObject.intval` / // `W_FloatObject.floatval`) through the producer's struct-layout // `SimpleFieldDescr` (header modeled, `index_in_parent` = 2). The @@ -5869,7 +5876,11 @@ pub fn make_descr_from_bh(bh: &majit_translate::jitcode::BhDescr) -> DescrRef { // produces a `SimpleFieldDescr` whose `index()` matches the // upstream value rather than a `u32::MAX` sentinel. let field = majit_translate::jitcode::BhFieldSpec { - index: *index_in_parent as u32, + // `unwrap_or(0)` and not the `u32::MAX` "no index" sentinel + // used elsewhere in this file: `0` is the value this field has + // carried on this path all along, and swapping in the sentinel + // would change a descr's identity, not just measure it. + index: index_in_parent.unwrap_or(0) as u32, field_key, name: full_name, offset: *offset, @@ -5879,9 +5890,10 @@ pub fn make_descr_from_bh(bh: &majit_translate::jitcode::BhDescr) -> DescrRef { is_field_signed: *is_field_signed, is_immutable: *is_immutable, is_quasi_immutable: *is_quasi_immutable, - index_in_parent: *index_in_parent, + index_in_parent: index_in_parent.unwrap_or(0), }; - field_descr_from_bh_field(&field, parent.as_ref()) + // The claim itself goes beside the spec, unflattened. + field_descr_from_bh_field(&field, parent.as_ref(), *index_in_parent) } BhDescr::Array { base_size, @@ -6134,7 +6146,7 @@ pub fn make_descr_from_bh(bh: &majit_translate::jitcode::BhDescr) -> DescrRef { name.clone(), name, ) - .with_index_in_parent(index_in_parent), + .with_index_in_parent(index_in_parent.unwrap_or(0)), ); // `descr.py:423-438 get_interiorfield_descr` cache-or-mint keyed on // the outer ARRAY identity, so the analyzer's @@ -6461,7 +6473,9 @@ fn mint_field( *flag, u32::MAX, false, - *index_in_parent, + // The analyzer's `field_pos`, always computed — see the note above on + // its two-word header offset against the runtime publish's numbering. + Some(*index_in_parent), )) } diff --git a/pyre/pyre-jit-trace/src/jitcode_runtime.rs b/pyre/pyre-jit-trace/src/jitcode_runtime.rs index 33193af7b99..123185aeb3d 100644 --- a/pyre/pyre-jit-trace/src/jitcode_runtime.rs +++ b/pyre/pyre-jit-trace/src/jitcode_runtime.rs @@ -310,7 +310,7 @@ pub fn portal_jitcode_for_key(key: &str) -> Option> { // shifts whenever the jitcode set changes, so it must be resolved by // name and never hardcoded. // -// This by-name lookup is the foundation #171 P3 needs to descend the FBW +// This by-name lookup is the foundation list-append P3 needs to descend the FBW // walker into the orthodox charon list-append body (issue #62/#23): the // dynamic `lst.append` recognition arm resolves the body here, then // builds a by-index sub-walk from `get_jitcode_by_index`. @@ -767,11 +767,18 @@ pub fn descr_set_jit_stats() -> String { /// state `descr.py` cannot reach: `get_size_descr` returns on cache hit, so a /// shell published first outranks the real layout for the rest of the run. pub fn field_position_jit_stats() -> String { - let [parent_absent, parent_empty, rederived, unresolved] = - majit_ir::descr::GcCache::field_position_census(); - let [spec_checked, spec_misplaced] = majit_ir::descr::GcCache::spec_position_census(); - let [attached_checked, attached_misplaced] = - majit_ir::descr::GcCache::attached_position_census(); + // Through `field_position_counts` rather than the censuses directly, so + // this line and the wasm exports cannot describe different populations. + let FieldPositionCounts { + parent_absent, + parent_empty, + rederived, + unresolved, + spec_checked, + spec_misplaced, + attached_checked, + attached_misplaced, + } = field_position_counts(); let ( [published, fieldless, shadowing, aliased, aliased_multi], [slots, misplaced], @@ -833,6 +840,30 @@ pub struct DescrSetCounts { pub stale_absent: u64, } +/// Name the `field_pos_unresolved` mints, under `MAJIT_FIELD_POS_UNRESOLVED=1`. +/// +/// Same split as `report_descr_spelling_gate`: the count rides the gated +/// `[jit-stats]` line and this is the detail behind it. The count says the slot +/// hazard was reached; only the names say by whom, and that decides whether the +/// fix belongs at one producer or at the resolution layer. +/// +/// `all_descrs` is the caller's — `metainterp_sd.all_descrs`, the table +/// `pyrex` already reads for its own diag line, not this module's +/// [`all_descrs`] opcode pool. It rides along as the denominator that says the +/// descr universe was loaded at all: an empty table under a nonzero +/// `all_descrs` is a clean tree, and an empty table under a zero one is a run +/// that never got far enough to have an answer. +/// +/// Empty when the knob is unset — a bare zero is never printed for a census +/// that did not run. How many rows it prints is the knob's own value +/// (`MAJIT_FIELD_POS_UNRESOLVED=`, `1` for all of them). +pub fn field_position_unresolved_report(all_descrs: usize) -> Vec { + if !majit_ir::descr::field_position_unresolved_naming_enabled() { + return Vec::new(); + } + majit_ir::descr::GcCache::field_position_unresolved_sample(all_descrs) +} + /// The two producer-side field-position invariants as numbers, for the same /// reason [`descr_set_counts`] exists: the wasm guest has no stderr, so it /// exports them individually (`pyre_jit_field_pos_*` in `pyre-wasm`) and the @@ -846,10 +877,16 @@ pub struct DescrSetCounts { /// wasm, and without these exports a wasm-only rise reads as absent-and- /// therefore-zero, i.e. healthy. pub fn field_position_counts() -> FieldPositionCounts { + let [parent_absent, parent_empty, rederived, unresolved] = + majit_ir::descr::GcCache::field_position_census(); let [spec_checked, spec_misplaced] = majit_ir::descr::GcCache::spec_position_census(); let [attached_checked, attached_misplaced] = majit_ir::descr::GcCache::attached_position_census(); FieldPositionCounts { + parent_absent: parent_absent as u64, + parent_empty: parent_empty as u64, + rederived: rederived as u64, + unresolved: unresolved as u64, spec_checked: spec_checked as u64, spec_misplaced: spec_misplaced as u64, attached_checked: attached_checked as u64, @@ -861,7 +898,20 @@ pub fn field_position_counts() -> FieldPositionCounts { /// cannot read the same as one that checked everything, but host-dependent and /// therefore not in `JITSTATS_SNAPSHOT_FIELDS`. The two `*_misplaced` are /// `JITSTATS_BADNESS_FIELDS` members and healthy only at zero. +/// +/// The four census members are the `derive_index_in_parent` dispositions, and +/// they are here rather than in a second struct so that the printed line and +/// the wasm exports cannot describe different populations: `field_position_jit_stats` +/// now formats THIS value instead of re-reading the censuses itself. Two +/// independent readers of the same three counters could only ever agree by +/// convention, and the convention is what rots — the printed line is what a +/// human reads and the exports are what the gate reads, so a drift between them +/// is invisible from either side. pub struct FieldPositionCounts { + pub parent_absent: u64, + pub parent_empty: u64, + pub rederived: u64, + pub unresolved: u64, pub spec_checked: u64, pub spec_misplaced: u64, pub attached_checked: u64, @@ -1076,7 +1126,7 @@ fn field_descr_identity_census(refs: &[DescrRef]) { .and_then(|p| p.as_size_descr()) .map(|sd| sd.all_fielddescrs().len()); samples.push(format!( - "{owner}.{name}[{index_in_parent}] T{:#x} pool={} walker={} \ + "{owner}.{name}[{index_in_parent:?}] T{:#x} pool={} walker={} \ all_fielddescrs={n_all:?} _cache_field keys={:?}", parent.type_id, pool_class.label(), @@ -1794,7 +1844,7 @@ mod tests { #[test] fn list_append_jitcode_resolves_charon_body() { - // #171 P3 foundation (deferred Route C): the orthodox charon + // list-append P3 foundation (deferred Route C): the orthodox charon // `w_list_append` body is present and reachable by name in the // build-time pipeline (the single-source descent the FBW walker would // enter once the prologue strategy-helper fnaddrs are registered). diff --git a/pyre/pyre-jit-trace/src/state.rs b/pyre/pyre-jit-trace/src/state.rs index 0806ea3de97..48c0e2a0f54 100644 --- a/pyre/pyre-jit-trace/src/state.rs +++ b/pyre/pyre-jit-trace/src/state.rs @@ -1096,14 +1096,102 @@ pub unsafe fn walk_jitcode_constants_refs_area( walk_jitcode_constants_refs_in(sd, visitor); } +/// Relocation probe for the non-moving invariant stated on +/// [`walk_jitcode_constants_refs`] above. `drag_out_root` relocates a root +/// only when it is a nursery object start, so a slot whose value the visitor +/// CHANGES is proof that a movable object entered the pool and that marking +/// in place without forwarding leaves a stale address behind. +/// +/// `WALKS`/`SLOTS` are the arming witnesses: `RELOCATED == 0` means nothing +/// moved only when `SLOTS > 0`. With `SLOTS == 0` the walker observed +/// nothing and the run says nothing either way. +/// +/// Gated on `PYRE_PROBE14=1`; reports on stderr as it happens because the +/// fault this chases is a non-unwinding abort that never reaches shutdown. +pub static PROBE14_WALKS: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); +pub static PROBE14_SLOTS: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); +pub static PROBE14_RELOCATED: std::sync::atomic::AtomicUsize = + std::sync::atomic::AtomicUsize::new(0); + +fn probe14_enabled() -> bool { + static ON: std::sync::OnceLock = std::sync::OnceLock::new(); + *ON.get_or_init(|| std::env::var("PYRE_PROBE14").as_deref() == Ok("1")) +} + fn walk_jitcode_constants_refs_in( sd: &MetaInterpStaticData, visitor: &mut dyn FnMut(&mut majit_ir::GcRef), ) { - for jc in sd.jitcodes.iter() { - for &slot in jc.payload.jitcode.constants_r.iter() { + use std::sync::atomic::Ordering::Relaxed; + let probe = probe14_enabled(); + if probe { + PROBE14_WALKS.fetch_add(1, Relaxed); + } + for (jitcode_index, jc) in sd.jitcodes.iter().enumerate() { + let pool_len = jc.payload.jitcode.constants_r.len(); + for (pool_slot, &slot) in jc.payload.jitcode.constants_r.iter().enumerate() { let mut gcref = majit_ir::GcRef(slot as usize); + let before = gcref.0; visitor(&mut gcref); + if probe { + PROBE14_SLOTS.fetch_add(1, Relaxed); + if gcref.0 != before { + let n = PROBE14_RELOCATED.fetch_add(1, Relaxed) + 1; + if n <= 20 { + // `jitcode` is the position in the walked `sd.jitcodes` + // and `pool_slot` the position inside that jitcode's own + // `constants_r`, so each report names the pool it came + // from. `cum_slot` is the running slot-visit ordinal + // across every walk and every jitcode -- it is NOT a + // within-pool index, and earlier runs are keyed on it. + // + // This locates the pool. It does NOT name the WRITER: + // nothing here distinguishes a pool built by + // `add_const_r` from one built by `emit_const_r_bits`. + eprintln!( + "[probe14] RELOCATION DISCARDED #{n}: constants_r slot \ + 0x{before:x} -> 0x{:x} (walk {}, cum_slot {}, \ + jitcode {}/{}, pool_slot {}/{})", + gcref.0, + PROBE14_WALKS.load(Relaxed), + PROBE14_SLOTS.load(Relaxed), + jitcode_index, + sd.jitcodes.len(), + pool_slot, + pool_len, + ); + } + } + } + } + } + // ARMING WITNESS. The relocation report above prints only when a slot + // MOVES, so on a run with no relocations it emits nothing at all — and + // "nothing" is indistinguishable from "this walker never ran" or "the pool + // was empty". Those are the REFUTES and NOT-EXERCISED branches of this + // probe's pre-registration, and they must not share a rendering. Emit the + // observed population unconditionally so a silent run is always readable as + // one or the other. Printed as it happens, not at shutdown: the fault this + // chases is a non-unwinding abort that never reaches shutdown. + if probe { + let walks = PROBE14_WALKS.load(Relaxed); + // Window widened after the first run: `walks <= 3 || walks % 200 == 0` + // sampled ONLY the walks before any jitcode was installed (jitcodes=0, + // slots=0) and then nothing until walk 200, but the abort lands around + // walk 5. The arming witness therefore reported slots=0 -- "NOT + // EXERCISED" -- for runs whose own relocation reports proved the + // opposite. An arming witness whose sampling window misses the armed + // regime is worse than none: it manufactures the null it exists to rule + // out. Cover every early walk, then thin out. + if walks <= 50 || walks % 50 == 0 { + eprintln!( + "[probe14] ARMED walk={} slots_cumulative={} relocated_cumulative={} \ + jitcodes={}", + walks, + PROBE14_SLOTS.load(Relaxed), + PROBE14_RELOCATED.load(Relaxed), + sd.jitcodes.len(), + ); } } } @@ -1655,9 +1743,8 @@ pub fn frame_liveness_reg_indices_at(jitcode_index: i32, pc: i32) -> Vec { /// /// `pyjitpl.py:218-225 get_list_of_active_boxes` reads each live register /// directly from its kind-specific bank (`registers_i[reg]` / -/// `registers_r[reg]` / `registers_f[reg]`); commit -/// `3fdb617f5d1` removed pyre's prior `registers_r_semantic` fallback to -/// match that contract. Production tracers fill the kind banks via +/// `registers_r[reg]` / `registers_f[reg]`). There is deliberately no +/// `registers_r_semantic` fallback. Production tracers fill the kind banks via /// `bcd_op` dispatch as the trace is recorded, but unit-test fixtures /// build a `PyreSym` directly through `from_test_state` and never run the /// dispatch loop — without seeding, `current_fail_args` returns a vector @@ -5509,7 +5596,7 @@ pub(crate) fn can_flush_walk_end_state_after_outer_call( /// during the publish can collect, so the copy has to be registered as resume /// roots (`push_resume_ref_roots`) for the duration. /// -/// ⚠️Restoring the FRAME is not the same as undoing the drive. This and +/// Restoring the FRAME is not the same as undoing the drive. This and /// [`capture_frame_scalars`] together cover the whole mutable virtualizable /// surface — the only vable fields with a production emit site are `last_instr` /// (index 0) and `valuestackdepth` (2), plus this array; `setfield_vable_r`/`_f` @@ -8613,7 +8700,8 @@ fn bh_field_descr_from_info(fd: &majit_ir::FieldDescrInfo) -> majit_translate::j is_field_signed: matches!(fd.field_type, Type::Int), is_immutable: false, is_quasi_immutable: false, - index_in_parent: fd.index as usize, + // A live `FieldDescrInfo` carries a resolved index. + index_in_parent: Some(fd.index as usize), parent: None, name: String::new(), owner: String::new(), diff --git a/pyre/pyre-wasm-runner/src/main.rs b/pyre/pyre-wasm-runner/src/main.rs index df74f8b35c4..e148e8d5e82 100644 --- a/pyre/pyre-wasm-runner/src/main.rs +++ b/pyre/pyre-wasm-runner/src/main.rs @@ -790,6 +790,21 @@ fn run(module_path: &Path, source: &str, script: &Path) -> Result { "retrace_arity_giveup", "ptp_push", "ptp_pop", + "forced_never_compiled", + "mst_entered", + "mst_sync_before_false", + "mst_live_values_mismatch", + "stfe_declined_compiled", + "stfe_declined_tracing", + "stfe_declined_tracing_stale", + "bridge_unattempted_close", + "xloop_close_decision_reached", + "xloop_close_target_compiled", + "xloop_close_published", + "abrt_unclassified_default", + "unroll_cancelled_invalid_loop", + "unroll_free_retry_rescued", + "unroll_free_retry_failed", ]; let mut parts = Vec::new(); for (i, lbl) in labels.iter().enumerate() {