Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate to tree-sitter #33

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ target
/invalid.rs
/invalid
.direnv/
g
g
perf.data*
*.svg
42 changes: 42 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 10 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[workspace]
members = ["./", "./testsuite"]
exclude = ["test-cases/*", "full-tests/*"]
exclude = ["full-tests/*"]

[package]
name = "cargo-minimize"
Expand All @@ -16,8 +16,12 @@ license = "MIT OR Apache-2.0"
[profile.release]
lto = "thin"

[profile.dev]
opt-level = 1
[profile.dev.package.proc-macro2]
opt-level = 3
[profile.dev.package.syn]
opt-level = 3
[profile.dev.package.genemichaels]
opt-level = 3

[dependencies]
anyhow = "1.0.65"
Expand All @@ -36,4 +40,7 @@ tempfile = "3.3.0"
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
tracing-tree = "0.2.2"
tree-sitter = "0.20.10"
tree-sitter-edit = "0.3.0"
tree-sitter-rust = "0.20.4"
walkdir = "2.3.2"
4 changes: 2 additions & 2 deletions src/dylib_flag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ impl FromStr for RustFunction {
fn wrap_func_body(func: &str) -> Result<String> {
let closure = syn::parse_str::<syn::ExprClosure>(func).context("invalid rust syntax")?;

let syn_file = syn::parse_quote! {
let file = quote::quote! {
#[repr(C)]
pub struct __RawOutput {
out_ptr: *const u8,
Expand Down Expand Up @@ -80,7 +80,7 @@ fn wrap_func_body(func: &str) -> Result<String> {
}
};

crate::formatting::format(syn_file)
Ok(file.to_string())
}

impl RustFunction {
Expand Down
12 changes: 0 additions & 12 deletions src/formatting.rs

This file was deleted.

2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::{

mod build;
mod dylib_flag;
mod formatting;
mod tree_sitter;
mod passes;
mod processor;

Expand Down
13 changes: 12 additions & 1 deletion src/passes/field_deleter.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use quote::ToTokens;
use syn::{visit_mut::VisitMut, Fields};

use crate::processor::{tracking, Pass, PassController, ProcessState, SourceFile};
use crate::processor::{tracking, Pass, PassController, ProcessState, SourceFile, MinimizeEdit};

struct Visitor<'a> {
current_path: Vec<String>,
Expand Down Expand Up @@ -75,6 +75,17 @@ impl Pass for FieldDeleter {
visitor.process_state
}

fn edits_for_node(&mut self, node: tree_sitter::Node, _edits: &mut Vec<MinimizeEdit>) {
match node.kind() {
// Braced structs
"field_declaration_list" => {}
// Tuple structs
"ordered_field_declaration_list" => {}
_ => {}
}

}

fn name(&self) -> &'static str {
"field-deleter"
}
Expand Down
52 changes: 9 additions & 43 deletions src/passes/privatize.rs
Original file line number Diff line number Diff line change
@@ -1,52 +1,18 @@
use quote::ToTokens;
use syn::{parse_quote, visit_mut::VisitMut, Visibility};
use tree_sitter_edit::NodeId;

use crate::processor::{tracking, Pass, PassController, ProcessState, SourceFile};

struct Visitor<'a> {
pub_crate: Visibility,
process_state: ProcessState,
current_path: Vec<String>,
checker: &'a mut PassController,
}

impl<'a> Visitor<'a> {
fn new(checker: &'a mut PassController) -> Self {
Self {
process_state: ProcessState::NoChange,
pub_crate: parse_quote! { pub(crate) },
current_path: Vec::new(),
checker,
}
}
}

impl VisitMut for Visitor<'_> {
fn visit_visibility_mut(&mut self, vis: &mut Visibility) {
if let Visibility::Public(_) = vis {
if self.checker.can_process(&self.current_path) {
self.process_state = ProcessState::Changed;
*vis = self.pub_crate.clone();
}
}
}

tracking!();
}
use crate::processor::{MinimizeEdit, MinimizeEditKind, Pass};

#[derive(Default)]
pub struct Privatize {}

impl Pass for Privatize {
fn process_file(
&mut self,
krate: &mut syn::File,
_: &SourceFile,
checker: &mut PassController,
) -> ProcessState {
let mut visitor = Visitor::new(checker);
visitor.visit_file_mut(krate);
visitor.process_state
fn edits_for_node(&mut self, node: tree_sitter::Node, edits: &mut Vec<MinimizeEdit>) {
if node.kind() == "visibility_modifier" {
edits.push(MinimizeEdit {
node_id: NodeId::new(&node),
kind: MinimizeEditKind::DeleteNode,
});
}
}

fn name(&self) -> &'static str {
Expand Down
70 changes: 23 additions & 47 deletions src/processor/checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use crate::Options;

use self::worklist::Worklist;

use super::MinimizeEdit;

#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
struct AstPath(Vec<String>);

Expand Down Expand Up @@ -31,22 +33,18 @@ pub(crate) struct PassController {
/// The current state of the bisection.
#[derive(Debug)]
enum PassControllerState {
/// Initially, we have a bunch of candidates (minimization sites) that could be applied.
/// We collect them in the initial application of the pass where we try to apply all candiates.
/// If that works, great! We're done. But often it doesn't and we enter the next stage.
InitialCollection { candidates: Vec<AstPath> },
/// After applying all candidates fails, we know that we have a few bad candidates.
/// Now our job is to apply all the good candidates as efficiently as possible.
Bisecting {
/// These candidates could be applied successfully while still reproducing the issue.
/// They are now on disk and will be included in all subsequent runs.
/// This is only used for debugging, we could also just throw them away.
committed: BTreeSet<AstPath>,
committed: BTreeSet<MinimizeEdit>,
/// These candidates failed in isolation and are therefore bad.
/// This is only used for debugging, we could also just throw them away.
failed: BTreeSet<AstPath>,
failed: BTreeSet<MinimizeEdit>,
/// The set of candidates that we want to apply in this iteration.
current: BTreeSet<AstPath>,
current: Vec<MinimizeEdit>,
/// The list of `current`s that we want to try in the future.
worklist: Worklist,
},
Expand All @@ -55,44 +53,44 @@ enum PassControllerState {
}

mod worklist {
use super::AstPath;
use crate::processor::MinimizeEdit;

/// A worklist that ensures that the inner list is never empty.
#[derive(Debug)]
pub(super) struct Worklist(Vec<Vec<AstPath>>);
pub(super) struct Worklist(Vec<Vec<MinimizeEdit>>);

impl Worklist {
pub(super) fn new() -> Self {
Self(Vec::new())
}

pub(super) fn push(&mut self, next: Vec<AstPath>) {
pub(super) fn push(&mut self, next: Vec<MinimizeEdit>) {
if !next.is_empty() {
self.0.push(next);
}
}

pub(super) fn pop(&mut self) -> Option<Vec<AstPath>> {
pub(super) fn pop(&mut self) -> Option<Vec<MinimizeEdit>> {
self.0.pop()
}
}
}

impl PassController {
pub fn new(options: Options) -> Self {
pub fn new(options: Options, edits: Vec<MinimizeEdit>) -> Self {
Self {
state: PassControllerState::InitialCollection {
candidates: Vec::new(),
state: PassControllerState::Bisecting {
committed: BTreeSet::new(),
failed: BTreeSet::new(),
current: edits,
worklist: Worklist::new(),
},
options,
}
}

pub fn reproduces(&mut self) {
match &mut self.state {
PassControllerState::InitialCollection { .. } => {
self.state = PassControllerState::Success;
}
PassControllerState::Bisecting {
committed,
failed: _,
Expand All @@ -110,20 +108,6 @@ impl PassController {
/// The changes did not reproduce the regression. Bisect further.
pub fn does_not_reproduce(&mut self) {
match &mut self.state {
PassControllerState::InitialCollection { candidates } => {
// Applying them all was too much, let's bisect!
let (current, first_worklist_item) = split_owned(mem::take(candidates));

let mut worklist = Worklist::new();
worklist.push(first_worklist_item);

self.state = PassControllerState::Bisecting {
committed: BTreeSet::new(),
failed: BTreeSet::new(),
current,
worklist,
};
}
PassControllerState::Bisecting {
committed,
failed,
Expand Down Expand Up @@ -158,37 +142,29 @@ impl PassController {
/// The pass did not apply any changes. We're done.
pub fn no_change(&mut self) {
match &self.state {
PassControllerState::InitialCollection { candidates } => {
assert!(
candidates.is_empty(),
"No change but received candidates. The responsible pass does not seem to track the ProcessState correctly: {candidates:?}"
);
self.state = PassControllerState::Success;
}
PassControllerState::Bisecting { current, .. } => {
unreachable!("Pass said it didn't change anything in the bisection phase, nils forgot what this means: {current:?}");
assert!(current.is_empty(), "there are edits available and yet nothing changed, that's nonsense, there's a bug somewhere (i dont know where)");
self.state = PassControllerState::Success;
}
PassControllerState::Success { .. } => {}
}
}

pub fn is_finished(&mut self) -> bool {
match &mut self.state {
PassControllerState::InitialCollection { .. } => false,
PassControllerState::Bisecting { .. } => false,
PassControllerState::Success { .. } => true,
}
}

pub fn can_process(&mut self, _: &[String]) -> bool {
false
}

/// Checks whether a pass may apply the changes for a minimization site.
pub fn can_process(&mut self, path: &[String]) -> bool {
pub fn current_work_items(&mut self) -> &[MinimizeEdit] {
match &mut self.state {
PassControllerState::InitialCollection { candidates } => {
// For the initial collection, we collect the candidate and apply them all.
candidates.push(AstPath(path.to_owned()));
true
}
PassControllerState::Bisecting { current, .. } => current.contains(path),
PassControllerState::Bisecting { current, .. } => current,
PassControllerState::Success { .. } => {
unreachable!("Processed further after success");
}
Expand Down
Loading
Loading