stack_graphs/
c.rs

1// -*- coding: utf-8 -*-
2// ------------------------------------------------------------------------------------------------
3// Copyright © 2021, stack-graphs authors.
4// Licensed under either of Apache License, Version 2.0, or MIT license, at your option.
5// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details.
6// ------------------------------------------------------------------------------------------------
7
8//! Defines a C API for working with stack graphs in other languages.
9
10#![allow(non_camel_case_types)]
11
12use std::convert::TryInto;
13use std::sync::atomic::AtomicUsize;
14
15use libc::c_char;
16
17use crate::arena::Handle;
18use crate::graph::File;
19use crate::graph::InternedString;
20use crate::graph::Node;
21use crate::graph::NodeID;
22use crate::graph::StackGraph;
23use crate::graph::Symbol;
24use crate::partial::PartialPath;
25use crate::partial::PartialPathEdge;
26use crate::partial::PartialPathEdgeList;
27use crate::partial::PartialPaths;
28use crate::partial::PartialScopeStack;
29use crate::partial::PartialScopedSymbol;
30use crate::partial::PartialSymbolStack;
31use crate::stitching::Database;
32use crate::stitching::DatabaseCandidates;
33use crate::stitching::ForwardPartialPathStitcher;
34use crate::stitching::GraphEdgeCandidates;
35use crate::stitching::StitcherConfig;
36use crate::CancellationError;
37use crate::CancellationFlag;
38
39/// Contains all of the nodes and edges that make up a stack graph.
40pub struct sg_stack_graph {
41    pub inner: StackGraph,
42}
43
44/// Creates a new, initially empty stack graph.
45#[no_mangle]
46pub extern "C" fn sg_stack_graph_new() -> *mut sg_stack_graph {
47    Box::into_raw(Box::new(sg_stack_graph {
48        inner: StackGraph::new(),
49    }))
50}
51
52/// Frees a stack graph, and all of its contents.
53#[no_mangle]
54pub extern "C" fn sg_stack_graph_free(graph: *mut sg_stack_graph) {
55    drop(unsafe { Box::from_raw(graph) })
56}
57
58/// Manages the state of a collection of partial paths to be used in the path-stitching algorithm.
59pub struct sg_partial_path_arena {
60    pub inner: PartialPaths,
61}
62
63/// Creates a new, initially empty partial path arena.
64#[no_mangle]
65pub extern "C" fn sg_partial_path_arena_new() -> *mut sg_partial_path_arena {
66    Box::into_raw(Box::new(sg_partial_path_arena {
67        inner: PartialPaths::new(),
68    }))
69}
70
71/// Frees a path arena, and all of its contents.
72#[no_mangle]
73pub extern "C" fn sg_partial_path_arena_free(partials: *mut sg_partial_path_arena) {
74    drop(unsafe { Box::from_raw(partials) })
75}
76
77/// Contains a "database" of partial paths.
78///
79/// This type is meant to be a lazily loaded "view" into a proper storage layer.  During the
80/// path-stitching algorithm, we repeatedly try to extend a currently incomplete path with any
81/// partial paths that are compatible with it.  For large codebases, or projects with a large
82/// number of dependencies, it can be prohibitive to load in _all_ of the partial paths up-front.
83/// We've written the path-stitching algorithm so that you have a chance to only load in the
84/// partial paths that are actually needed, placing them into a sg_partial_path_database instance
85/// as they're needed.
86pub struct sg_partial_path_database {
87    pub inner: Database,
88}
89
90/// Creates a new, initially empty partial path database.
91#[no_mangle]
92pub extern "C" fn sg_partial_path_database_new() -> *mut sg_partial_path_database {
93    Box::into_raw(Box::new(sg_partial_path_database {
94        inner: Database::new(),
95    }))
96}
97
98/// Frees a partial path database, and all of its contents.
99#[no_mangle]
100pub extern "C" fn sg_partial_path_database_free(db: *mut sg_partial_path_database) {
101    drop(unsafe { Box::from_raw(db) })
102}
103
104/// The null value for all of our handles.
105pub const SG_NULL_HANDLE: u32 = 0;
106
107/// The handle of an empty list.
108pub const SG_LIST_EMPTY_HANDLE: u32 = 0xffffffff;
109
110/// Describes in which direction the content of a deque is stored in memory.
111#[repr(C)]
112#[derive(Clone, Copy, Debug, Eq, PartialEq)]
113pub enum sg_deque_direction {
114    SG_DEQUE_FORWARDS,
115    SG_DEQUE_BACKWARDS,
116}
117
118impl Default for sg_deque_direction {
119    fn default() -> sg_deque_direction {
120        sg_deque_direction::SG_DEQUE_FORWARDS
121    }
122}
123
124/// Ensures all partial paths in the database are availabe in both forwards and backwards orientation.
125#[no_mangle]
126pub extern "C" fn sg_partial_path_database_ensure_both_directions(
127    db: *mut sg_partial_path_database,
128    partials: *mut sg_partial_path_arena,
129) {
130    let db = unsafe { &mut (*db).inner };
131    let partials = unsafe { &mut (*partials).inner };
132    db.ensure_both_directions(partials);
133}
134
135/// Ensures all partial paths in the database are in forwards orientation.
136#[no_mangle]
137pub extern "C" fn sg_partial_path_database_ensure_forwards(
138    db: *mut sg_partial_path_database,
139    partials: *mut sg_partial_path_arena,
140) {
141    let db = unsafe { &mut (*db).inner };
142    let partials = unsafe { &mut (*partials).inner };
143    db.ensure_forwards(partials);
144}
145
146//-------------------------------------------------------------------------------------------------
147// Symbols
148
149/// A name that we are trying to resolve using stack graphs.
150///
151/// This typically represents a portion of an identifier as it appears in the source language.  It
152/// can also represent some other "operation" that can occur in source code, and which needs to be
153/// modeled in a stack graph — for instance, many languages will use a "fake" symbol named `.` to
154/// represent member access.
155#[repr(C)]
156pub struct sg_symbol {
157    pub symbol: *const c_char,
158    pub symbol_len: usize,
159}
160
161/// A handle to a symbol in a stack graph.  A zero handle represents a missing symbol.
162///
163/// We deduplicate symbols in a stack graph — that is, we ensure that there are never multiple
164/// `struct sg_symbol` instances with the same content.  That means that you can compare symbol
165/// handles using simple equality, without having to dereference them.
166pub type sg_symbol_handle = u32;
167
168/// An array of all of the symbols in a stack graph.  Symbol handles are indices into this array.
169/// There will never be a valid symbol at index 0; a handle with the value 0 represents a missing
170/// symbol.
171#[repr(C)]
172pub struct sg_symbols {
173    pub symbols: *const sg_symbol,
174    pub count: usize,
175}
176
177/// Returns a reference to the array of symbol data in this stack graph.  The resulting array
178/// pointer is only valid until the next call to any function that mutates the stack graph.
179#[no_mangle]
180pub extern "C" fn sg_stack_graph_symbols(graph: *const sg_stack_graph) -> sg_symbols {
181    let graph = unsafe { &(*graph).inner };
182    sg_symbols {
183        symbols: graph.symbols.as_ptr() as *const sg_symbol,
184        count: graph.symbols.len(),
185    }
186}
187
188/// Adds new symbols to the stack graph.  You provide all of the symbol content concatenated
189/// together into a single string, and an array of the lengths of each symbol.  You also provide an
190/// output array, which must have the same size as `lengths`.  We will place each symbol's handle
191/// in the output array.
192///
193/// We ensure that there is only ever one copy of a particular symbol stored in the graph — we
194/// guarantee that identical symbols will have the same handles, meaning that you can compare the
195/// handles using simple integer equality.
196///
197/// We copy the symbol data into the stack graph.  The symbol content you pass in does not need to
198/// outlive the call to this function.
199///
200/// Each symbol must be a valid UTF-8 string.  If any symbol isn't valid UTF-8, it won't be added
201/// to the stack graph, and the corresponding entry in the output array will be the null handle.
202#[no_mangle]
203pub extern "C" fn sg_stack_graph_add_symbols(
204    graph: *mut sg_stack_graph,
205    count: usize,
206    symbols: *const c_char,
207    lengths: *const usize,
208    handles_out: *mut sg_symbol_handle,
209) {
210    let graph = unsafe { &mut (*graph).inner };
211    let mut symbols = symbols as *const u8;
212    let lengths = unsafe { std::slice::from_raw_parts(lengths, count) };
213    let handles_out = unsafe {
214        std::slice::from_raw_parts_mut(handles_out as *mut Option<Handle<Symbol>>, count)
215    };
216    for i in 0..count {
217        let symbol = unsafe { std::slice::from_raw_parts(symbols, lengths[i]) };
218        handles_out[i] = match std::str::from_utf8(symbol) {
219            Ok(symbol) => Some(graph.add_symbol(symbol)),
220            Err(_) => None,
221        };
222        unsafe { symbols = symbols.add(lengths[i]) };
223    }
224}
225
226//-------------------------------------------------------------------------------------------------
227// Interned strings
228
229/// Arbitrary string content associated with some part of a stack graph.
230#[repr(C)]
231pub struct sg_string {
232    pub content: *const c_char,
233    pub length: usize,
234}
235
236/// A handle to an interned string in a stack graph.  A zero handle represents a missing string.
237///
238/// We deduplicate strings in a stack graph — that is, we ensure that there are never multiple
239/// `struct sg_string` instances with the same content.  That means that you can compare string
240/// handles using simple equality, without having to dereference them.
241pub type sg_string_handle = u32;
242
243/// An array of all of the interned strings in a stack graph.  String handles are indices into this
244/// array. There will never be a valid string at index 0; a handle with the value 0 represents a
245/// missing string.
246#[repr(C)]
247pub struct sg_strings {
248    pub strings: *const sg_string,
249    pub count: usize,
250}
251
252/// Returns a reference to the array of string data in this stack graph.  The resulting array
253/// pointer is only valid until the next call to any function that mutates the stack graph.
254#[no_mangle]
255pub extern "C" fn sg_stack_graph_strings(graph: *const sg_stack_graph) -> sg_strings {
256    let graph = unsafe { &(*graph).inner };
257    sg_strings {
258        strings: graph.strings.as_ptr() as *const sg_string,
259        count: graph.strings.len(),
260    }
261}
262
263/// Adds new strings to the stack graph.  You provide all of the string content concatenated
264/// together into a single string, and an array of the lengths of each string.  You also provide an
265/// output array, which must have the same size as `lengths`.  We will place each string's handle
266/// in the output array.
267///
268/// We ensure that there is only ever one copy of a particular string stored in the graph — we
269/// guarantee that identical strings will have the same handles, meaning that you can compare the
270/// handles using simple integer equality.
271///
272/// We copy the string data into the stack graph.  The string content you pass in does not need to
273/// outlive the call to this function.
274///
275/// Each string must be a valid UTF-8 string.  If any string isn't valid UTF-8, it won't be added
276/// to the stack graph, and the corresponding entry in the output array will be the null handle.
277#[no_mangle]
278pub extern "C" fn sg_stack_graph_add_strings(
279    graph: *mut sg_stack_graph,
280    count: usize,
281    strings: *const c_char,
282    lengths: *const usize,
283    handles_out: *mut sg_string_handle,
284) {
285    let graph = unsafe { &mut (*graph).inner };
286    let mut strings = strings as *const u8;
287    let lengths = unsafe { std::slice::from_raw_parts(lengths, count) };
288    let handles_out = unsafe {
289        std::slice::from_raw_parts_mut(handles_out as *mut Option<Handle<InternedString>>, count)
290    };
291    for i in 0..count {
292        let string = unsafe { std::slice::from_raw_parts(strings, lengths[i]) };
293        handles_out[i] = match std::str::from_utf8(string) {
294            Ok(string) => Some(graph.add_string(string)),
295            Err(_) => None,
296        };
297        unsafe { strings = strings.add(lengths[i]) };
298    }
299}
300
301//-------------------------------------------------------------------------------------------------
302// Files
303
304/// A source file that we have extracted stack graph data from.
305///
306/// It's up to you to choose what names to use for your files, but they must be unique within a
307/// stack graph.  If you are analyzing files from the local filesystem, the file's path is a good
308/// choice.  If your files belong to packages or repositories, they should include the package or
309/// repository IDs to make sure that files in different packages or repositories don't clash with
310/// each other.
311#[repr(C)]
312pub struct sg_file {
313    pub name: *const c_char,
314    pub name_len: usize,
315}
316
317/// A handle to a file in a stack graph.  A zero handle represents a missing file.
318///
319/// We deduplicate files in a stack graph — that is, we ensure that there are never multiple
320/// `struct sg_file` instances with the same filename.  That means that you can compare file
321/// handles using simple equality, without having to dereference them.
322pub type sg_file_handle = u32;
323
324impl Into<Handle<File>> for sg_file_handle {
325    fn into(self) -> Handle<File> {
326        unsafe { std::mem::transmute(self) }
327    }
328}
329
330/// An array of all of the files in a stack graph.  File handles are indices into this array.
331/// There will never be a valid file at index 0; a handle with the value 0 represents a missing
332/// file.
333#[repr(C)]
334pub struct sg_files {
335    pub files: *const sg_file,
336    pub count: usize,
337}
338
339/// Returns a reference to the array of file data in this stack graph.  The resulting array pointer
340/// is only valid until the next call to any function that mutates the stack graph.
341#[no_mangle]
342pub extern "C" fn sg_stack_graph_files(graph: *const sg_stack_graph) -> sg_files {
343    let graph = unsafe { &(*graph).inner };
344    sg_files {
345        files: graph.files.as_ptr() as *const sg_file,
346        count: graph.files.len(),
347    }
348}
349
350/// Adds new files to the stack graph.  You provide all of the file content concatenated together
351/// into a single string, and an array of the lengths of each file.  You also provide an output
352/// array, which must have the same size as `lengths`.  We will place each file's handle in the
353/// output array.
354///
355/// There can only ever be one file with a particular name in the graph.  If you try to add a file
356/// with a name that already exists, you'll get the same handle as a result.
357///
358/// We copy the filenames into the stack graph.  The filenames you pass in do not need to outlive
359/// the call to this function.
360///
361/// Each filename must be a valid UTF-8 string.  If any filename isn't valid UTF-8, it won't be
362/// added to the stack graph, and the corresponding entry in the output array will be the null
363/// handle.
364#[no_mangle]
365pub extern "C" fn sg_stack_graph_add_files(
366    graph: *mut sg_stack_graph,
367    count: usize,
368    files: *const c_char,
369    lengths: *const usize,
370    handles_out: *mut sg_file_handle,
371) {
372    let graph = unsafe { &mut (*graph).inner };
373    let mut files = files as *const u8;
374    let lengths = unsafe { std::slice::from_raw_parts(lengths, count) };
375    let handles_out =
376        unsafe { std::slice::from_raw_parts_mut(handles_out as *mut Option<Handle<File>>, count) };
377    for i in 0..count {
378        let file = unsafe { std::slice::from_raw_parts(files, lengths[i]) };
379        handles_out[i] = match std::str::from_utf8(file) {
380            Ok(file) => Some(graph.get_or_create_file(file)),
381            Err(_) => None,
382        };
383        unsafe { files = files.add(lengths[i]) };
384    }
385}
386
387//-------------------------------------------------------------------------------------------------
388// Nodes
389
390/// Uniquely identifies a node in a stack graph.
391///
392/// Each node (except for the _root node_ and _jump to scope_ node) lives in a file, and has a
393/// _local ID_ that must be unique within its file.
394#[repr(C)]
395#[derive(Clone, Copy, Default, Eq, PartialEq)]
396pub struct sg_node_id {
397    pub file: sg_file_handle,
398    pub local_id: u32,
399}
400
401impl sg_node_id {
402    fn is_empty(self) -> bool {
403        self.file == 0 && self.local_id == 0
404    }
405}
406
407impl Into<NodeID> for sg_node_id {
408    fn into(self) -> NodeID {
409        unsafe { std::mem::transmute(self) }
410    }
411}
412
413/// The local_id of the singleton root node.
414pub const SG_ROOT_NODE_ID: u32 = 1;
415
416/// The local_id of the singleton "jump to scope" node.
417pub const SG_JUMP_TO_NODE_ID: u32 = 2;
418
419/// A node in a stack graph.
420#[repr(C)]
421#[derive(Clone)]
422pub struct sg_node {
423    pub kind: sg_node_kind,
424    pub id: sg_node_id,
425    /// The symbol associated with this node.  For push nodes, this is the symbol that will be
426    /// pushed onto the symbol stack.  For pop nodes, this is the symbol that we expect to pop off
427    /// the symbol stack.  For all other node types, this will be null.
428    pub symbol: sg_symbol_handle,
429    /// The scope associated with this node.  For push scope nodes, this is the scope that will be
430    /// attached to the symbol before it's pushed onto the symbol stack.  For all other node types,
431    /// this will be null.
432    pub scope: sg_node_id,
433    /// Whether this node is an endpoint.  For push nodes, this indicates that the node represents
434    /// a reference in the source.  For pop nodes, this indicates that the node represents a
435    /// definition in the source.  For scopes, this indicates that the scope is exported. For all
436    /// other node types, this field will be unused.
437    pub is_endpoint: bool,
438}
439
440impl Into<Node> for sg_node {
441    fn into(self) -> Node {
442        unsafe { std::mem::transmute(self) }
443    }
444}
445
446/// The different kinds of node that can appear in a stack graph.
447#[repr(C)]
448#[derive(Clone, Copy)]
449pub enum sg_node_kind {
450    /// Removes everything from the current scope stack.
451    SG_NODE_KIND_DROP_SCOPES,
452    /// The singleton "jump to" node, which allows a name binding path to jump back to another part
453    /// of the graph.
454    SG_NODE_KIND_JUMP_TO,
455    /// Pops a scoped symbol from the symbol stack.  If the top of the symbol stack doesn't match
456    /// the requested symbol, or if the top of the symbol stack doesn't have an attached scope
457    /// list, then the path is not allowed to enter this node.
458    SG_NODE_KIND_POP_SCOPED_SYMBOL,
459    /// Pops a symbol from the symbol stack.  If the top of the symbol stack doesn't match the
460    /// requested symbol, then the path is not allowed to enter this node.
461    SG_NODE_KIND_POP_SYMBOL,
462    /// Pushes a scoped symbol onto the symbol stack.
463    SG_NODE_KIND_PUSH_SCOPED_SYMBOL,
464    /// Pushes a symbol onto the symbol stack.
465    SG_NODE_KIND_PUSH_SYMBOL,
466    /// The singleton root node, which allows a name binding path to cross between files.
467    SG_NODE_KIND_ROOT,
468    /// A node that adds structure to the graph. If the node is exported, it can be
469    /// referred to on the scope stack, which allows "jump to" nodes in any other
470    /// part of the graph can jump back here.
471    SG_NODE_KIND_SCOPE,
472}
473
474/// A handle to a node in a stack graph.  A zero handle represents a missing node.
475pub type sg_node_handle = u32;
476
477impl Into<Handle<Node>> for sg_node_handle {
478    fn into(self) -> Handle<Node> {
479        unsafe { std::mem::transmute(self) }
480    }
481}
482
483/// The handle of the singleton root node.
484pub const SG_ROOT_NODE_HANDLE: sg_node_handle = 1;
485
486/// The handle of the singleton "jump to scope" node.
487pub const SG_JUMP_TO_NODE_HANDLE: sg_node_handle = 2;
488
489/// An array of all of the nodes in a stack graph.  Node handles are indices into this array.
490/// There will never be a valid node at index 0; a handle with the value 0 represents a missing
491/// node.
492#[repr(C)]
493pub struct sg_nodes {
494    pub nodes: *const sg_node,
495    pub count: usize,
496}
497
498/// Returns a reference to the array of nodes in this stack graph.  The resulting array pointer is
499/// only valid until the next call to any function that mutates the stack graph.
500#[no_mangle]
501pub extern "C" fn sg_stack_graph_nodes(graph: *const sg_stack_graph) -> sg_nodes {
502    let graph = unsafe { &(*graph).inner };
503    sg_nodes {
504        nodes: graph.nodes.as_ptr() as *const sg_node,
505        count: graph.nodes.len(),
506    }
507}
508
509/// Adds new nodes to the stack graph.  You provide an array of `struct sg_node` instances.  You
510/// also provide an output array, which must have the same length as `nodes`, in which we will
511/// place each node's handle in the stack graph.
512///
513/// We copy the node content into the stack graph.  The array you pass in does not need to outlive
514/// the call to this function.
515///
516/// You cannot add new instances of the root node or "jump to scope" node, since those are
517/// singletons and already exist in the stack graph.
518///
519/// If you try to add a new node that has the same ID as an existing node in the stack graph, the
520/// new node will be ignored, and the corresponding entry in the `handles_out` array will contain
521/// the handle of the _existing_ node with that ID.
522///
523/// If any node that you pass in is invalid, it will not be added to the graph, and the
524/// corresponding entry in the `handles_out` array will be null.
525#[no_mangle]
526pub extern "C" fn sg_stack_graph_get_or_create_nodes(
527    graph: *mut sg_stack_graph,
528    count: usize,
529    nodes: *const sg_node,
530    handles_out: *mut sg_node_handle,
531) {
532    let graph = unsafe { &mut (*graph).inner };
533    let nodes = unsafe { std::slice::from_raw_parts(nodes, count) };
534    let handles_out =
535        unsafe { std::slice::from_raw_parts_mut(handles_out as *mut Option<Handle<Node>>, count) };
536    for i in 0..count {
537        let node_id = nodes[i].id;
538        handles_out[i] = validate_node(graph, &nodes[i])
539            .map(|node| graph.get_or_create_node(node_id.into(), node));
540    }
541}
542
543fn validate_node_id(graph: &StackGraph, node_id: sg_node_id) -> Option<()> {
544    if node_id.file == 0 || node_id.file >= (graph.files.len() as u32) {
545        return None;
546    }
547    Some(())
548}
549
550fn validate_node(graph: &StackGraph, node: &sg_node) -> Option<Node> {
551    if matches!(
552        &node.kind,
553        sg_node_kind::SG_NODE_KIND_JUMP_TO | sg_node_kind::SG_NODE_KIND_ROOT
554    ) {
555        // You can never add a singleton node, since there already is one!
556        return None;
557    }
558
559    // Every node must have a valid ID, which refers to an existing file.
560    validate_node_id(graph, node.id)?;
561
562    // Push and pop nodes must have a non-null symbol, and all other nodes must have a null symbol.
563    if (node.symbol != 0)
564        != matches!(
565            &node.kind,
566            sg_node_kind::SG_NODE_KIND_POP_SCOPED_SYMBOL
567                | sg_node_kind::SG_NODE_KIND_POP_SYMBOL
568                | sg_node_kind::SG_NODE_KIND_PUSH_SCOPED_SYMBOL
569                | sg_node_kind::SG_NODE_KIND_PUSH_SYMBOL
570        )
571    {
572        return None;
573    }
574
575    // Push scoped symbol nodes must have a non-null scope, and all other nodes must have a null
576    // scope.
577    if (node.scope.is_empty())
578        == matches!(&node.kind, sg_node_kind::SG_NODE_KIND_PUSH_SCOPED_SYMBOL)
579    {
580        return None;
581    }
582
583    Some(node.clone().into())
584}
585
586//-------------------------------------------------------------------------------------------------
587// Edges
588
589/// Connects two nodes in a stack graph.
590///
591/// These edges provide the basic graph connectivity that allow us to search for name binding paths
592/// in a stack graph.  (Though not all sequence of edges is a well-formed name binding: the nodes
593/// that you encounter along the path must also satisfy all of the rules for maintaining correct
594/// symbol and scope stacks.)
595#[repr(C)]
596pub struct sg_edge {
597    pub source: sg_node_handle,
598    pub sink: sg_node_handle,
599    pub precedence: i32,
600}
601
602/// Adds new edges to the stack graph.  You provide an array of `struct sg_edges` instances.  A
603/// stack graph can contain at most one edge between any two nodes.  It is not an error if you try
604/// to add an edge that already exists, but it won't have any effect on the graph.
605#[no_mangle]
606pub extern "C" fn sg_stack_graph_add_edges(
607    graph: *mut sg_stack_graph,
608    count: usize,
609    edges: *const sg_edge,
610) {
611    let graph = unsafe { &mut (*graph).inner };
612    let edges = unsafe { std::slice::from_raw_parts(edges, count) };
613    for i in 0..count {
614        let source = unsafe { std::mem::transmute(edges[i].source) };
615        let sink = unsafe { std::mem::transmute(edges[i].sink) };
616        graph.add_edge(source, sink, edges[i].precedence);
617    }
618}
619
620//-------------------------------------------------------------------------------------------------
621// Source info
622
623/// Contains information about a range of code in a source code file.
624#[repr(C)]
625#[derive(Clone, Copy, Default)]
626pub struct sg_source_info {
627    /// The location in its containing file of the source code that this node represents.
628    pub span: sg_span,
629    /// The kind of syntax entity this node represents (e.g. `function`, `class`, `method`, etc.).
630    pub syntax_type: sg_string_handle,
631    /// The full content of the line containing this node in its source file.
632    pub containing_line: sg_string_handle,
633    /// The location in its containing file of the source code that this node's definiens represents.
634    /// This is used for things like the bodies of functions, rather than the RHSes of equations.
635    /// If you need one of these to make the type checker happy, but you don't have one, just use
636    /// sg_span::default(), as this will correspond to the all-0s spans which mean "no definiens".
637    pub definiens_span: sg_span,
638    /// The fully qualified name is a representation of the symbol that captures its name and its
639    /// embedded context (e.g. `foo.bar` for the symbol `bar` defined in the module `foo`).
640    pub fully_qualified_name: sg_string_handle,
641}
642
643/// All of the position information that we have about a range of content in a source file
644#[repr(C)]
645#[derive(Clone, Copy, Default)]
646pub struct sg_span {
647    pub start: sg_position,
648    pub end: sg_position,
649}
650
651/// All of the position information that we have about a character in a source file
652#[repr(C)]
653#[derive(Clone, Copy, Default)]
654pub struct sg_position {
655    /// The 0-indexed line number containing the character
656    pub line: usize,
657    /// The offset of the character within its containing line, expressed as both a UTF-8 byte
658    /// index and a UTF-16 code point index
659    pub column: sg_offset,
660    /// The UTF-8 byte indexes (within the file) of the start and end of the line containing the
661    /// character
662    pub containing_line: sg_utf8_bounds,
663    /// The UTF-8 byte indexes (within the file) of the start and end of the line containing the
664    /// character, with any leading and trailing whitespace removed
665    pub trimmed_line: sg_utf8_bounds,
666}
667
668/// The offset of a character within a string (typically a line of source code), using several
669/// different units
670///
671/// All offsets are 0-indexed.
672#[repr(C)]
673#[derive(Clone, Copy, Default)]
674pub struct sg_offset {
675    /// The number of UTF-8-encoded bytes appearing before this character in the string
676    pub utf8_offset: usize,
677    /// The number of UTF-16 code units appearing before this character in the string
678    pub utf16_offset: usize,
679    /// The number of graphemes appearing before this character in the string
680    pub grapheme_offset: usize,
681}
682
683/// A half-open range identifying a range of characters in a string.
684#[repr(C)]
685#[derive(Clone, Copy, Default)]
686pub struct sg_utf8_bounds {
687    /// The UTF-8 byte index of the first character in the range.
688    pub start: usize,
689    /// The UTF-8 byte index of the first character _after_ the range.
690    pub end: usize,
691}
692
693/// An array of all of the source information in a stack graph.  Source information is associated
694/// with nodes, so node handles are indices into this array.  It is _not_ guaranteed that there
695/// will an entry in this array for every node handle; if you have a node handle whose value is
696/// larger than `count`, then use a 0-valued `sg_source_info` if you need source information for
697/// that node.
698///
699/// There will never be a valid entry at index 0; a handle with the value 0 represents a missing
700/// node.
701#[repr(C)]
702pub struct sg_source_infos {
703    pub infos: *const sg_source_info,
704    pub count: usize,
705}
706
707/// Returns a reference to the array of source information in this stack graph.  The resulting
708/// array pointer is only valid until the next call to any function that mutates the stack graph.
709#[no_mangle]
710pub extern "C" fn sg_stack_graph_source_infos(graph: *const sg_stack_graph) -> sg_source_infos {
711    let graph = unsafe { &(*graph).inner };
712    sg_source_infos {
713        infos: graph.source_info.as_ptr() as *const sg_source_info,
714        count: graph.source_info.len(),
715    }
716}
717
718/// A tuple of a node handle and source information for that node.  Used with the
719/// `sg_add_source_info` function to add source information to a stack graph.
720#[repr(C)]
721#[derive(Clone, Copy)]
722pub struct sg_node_source_info {
723    pub node: sg_node_handle,
724    pub source_info: sg_source_info,
725}
726
727/// Adds new source information to the stack graph.  You provide an array of `sg_node_source_info`
728/// instances.  Any existing source information for any node mentioned in the array is overwritten.
729#[no_mangle]
730pub extern "C" fn sg_stack_graph_add_source_infos(
731    graph: *mut sg_stack_graph,
732    count: usize,
733    infos: *const sg_node_source_info,
734) {
735    let graph = unsafe { &mut (*graph).inner };
736    let infos = unsafe { std::slice::from_raw_parts(infos, count) };
737    for i in 0..count {
738        let node = unsafe { std::mem::transmute(infos[i].node) };
739        let info = graph.source_info_mut(node);
740        *info = unsafe { std::mem::transmute(infos[i].source_info) };
741    }
742}
743
744//-------------------------------------------------------------------------------------------------
745// Partial symbol stacks
746
747/// Represents an unknown list of scoped symbols.
748pub type sg_symbol_stack_variable = u32;
749
750/// A symbol with an unknown, but possibly empty, list of exported scopes attached to it.
751#[repr(C)]
752#[derive(Clone, Copy, Eq, PartialEq)]
753pub struct sg_partial_scoped_symbol {
754    pub symbol: sg_symbol_handle,
755    pub scopes: sg_partial_scope_stack,
756}
757
758impl Into<PartialScopedSymbol> for sg_partial_scoped_symbol {
759    fn into(self) -> PartialScopedSymbol {
760        unsafe { std::mem::transmute(self) }
761    }
762}
763
764/// A pattern that might match against a symbol stack.  Consists of a (possibly empty) list of
765/// partial scoped symbols.
766///
767/// (Note that unlike partial scope stacks, we don't store any "symbol stack variable" here.  We
768/// could!  But with our current path-finding rules, every partial path will always have exactly
769/// one symbol stack variable, which will appear at the end of its precondition and postcondition.
770/// So for simplicity we just leave it out.  At some point in the future we might add it in so that
771/// the symbol and scope stack formalisms and implementations are more obviously symmetric.)
772#[repr(C)]
773#[derive(Clone, Copy, Default, Eq, PartialEq)]
774pub struct sg_partial_symbol_stack {
775    /// The handle of the first element in the partial symbol stack, or SG_LIST_EMPTY_HANDLE if the
776    /// list is empty, or 0 if the list is null.
777    pub cells: sg_partial_symbol_stack_cell_handle,
778    pub direction: sg_deque_direction,
779    pub length: u32,
780    /// The symbol stack variable representing the unknown content of a partial symbol stack, or 0
781    /// if the variable is missing.  (If so, this partial symbol stack can only match a symbol
782    /// stack with exactly the list of symbols in `cells`, instead of any symbol stack with those
783    /// symbols as a prefix.)
784    pub variable: sg_symbol_stack_variable,
785}
786
787impl From<PartialSymbolStack> for sg_partial_symbol_stack {
788    fn from(stack: PartialSymbolStack) -> sg_partial_symbol_stack {
789        unsafe { std::mem::transmute(stack) }
790    }
791}
792
793/// A handle to an element of a partial symbol stack.  A zero handle represents a missing partial
794/// symbol stack.  A UINT32_MAX handle represents an empty partial symbol stack.
795pub type sg_partial_symbol_stack_cell_handle = u32;
796
797/// An element of a partial symbol stack.
798#[repr(C)]
799pub struct sg_partial_symbol_stack_cell {
800    /// The partial scoped symbol at this position in the partial symbol stack.
801    pub head: sg_partial_scoped_symbol,
802    /// The handle of the next element in the partial symbol stack, or SG_LIST_EMPTY_HANDLE if this
803    /// is the last element.
804    pub tail: sg_partial_symbol_stack_cell_handle,
805    /// The handle of the reversal of this partial scope stack.
806    pub reversed: sg_partial_symbol_stack_cell_handle,
807}
808
809/// The array of all of the partial symbol stack content in a partial path arena.
810#[repr(C)]
811pub struct sg_partial_symbol_stack_cells {
812    pub cells: *const sg_partial_symbol_stack_cell,
813    pub count: usize,
814}
815
816/// Returns a reference to the array of partial symbol stack content in a partial path arena.  The
817/// resulting array pointer is only valid until the next call to any function that mutates the path
818/// arena.
819#[no_mangle]
820pub extern "C" fn sg_partial_path_arena_partial_symbol_stack_cells(
821    partials: *const sg_partial_path_arena,
822) -> sg_partial_symbol_stack_cells {
823    let partials = unsafe { &(*partials).inner };
824    sg_partial_symbol_stack_cells {
825        cells: partials.partial_symbol_stacks.as_ptr() as *const sg_partial_symbol_stack_cell,
826        count: partials.partial_symbol_stacks.len(),
827    }
828}
829
830/// Adds new partial symbol stacks to the partial path arena.  `count` is the number of partial
831/// symbol stacks you want to create.  The content of each partial symbol stack comes from two
832/// arrays.  The `lengths` array must have `count` elements, and provides the number of symbols in
833/// each partial symbol stack.  The `symbols` array contains the contents of each of these partial
834/// symbol stacks in one contiguous array.  Its length must be the sum of all of the counts in the
835/// `lengths` array.  The `variables` array must have `count` elements, and provides the optional
836/// symbol stack variable for each partial symbol stack.
837///
838/// You must also provide an `out` array, which must also have room for `count` elements.  We will
839/// fill this array in with the `sg_partial_symbol_stack` instances for each partial symbol stack
840/// that is created.
841#[no_mangle]
842pub extern "C" fn sg_partial_path_arena_add_partial_symbol_stacks(
843    partials: *mut sg_partial_path_arena,
844    count: usize,
845    mut symbols: *const sg_partial_scoped_symbol,
846    lengths: *const usize,
847    variables: *const sg_symbol_stack_variable,
848    out: *mut sg_partial_symbol_stack,
849) {
850    let partials = unsafe { &mut (*partials).inner };
851    let lengths = unsafe { std::slice::from_raw_parts(lengths, count) };
852    let variables = unsafe { std::slice::from_raw_parts(variables, count) };
853    let out = unsafe { std::slice::from_raw_parts_mut(out, count) };
854    for i in 0..count {
855        let length = lengths[i];
856        let symbols_slice = unsafe { std::slice::from_raw_parts(symbols, length) };
857        let mut stack = if variables[i] == 0 {
858            PartialSymbolStack::empty()
859        } else {
860            PartialSymbolStack::from_variable(variables[i].try_into().unwrap())
861        };
862        for j in 0..length {
863            let symbol = symbols_slice[j].into();
864            stack.push_back(partials, symbol);
865        }
866        // We pushed the edges onto the list in reverse order.  Requesting a forwards iterator
867        // before we return ensures that it will also be available in forwards order.
868        let _ = stack.iter(partials);
869        out[i] = stack.into();
870        unsafe { symbols = symbols.add(length) };
871    }
872}
873
874//-------------------------------------------------------------------------------------------------
875// Partial scope stacks
876
877/// Represents an unknown list of exported scopes.
878pub type sg_scope_stack_variable = u32;
879
880/// A pattern that might match against a scope stack.  Consists of a (possibly empty) list of
881/// exported scopes, along with an optional scope stack variable.
882#[repr(C)]
883#[derive(Clone, Copy, Default, Eq, PartialEq)]
884pub struct sg_partial_scope_stack {
885    /// The handle of the first element in the partial scope stack, or SG_LIST_EMPTY_HANDLE if the
886    /// list is empty, or 0 if the list is null.
887    pub cells: sg_partial_scope_stack_cell_handle,
888    pub direction: sg_deque_direction,
889    pub length: u32,
890    /// The scope stack variable representing the unknown content of a partial scope stack, or 0 if
891    /// the variable is missing.  (If so, this partial scope stack can only match a scope stack
892    /// with exactly the list of scopes in `cells`, instead of any scope stack with those scopes as
893    /// a prefix.)
894    pub variable: sg_scope_stack_variable,
895}
896
897impl From<PartialScopeStack> for sg_partial_scope_stack {
898    fn from(stack: PartialScopeStack) -> sg_partial_scope_stack {
899        unsafe { std::mem::transmute(stack) }
900    }
901}
902
903/// A handle to an element of a partial scope stack.  A zero handle represents a missing partial
904/// scope stack.  A UINT32_MAX handle represents an empty partial scope stack.
905pub type sg_partial_scope_stack_cell_handle = u32;
906
907/// An element of a partial scope stack.
908#[repr(C)]
909pub struct sg_partial_scope_stack_cell {
910    /// The exported scope at this position in the partial scope stack.
911    pub head: sg_node_handle,
912    /// The handle of the next element in the partial scope stack, or SG_LIST_EMPTY_HANDLE if this
913    /// is the last element.
914    pub tail: sg_partial_scope_stack_cell_handle,
915    /// The handle of the reversal of this partial scope stack.
916    pub reversed: sg_partial_scope_stack_cell_handle,
917}
918
919/// The array of all of the partial scope stack content in a partial path arena.
920#[repr(C)]
921pub struct sg_partial_scope_stack_cells {
922    pub cells: *const sg_partial_scope_stack_cell,
923    pub count: usize,
924}
925
926/// Returns a reference to the array of partial scope stack content in a partial path arena.  The
927/// resulting array pointer is only valid until the next call to any function that mutates the
928/// partial path arena.
929#[no_mangle]
930pub extern "C" fn sg_partial_path_arena_partial_scope_stack_cells(
931    partials: *const sg_partial_path_arena,
932) -> sg_partial_scope_stack_cells {
933    let partials = unsafe { &(*partials).inner };
934    sg_partial_scope_stack_cells {
935        cells: partials.partial_scope_stacks.as_ptr() as *const sg_partial_scope_stack_cell,
936        count: partials.partial_scope_stacks.len(),
937    }
938}
939
940/// Adds new partial scope stacks to the partial path arena.  `count` is the number of partial
941/// scope stacks you want to create.  The content of each partial scope stack comes from three
942/// arrays.  The `lengths` array must have `count` elements, and provides the number of scopes in
943/// each scope stack.  The `scopes` array contains the contents of each of these scope stacks in
944/// one contiguous array.  Its length must be the sum of all of the counts in the `lengths` array.
945/// The `variables` array must have `count` elements, and provides the optional scope stack
946/// variable for each partial scope stack.
947///
948/// You must also provide an `out` array, which must also have room for `count` elements.  We will
949/// fill this array in with the `sg_partial_scope_stack` instances for each partial scope stack
950/// that is created.
951#[no_mangle]
952pub extern "C" fn sg_partial_path_arena_add_partial_scope_stacks(
953    partials: *mut sg_partial_path_arena,
954    count: usize,
955    mut scopes: *const sg_node_handle,
956    lengths: *const usize,
957    variables: *const sg_scope_stack_variable,
958    out: *mut sg_partial_scope_stack,
959) {
960    let partials = unsafe { &mut (*partials).inner };
961    let lengths = unsafe { std::slice::from_raw_parts(lengths, count) };
962    let variables = unsafe { std::slice::from_raw_parts(variables, count) };
963    let out = unsafe { std::slice::from_raw_parts_mut(out, count) };
964    for i in 0..count {
965        let length = lengths[i];
966        let scopes_slice = unsafe { std::slice::from_raw_parts(scopes, length) };
967        let mut stack = if variables[i] == 0 {
968            PartialScopeStack::empty()
969        } else {
970            PartialScopeStack::from_variable(variables[i].try_into().unwrap())
971        };
972        for j in 0..length {
973            let node = scopes_slice[j].into();
974            stack.push_back(partials, node);
975        }
976        // We pushed the edges onto the list in reverse order.  Requesting a forwards iterator
977        // before we return ensures that it will also be available in forwards order.
978        let _ = stack.iter_scopes(partials);
979        out[i] = stack.into();
980        unsafe { scopes = scopes.add(length) };
981    }
982}
983
984//-------------------------------------------------------------------------------------------------
985// Partial edge lists
986
987/// Details about one of the edges in a partial path
988#[repr(C)]
989#[derive(Clone, Copy, Eq, PartialEq)]
990pub struct sg_partial_path_edge {
991    pub source_node_id: sg_node_id,
992    pub precedence: i32,
993}
994
995impl Into<PartialPathEdge> for sg_partial_path_edge {
996    fn into(self) -> PartialPathEdge {
997        unsafe { std::mem::transmute(self) }
998    }
999}
1000
1001/// The edges in a path keep track of precedence information so that we can correctly handle
1002/// shadowed definitions.
1003#[repr(C)]
1004#[derive(Clone, Copy, Default, Eq, PartialEq)]
1005pub struct sg_partial_path_edge_list {
1006    /// The handle of the first element in the edge list, or SG_LIST_EMPTY_HANDLE if the list is
1007    /// empty, or 0 if the list is null.
1008    pub cells: sg_partial_path_edge_list_cell_handle,
1009    pub direction: sg_deque_direction,
1010    pub length: u32,
1011}
1012
1013impl From<PartialPathEdgeList> for sg_partial_path_edge_list {
1014    fn from(edges: PartialPathEdgeList) -> sg_partial_path_edge_list {
1015        unsafe { std::mem::transmute(edges) }
1016    }
1017}
1018
1019/// A handle to an element of a partial path edge list.  A zero handle represents a missing partial
1020/// path edge list.  A UINT32_MAX handle represents an empty partial path edge list.
1021pub type sg_partial_path_edge_list_cell_handle = u32;
1022
1023/// An element of a partial path edge list.
1024#[repr(C)]
1025pub struct sg_partial_path_edge_list_cell {
1026    /// The partial path edge at this position in the partial path edge list.
1027    pub head: sg_partial_path_edge,
1028    /// The handle of the next element in the partial path edge list, or SG_LIST_EMPTY_HANDLE if
1029    /// this is the last element.
1030    pub tail: sg_partial_path_edge_list_cell_handle,
1031    /// The handle of the reversal of this list.
1032    pub reversed: sg_partial_path_edge_list_cell_handle,
1033}
1034
1035/// The array of all of the partial path edge list content in a partial path arena.
1036#[repr(C)]
1037pub struct sg_partial_path_edge_list_cells {
1038    pub cells: *const sg_partial_path_edge_list_cell,
1039    pub count: usize,
1040}
1041
1042/// Returns a reference to the array of partial path edge list content in a partial path arena.
1043/// The resulting array pointer is only valid until the next call to any function that mutates the
1044/// partial path arena.
1045#[no_mangle]
1046pub extern "C" fn sg_partial_path_arena_partial_path_edge_list_cells(
1047    partials: *const sg_partial_path_arena,
1048) -> sg_partial_path_edge_list_cells {
1049    let partials = unsafe { &(*partials).inner };
1050    sg_partial_path_edge_list_cells {
1051        cells: partials.partial_path_edges.as_ptr() as *const sg_partial_path_edge_list_cell,
1052        count: partials.partial_path_edges.len(),
1053    }
1054}
1055
1056/// Adds new partial path edge lists to the partial path arena.  `count` is the number of partial
1057/// path edge lists you want to create.  The content of each partial path edge list comes from two
1058/// arrays.  The `lengths` array must have `count` elements, and provides the number of edges in
1059/// each partial path edge list.  The `edges` array contains the contents of each of these partial
1060/// path edge lists in one contiguous array.  Its length must be the sum of all of the counts in
1061/// the `lengths` array.
1062///
1063/// You must also provide an `out` array, which must also have room for `count` elements.  We will
1064/// fill this array in with the `sg_partial_path_edge_list` instances for each partial path edge
1065/// list that is created.
1066#[no_mangle]
1067pub extern "C" fn sg_partial_path_arena_add_partial_path_edge_lists(
1068    partials: *mut sg_partial_path_arena,
1069    count: usize,
1070    mut edges: *const sg_partial_path_edge,
1071    lengths: *const usize,
1072    out: *mut sg_partial_path_edge_list,
1073) {
1074    let partials = unsafe { &mut (*partials).inner };
1075    let lengths = unsafe { std::slice::from_raw_parts(lengths, count) };
1076    let out = unsafe { std::slice::from_raw_parts_mut(out, count) };
1077    for i in 0..count {
1078        let length = lengths[i];
1079        let edges_slice = unsafe { std::slice::from_raw_parts(edges, length) };
1080        let mut list = PartialPathEdgeList::empty();
1081        for j in 0..length {
1082            let edge: PartialPathEdge = edges_slice[j].into();
1083            list.push_back(partials, edge);
1084        }
1085        // We pushed the edges onto the list in reverse order.  Requesting a forwards iterator
1086        // before we return ensures that it will also be available in forwards order.
1087        let _ = list.iter(partials);
1088        out[i] = list.into();
1089        unsafe { edges = edges.add(length) };
1090    }
1091}
1092
1093//-------------------------------------------------------------------------------------------------
1094// Partial paths
1095
1096/// A portion of a name-binding path.
1097///
1098/// Partial paths can be computed _incrementally_, in which case all of the edges in the partial
1099/// path belong to a single file.  At query time, we can efficiently concatenate partial paths to
1100/// yield a name-binding path.
1101///
1102/// Paths describe the contents of the symbol stack and scope stack at the end of the path.
1103/// Partial paths, on the other hand, have _preconditions_ and _postconditions_ for each stack.
1104/// The precondition describes what the stack must look like for us to be able to concatenate this
1105/// partial path onto the end of a path.  The postcondition describes what the resulting stack
1106/// looks like after doing so.
1107///
1108/// The preconditions can contain _scope stack variables_, which describe parts of the scope stack
1109/// (or parts of a scope symbol's attached scope list) whose contents we don't care about.  The
1110/// postconditions can _also_ refer to those variables, and describe how those variable parts of
1111/// the input scope stacks are carried through unmodified into the resulting scope stack.
1112#[repr(C)]
1113#[derive(Clone, Copy)]
1114pub struct sg_partial_path {
1115    pub start_node: sg_node_handle,
1116    pub end_node: sg_node_handle,
1117    pub symbol_stack_precondition: sg_partial_symbol_stack,
1118    pub symbol_stack_postcondition: sg_partial_symbol_stack,
1119    pub scope_stack_precondition: sg_partial_scope_stack,
1120    pub scope_stack_postcondition: sg_partial_scope_stack,
1121    pub edges: sg_partial_path_edge_list,
1122}
1123
1124impl Into<PartialPath> for sg_partial_path {
1125    fn into(self) -> PartialPath {
1126        unsafe { std::mem::transmute(self) }
1127    }
1128}
1129
1130/// A list of paths found by the path-finding algorithm.
1131#[derive(Default)]
1132pub struct sg_partial_path_list {
1133    partial_paths: Vec<PartialPath>,
1134}
1135
1136/// Creates a new, empty sg_partial_path_list.
1137#[no_mangle]
1138pub extern "C" fn sg_partial_path_list_new() -> *mut sg_partial_path_list {
1139    Box::into_raw(Box::new(sg_partial_path_list::default()))
1140}
1141
1142#[no_mangle]
1143pub extern "C" fn sg_partial_path_list_free(partial_path_list: *mut sg_partial_path_list) {
1144    drop(unsafe { Box::from_raw(partial_path_list) });
1145}
1146
1147#[no_mangle]
1148pub extern "C" fn sg_partial_path_list_count(
1149    partial_path_list: *const sg_partial_path_list,
1150) -> usize {
1151    let partial_path_list = unsafe { &*partial_path_list };
1152    partial_path_list.partial_paths.len()
1153}
1154
1155#[no_mangle]
1156pub extern "C" fn sg_partial_path_list_paths(
1157    partial_path_list: *const sg_partial_path_list,
1158) -> *const sg_partial_path {
1159    let partial_path_list = unsafe { &*partial_path_list };
1160    partial_path_list.partial_paths.as_ptr() as *const _
1161}
1162
1163/// Finds all partial paths in a file that are _productive_ and _as complete as possible_, placing
1164/// the result into the `partial_path_list` output parameter.  You must free the path list when you
1165/// are done with it by calling `sg_partial_path_list_done`.
1166///
1167/// This function will not return until all reachable paths have been processed, so `graph` must
1168/// already contain a complete stack graph.  If you have a very large stack graph stored in some
1169/// other storage system, and want more control over lazily loading only the necessary pieces, then
1170/// you should use sg_forward_path_stitcher.
1171#[no_mangle]
1172pub extern "C" fn sg_partial_path_arena_find_partial_paths_in_file(
1173    graph: *const sg_stack_graph,
1174    partials: *mut sg_partial_path_arena,
1175    file: sg_file_handle,
1176    partial_path_list: *mut sg_partial_path_list,
1177    stitcher_config: *const sg_stitcher_config,
1178    cancellation_flag: *const usize,
1179) -> sg_result {
1180    let graph = unsafe { &(*graph).inner };
1181    let partials = unsafe { &mut (*partials).inner };
1182    let file = file.into();
1183    let partial_path_list = unsafe { &mut *partial_path_list };
1184    let stitcher_config = unsafe { *stitcher_config };
1185    let cancellation_flag: Option<&AtomicUsize> =
1186        unsafe { std::mem::transmute(cancellation_flag.as_ref()) };
1187    ForwardPartialPathStitcher::find_minimal_partial_path_set_in_file(
1188        graph,
1189        partials,
1190        file,
1191        stitcher_config.into(),
1192        &AtomicUsizeCancellationFlag(cancellation_flag),
1193        |_graph, partials, path| {
1194            let mut path = path.clone();
1195            path.ensure_both_directions(partials);
1196            partial_path_list.partial_paths.push(path);
1197        },
1198    )
1199    .into()
1200}
1201
1202/// Finds all complete paths reachable from a set of starting nodes, placing the result into the
1203/// `path_list` output parameter.  You must free the path list when you are done with it by calling
1204/// `sg_path_list_done`.
1205///
1206/// This function will not return until all reachable paths have been processed, so `graph` must
1207/// already contain a complete stack graph.  If you have a very large stack graph stored in some
1208/// other storage system, and want more control over lazily loading only the necessary pieces, then
1209/// you should use sg_forward_path_stitcher.
1210#[no_mangle]
1211pub extern "C" fn sg_partial_path_arena_find_all_complete_paths(
1212    graph: *const sg_stack_graph,
1213    partials: *mut sg_partial_path_arena,
1214    starting_node_count: usize,
1215    starting_nodes: *const sg_node_handle,
1216    path_list: *mut sg_partial_path_list,
1217    stitcher_config: *const sg_stitcher_config,
1218    cancellation_flag: *const usize,
1219) -> sg_result {
1220    let graph = unsafe { &(*graph).inner };
1221    let partials = unsafe { &mut (*partials).inner };
1222    let starting_nodes = unsafe { std::slice::from_raw_parts(starting_nodes, starting_node_count) };
1223    let stitcher_config = unsafe { *stitcher_config };
1224    let path_list = unsafe { &mut *path_list };
1225    let cancellation_flag: Option<&AtomicUsize> =
1226        unsafe { std::mem::transmute(cancellation_flag.as_ref()) };
1227    ForwardPartialPathStitcher::find_all_complete_partial_paths(
1228        &mut GraphEdgeCandidates::new(graph, partials, None),
1229        starting_nodes.iter().copied().map(sg_node_handle::into),
1230        stitcher_config.into(),
1231        &AtomicUsizeCancellationFlag(cancellation_flag),
1232        |graph, _partials, path| {
1233            if path.is_complete(graph) {
1234                path_list.partial_paths.push(path.clone());
1235            }
1236        },
1237    )
1238    .into()
1239}
1240
1241/// A handle to a partial path in a partial path database.  A zero handle represents a missing
1242/// partial path.
1243pub type sg_partial_path_handle = u32;
1244
1245/// An array of all of the partial paths in a partial path database.  Partial path handles are
1246/// indices into this array.  There will never be a valid partial path at index 0; a handle with
1247/// the value 0 represents a missing partial path.
1248#[repr(C)]
1249pub struct sg_partial_paths {
1250    pub paths: *const sg_partial_path,
1251    pub count: usize,
1252}
1253
1254/// Returns a reference to the array of partial path data in this partial path database.  The
1255/// resulting array pointer is only valid until the next call to any function that mutates the
1256/// partial path database.
1257#[no_mangle]
1258pub extern "C" fn sg_partial_path_database_partial_paths(
1259    db: *const sg_partial_path_database,
1260) -> sg_partial_paths {
1261    let db = unsafe { &(*db).inner };
1262    sg_partial_paths {
1263        paths: db.partial_paths.as_ptr() as *const sg_partial_path,
1264        count: db.partial_paths.len(),
1265    }
1266}
1267
1268/// Adds new partial paths to the partial path database.  `paths` is the array of partial paths
1269/// that you want to add; `count` is the number of them.
1270///
1271/// We copy the partial path content into the partial path database.  The array you pass in does
1272/// not need to outlive the call to this function.
1273///
1274/// You should take care not to add a partial path to the database multiple times.  This won't
1275/// cause an _error_, in that nothing will break, but it will probably cause you to get duplicate
1276/// paths from the path-stitching algorithm.
1277///
1278/// You must also provide an `out` array, which must also have room for `count` elements.  We will
1279/// fill this array in with the `sg_partial_path_edge_list` instances for each partial path edge
1280/// list that is created.
1281#[no_mangle]
1282pub extern "C" fn sg_partial_path_database_add_partial_paths(
1283    graph: *const sg_stack_graph,
1284    partials: *mut sg_partial_path_arena,
1285    db: *mut sg_partial_path_database,
1286    count: usize,
1287    paths: *const sg_partial_path,
1288    out: *mut sg_partial_path_handle,
1289) {
1290    let graph = unsafe { &(*graph).inner };
1291    let partials = unsafe { &mut (*partials).inner };
1292    let db = unsafe { &mut (*db).inner };
1293    let paths = unsafe { std::slice::from_raw_parts(paths, count) };
1294    let out = unsafe { std::slice::from_raw_parts_mut(out as *mut Handle<PartialPath>, count) };
1295    for i in 0..count {
1296        out[i] = db.add_partial_path(graph, partials, paths[i].into());
1297    }
1298}
1299
1300//-------------------------------------------------------------------------------------------------
1301// Local nodes
1302
1303/// Encodes a set of node handles.
1304///
1305/// The elements are encoded in a bit set.  Use the traditional mask and shift pattern to determine
1306/// if a particular handle is in the set:
1307///
1308/// ``` c
1309/// size_t element_index = handle / 32;
1310/// size_t bit_index = handle % 32;
1311/// size_t bit_mask = 1 << bit_index;
1312/// bool bit_is_set =
1313///     element_index < set.length &&
1314///     (set.elements[element_index] & bit_mask) != 0;
1315/// ```
1316#[repr(C)]
1317pub struct sg_node_handle_set {
1318    pub elements: *const u32,
1319    /// Note that this is the number of uint32_t's in `elements`, NOT the number of bits in the set.
1320    pub length: usize,
1321}
1322
1323/// Determines which nodes in the stack graph are “local”, taking into account the partial paths in
1324/// this database.  The result is valid until the next call to this function, or until the database
1325/// is freed.
1326///
1327/// A local node has no partial path that connects it to the root node in either direction. That
1328/// means that it cannot participate in any paths that leave the file.
1329///
1330/// This method is meant to be used at index time, to calculate the set of nodes that are local
1331/// after having just calculated the set of partial paths for the file.
1332#[no_mangle]
1333pub extern "C" fn sg_partial_path_database_find_local_nodes(db: *mut sg_partial_path_database) {
1334    let db = unsafe { &mut (*db).inner };
1335    db.find_local_nodes();
1336}
1337
1338/// Marks that a list of stack graph nodes are local.
1339///
1340/// This method is meant to be used at query time.  You will have precalculated the set of local
1341/// nodes for a file at index time; at query time, you will load this information from your storage
1342/// layer and use this method to update our internal view of which nodes are local.
1343#[no_mangle]
1344pub extern "C" fn sg_partial_path_database_mark_local_nodes(
1345    db: *mut sg_partial_path_database,
1346    count: usize,
1347    nodes: *const sg_node_handle,
1348) {
1349    let db = unsafe { &mut (*db).inner };
1350    let nodes = unsafe { std::slice::from_raw_parts(nodes, count) };
1351    for node in nodes {
1352        db.mark_local_node(node.clone().into());
1353    }
1354}
1355
1356/// Returns a reference to the set of stack graph nodes that are local, according to this database
1357/// of partial paths.  The resulting set is only valid until the next call to any function that
1358/// mutates the partial path database.
1359#[no_mangle]
1360pub extern "C" fn sg_partial_path_database_local_nodes(
1361    db: *const sg_partial_path_database,
1362) -> sg_node_handle_set {
1363    let db = unsafe { &(*db).inner };
1364    sg_node_handle_set {
1365        elements: db.local_nodes.as_ptr(),
1366        length: db.local_nodes.len(),
1367    }
1368}
1369
1370//-------------------------------------------------------------------------------------------------
1371// Forward partial path stitching
1372
1373/// Implements a phased forward partial path stitching algorithm.
1374///
1375/// Our overall goal is to start with a set of _seed_ partial paths, and to repeatedly extend each
1376/// partial path by concatenating another, compatible partial path onto the end of it.  (If there
1377/// are multiple compatible partial paths, we concatenate each of them separately, resulting in
1378/// more than one extension for the current path.)
1379///
1380/// We perform this processing in _phases_.  At the start of each phase, we have a _current set_ of
1381/// partial paths that need to be processed.  As we extend those partial paths, we add the
1382/// extensions to the set of partial paths to process in the _next_ phase.  Phases are processed
1383/// one at a time, each time you invoke `sg_forward_partial_path_stitcher_process_next_phase`.
1384///
1385/// After each phase has completed, the `previous_phase_paths` and `previous_phase_paths_length`
1386/// fields give you all of the partial paths that were discovered during that phase.  That gives
1387/// you a chance to add to the `sg_partial_path_database` all of the other partial paths that we
1388/// might need to extend those partial paths with before invoking the next phase.
1389#[repr(C)]
1390pub struct sg_forward_partial_path_stitcher {
1391    /// The new candidate partial paths that were discovered in the most recent phase.
1392    pub previous_phase_partial_paths: *const sg_partial_path,
1393    /// The number of new candidate partial paths that were discovered in the most recent phase.
1394    /// If this is 0, then the partial path stitching algorithm is complete.
1395    pub previous_phase_partial_paths_length: usize,
1396    /// Whether the stitching algorithm is complete.  You should keep calling
1397    /// `sg_forward_partial_path_stitcher_process_next_phase` until this field is true.
1398    pub is_complete: bool,
1399}
1400
1401// Configuration for partial path stitchers.
1402#[repr(C)]
1403#[derive(Clone, Copy)]
1404pub struct sg_stitcher_config {
1405    /// Enables similar path detection during stiching.
1406    pub detect_similar_paths: bool,
1407}
1408
1409impl Into<StitcherConfig> for sg_stitcher_config {
1410    fn into(self) -> StitcherConfig {
1411        StitcherConfig::default().with_detect_similar_paths(self.detect_similar_paths)
1412    }
1413}
1414
1415// This is the Rust equivalent of a common C trick, where you have two versions of a struct — a
1416// publicly visible one and a private one containing internal implementation details.  In our case,
1417// `sg_forward_partial_path_stitcher` is the public struct, and
1418// `InternalForwardPartialPathStitcher` is the internal one.  The main requirement is that the
1419// private struct must start with a copy of all of the fields in the public struct — ensuring that
1420// those fields occur at the same offset in both.  The private struct can contain additional
1421// (private) fields, but they must appear _after_ all of the publicly visible fields.
1422//
1423// In our case, we do this because we don't want to expose the existence or details of the
1424// ForwardPartialPathStitcher type via the C API.
1425#[repr(C)]
1426struct InternalForwardPartialPathStitcher {
1427    previous_phase_partial_paths: *const PartialPath,
1428    previous_phase_partial_paths_length: usize,
1429    is_complete: bool,
1430    stitcher: ForwardPartialPathStitcher<Handle<PartialPath>>,
1431}
1432
1433impl InternalForwardPartialPathStitcher {
1434    fn new(
1435        stitcher: ForwardPartialPathStitcher<Handle<PartialPath>>,
1436        partials: &mut PartialPaths,
1437    ) -> InternalForwardPartialPathStitcher {
1438        let mut this = InternalForwardPartialPathStitcher {
1439            previous_phase_partial_paths: std::ptr::null(),
1440            previous_phase_partial_paths_length: 0,
1441            is_complete: false,
1442            stitcher,
1443        };
1444        this.update_previous_phase_partial_paths(partials);
1445        this
1446    }
1447
1448    fn update_previous_phase_partial_paths(&mut self, partials: &mut PartialPaths) {
1449        for path in self.stitcher.previous_phase_partial_paths_slice_mut() {
1450            path.ensure_both_directions(partials);
1451        }
1452        let slice = self.stitcher.previous_phase_partial_paths_slice();
1453        self.previous_phase_partial_paths = slice.as_ptr();
1454        self.previous_phase_partial_paths_length = slice.len();
1455        self.is_complete = self.stitcher.is_complete();
1456    }
1457}
1458
1459/// Creates a new forward partial path stitcher that is "seeded" with a set of starting stack graph
1460/// nodes. The path stitcher will be set up to find complete paths only.
1461#[no_mangle]
1462pub extern "C" fn sg_forward_partial_path_stitcher_from_nodes(
1463    graph: *const sg_stack_graph,
1464    partials: *mut sg_partial_path_arena,
1465    count: usize,
1466    starting_nodes: *const sg_node_handle,
1467) -> *mut sg_forward_partial_path_stitcher {
1468    let graph = unsafe { &(*graph).inner };
1469    let partials = unsafe { &mut (*partials).inner };
1470    let starting_nodes = unsafe { std::slice::from_raw_parts(starting_nodes, count) };
1471    let initial_paths = starting_nodes
1472        .iter()
1473        .copied()
1474        .map(sg_node_handle::into)
1475        .map(|n| {
1476            let mut p = PartialPath::from_node(graph, partials, n);
1477            p.eliminate_precondition_stack_variables(partials);
1478            p
1479        })
1480        .collect::<Vec<_>>();
1481    let stitcher = ForwardPartialPathStitcher::from_partial_paths(graph, partials, initial_paths);
1482    Box::into_raw(Box::new(InternalForwardPartialPathStitcher::new(
1483        stitcher, partials,
1484    ))) as *mut _
1485}
1486
1487/// Creates a new forward partial path stitcher that is "seeded" with a set of initial partial
1488/// paths.
1489#[no_mangle]
1490pub extern "C" fn sg_forward_partial_path_stitcher_from_partial_paths(
1491    graph: *const sg_stack_graph,
1492    partials: *mut sg_partial_path_arena,
1493    count: usize,
1494    initial_partial_paths: *const sg_partial_path,
1495) -> *mut sg_forward_partial_path_stitcher {
1496    let graph = unsafe { &(*graph).inner };
1497    let partials = unsafe { &mut (*partials).inner };
1498    let initial_partial_paths =
1499        unsafe { std::slice::from_raw_parts(initial_partial_paths as *const PartialPath, count) };
1500    let stitcher = ForwardPartialPathStitcher::from_partial_paths(
1501        graph,
1502        partials,
1503        initial_partial_paths.to_vec(),
1504    );
1505    Box::into_raw(Box::new(InternalForwardPartialPathStitcher::new(
1506        stitcher, partials,
1507    ))) as *mut _
1508}
1509
1510/// Sets whether similar path detection should be enabled during path stitching. Paths are similar
1511/// if start and end node, and pre- and postconditions are the same. The presence of similar paths
1512/// can lead to exponential blow up during path stitching. Similar path detection is disabled by
1513/// default because of the accociated preformance cost.
1514#[no_mangle]
1515pub extern "C" fn sg_forward_partial_path_stitcher_set_similar_path_detection(
1516    stitcher: *mut sg_forward_partial_path_stitcher,
1517    detect_similar_paths: bool,
1518) {
1519    let stitcher = unsafe { &mut *(stitcher as *mut InternalForwardPartialPathStitcher) };
1520    stitcher
1521        .stitcher
1522        .set_similar_path_detection(detect_similar_paths);
1523}
1524
1525/// Sets the maximum amount of work that can be performed during each phase of the algorithm. By
1526/// bounding our work this way, you can ensure that it's not possible for our CPU-bound algorithm
1527/// to starve any worker threads or processes that you might be using.  If you don't call this
1528/// method, then we allow ourselves to process all of the extensions of all of the paths found in
1529/// the previous phase, with no additional bound.
1530#[no_mangle]
1531pub extern "C" fn sg_forward_partial_path_stitcher_set_max_work_per_phase(
1532    stitcher: *mut sg_forward_partial_path_stitcher,
1533    max_work: usize,
1534) {
1535    let stitcher = unsafe { &mut *(stitcher as *mut InternalForwardPartialPathStitcher) };
1536    stitcher.stitcher.set_max_work_per_phase(max_work);
1537}
1538
1539/// Runs the next phase of the algorithm.  We will have built up a set of incomplete partial paths
1540/// during the _previous_ phase.  Before calling this function, you must ensure that `db` contains
1541/// all of the possible partial paths that we might want to extend any of those candidate partial
1542/// paths with.
1543///
1544/// After this method returns, you can retrieve a list of the (possibly incomplete) partial paths
1545/// that were encountered during this phase via the `previous_phase_partial_paths` and
1546/// `previous_phase_partial_paths_length` fields.
1547#[no_mangle]
1548pub extern "C" fn sg_forward_partial_path_stitcher_process_next_phase(
1549    graph: *const sg_stack_graph,
1550    partials: *mut sg_partial_path_arena,
1551    db: *mut sg_partial_path_database,
1552    stitcher: *mut sg_forward_partial_path_stitcher,
1553) {
1554    let graph = unsafe { &(*graph).inner };
1555    let partials = unsafe { &mut (*partials).inner };
1556    let db = unsafe { &mut (*db).inner };
1557    let stitcher = unsafe { &mut *(stitcher as *mut InternalForwardPartialPathStitcher) };
1558    stitcher.stitcher.process_next_phase(
1559        &mut DatabaseCandidates::new(graph, partials, db),
1560        |_, _, _| true,
1561    );
1562    stitcher.update_previous_phase_partial_paths(partials);
1563}
1564
1565/// Frees a forward path stitcher.
1566#[no_mangle]
1567pub extern "C" fn sg_forward_partial_path_stitcher_free(
1568    stitcher: *mut sg_forward_partial_path_stitcher,
1569) {
1570    drop(unsafe { Box::from_raw(stitcher as *mut InternalForwardPartialPathStitcher) });
1571}
1572
1573//-------------------------------------------------------------------------------------------------
1574// Cancellation
1575
1576struct AtomicUsizeCancellationFlag<'a>(Option<&'a AtomicUsize>);
1577impl CancellationFlag for AtomicUsizeCancellationFlag<'_> {
1578    fn check(&self, at: &'static str) -> Result<(), crate::CancellationError> {
1579        self.0
1580            .map(|flag| {
1581                if flag.fetch_and(0b0, std::sync::atomic::Ordering::Relaxed) != 0 {
1582                    Err(CancellationError(at))
1583                } else {
1584                    Ok(())
1585                }
1586            })
1587            .unwrap_or(Ok(()))
1588    }
1589}
1590
1591/// Describes the result of a computation
1592#[repr(C)]
1593#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1594pub enum sg_result {
1595    SG_RESULT_SUCCESS,
1596    SG_RESULT_CANCELLED,
1597}
1598
1599impl<T> From<Result<T, CancellationError>> for sg_result {
1600    fn from(result: Result<T, CancellationError>) -> Self {
1601        match result {
1602            Ok(_) => Self::SG_RESULT_SUCCESS,
1603            Err(_) => Self::SG_RESULT_CANCELLED,
1604        }
1605    }
1606}