From 9f8ff5eb12e1ac572af407284f105783179c4187 Mon Sep 17 00:00:00 2001 From: Juan C Rodriguez Date: Wed, 14 Jul 2021 09:28:16 -0300 Subject: [PATCH] Merge Rahul's changes (#173) * Create optimization_ideas.Rmd (#156) Creates optimization_ideas vignette * DCE: examples and Idea section from GCI-2019 DCE: examples and Idea section from GCI-2019 * A PR combining the PRs of all Google Code-in students related to loop-invariate optimizer (#163) thanks! * GCI PR for constant folding optimizer (#164) Co-authored-by: Stephanie <20eristoffs@asij.ac.jp> Co-authored-by: Umang Majumder <54509868+ZeroDawn0D@users.noreply.github.com> Co-authored-by: udaydatar7 <54199456+udaydatar7@users.noreply.github.com> Co-authored-by: Eric Liu Co-authored-by: Pratish Co-authored-by: Rahul Saxena * GCI PRs for opt-common-subexpr (#165) thanks! Co-authored-by: Adrian Serapio Co-authored-by: azamlynny Co-authored-by: Umang Majumder <54509868+ZeroDawn0D@users.noreply.github.com> Co-authored-by: Jack-horwell <36800721+Jack-horwell@users.noreply.github.com> Co-authored-by: Pratish Co-authored-by: udaydatar7 <54199456+udaydatar7@users.noreply.github.com> Co-authored-by: Juan Cruz Rodriguez Co-authored-by: Rahul Saxena * All GCI PRs for constant propagation optimizer (#166) thanks! Co-authored-by: Pratish Co-authored-by: udaydatar7 <54199456+udaydatar7@users.noreply.github.com> Co-authored-by: Umang Majumder <54509868+ZeroDawn0D@users.noreply.github.com> Co-authored-by: Juan Cruz Rodriguez Co-authored-by: Rahul Saxena * All GCI PRs for dead expression optimizer. (#167) thanks! Co-authored-by: Adrian Serapio Co-authored-by: Umang Majumder <54509868+ZeroDawn0D@users.noreply.github.com> Co-authored-by: Pratish Co-authored-by: Eric Liu Co-authored-by: Rahul Saxena * All GCI PRs for Dead Store Optimizer (#168) thanks! Co-authored-by: Umang Majumder <54509868+ZeroDawn0D@users.noreply.github.com> Co-authored-by: Eric Liu Co-authored-by: Pratish Co-authored-by: Adrian Serapio Co-authored-by: Rishi R <56316487+Rishi0812@users.noreply.github.com> Co-authored-by: Rahul Saxena Co-authored-by: Juan Cruz Rodriguez * Conditional Thread Optimizer PR (#162) * Explained Dead Code Elimination * Update opt-dead-code.R * Update opt-dead-code.R * Create opt-dead-code-example.R * Update opt-dead-code.R * Added examples Examples of unoptimized and optimized code to explain dead code elimination * Added #' to example * Non-technical description of dead code elimination * Update to definition in ##Idea GCI user ID Rishi0812 * Added example of Dead Code Elimination * Added Idea Section to Dead Code Elimination Vignette * Updating NEWS.md * Changing \donttest for \dontrun, it seems 4.0.0 will check donttest examples. * Rebuilding site. * Removing renv. * Removing a separate example file for DCE This was introduced when I merged a PR from a student's fork of the `rco`. * Finalizing the Idea section of the DCE * Selected final examples for DCE * Final example selected * Resolved spacing issue * Resolved Spacing Issue - 2 * Resolved Spacing Issue - 3 * Improved the Idea section * Fixed the performance typo * Revert "Fixed the performance typo" This reverts commit c791bef18894110192fd05ba10e2c4c832d45ea7. * Update opt-dead-code.Rmd typo fix * Added a few functions * Added a few functions * Added logic for duplicate expr * Meh * Added merge logic * The entire process of merging implemented. Phhew!! * Ran into the else parse bug * First iteration complete * Final commit for cond_thread * Meh * Resolved duplicate vignette * Solved another issue in vignette build * I'm sleepy now, this is last * Missed a comma :( * I dunno what's happening anymore * Fixed the >= function logic * Last ditch * Ok I'm officially crying now T_T * Now no test would fail * Seriously, this is the last time * Bhak * erfg * Update parse.R * Update opt-constant-folding.R * Update opt-dead-code.R * Dude c'mmon * Update optimize-files.R * Update opt-dead-code.R * plixx * Added more tests to increase coverage * Another test for extra coverage * Implemented suggested changes * Some missing edits * Improved Tests * Fixed boolean condition * Improved indentations * documented * Workaround for R CMD check failed * Version bump & code styling * Examples edit * Fixed the indentation bug * Code Styiling * Provided another example in vignette * Improving vignette * Styling test cases * Fixed unwanted changes due to `;` * Ran all checks, works fine Co-authored-by: Pratish Co-authored-by: Ian Chang Co-authored-by: Jack-horwell <36800721+Jack-horwell@users.noreply.github.com> Co-authored-by: Rishi R <56316487+Rishi0812@users.noreply.github.com> Co-authored-by: Adrian Serapio Co-authored-by: Juan Cruz Rodriguez Co-authored-by: Rahul Saxena * Adding the Memory Allocation Optimizer (#169) * Initial Commit * Second commit - firm logic in place * First iteration of a working prototype * Just another test text * Tests and Vignettes for mem-alloc opt * Final edits in diff files for the new optimizer * Removed unnecessary files * Reverted changes in man/opt_dead * Reverted changes in man/opt_dead * Reverted changes in man/const_prop * Reverted changes in man/common_subexpr Co-authored-by: Rahul Saxena Co-authored-by: Juan C Rodriguez * Possible fix for Issue#107 (#170) * Possible fix for issue#107 * Reverting unrelated changes * Reverting unrelated changes * Reverting unrelated changes Co-authored-by: Rahul Saxena * Added the reporting functions (#171) * Added the reporting functions * Added comments Co-authored-by: Rahul Saxena * Please add this doc in the Article section of the official rco website (#172) * Please add this in the Article section of the official rco website * Fixed some blockquote issues * Trying new changes * Delete Document.dcf Co-authored-by: Rahul Saxena Co-authored-by: Juan C Rodriguez * Checking merge Co-authored-by: Rahul Saxena Co-authored-by: Stephanie <20eristoffs@asij.ac.jp> Co-authored-by: Umang Majumder <54509868+ZeroDawn0D@users.noreply.github.com> Co-authored-by: udaydatar7 <54199456+udaydatar7@users.noreply.github.com> Co-authored-by: Eric Liu Co-authored-by: Pratish Co-authored-by: Rahul Saxena Co-authored-by: Adrian Serapio Co-authored-by: azamlynny Co-authored-by: Jack-horwell <36800721+Jack-horwell@users.noreply.github.com> Co-authored-by: Rishi R <56316487+Rishi0812@users.noreply.github.com> Co-authored-by: Ian Chang --- NAMESPACE | 5 + NEWS.md | 5 + R/opt-common-subexpr.R | 8 + R/opt-cond-thread.R | 695 ++++++++++++++++++ R/opt-constant-propagation.R | 8 + R/opt-dead-expr.R | 29 +- R/opt-dead-store.R | 12 + R/opt-memory-alloc.R | 461 ++++++++++++ R/optimizers.R | 12 +- R/reporting-fun-files.R | 62 ++ R/reporting-fun-folder.R | 31 + R/reporting-fun-text.R | 16 + README.Rmd | 4 +- README.md | 42 +- _pkgdown.yml | 6 + docs/404.html | 13 +- docs/CODE_OF_CONDUCT.html | 13 +- docs/CONTRIBUTING.html | 13 +- docs/articles/contributing-an-optimizer.html | 131 ++-- .../anchor-sections-1.0/anchor-sections.css | 4 + .../anchor-sections-1.0/anchor-sections.js | 33 + .../header-attrs-2.5/header-attrs.js | 12 + docs/articles/docker-readme.html | 34 +- .../anchor-sections-1.0/anchor-sections.css | 4 + .../anchor-sections-1.0/anchor-sections.js | 33 + .../header-attrs-2.5/header-attrs.js | 12 + docs/articles/gsoc2020-final-report.html | 344 +++++++++ .../anchor-sections-1.0/anchor-sections.css | 4 + .../anchor-sections-1.0/anchor-sections.js | 33 + .../cond_thread_thread_benchmark-1.png | Bin 0 -> 54612 bytes .../figure-html/pre_allocate_benchmark-1.png | Bin 0 -> 50907 bytes .../header-attrs-2.5/header-attrs.js | 12 + docs/articles/index.html | 21 +- docs/articles/opt-common-subexpr.html | 79 +- .../anchor-sections-1.0/anchor-sections.css | 4 + .../anchor-sections-1.0/anchor-sections.js | 33 + .../figure-html/unnamed-chunk-6-1.png | Bin 15016 -> 30928 bytes .../header-attrs-2.5/header-attrs.js | 12 + docs/articles/opt-cond-thread.html | 256 +++++++ .../anchor-sections-1.0/anchor-sections.css | 4 + .../anchor-sections-1.0/anchor-sections.js | 33 + .../figure-html/unnamed-chunk-4-1.png | Bin 0 -> 36788 bytes .../header-attrs-2.5/header-attrs.js | 12 + docs/articles/opt-constant-folding.html | 95 ++- .../anchor-sections-1.0/anchor-sections.css | 4 + .../anchor-sections-1.0/anchor-sections.js | 33 + .../figure-html/unnamed-chunk-4-1.png | Bin 14135 -> 34666 bytes .../header-attrs-2.5/header-attrs.js | 12 + docs/articles/opt-constant-propagation.html | 129 ++-- .../anchor-sections-1.0/anchor-sections.css | 4 + .../anchor-sections-1.0/anchor-sections.js | 33 + .../figure-html/unnamed-chunk-6-1.png | Bin 12959 -> 44030 bytes .../header-attrs-2.5/header-attrs.js | 12 + docs/articles/opt-dead-code.html | 115 +-- .../anchor-sections-1.0/anchor-sections.css | 4 + .../anchor-sections-1.0/anchor-sections.js | 33 + .../figure-html/unnamed-chunk-8-1.png | Bin 15627 -> 39933 bytes .../header-attrs-2.5/header-attrs.js | 12 + docs/articles/opt-dead-expr.html | 89 ++- .../anchor-sections-1.0/anchor-sections.css | 4 + .../anchor-sections-1.0/anchor-sections.js | 33 + .../figure-html/unnamed-chunk-5-1.png | Bin 14082 -> 34969 bytes .../header-attrs-2.5/header-attrs.js | 12 + docs/articles/opt-dead-store.html | 164 +++-- .../anchor-sections-1.0/anchor-sections.css | 4 + .../anchor-sections-1.0/anchor-sections.js | 33 + .../figure-html/unnamed-chunk-8-1.png | Bin 15209 -> 38322 bytes .../header-attrs-2.5/header-attrs.js | 12 + docs/articles/opt-loop-invariant.html | 152 ++-- .../anchor-sections-1.0/anchor-sections.css | 4 + .../anchor-sections-1.0/anchor-sections.js | 33 + .../figure-html/unnamed-chunk-5-1.png | Bin 15224 -> 32955 bytes .../header-attrs-2.5/header-attrs.js | 12 + docs/articles/opt-memory-alloc.html | 255 +++++++ .../anchor-sections-1.0/anchor-sections.css | 4 + .../anchor-sections-1.0/anchor-sections.js | 33 + .../figure-html/unnamed-chunk-4-1.png | Bin 0 -> 45642 bytes .../header-attrs-2.5/header-attrs.js | 12 + docs/articles/potential-optimizers.html | 365 +++++++++ .../anchor-sections-1.0/anchor-sections.css | 4 + .../anchor-sections-1.0/anchor-sections.js | 33 + .../figure-html/col_ext_benchmark-1.png | Bin 0 -> 49512 bytes .../inline_expansion_benchmark-1.png | Bin 0 -> 48960 bytes .../figure-html/pre_allocate_benchmark-1.png | Bin 0 -> 45396 bytes .../figure-html/val_ext_benchmark-1.png | Bin 0 -> 69002 bytes .../figure-html/vectorized_benchmark-1.png | Bin 0 -> 40570 bytes .../header-attrs-2.5/header-attrs.js | 12 + docs/authors.html | 13 +- docs/index.html | 187 ++--- docs/news/index.html | 66 +- docs/pkgdown.css | 4 +- docs/pkgdown.yml | 10 +- docs/reference/Rplot001.png | Bin 0 -> 1011 bytes docs/reference/all_optimizers.html | 21 +- docs/reference/generate_files_opt_report.html | 220 ++++++ .../reference/generate_folder_opt_report.html | 235 ++++++ docs/reference/generate_text_opt_report.html | 220 ++++++ docs/reference/index.html | 43 +- docs/reference/max_optimizers.html | 21 +- docs/reference/opt_common_subexpr.html | 37 +- docs/reference/opt_cond_thread.html | 246 +++++++ docs/reference/opt_constant_folding.html | 24 +- docs/reference/opt_constant_propagation.html | 36 +- docs/reference/opt_dead_code.html | 24 +- docs/reference/opt_dead_expr.html | 24 +- docs/reference/opt_dead_store.html | 43 +- docs/reference/opt_loop_invariant.html | 24 +- docs/reference/opt_memory_alloc.html | 231 ++++++ docs/reference/optimize_files.html | 25 +- docs/reference/optimize_folder.html | 29 +- docs/reference/optimize_text.html | 15 +- docs/reference/rco_gui.html | 22 +- man/all_optimizers.Rd | 4 +- man/generate_files_opt_report.Rd | 16 + man/generate_folder_opt_report.Rd | 27 + man/generate_text_opt_report.Rd | 16 + man/max_optimizers.Rd | 4 +- man/opt_common_subexpr.Rd | 8 + man/opt_cond_thread.Rd | 34 + man/opt_constant_propagation.Rd | 8 + man/opt_dead_store.Rd | 12 + man/opt_memory_alloc.Rd | 25 + tests/testthat/test-opt_cond_thread.R | 421 +++++++++++ tests/testthat/test-opt_dead_expr.R | 195 +++++ tests/testthat/test-opt_memory_alloc.R | 231 ++++++ vignettes/.gitignore | 1 + vignettes/gsoc2020-final-report.Rmd | 185 +++++ vignettes/opt-common-subexpr.Rmd | 4 + vignettes/opt-cond-thread.Rmd | 88 +++ vignettes/opt-constant-folding.Rmd | 4 + vignettes/opt-constant-propagation.Rmd | 4 + vignettes/opt-dead-code.Rmd | 4 + vignettes/opt-dead-expr.Rmd | 4 + vignettes/opt-dead-store.Rmd | 4 + vignettes/opt-loop-invariant.Rmd | 4 + vignettes/opt-memory-alloc.Rmd | 98 +++ vignettes/potential-optimizers.Rmd | 235 ++++++ 137 files changed, 7113 insertions(+), 644 deletions(-) create mode 100644 R/opt-cond-thread.R create mode 100644 R/opt-memory-alloc.R create mode 100644 R/reporting-fun-files.R create mode 100644 R/reporting-fun-folder.R create mode 100644 R/reporting-fun-text.R create mode 100644 docs/articles/contributing-an-optimizer_files/anchor-sections-1.0/anchor-sections.css create mode 100644 docs/articles/contributing-an-optimizer_files/anchor-sections-1.0/anchor-sections.js create mode 100644 docs/articles/contributing-an-optimizer_files/header-attrs-2.5/header-attrs.js create mode 100644 docs/articles/docker-readme_files/anchor-sections-1.0/anchor-sections.css create mode 100644 docs/articles/docker-readme_files/anchor-sections-1.0/anchor-sections.js create mode 100644 docs/articles/docker-readme_files/header-attrs-2.5/header-attrs.js create mode 100644 docs/articles/gsoc2020-final-report.html create mode 100644 docs/articles/gsoc2020-final-report_files/anchor-sections-1.0/anchor-sections.css create mode 100644 docs/articles/gsoc2020-final-report_files/anchor-sections-1.0/anchor-sections.js create mode 100644 docs/articles/gsoc2020-final-report_files/figure-html/cond_thread_thread_benchmark-1.png create mode 100644 docs/articles/gsoc2020-final-report_files/figure-html/pre_allocate_benchmark-1.png create mode 100644 docs/articles/gsoc2020-final-report_files/header-attrs-2.5/header-attrs.js create mode 100644 docs/articles/opt-common-subexpr_files/anchor-sections-1.0/anchor-sections.css create mode 100644 docs/articles/opt-common-subexpr_files/anchor-sections-1.0/anchor-sections.js create mode 100644 docs/articles/opt-common-subexpr_files/header-attrs-2.5/header-attrs.js create mode 100644 docs/articles/opt-cond-thread.html create mode 100644 docs/articles/opt-cond-thread_files/anchor-sections-1.0/anchor-sections.css create mode 100644 docs/articles/opt-cond-thread_files/anchor-sections-1.0/anchor-sections.js create mode 100644 docs/articles/opt-cond-thread_files/figure-html/unnamed-chunk-4-1.png create mode 100644 docs/articles/opt-cond-thread_files/header-attrs-2.5/header-attrs.js create mode 100644 docs/articles/opt-constant-folding_files/anchor-sections-1.0/anchor-sections.css create mode 100644 docs/articles/opt-constant-folding_files/anchor-sections-1.0/anchor-sections.js create mode 100644 docs/articles/opt-constant-folding_files/header-attrs-2.5/header-attrs.js create mode 100644 docs/articles/opt-constant-propagation_files/anchor-sections-1.0/anchor-sections.css create mode 100644 docs/articles/opt-constant-propagation_files/anchor-sections-1.0/anchor-sections.js create mode 100644 docs/articles/opt-constant-propagation_files/header-attrs-2.5/header-attrs.js create mode 100644 docs/articles/opt-dead-code_files/anchor-sections-1.0/anchor-sections.css create mode 100644 docs/articles/opt-dead-code_files/anchor-sections-1.0/anchor-sections.js create mode 100644 docs/articles/opt-dead-code_files/header-attrs-2.5/header-attrs.js create mode 100644 docs/articles/opt-dead-expr_files/anchor-sections-1.0/anchor-sections.css create mode 100644 docs/articles/opt-dead-expr_files/anchor-sections-1.0/anchor-sections.js create mode 100644 docs/articles/opt-dead-expr_files/header-attrs-2.5/header-attrs.js create mode 100644 docs/articles/opt-dead-store_files/anchor-sections-1.0/anchor-sections.css create mode 100644 docs/articles/opt-dead-store_files/anchor-sections-1.0/anchor-sections.js create mode 100644 docs/articles/opt-dead-store_files/header-attrs-2.5/header-attrs.js create mode 100644 docs/articles/opt-loop-invariant_files/anchor-sections-1.0/anchor-sections.css create mode 100644 docs/articles/opt-loop-invariant_files/anchor-sections-1.0/anchor-sections.js create mode 100644 docs/articles/opt-loop-invariant_files/header-attrs-2.5/header-attrs.js create mode 100644 docs/articles/opt-memory-alloc.html create mode 100644 docs/articles/opt-memory-alloc_files/anchor-sections-1.0/anchor-sections.css create mode 100644 docs/articles/opt-memory-alloc_files/anchor-sections-1.0/anchor-sections.js create mode 100644 docs/articles/opt-memory-alloc_files/figure-html/unnamed-chunk-4-1.png create mode 100644 docs/articles/opt-memory-alloc_files/header-attrs-2.5/header-attrs.js create mode 100644 docs/articles/potential-optimizers.html create mode 100644 docs/articles/potential-optimizers_files/anchor-sections-1.0/anchor-sections.css create mode 100644 docs/articles/potential-optimizers_files/anchor-sections-1.0/anchor-sections.js create mode 100644 docs/articles/potential-optimizers_files/figure-html/col_ext_benchmark-1.png create mode 100644 docs/articles/potential-optimizers_files/figure-html/inline_expansion_benchmark-1.png create mode 100644 docs/articles/potential-optimizers_files/figure-html/pre_allocate_benchmark-1.png create mode 100644 docs/articles/potential-optimizers_files/figure-html/val_ext_benchmark-1.png create mode 100644 docs/articles/potential-optimizers_files/figure-html/vectorized_benchmark-1.png create mode 100644 docs/articles/potential-optimizers_files/header-attrs-2.5/header-attrs.js create mode 100644 docs/reference/Rplot001.png create mode 100644 docs/reference/generate_files_opt_report.html create mode 100644 docs/reference/generate_folder_opt_report.html create mode 100644 docs/reference/generate_text_opt_report.html create mode 100644 docs/reference/opt_cond_thread.html create mode 100644 docs/reference/opt_memory_alloc.html create mode 100644 man/generate_files_opt_report.Rd create mode 100644 man/generate_folder_opt_report.Rd create mode 100644 man/generate_text_opt_report.Rd create mode 100644 man/opt_cond_thread.Rd create mode 100644 man/opt_memory_alloc.Rd create mode 100644 tests/testthat/test-opt_cond_thread.R create mode 100644 tests/testthat/test-opt_memory_alloc.R create mode 100644 vignettes/gsoc2020-final-report.Rmd create mode 100644 vignettes/opt-cond-thread.Rmd create mode 100644 vignettes/opt-memory-alloc.Rmd create mode 100644 vignettes/potential-optimizers.Rmd diff --git a/NAMESPACE b/NAMESPACE index b19674c..efff424 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,14 +1,19 @@ # Generated by roxygen2: do not edit by hand export(all_optimizers) +export(generate_files_opt_report) +export(generate_folder_opt_report) +export(generate_text_opt_report) export(max_optimizers) export(opt_common_subexpr) +export(opt_cond_thread) export(opt_constant_folding) export(opt_constant_propagation) export(opt_dead_code) export(opt_dead_expr) export(opt_dead_store) export(opt_loop_invariant) +export(opt_memory_alloc) export(optimize_files) export(optimize_folder) export(optimize_text) diff --git a/NEWS.md b/NEWS.md index 504d3a3..832b6a2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,8 @@ +# rco 1.0.2 + + - Adding Memory Allocation optimizer. + - Adding Conditional Threading optimizer. + # rco 1.0.1 - Bug fixing. diff --git a/R/opt-common-subexpr.R b/R/opt-common-subexpr.R index 1d9cd3e..3e5acf1 100644 --- a/R/opt-common-subexpr.R +++ b/R/opt-common-subexpr.R @@ -16,6 +16,14 @@ #' sep = "\n" #' ) #' cat(opt_common_subexpr(list(code))$codes[[1]]) +#' +#' heron_formula <- paste( +#' "area <- (a/2 + b/2 +c/2) * (a/2 + b/2 + c/2 - a) * (a/2 + b/2 + c/2 - b) *", +#' " (a/2 + b/2 + c/2 - c)", +#' "area <- sqrt(area)", +#' sep = '\n' +#' ) +#' cat(opt_common_subexpr(list(heron_formula))$codes[[1]]) #' @export #' opt_common_subexpr <- function(texts, n_values = 2, in_fun_call = FALSE) { diff --git a/R/opt-cond-thread.R b/R/opt-cond-thread.R new file mode 100644 index 0000000..f0d58c6 --- /dev/null +++ b/R/opt-cond-thread.R @@ -0,0 +1,695 @@ +#' Optimizer: Conditional Threading. +#' +#' Performs one conditional threading pass. +#' Carefully examine the results after running this function! +#' +#' @param code A list of character vectors with the code to optimize. +#' +#' @examples +#' code <- paste( +#' "num <- sample(100, 1)", +#' "even_sum <- 0", +#' "odd_sum_a <- 0", +#' "odd_sum_b <- 0", +#' "if (num %% 2 == 1) {", +#' " odd_sum_a <- odd_sum_a + num", +#' "}", +#' "if (num %% 2 == 1) {", +#' " odd_num_b <- odd_num_b + num", +#' "}", +#' "if (!(num %% 2 == 1)) {", +#' " even_sum <- even_sum + num", +#' "}", +#' sep = "\n" +#' ) +#' cat(opt_cond_thread(list(code))$codes[[1]]) +#' @export + +opt_cond_thread <- function(code) { + res <- list() + res$codes <- lapply(code, ct_one_file) + return(res) +} + +# Executes conditional threading on one text of code. +# +# @param code A character vector with code to optimize. +# +ct_one_file <- function(code) { + parsed_dataset <- parse_text(code) + flatten_pd <- flatten_leaves(parsed_dataset) + result_flatten_pd <- ct_one_flatten_pd(parsed_dataset, flatten_pd) + return(deparse_data(result_flatten_pd)) +} + +# Checks if the given `node_id` is an `IF` statement +# +# @param fpd A flatten parsed data data.frame. +# @param node_id A numeric indicating the node ID of the function def expression. +# +check_if <- function(fpd, node_id) { + return("IF" %in% fpd[fpd$parent == node_id, "token"]) +} + +# Checks if the given `node_id` has consequent `IF` statements +# +# @param fpd A flatten parsed data data.frame. +# @param node_id A numeric indicating the node ID of the function def expression. +# @param exam_nodes A list consisting of all the nodes that needs to be checked for `IF` statements +# +check_if_next <- function(fpd, node_id, exam_nodes) { + if (check_if(fpd, node_id)) { + # Checks whether the given `node_id` is an `IF` statement itself + row_num <- which(exam_nodes$id == node_id, arr.ind = TRUE) + curr_parents <- get_children(fpd, node_id)$id + if (row_num != nrow(exam_nodes)) { + # Checks whether there exists another node after the given `node_id` in the `exam_nodes` + for (i in exam_nodes[-seq_len(row_num), "id"]) { + # Eliminating nodes with `node_id` and nodes before that + if (exam_nodes[exam_nodes$id == i, "parent"] >= 0 & !(i %in% curr_parents)) { + # Checking parent for positivity to be to able to avoid comments and second condition is to avoid nested `IF` + return(check_if(fpd, i)) + } + } + # If the function doesnot broke till now, return false + return(FALSE) + } + else { + # If the given node if the last node + return(FALSE) + } + } else { + # If the give node with node_id itself isn't an IF statement + return(FALSE) + } +} + +# Returns `id` of the `IF` condition of the node_id +# +# @param fpd A flatten parsed data data.frame. +# @param node_id A numeric indicating the node ID of the function def expression. +# +first_if_expr <- function(fpd, node_id) { + # `pos_id` of the keyword `IF` + if_pos <- get_children(fpd, node_id)[get_children(fpd, node_id)$token == "IF", "pos_id"] + # `pos_id` of the opening bracket of the `IF` condition + start_bracket <- get_children(fpd, node_id)[get_children(fpd, node_id)$pos_id > if_pos & + get_children(fpd, node_id)$token == "'('", "pos_id"][1] + # `parent` of the opening bracket + start_bracket_parent <- get_children(fpd, node_id)[get_children(fpd, node_id)$pos_id > if_pos & + get_children(fpd, node_id)$token == "'('", "parent"][1] + # `pos_id` of the closing bracket of the `IF` condition + end_bracket <- get_children(fpd, node_id)[get_children(fpd, node_id)$pos_id > if_pos & + get_children(fpd, node_id)$token == "')'" & + get_children(fpd, node_id)$parent == start_bracket_parent, "pos_id"] + # `id` of the expression found between the two brackets + if_expr_id <- get_children(fpd, node_id)[get_children(fpd, node_id)$pos_id > start_bracket & + get_children(fpd, node_id)$pos_id < end_bracket & + get_children(fpd, node_id)$token == "expr", "id"][1] + return(if_expr_id) +} + +# Used to find the condition of the consecutive IF block of the given node_id in the given fpd. +# +# @param fpd A flatten parsed data data.frame. +# @param node_id A numeric indicating the node ID of the function def expression. +# @param exam_nodes A list consisting of all the nodes that needs to be checked for `IF` statements +# +consecutive_if_expr <- function(fpd, node_id, exam_nodes) { + flag <- FALSE + # Find the row number of given node in `exam_nodes` + row_num <- which(exam_nodes$id == node_id, arr.ind = TRUE) + curr_parents <- get_children(fpd, node_id)$id + for (i in exam_nodes[-seq_len(row_num), "id"]) { + # Iterating for all nodes after the given node with the `node_id` + if (exam_nodes[exam_nodes$id == i, "parent"] >= 0 & !(i %in% curr_parents)) { + # Marking the immediate next node, that is not the sub-node of the given `node_id` + start_id <- i + flag <- TRUE + break + } + } + if (!flag) { # If no such node was found + return(flag) + } + + # Check for `IF` statement in the marked node + if ("IF" %in% get_children(fpd, start_id)$token) { + # If `IF` statement was found in this node too, find its `IF` condition too + first_if_expr(fpd, start_id) + } else { + return(FALSE) + } +} + +# Checks for exact negations in the conditions of the consecutive IF statements for the given node_id in the given fpd. +# For example: if(a) {//do A}; if(!a) {//do B} +# +# @param fpd A flatten parsed data data.frame. +# @param node1 A numeric indicating the node ID of the first IF expression. +# @param node2 A numeric indicating the node ID of the second IF expression. +# +check_negation <- function(fpd, node1, node2) { + # Assume second expr has the `!` symbol + first_expr_a <- fpd[fpd$id == node1, "text"] + second_expr_a <- fpd[fpd$id == node2, "text"] + + # Assume first expr has the `!` symbol + first_expr_b <- fpd[fpd$id == node2, "text"] + second_expr_b <- fpd[fpd$id == node1, "text"] + + # `gsub` is used to remove extra spaces from either expressions, + # so that difference does not arise due to spacing + + # The case when the second expr has the `!` symbol + check_first1a <- gsub(" ", "", paste("!", "(", first_expr_a, ")", sep = ""), fixed = TRUE) # Takes care of `if(!(a)) {}` + check_first2a <- gsub(" ", "", paste("!", first_expr_a, sep = ""), fixed = TRUE) # Takes care of `if(!a){}` + check_second_a <- gsub(" ", "", second_expr_a, fixed = TRUE) + + # The case when the first expr has the `!` symbol + check_first1b <- gsub(" ", "", paste("!", "(", first_expr_b, ")", sep = ""), fixed = TRUE) # Takes care of `if(!(a)){}` + check_first2b <- gsub(" ", "", paste("!", first_expr_b, sep = ""), fixed = TRUE) # Takes care of `if(!a){}` + check_second_b <- gsub(" ", "", second_expr_b, fixed = TRUE) + + return(check_first1a == check_second_a | check_first2a == check_second_a | check_first1b == check_second_b | check_first2b == check_second_b) +} + +# Checks for negations in the equality conditions of the consecutive IF statements for the given node_id in the given fpd. +# Example: if(a == b) {//do A} ; if(a != b) {// do B} +# +# @param fpd A flatten parsed data data.frame. +# @param node1 A numeric indicating the node ID of the first IF expression. +# @param node2 A numeric indicating the node ID of the second IF expression. +# +check_not_equal <- function(fpd, node1, node2) { + first_expr <- get_children(fpd, node1, include_father = FALSE) + second_expr <- get_children(fpd, node2, include_father = FALSE) + if (nrow(first_expr) != nrow(second_expr)) { + # This if condition ensures that the `IF` conditions are not too different + return(FALSE) + } + + # This for loop ensures that the only difference between the two conditions if of the token `EQ` and `NE` + for (i in nrow(first_expr)) { + if (first_expr[i, "text"] != second_expr[i, "text"]) { + if (!((first_expr[i, "token"] == "EQ" & second_expr[i, "token"] == "NE") | (first_expr[i, "token"] == "NE" & second_expr[i, "token"] == "EQ"))) { + return(FALSE) + } + } + } + return(TRUE) +} + +# Checks for exact duplicates conditions of the consecutive IF statements for the given node_id in the given fpd. +# Example: if(a) {//do A} if(a) {// do B} +# +# @param fpd A flatten parsed data data.frame. +# @param node1 A numeric indicating the node ID of the first IF expression. +# @param node2 A numeric indicating the node ID of the second IF expression. +# +check_duplicate_expr <- function(fpd, node1, node2) { + first_expr <- fpd[fpd$id == node1, "text"] + second_expr <- fpd[fpd$id == node2, "text"] + + # `gsub` is used to remove extra spaces from either expressions, + # so that difference does not arise due to spacing + first_expr <- gsub(" ", "", first_expr, fixed = TRUE) + second_expr <- gsub(" ", "", second_expr, fixed = TRUE) + + return(first_expr == second_expr) +} + +# Checks for exact negations in the greater than equal to logic in the given nodes +# Example if(a >= b) {// do A} ; if(a < b) {//do B} +# +# @param fpd A flatten parsed data data.frame. +# @param node1 A numeric indicating the node ID of the first IF expression. +# @param node2 A numeric indicating the node ID of the second IF expression. +# +check_comparsion_logic_ge <- function(fpd, node1, node2) { + first_expr <- fpd[fpd$id == node1, "text"] + second_expr <- fpd[fpd$id == node2, "text"] + + # Check for the GE symbol in the first_expr + if (length(grep(">=", first_expr)) > 0) { + # Remove spacing from first expression + first_expr <- gsub(" ", "", first_expr, fixed = T) + # Convert the `>=` symbol to `<` + first_expr <- gsub(">=", "<", first_expr) + # Remove spacing from second expression + second_expr <- gsub(" ", "", second_expr, fixed = TRUE) + # Compare first and second expressions + return(first_expr == second_expr) + } + # This is for the case when the GE symbol is in the second_expr + else if (length(grep(">=", second_expr)) > 0) { + second_expr <- gsub(" ", "", second_expr, fixed = TRUE) + first_expr <- gsub(" ", "", first_expr, fixed = TRUE) + second_expr <- gsub(">=", "<", second_expr) + return(first_expr == second_expr) + } + else { + # The case where no expression contains the GE symbol + return(FALSE) + } +} + +# Checks for exact negations in the lesser than equal to logic in the given nodes +# Example if(a <= b) {// do A} ; if(a > b) {//do B} +# +# @param fpd A flatten parsed data data.frame. +# @param node1 A numeric indicating the node ID of the first IF expression. +# @param node2 A numeric indicating the node ID of the second IF expression. +# +check_comparsion_logic_le <- function(fpd, node1, node2) { + first_expr <- fpd[fpd$id == node1, "text"] + second_expr <- fpd[fpd$id == node2, "text"] + # Check for the LE symbol in the first_expr + if (length(grep("<=", first_expr)) > 0) { + # Remove spacing from first expression + first_expr <- gsub(" ", "", first_expr, fixed = TRUE) + # Convert the `<=` symbol to `>` + first_expr <- gsub("<=", ">", first_expr) + # Remove spacing from first expression + second_expr <- gsub(" ", "", second_expr, fixed = TRUE) + # Compare first and second expressions + return(first_expr == second_expr) + } + # Check for the LE symbol in the second expression + else if (length(grep("<=", second_expr)) > 0) { + first_expr <- gsub(" ", "", first_expr, fixed = TRUE) + second_expr <- gsub(" ", "", second_expr, fixed = TRUE) + second_expr <- gsub("<=", ">", second_expr) + return(first_expr == second_expr) + } + else { + # Neither expression had the LE symbol + return(FALSE) + } +} + +# Checks for function calls in the IF statement conditions +# +# @param fpd A flatten parsed data.frame. +# @param node1 A numeric indicating the node ID of the first IF expression. +# @param node2 A numeric indicating the node ID of the second IF expression. +# +has_func_calls <- function(fpd, node1, node2) { + return("SYMBOL_FUNCTION_CALL" %in% get_children(fpd, node1)$token || + "SYMBOL_FUNCTION_CALL" %in% get_children(fpd, node2)) +} + +# Retrieves the `IF` block expression from the node index(itr) in exam_nodes +# +# @param fpd A flatten parsed data.frame +# @param exam_nodes A list of all nodes that need to be examined for the optimization +# @param itr The position of the iterator in exam_nodes +# +get_if_block_expr <- function(fpd, exam_nodes, itr) { + expr_id <- exam_nodes[itr, "id"] + + # This function is similar to `first_if_expr()` + if_pos <- get_children(fpd, expr_id)[get_children(fpd, expr_id)$token == "IF", "pos_id"] + start_bracket <- get_children(fpd, expr_id)[get_children(fpd, expr_id)$pos_id > if_pos & + get_children(fpd, expr_id)$token == "'('", "pos_id"][1] + start_bracket_parent <- get_children(fpd, expr_id)[get_children(fpd, expr_id)$pos_id > if_pos & + get_children(fpd, expr_id)$token == "'('", "parent"][1] + end_bracket <- get_children(fpd, expr_id)[get_children(fpd, expr_id)$pos_id > start_bracket & + get_children(fpd, expr_id)$token == "')'" & + get_children(fpd, expr_id)$parent == start_bracket_parent, "pos_id"] + + # Return the text of the node just after the closing bracket of `IF` statement + return(fpd[fpd$pos_id == (end_bracket + 1) & fpd$token == "expr", "text"]) +} + +# Retrieves the modified IF block expression from the index of node in modified exam_nodes +# +# @param fpd A flatten parsed data.frame +# @param exam_nodes A list of all nodes that need to be examined for the optimization +# @param index The position of the iterator in exam_nodes +# +get_modified_if_expr <- function(fpd, exam_nodes, index) { + # A new parsed data.frame for when the code-block under `IF` changes + modified_fpd <- flatten_leaves(parse_text(exam_nodes[index, "text"])) + + # Retrieve the code-block under the new `IF` statement + if_pos <- modified_fpd[modified_fpd$token == "IF", "pos_id"] + start_bracket <- modified_fpd[modified_fpd$pos_id > if_pos & + modified_fpd$token == "'('", "pos_id"][1] + start_bracket_parent <- modified_fpd[modified_fpd$pos_id > if_pos & + modified_fpd$token == "'('", "parent"][1] + end_bracket <- modified_fpd[modified_fpd$pos_id > start_bracket & + modified_fpd$token == "')'" & + modified_fpd$parent == start_bracket_parent, "pos_id"] + return(modified_fpd[modified_fpd$pos_id == (end_bracket + 1) & modified_fpd$token == "expr", "text"]) +} + +# Determines the nodes that have to be removed based on the index (itr) of the exam_nodes +# For example, if two IF statements are merged then the node of one IF must be removed from exam_nodes +# +# @param fpd A flatten parsed data.frame +# @param exam_nodes A list of all nodes that need to be examined for the optimization +# @param itr The position of the iterator in exam_nodes +# +node_removal_fun <- function(itr, exam_nodes, fpd) { + return_id <- res_list <- NULL + curr_id <- exam_nodes[itr, "id"] + curr_parents <- get_children(fpd, curr_id)$id + for (i in exam_nodes[-seq_len(itr), "id"]) { + if (exam_nodes[exam_nodes$id == i, "parent"] >= 0 & !(i %in% curr_parents)) { + return_id <- i + break + } + } + curr_parents2 <- get_children(fpd, return_id)$id + for (i in exam_nodes$id) { + if (i %in% curr_parents2) { + res_list <- append(res_list, which(i == exam_nodes$id, arr.ind = TRUE)) + } + } + rest_list <- NULL + itr_parents <- get_children(fpd, exam_nodes[itr, "id"])$id + for (i in exam_nodes$id) { + if (i %in% itr_parents & exam_nodes[exam_nodes$id == i, "parent"] >= 0) { + rest_list <- append(rest_list, which(i == exam_nodes$id, arr.ind = TRUE)) + } + } + rest_list <- rest_list[-1] + return(append(res_list, rest_list)) +} + + +# Executes conditional threading of a flatten_pd. +# +# @param flatten_pd A flatten parsed data data.frame. +# +ct_one_flatten_pd <- function(parsed_dataset, flatten_pd) { + pd <- parsed_dataset + fpd <- flatten_pd + # Procedure to include nodes from Functions into exam nodes + fun_ids <- fpd$id[fpd$token == "FUNCTION"] + fun_prnt_ids <- fpd$parent[fpd$id %in% fun_ids] + + fun_exam_nodes <- NULL + for (i in fun_prnt_ids) { + fun_exam_nodes <- rbind(fun_exam_nodes, get_children(fpd, i, FALSE)[get_children(fpd, i, FALSE)$token == "expr", ]) + } + + # Procedure to include nodes from Loops into exam nodes + loop_ids <- fpd[fpd$token %in% loops, "id"] + loop_parent_ids <- fpd[fpd$id %in% loop_ids, "parent"] + loop_exam_nodes <- NULL + for (i in loop_parent_ids) { + loop_exam_nodes <- rbind(loop_exam_nodes, get_children(fpd, i, FALSE)[get_children(fpd, i, FALSE)$token == "expr", ]) + } + + exam_nodes <- get_roots(pd) + exam_nodes <- rbind(exam_nodes, fun_exam_nodes) + exam_nodes <- rbind(exam_nodes, fun_exam_nodes, loop_exam_nodes) + + # Procedure to include nodes from nested IF loops + stray_nodes <- fpd[fpd$token == "IF", "parent"] + stray_exam_nodes <- NULL + for (i in stray_nodes) { + stray_exam_nodes <- rbind(stray_exam_nodes, fpd[fpd$id == i, ]) + } + for (i in stray_exam_nodes$id) { + if (!(i %in% exam_nodes$id)) { + exam_nodes <- rbind(exam_nodes, fpd[fpd$id == i, ]) + } + } + + # Removing duplicate nodes from exam_nodes + e_nodes <- NULL + for (i in unique(exam_nodes$id)) { + e_nodes <- rbind(e_nodes, unique(exam_nodes[exam_nodes$id == i, ])) + } + + exam_nodes <- e_nodes + exam_nodes_copy <- exam_nodes # Creating a copy as exam_nodes would be edited later + + # Inspection of the exam_nodes begin + + # First we will handle all the statements that have to be merged. + to_change_node <- to_remove_node <- merge_to <- merge_from <- NULL + + for (itr in seq_len(length(exam_nodes$id))) { + # For each node in exam_nodes, check if its an `IF` statement and if yes, then also check the immediate next node + i <- exam_nodes[itr, "id"] + if (check_if(fpd, i)) { + if (check_if_next(fpd, i, exam_nodes)) { + node1 <- first_if_expr(fpd, i) + node2 <- consecutive_if_expr(fpd, i, exam_nodes) + # Check that no `IF` statement conditions have a function call and then check whether the two IF statements are identical + if (!has_func_calls(fpd, node1, node2)) { + if (check_duplicate_expr(fpd, node1, node2)) { + # If found to be duplicate, make arrangements to merge them + merge_to <- append(merge_to, itr) + merge_from <- append(merge_from, node_removal_fun(itr, exam_nodes = exam_nodes, fpd = fpd)[1]) + to_change_node <- append(to_change_node, itr) + to_remove_node <- append(to_remove_node, node_removal_fun(itr, exam_nodes = exam_nodes, fpd = fpd)) + } + } + } + } + } + + # deletion_node contains all the nodes that need to be deleted after merging IF statements with duplicate conditions + deletion_nodes <- vector(mode = "numeric") + + for (i in to_remove_node) { + deletion_nodes <- append(deletion_nodes, exam_nodes[i, "id"]) + } + + for (i in to_change_node) { + deletion_nodes <- append(deletion_nodes, exam_nodes[i, "id"]) + } + + # The following sequence is used to change the text of exam_nodes text where merging has to take place + # to_expr refers to where the merging will take place and from_expr represents from where the code-block for merging will come + to_expr <- from_expr <- NULL + + for (i in seq_len(length(merge_from))) { + to_expr[i] <- get_if_block_expr(fpd, exam_nodes, merge_to[i]) + + if (grepl("{", to_expr[i], fixed = TRUE)) { + to_expr[i] <- gsub("{", "", to_expr[i], fixed = TRUE) + to_expr[i] <- gsub("}", "", to_expr[i], fixed = TRUE) + to_expr[i] <- trimws(to_expr[i]) + } + + from_expr[i] <- get_if_block_expr(fpd, exam_nodes, merge_from[i]) + + # Case where the IF code block starts from the same line + if (grepl("{", from_expr[i], fixed = TRUE)) { + from_expr[i] <- gsub("{", "", from_expr[i], fixed = TRUE) + from_expr[i] <- gsub("}", "", from_expr[i], fixed = TRUE) + from_expr[i] <- trimws(from_expr[i]) + } + } + + # Merging takes place here + for (i in seq_len(length(to_change_node))) { + if_cond <- fpd[fpd$id == first_if_expr(fpd, exam_nodes[to_change_node[i], "id"]), "text"] + string1 <- paste("if", "(", if_cond, ") ", "{\n", sep = "") + string2 <- paste(to_expr[i], "\n", from_expr[i], "\n}", sep = " ") + exam_nodes[to_change_node[i], "text"] <- paste0(string1, string2) + } + + # Removing the not-required nodes from exam_nodes + if (length(to_remove_node) > 0) { + exam_nodes <- exam_nodes[-(to_remove_node), ] + } + + # Here conversion of marked `IF` statements to `ELSE` takes place + to_change_node <- to_remove_node <- convert_to_else <- NULL + + for (itr in seq_len(length(exam_nodes$id))) { + # For each node in exam_nodes, check if its an `IF` statement and if yes, then also check the immediate next node + i <- exam_nodes[itr, "id"] + if (check_if(fpd, i)) { + if (check_if_next(fpd, i, exam_nodes)) { + node1 <- first_if_expr(fpd, i) + node2 <- consecutive_if_expr(fpd, i, exam_nodes) + if (!has_func_calls(fpd, node1, node2)) { + if (check_negation(fpd, node1, node2) | + check_not_equal(fpd, node1, node2) | + check_comparsion_logic_ge(fpd, node1, node2) | + check_comparsion_logic_le(fpd, node1, node2)) { + # Check all the conditions that can convert two `IF` statements to one `If-Else` block + convert_to_else <- append(convert_to_else, node_removal_fun(itr, exam_nodes = exam_nodes, fpd = fpd)[1]) + to_change_node <- append(to_change_node, itr) + to_remove_node <- append(to_remove_node, node_removal_fun(itr, exam_nodes = exam_nodes, fpd = fpd)) + } + } + } + } + } + + for (i in to_remove_node) { + deletion_nodes <- append(deletion_nodes, exam_nodes[i, "id"]) + } + + for (i in to_change_node) { + deletion_nodes <- append(deletion_nodes, exam_nodes[i, "id"]) + } + + else_expr <- NULL + + for (i in seq_len(length(convert_to_else))) { + else_expr[i] <- get_modified_if_expr(fpd, exam_nodes, convert_to_else[i]) + + if (grepl("{", else_expr[i], fixed = TRUE)) { + # For cases in which the statement starts from the same line + else_expr[i] <- gsub("{", "", else_expr[i], fixed = TRUE) + else_expr[i] <- gsub("}", "", else_expr[i], fixed = TRUE) + else_expr[i] <- trimws(else_expr[i]) + } + } + # Conversion of an `IF` statement to `ELSE` takes place here + for (i in seq_len(length(to_change_node))) { + string1 <- exam_nodes[to_change_node[i], "text"] + exam_nodes[to_change_node[i], "text"] <- paste0(string1, paste("else", "{\n", else_expr[i], " \n}", sep = " ")) + } + + # Removing the redundant nodes after conversion to else + if (length(to_remove_node) > 0) { + exam_nodes <- exam_nodes[-(to_remove_node), ] + } + + exam_nodes <- exam_nodes[order(exam_nodes$pos_id), ] + + # Segregating the nodes that are not to be edited + not_to_edit <- c() + + for (i in exam_nodes_copy$id) { + if (!(i %in% deletion_nodes)) { + not_to_edit <- rbind(not_to_edit, exam_nodes_copy[exam_nodes_copy$id == i, ]) + } + } + + # Tidying up the not_to_edit list + for (i in seq_len(nrow(not_to_edit))) { + not_to_edit <- rbind(not_to_edit, get_children(pd, not_to_edit[i, "id"])) + } + + not_to_edit <- not_to_edit[order(not_to_edit$pos_id), ] + + # Removing entries with duplicate IDs. + not_to_edit_final <- NULL + for (i in unique(not_to_edit$id)) { + not_to_edit_final <- rbind(not_to_edit_final, unique(not_to_edit[not_to_edit$id == i, ])) + } + + # Creating a list that consists of all the nodes to be changed + final_exam_nodes <- c() + for (i in to_change_node) { + final_node_id <- exam_nodes_copy[i, "id"] + final_exam_nodes <- rbind(final_exam_nodes, exam_nodes[exam_nodes$id == final_node_id, ]) + } + + # Creating new properties for the new_fpd + new_fpd <- NULL + if (length(final_exam_nodes$id) > 0) { + for (itr in seq_len(nrow(final_exam_nodes))) + { + act_fpd <- final_exam_nodes[itr, ] + new_act_fpd <- parse_text(act_fpd$text) + + # Setting new ids for the newly edited and parsed codes + new_act_fpd$id <- paste0(act_fpd$id, "_", new_act_fpd$id) + + # Keeping old parents for new fpd + new_act_fpd$parent[new_act_fpd$parent != 0] <- paste0(act_fpd$id, "_", new_act_fpd$parent[new_act_fpd$parent != 0]) + new_act_fpd$parent[new_act_fpd$parent == 0] <- act_fpd$parent + + # Calling a pre-wriiten rco::function.... + new_act_fpd$pos_id <- create_new_pos_id(act_fpd, nrow(new_act_fpd), act_fpd$id) + + # Fixing the next_spaces section of new_fpd + new_act_fpd$next_spaces[nrow(new_act_fpd)] <- act_fpd$next_spaces + + # Fixing the next_lines section of new_fpd + new_act_fpd$next_lines[nrow(new_act_fpd)] <- act_fpd$next_lines + + # Fixing the prev_spaces section of new_fpd + new_act_fpd$prev_spaces[which(new_act_fpd$terminal)[[1]]] <- act_fpd$prev_spaces + + # Merging the new_fpd and the act_fpd(obtained upon iteration) + new_fpd <- rbind(new_fpd, new_act_fpd) + + # Ordering the new_fpd according to the pos_id + new_fpd <- new_fpd[order(new_fpd$pos_id), ] + } + } + + resultant_fpd <- rbind(not_to_edit_final, new_fpd) + resultant_fpd <- resultant_fpd[order(resultant_fpd$pos_id), ] + + test_fpd <- flatten_leaves(resultant_fpd) + + # Removing the nodes that were in deletion_nodes + inspection_ids <- c() + remove_indices <- c() + for (i in deletion_nodes) { + inspection_ids <- get_children(fpd, i)$id + for (j in inspection_ids) { + if (length(which(test_fpd$id == j, arr.ind = T)) == 1) { + remove_indices <- append(remove_indices, which(test_fpd$id == j, arr.ind = T)) + } + } + } + + if (length(remove_indices) > 0) { + test_fpd <- test_fpd[-remove_indices, ] + } + + # Inserting appropriate values in the new_fpd + comments_ids <- NULL + curr_new_line_ids <- NULL + next_line_ids <- NULL + correction_nodes <- NULL + correction_ids <- NULL + + correction_nodes <- test_fpd[test_fpd$parent == 0 & is.na(test_fpd$next_spaces) & is.na(test_fpd$next_lines) & is.na(test_fpd$prev_spaces), ] + + for (i in correction_nodes$id) { + correction_ids <- append(correction_ids, which(test_fpd$id == i, arr.ind = TRUE)) + } + + test_fpd[which(is.na(test_fpd$next_lines)), "next_lines"] <- 0 + test_fpd[which(is.na(test_fpd$next_spaces)), "next_spaces"] <- 0 + test_fpd[which(is.na(test_fpd$prev_spaces)), "prev_spaces"] <- 0 + + comments_ids <- which(test_fpd$parent < 0) + correction_ids <- append(correction_ids, comments_ids) + + for (i in correction_ids) { + if ((i - 1) > 0) { + test_fpd[i - 1, "next_lines"] <- 1 + } else { + next + } + } + + #Copying the behaviour of "';'" from fpd to test_fpd + if("';'" %in% test_fpd$token) { + semiColon_indices <- which("';'" == fpd$token) + for(i in seq_len(length(semiColon_indices))) { + fpd_nextSpaces <- fpd[semiColon_indices[i], "next_spaces"] + fpd_nextLines <- fpd[semiColon_indices[i], "next_lines"] + fpd_prevSpaces <- fpd[semiColon_indices[i], "prev_spaces"] + + fpd_id <- fpd[semiColon_indices[i], "id"] + testFpd_index <- which(fpd_id == test_fpd$id) + + test_fpd[testFpd_index, "next_spaces"] <- fpd_nextSpaces + test_fpd[testFpd_index, "next_lines"] <- fpd_nextLines + test_fpd[testFpd_index, "prev_spaces"] <- fpd_prevSpaces + } + } + + test_fpd <- test_fpd[!(test_fpd$parent == 0 & test_fpd$terminal == FALSE), ] + + return(test_fpd) +} diff --git a/R/opt-constant-propagation.R b/R/opt-constant-propagation.R index c8aa3fd..c99e167 100644 --- a/R/opt-constant-propagation.R +++ b/R/opt-constant-propagation.R @@ -16,6 +16,14 @@ #' sep = "\n" #' ) #' cat(opt_constant_propagation(list(code))$codes[[1]]) +#' +#' hemisphere_vol <- paste( +#' "pi <- 3.141593 ", +#' "radius <- 25 ", +#' "hemis_vol <- 2/3 * pi * radius ^ 3 ", +#' sep = "\n" +#' ) +#' cat(opt_constant_propagation(list(hemisphere_vol))$codes[[1]]) #' @export #' opt_constant_propagation <- function(texts, in_fun_call = FALSE) { diff --git a/R/opt-dead-expr.R b/R/opt-dead-expr.R index 5603140..52c2e98 100644 --- a/R/opt-dead-expr.R +++ b/R/opt-dead-expr.R @@ -88,9 +88,18 @@ get_unassigned_exprs <- function(pd, id) { act_prnt_pd <- get_children(act_pd, act_parent) act_sblngs <- act_prnt_pd[act_prnt_pd$parent == act_parent, ] if (act_sblngs$token[[1]] == "'{'") { - new_visit <- c(new_visit, act_sblngs$id[act_sblngs$token == "expr"]) + new_visit <- c(new_visit, act_sblngs$id[act_sblngs$token == "expr" | act_sblngs$token == "exprlist"]) + } else if (act_sblngs$token[[1]] == "exprlist") { + exprlist_ids <- act_prnt_pd[act_prnt_pd$token == "exprlist", "id"] + for (exprlist_idx in exprlist_ids) { + separate_exprs <- act_prnt_pd[act_prnt_pd$parent == exprlist_idx & act_prnt_pd$token == "expr", "id"] + new_visit <- c(new_visit, separate_exprs) + } + new_visit <- unique(new_visit) + } else if (all(act_sblngs$token %in% c("expr", "';'"))) { + new_visit <- c(new_visit, act_sblngs[act_sblngs$token == "expr", "id"]) } else if (all(act_prnt_pd$token %in% - c(constants, ops, precedence_ops, "expr", "SYMBOL"))) { + c(constants, ops, precedence_ops, "expr", "exprlist","SYMBOL"))) { # it is an expression exprs_ids <- c(exprs_ids, act_parent) } else if (any(c(loops, "IF") %in% act_sblngs$token)) { @@ -121,6 +130,8 @@ get_unassigned_exprs <- function(pd, id) { act_pd$parent[act_pd$id == act_id], ] any(assigns %in% act_sblngs$token) # the expr is being assigned })] + + unique(exprs_ids) } # Returns the IDs of the exprs that can return in a function. @@ -142,10 +153,16 @@ get_fun_last_exprs <- function(pd, id) { act_sblngs <- act_prnt_pd[act_prnt_pd$parent == act_parent, ] if (act_sblngs$token[[1]] == "'{'") { # has multiple exprs, check only the last one - new_visit <- c( - new_visit, - utils::tail(act_sblngs$id[act_sblngs$token == "expr"], 1) - ) + if (act_sblngs[(nrow(act_sblngs)-1), "token"] == "exprlist") { + exprlist_id <- act_sblngs[(nrow(act_sblngs)-1), "id"] + exprlist_expr_ids <- act_prnt_pd[act_prnt_pd$parent == exprlist_id & act_prnt_pd$token == "expr", "id"] + new_visit <- c(new_visit, utils::tail(exprlist_expr_ids, n = 1)) + } else { + new_visit <- c( + new_visit, + utils::tail(act_sblngs$id[act_sblngs$token == "expr"], 1) + ) + } } else if (any(loops %in% act_sblngs$token)) { next } else if ("IF" %in% act_sblngs$token) { diff --git a/R/opt-dead-store.R b/R/opt-dead-store.R index c9f30d5..be7ef18 100644 --- a/R/opt-dead-store.R +++ b/R/opt-dead-store.R @@ -14,6 +14,18 @@ #' sep = "\n" #' ) #' cat(opt_dead_store(list(code))$codes[[1]]) +#' +#' code <- paste( +#' "sinpi <- function() {", +#' " pi <- 3.1415", +#' " e <- 2.718", +#' " phi <- 1.618", +#' " sin(pi)", +#' "}", +#' sep = "\n" +#' ) +#' cat(opt_dead_store(list(code))$codes[[1]]) +#' #' @export #' opt_dead_store <- function(texts) { diff --git a/R/opt-memory-alloc.R b/R/opt-memory-alloc.R new file mode 100644 index 0000000..fbf265f --- /dev/null +++ b/R/opt-memory-alloc.R @@ -0,0 +1,461 @@ +#' Optimizer: Memory Allocation. +#' +#' Performs one memory allocation pass. +#' Carefully examine the results after running this function! +#' +#' @param code A list of character vectors with the code to optimize. +#' +#' @examples +#' code <- paste( +#' "v <- NULL", +#' "for (i in 1:5) {", +#' " v[i] <- i^2", +#' "}", +#' sep = "\n" +#' ) +#' cat(opt_memory_alloc(list(code))$codes[[1]]) +#' @export + +opt_memory_alloc <- function(code) { + res <- list() + res$codes <- lapply(code, ma_one_file) + return(res) +} + +# Executes memory allocation on one text of code. +# +# @param code A character vector with code to optimize. +# +ma_one_file <- function(code) { + parsed_dataset <- parse_text(code) + flatten_pd <- flatten_leaves(parsed_dataset) + if(nrow(parsed_dataset) == 0) { + return (deparse_data(flatten_pd)) + } + result_flatten_pd <- ma_one_flatten_pd(parsed_dataset, flatten_pd) + return(deparse_data(result_flatten_pd)) +} + +# Restricts the operations of the optimizer to the body of a single `FOR` loop in code snippets containing nested `FOR` loops +# +# @param body_fpd A parsed data.frame containing the `body` of a `FOR` loop +# +restrict_body_to_one_loop <- function(body_fpd) { + list_of_fors <- body_fpd[body_fpd$token == "FOR", "pos_id"] + if(length(list_of_fors) > 1) { + body_fpd <- body_fpd[body_fpd$pos_id < list_of_fors[[2]], ] + } + body_fpd +} + +# Restricts the operations of the optimizer to the condition of a single `FOR` loop in code snippets containing nested `FOR` loops +# +# @param cond_fpd A parsed data.frame containing the `condition` of a `FOR` loop +# +restrict_condition_to_one_loop <- function(cond_fpd) { + list_of_conds <- cond_fpd[cond_fpd$token == "forcond", "pos_id"] + if(length(list_of_conds) > 1) { + cond_fpd <- cond_fpd[cond_fpd$pos_id < list_of_conds[[2]], ] + } + cond_fpd +} + +# Returns the name of the vector being assigned in the given expression. Returns FALSE if no vector is being assigned. +# +# @param node_id A numeral indicating the node ID of the expression from which the vector's name is to be extracted +# @param fpd A flattened parsed data.frame of the original code snippet given +# +extract_vector_name <- function(fpd, node_id) { + assignment_pd <- get_children(fpd, node_id) + assignment_index <- which(assignment_pd$token %in% assigns) + if(length(assignment_index) > 0) { + assignment_type <- assignment_pd[assignment_index, "token"] + if(assignment_type == "EQ_ASSIGN" || assignment_type == "LEFT_ASSIGN") { + vector_name <- assignment_pd[(assignment_index - 1), "text"] + } else { + vector_name <- assignment_pd[(assignment_index + 1), "text"] + } + return (vector_name) + } else { + return (FALSE) + } +} + +# Checks whether a number is explicitly mentioned in the `condition` of a `FOR` loop. +# Returns the number mentioned if present, else returns `FALSE` +# +# @param cond_fpd A parsed data.frame containing the `condition` of a `FOR` loop +# @param index_name A string representing the name of the iterator of the `FOR` loop +# @param in_pos A numeric indicating the `pos_id` of `in` from the condition of a `FOR` loop +# +check_forcond_num_declaration <- function(cond_fpd, index_name, in_pos) { + forcond_number_flag <- FALSE + if("':'" %in% cond_fpd[cond_fpd$pos_id > in_pos, "token"]) { + colon_pos <- which(cond_fpd$token == "':'", arr.ind = TRUE) + colon_pos_id <- cond_fpd[colon_pos, "pos_id"] + if("NUM_CONST" %in% cond_fpd[cond_fpd$pos_id > colon_pos_id, "token"]) { + forcond_number_flag <- TRUE + cond_fpd <- cond_fpd[cond_fpd$pos_id > colon_pos_id, ] + memory_alloc_number <- as.integer(cond_fpd[cond_fpd$token == "NUM_CONST", "text"]) + } + } + else if("SYMBOL_FUNCTION_CALL" %in% cond_fpd[cond_fpd$pos_id > in_pos & + cond_fpd$text == "seq_len", "token"]) { + seqLen_pos_id <- cond_fpd[cond_fpd$pos_id > in_pos & + cond_fpd$token == "SYMBOL_FUNCTION_CALL" & + cond_fpd$text == "seq_len", "pos_id"] + if("NUM_CONST" %in% cond_fpd[cond_fpd$pos_id > seqLen_pos_id, "token"]) { + forcond_number_flag <- TRUE + cond_fpd <- cond_fpd[cond_fpd$pos_id > seqLen_pos_id, ] + memory_alloc_number <- as.integer(cond_fpd[cond_fpd$token == "NUM_CONST", "text"]) + } + forcond_number_flag <- FALSE + } + else if("SYMBOL_FUNCTION_CALL" %in% cond_fpd[cond_fpd$pos_id > in_pos & + cond_fpd$text == "c", "token"]) { + c_pos_id <- cond_fpd[cond_fpd$pos_id > in_pos & + cond_fpd$token == "SYMBOL_FUNCTION_CALL" & + cond_fpd$text == "c", "pos_id"] + if("NUM_CONST" %in% cond_fpd[cond_fpd$pos_id > c_pos_id, "token"]) { + forcond_number_flag <- TRUE + cond_fpd <- cond_fpd[cond_fpd$pos_id > c_pos_id, ] + memory_alloc_number <- 0 + for(i in cond_fpd$token) { + if(i == "NUM_CONST") { + memory_alloc_number = memory_alloc_number + 1 + } + } + } + forcond_number_flag <- FALSE + } + if(forcond_number_flag == TRUE) { + return (memory_alloc_number) + } else { + return (FALSE) + } +} + +# Checks for presence of functions in the body of the `FOR` loop +# +# @param body_fpd A parsed data.frame containing the `body` of a `FOR` loop +# +check_fun_call <- function(body_fpd) { + return ("SYMBOL_FUNCTION_CALL" %in% body_fpd$token) +} + +# Checks whether the index is explicitly mentioned at the time of assignment inside the loop +# Returns list of vectors that have the index, if none are present empty list is returned +# +# @param body_fpd A parsed data.frame containing the `body` of a `FOR` loop +# @param index_name A string representing the name of the iterator of the `FOR` loop +# +check_index_assignment <- function(body_fpd, index_name) { + assign_operator_list <- list() + assign_operator_list <- body_fpd[body_fpd$token %in% assigns, ] + check_index_assignment_list <- failed_list <- NULL + for(i in seq_len(length(assign_operator_list$id))) { + check_pos_id <- assign_operator_list[i, "pos_id"] + check_assign_op <- assign_operator_list[i, "token"] + check_parent_id <- assign_operator_list[i, "parent"] + + open_bracket_flag <- close_bracket_flag <- FALSE + name_of_vector <- vector(mode = "character", length = 1) + ##If = or <- is used, then we will search the expr before the assignment operator + if(check_assign_op == "EQ_ASSIGN" | check_assign_op == "LEFT_ASSIGN") { + assigned_vector_node_id <- body_fpd[body_fpd$token == "expr" & + body_fpd$parent == check_parent_id & + body_fpd$pos_id < check_pos_id, "id"] + + if(length(assigned_vector_node_id) == 0) { + return (check_index_assignment_list) + } + + parsed_assigned_expr <- get_children(body_fpd, assigned_vector_node_id) + + index_name_idx <- which(parsed_assigned_expr$text == index_name & + parsed_assigned_expr$token == "SYMBOL", arr.ind = TRUE) + + open_bracket_flag <- (parsed_assigned_expr[(index_name_idx - 1), "text"] == "[") + close_bracket_flag <- (parsed_assigned_expr[(index_name_idx + 1), "text"] == "]") + + open_bracket_index <- which(parsed_assigned_expr$token == "'['", arr.ind = TRUE) + name_of_vector <- parsed_assigned_expr[(open_bracket_index-1), "text"] + + } else { + assigned_vector_node_id <- body_fpd[body_fpd$token == "expr" & + body_fpd$parent == check_parent_id & + body_fpd$pos_id > check_pos_id, "id"] + + if(length(assigned_vector_node_id) == 0) { + return (check_index_assignment_list) + } + + parsed_assigned_expr <- get_children(body_fpd, assigned_vector_node_id) + + index_name_idx <- which(parsed_assigned_expr$text == index_name & + parsed_assigned_expr$token == "SYMBOL", arr.ind = TRUE) + + open_bracket_flag <- (parsed_assigned_expr[(index_name_idx - 1), "text"] == "[") + close_bracket_flag <- (parsed_assigned_expr[(index_name_idx + 1), "text"] == "]") + + open_bracket_index <- which(parsed_assigned_expr$token == "'['", arr.ind = TRUE) + name_of_vector <- parsed_assigned_expr[(open_bracket_index-1), "text"] + } + + if(open_bracket_flag & close_bracket_flag) { + check_index_assignment_list <- c(check_index_assignment_list, name_of_vector) + } else { + check_index_assignment_list <- c(check_index_assignment_list, name_of_vector) + failed_list <- c(failed_list, name_of_vector) + } + } + return (unique(check_index_assignment_list[!(check_index_assignment_list %in% failed_list)])) +} + +ma_one_flatten_pd <- function(parsed_dataset, flatten_pd) { + pd <- parsed_dataset + fpd <- flatten_pd + fpd <- eq_assign_to_expr(fpd) ## eq_assign_to_expr helps to convert the `EQ_ASSIGN` token to `expr` + + ## taking a different approach from simply calling get_children(fpd) and including all `expr`. + exam_nodes <- fpd[fpd$token == "expr", ] + exam_nodes <- rbind(exam_nodes, get_roots(fpd)) + exam_nodes <- unique(exam_nodes) + + ## custom_exam_nodes + ## custom_exam_nodes will contain only loops and the assignment nodes from the entire exam_nodes + null_parents <- fpd[fpd$token == "NULL_CONST", "parent"] + na_parents <- fpd[fpd$token == "NUM_CONST" & fpd$text == "NA", "parent"] + init_list <- c("c()", "numeric()", "logical()", "double()", "factor()", "integer()") + alternate_initialization_parents <- fpd[fpd$token == "expr" & fpd$text %in% init_list, "parent"] + + null_parents <- c(null_parents, c(alternate_initialization_parents, na_parents)) + + edit_nodes_list <- NULL + for(i in seq_len(length(alternate_initialization_parents))) { + alt_init_pd <- get_children(fpd, alternate_initialization_parents[i], FALSE) + expr_nodes <- alt_init_pd[alt_init_pd$token == "expr", "id"] + edit_nodes_list <- c(edit_nodes_list, expr_nodes) + } + + custom_exam_nodes <- NULL + for(i in null_parents) { + custom_exam_nodes <- rbind(custom_exam_nodes, fpd[fpd$id == i, ]) + } + for(i in exam_nodes$id) { + if(nrow(get_children(fpd, i)) > 1) { + if(get_children(fpd, i, FALSE)[1, "token"] == "FOR") { + custom_exam_nodes <- rbind(custom_exam_nodes, fpd[fpd$id == i, ]) + } + } + } + # Only order a *custom_exam_nodes* that is not NULL + if(!(is.null(custom_exam_nodes))) { + custom_exam_nodes <- custom_exam_nodes[order(custom_exam_nodes$pos_id), ] + } + + ## Here we analyse all the nodes of custom_exam_nodes that consists of assignemnts and FOR loops + vector_initialization_list <- list() + initializations_to_change <- NULL + vectors_memory_list <- list() + + ## if condition handles the loops from custom_exam_nodes and the else part handles the vector assignment from custom_exam_nodes + for(i in seq_len(length(custom_exam_nodes$id))) { + if(get_children(fpd, custom_exam_nodes[i, "id"], F)[1, "token"] == "FOR") { + i <- custom_exam_nodes[i, "id"] + sub_fpd <- get_children(fpd, i) + + cond_fpd <- get_children(sub_fpd, sub_fpd[sub_fpd$token=="forcond", "id"]) + cond_fpd <- restrict_condition_to_one_loop(cond_fpd) + + body_fpd <- sub_fpd[!(sub_fpd$id %in% cond_fpd$id), ] + body_fpd <- restrict_body_to_one_loop(body_fpd) + + in_pos <- cond_fpd[cond_fpd$token == "IN", "pos_id"] + index_name <- cond_fpd[cond_fpd$pos_id < in_pos & + cond_fpd$token == "SYMBOL", "text"] + + ## Checking condition 1: No function calls inside the loop, to counter the possibility of use of `<<-` in that particular function + fun_call_flag <- check_fun_call(body_fpd) + + ## Checking condition 2: A number being explicitly mentioned in the for condition, and isolating that number if condition is true + forcond_number_flag <- check_forcond_num_declaration(cond_fpd, index_name, in_pos) + if(typeof(forcond_number_flag) != "logical") { + memory_alloc_num <- forcond_number_flag + } + + ## Checking condition 3: The index should be mentioned specifically of the vector to which something is being assigned + passing_vectors <- check_index_assignment(body_fpd, index_name) + index_assignment_flag <- (length(passing_vectors) == 0) + + if(fun_call_flag | typeof(forcond_number_flag) == "logical" | index_assignment_flag) { + next + } else { + initializations_to_change <- c(initializations_to_change, passing_vectors) + initializations_to_change <- unique(initializations_to_change) + for(vec in passing_vectors) { + if(vec %in% names(vectors_memory_list)) { + if(vectors_memory_list[[vec]] < memory_alloc_num) { + vectors_memory_list[[vec]] <- memory_alloc_num + } else { + next + } + } else { + vectors_memory_list[[vec]] <- memory_alloc_num + } + } + } + } else{ + # Here we associate all the vectors (of the same name too) with the IDs with which they were initialized. + initialized_vector_name <- extract_vector_name(fpd, custom_exam_nodes[i, "id"]) + if(initialized_vector_name %in% names(vector_initialization_list)) { + vector_initialization_list[[initialized_vector_name]] <- c(vector_initialization_list[[initialized_vector_name]], custom_exam_nodes[i, "id"]) + } else { + vector_initialization_list[[initialized_vector_name]] <- custom_exam_nodes[i, "id"] + } + } + } + +# Now the id of the nodes that have to be edited is stored in vector_initialization_list, and when we remove those nodes from exam_nodes we have the not_to_edit fpd + + for(i in seq_len(length(vector_initialization_list))) { + if(names(vector_initialization_list)[i] %in% names(vectors_memory_list)) { + edit_nodes_list <- c(edit_nodes_list, vector_initialization_list[[i]]) + } + } + + not_to_edit <- exam_nodes[!(exam_nodes$id %in% edit_nodes_list), ] + not_to_edit_copy <- not_to_edit + + for(i in seq_len(length(not_to_edit_copy$id))) { + not_to_edit <- rbind(not_to_edit, get_children(fpd, not_to_edit_copy[i, "id"])) + } + + not_to_edit <- not_to_edit[order(not_to_edit$pos_id), ] + + not_to_edit <- unique(not_to_edit) + +# Creating the final_exam_nodes list from the `vector_initialization_list` and `vectors_memory_list` #### + + final_exam_nodes <- exam_nodes[exam_nodes$id %in% edit_nodes_list, ] + + to_remove_indices <- NULL + + ## Now we iterate over all the entries of final_exam_nodes and change their text + for(i in seq_len(length(final_exam_nodes$id))) { + name_of_vector <- extract_vector_name(fpd, final_exam_nodes[i, "id"]) + if(typeof(name_of_vector) == "logical") { + to_remove_indices <- c(to_remove_indices, i) + } else { + final_exam_nodes[i, "text"] <- sprintf("%s <- vector(length = %d)", name_of_vector, vectors_memory_list[[name_of_vector]]) + } + } + if(!(is.null(to_remove_indices))) { + final_exam_nodes <- final_exam_nodes[-(to_remove_indices), ] + } + +# Merging the final_exam_nodes and the not_to_edit and subsequent cleaning operations #### + +final_exam_nodes_ids <- final_exam_nodes$id + + new_fpd <- NULL + for(itr in seq_len(length(final_exam_nodes$id))) { + act_fpd <- final_exam_nodes[itr, ] + new_act_fpd <- parse_text(act_fpd$text) + + #Setting new ids for the newly edited and parsed codes + new_act_fpd$id <- paste0(act_fpd$id, "_", new_act_fpd$id) + + #Keeping old parents for new fpd + new_act_fpd$parent[new_act_fpd$parent != 0] <- paste0(act_fpd$id, "_", new_act_fpd$parent[new_act_fpd$parent != 0]) + new_act_fpd$parent[new_act_fpd$parent == 0] <- act_fpd$parent + + #Calling a pre-wriiten rco::function.... + new_act_fpd$pos_id <- create_new_pos_id(act_fpd, nrow(new_act_fpd), act_fpd$id) + + #Fixing the next_spaces section of new_fpd + new_act_fpd$next_spaces[nrow(new_act_fpd)] <- act_fpd$next_spaces + + #Fixing the next_lines section of new_fpd + new_act_fpd$next_lines[nrow(new_act_fpd)] <- act_fpd$next_lines + + #Fixing the prev_spaces section of new_fpd + new_act_fpd$prev_spaces[which(new_act_fpd$terminal)[[1]]] <- act_fpd$prev_spaces + + #Merging the new_fpd and the act_fpd(obtained upon iteration) + new_fpd <- rbind(new_fpd, new_act_fpd) + + #Ordering the new_fpd according to the pos_id + new_fpd <- new_fpd[order(new_fpd$pos_id), ] + } + +# Final Steps of molding the not_to_edit and final_exam_nodes into the same fpd #### + + resultant_fpd <- rbind(not_to_edit, new_fpd) + resultant_fpd <- resultant_fpd[order(resultant_fpd$pos_id), ] + + test_fpd <- flatten_leaves(resultant_fpd) + + comments_ids <- curr_new_line_ids <- next_line_ids <- correction_nodes <- correction_ids <- NULL + + correction_nodes <- test_fpd[test_fpd$parent == 0 & is.na(test_fpd$next_spaces) & is.na(test_fpd$next_lines) & is.na(test_fpd$prev_spaces), ] + + for(i in correction_nodes$id){ + correction_ids <- append(correction_ids, which(test_fpd$id == i, arr.ind = TRUE)) + } + + test_fpd[which(is.na(test_fpd$next_lines)), "next_lines"] <- 0 + test_fpd[which(is.na(test_fpd$next_spaces)), "next_spaces"] <- 0 + test_fpd[which(is.na(test_fpd$prev_spaces)), "prev_spaces"] <- 0 + + comments_ids <- which(test_fpd$parent < 0) + correction_ids <- append(correction_ids, comments_ids) + + for(i in correction_ids){ + if((i-1) > 0){ + test_fpd[i-1, "next_lines"] <- 1 + }else{ + next + } + } + + test_fpd <- test_fpd[!(test_fpd$parent == 0 & test_fpd$terminal == FALSE), ] + deletion_nodes <- NULL + for(i in final_exam_nodes_ids){ + if(i %in% test_fpd$id){ + deletion_nodes <- rbind(deletion_nodes, get_children(test_fpd, i)) + } + } + + next_line_ids <- as.double(test_fpd[test_fpd$id %in% deletion_nodes$id & test_fpd$next_lines == 1, "pos_id"]) + + test_fpd <- test_fpd[!(test_fpd$id %in% deletion_nodes$id), ] + + for(i in next_line_ids){ + curr_new_line_ids <- append(curr_new_line_ids, + max(which(test_fpd$pos_id < i))) + } + + for (i in curr_new_line_ids) { + test_fpd[i, ]$next_lines <- 1 + } + + #Copying the behaviour of "';'" from fpd to test_fpd + if("';'" %in% test_fpd$token) { + semiColon_indices <- which("';'" == fpd$token) + for(i in seq_len(length(semiColon_indices))) { + fpd_nextSpaces <- fpd[semiColon_indices[i], "next_spaces"] + fpd_nextLines <- fpd[semiColon_indices[i], "next_lines"] + fpd_prevSpaces <- fpd[semiColon_indices[i], "prev_spaces"] + + fpd_id <- fpd[semiColon_indices[i], "id"] + testFpd_index <- which(fpd_id == test_fpd$id) + + test_fpd[testFpd_index, "next_spaces"] <- fpd_nextSpaces + test_fpd[testFpd_index, "next_lines"] <- fpd_nextLines + test_fpd[testFpd_index, "prev_spaces"] <- fpd_prevSpaces + } + } + + return(test_fpd) +} diff --git a/R/optimizers.R b/R/optimizers.R index a00e23f..fd68b05 100644 --- a/R/optimizers.R +++ b/R/optimizers.R @@ -2,6 +2,7 @@ #' #' List of all the optimizer functions: #' \itemize{ +#' \item Conditional Threading \code{\link{opt_cond_thread}} #' \item Constant Folding \code{\link{opt_constant_folding}} #' \item Constant Propagation \code{\link{opt_constant_propagation}} #' \item Dead Code Elimination \code{\link{opt_dead_code}} @@ -9,18 +10,21 @@ #' \item Dead Expression Elimination \code{\link{opt_dead_expr}} #' \item Common Subexpression Elimination \code{\link{opt_common_subexpr}} #' \item Loop-invariant Code Motion \code{\link{opt_loop_invariant}} +#' \item Memory Allocation \code{\link{opt_memory_alloc}} #' } #' #' @export #' all_optimizers <- list( + "Conditional Threading" = opt_cond_thread, "Constant Folding" = opt_constant_folding, "Constant Propagation" = opt_constant_propagation, "Dead Code Elimination" = opt_dead_code, "Dead Store Elimination" = opt_dead_store, "Dead Expression Elimination" = opt_dead_expr, "Common Subexpression Elimination" = opt_common_subexpr, - "Loop-invariant Code Motion" = opt_loop_invariant + "Loop-invariant Code Motion" = opt_loop_invariant, + "Memory Allocation" = opt_memory_alloc ) #' Max optimizers list. @@ -29,6 +33,7 @@ all_optimizers <- list( #' enabled. #' Note that using this optimizers could change the semantics of the program! #' \itemize{ +#' \item Conditional Threading \code{\link{opt_cond_thread}} #' \item Constant Folding \code{\link{opt_constant_folding}} #' \item Constant Propagation \code{\link{opt_constant_propagation}} #' \item Dead Code Elimination \code{\link{opt_dead_code}} @@ -36,11 +41,13 @@ all_optimizers <- list( #' \item Dead Expression Elimination \code{\link{opt_dead_expr}} #' \item Common Subexpression Elimination \code{\link{opt_common_subexpr}} #' \item Loop-invariant Code Motion \code{\link{opt_loop_invariant}} +#' \item Memory Allocation \code{\link{opt_memory_alloc}} #' } #' #' @export #' max_optimizers <- list( + "Conditional Threading" = opt_cond_thread, "Constant Folding" = function(texts) { opt_constant_folding(texts, fold_floats = TRUE, in_fun_call = TRUE) }, @@ -53,5 +60,6 @@ max_optimizers <- list( "Common Subexpression Elimination" = function(texts) { opt_common_subexpr(texts, in_fun_call = TRUE) }, - "Loop-invariant Code Motion" = opt_loop_invariant + "Loop-invariant Code Motion" = opt_loop_invariant, + "Memory Allocation" = opt_memory_alloc ) diff --git a/R/reporting-fun-files.R b/R/reporting-fun-files.R new file mode 100644 index 0000000..70d2b83 --- /dev/null +++ b/R/reporting-fun-files.R @@ -0,0 +1,62 @@ +#' Report possible optimizations in `.R` files. +#' +#' @param files A character vector with paths to files to optimize. +#' @param optimizers A named list of optimizer functions. +#' +#' @export +#' +generate_files_opt_report <- function(files, optimizers = rco:::all_optimizers) { + # An introductory message, as the function could take quite some time depending on the number and size of files + message("Sit back, this may take some time...") + # Initializations + final_result <- codes <- list() + # Read the contents of a file. 3 files will form a list/vector/df of size 3 + codes <- lapply(files, read_code_file) + # Naming each entry in the list + names(codes) <- files + # Setting up the progress bar + pb_outer <- utils::txtProgressBar(1, length(codes)*length(optimizers), style=3) + pb_itr <- 0 + # Probing the contents of each file: + for (i in seq_len(length(codes))) { + # Original code from the file + og_code <- codes[[i]] + # Remove whitespaces for comparsion purposes + og_code_no_ws <- gsub(" ", "", og_code) + # Initialize an empty vector + opt_used_in_i <- vector(mode = "character") + # Running each file through each of the optimizers specified + for(j in seq_along(optimizers)) { + #Incrementing the progress bar + pb_itr <- pb_itr + 1 + utils::setTxtProgressBar(pb_outer, pb_itr) + # act_optim is the current optimizer + act_optim <- optimizers[[j]] + # act_optim_name is the name of the current optimizer + act_optim_name <- names(optimizers)[[j]] + # Optimized code + opt_j_code <- act_optim(og_code) + # Remove whitespaces for comparsion purposes + opt_j_code_no_ws <- gsub(" ", "", opt_j_code[[1]]) + # If code was optimized, then append the optimizer name to the file + if(!isTRUE(all.equal(og_code_no_ws, opt_j_code_no_ws))) + opt_used_in_i <- append(opt_used_in_i, act_optim_name) + } + # Setting the name for the files in the resultant list + code_file_name <- names(codes)[i] + final_result[[code_file_name]] <- opt_used_in_i + } + # Removing all entries with `character(0)` or no optimizations + final_result <- final_result[lapply(final_result, length) > 0] + # If no optimization required + if(length(final_result) == 0) { + message("No more optimizations are required in your script(s)") + } else { #If optimization is required + cat("\n") + message(sprintf("`rco` found %d files for optimization", as.integer(length(final_result)))) + message("Use the `rco::rco_gui()` function for detailed comparsion") + message("Here are the file(s) that could be optimized along with the required optimizers:") + cat("\n") + print(final_result) + } +} diff --git a/R/reporting-fun-folder.R b/R/reporting-fun-folder.R new file mode 100644 index 0000000..1c08e9b --- /dev/null +++ b/R/reporting-fun-folder.R @@ -0,0 +1,31 @@ +#' Report possible optimizations in folder containing `.R` files. +#' +#' @param folder Path to a directory with files to optimize. +#' @param optimizers A named list of optimizer functions. +#' @param pattern An optional regular expression. Only file names which match +#' the regular expression will be optimized. +#' @param recursive A logical value indicating whether or not files +#' in subdirectories of `folder` should be optimized as well. +#' +#' @export +#' +generate_folder_opt_report <- function(folder, optimizers = all_optimizers, + pattern = "\\.R$", recursive = TRUE) { + #Check if given folder exists or not + if (!dir.exists(folder)) { + stop("The specified folder does not exist.") + } + #List out the files from the specified folder + to_opt_files <- list.files( + path = folder, pattern = pattern, full.names = TRUE, recursive = recursive + ) + #Ensure folder is non-empty + if (length(to_opt_files) == 0) { + return() + } + message("Files to check for optimization:") + message(paste(to_opt_files, collapse = "\n")) + #Run the files in the `generate_files_opt_report` function + generate_files_opt_report(files = to_opt_files, + optimizers = optimizers) +} \ No newline at end of file diff --git a/R/reporting-fun-text.R b/R/reporting-fun-text.R new file mode 100644 index 0000000..f5ec24d --- /dev/null +++ b/R/reporting-fun-text.R @@ -0,0 +1,16 @@ +#' Report possible optimizations in `.R` code snippet. +#' +#' @param text A character vector with the code to optimize. +#' @param optimizers A named list of optimizer functions. +#' +#' @export +#' +generate_text_opt_report <- function(text, optimizers = all_optimizers) { + #Convert the given text into a temporary file + input_code_snippet <- tempfile() + write_code_file(text, input_code_snippet) + message("Ignore the name that will be assigned to your text/code snippet") + cat("\n") + #Run the temporary file in the `generate_files_opt_report` function + generate_files_opt_report(input_code_snippet, optimizers) +} diff --git a/README.Rmd b/README.Rmd index 0b1c404..87b83a1 100644 --- a/README.Rmd +++ b/README.Rmd @@ -6,7 +6,7 @@ output: github_document [![CRAN status](https://www.r-pkg.org/badges/version/rco)](https://CRAN.R-project.org/package=rco) -[![Lifecycle: stable](https://img.shields.io/badge/lifecycle-stable-brightgreen.svg)](https://www.tidyverse.org/lifecycle/#stable) +[![Lifecycle: stable](https://img.shields.io/badge/lifecycle-stable-brightgreen.svg)](https://lifecycle.r-lib.org/articles/stages.html#stable) [![Travis build status](https://travis-ci.org/jcrodriguez1989/rco.svg?branch=master)](https://travis-ci.org/jcrodriguez1989/rco) [![AppVeyor build status](https://ci.appveyor.com/api/projects/status/github/jcrodriguez1989/rco?branch=master&svg=true)](https://ci.appveyor.com/project/jcrodriguez1989/rco) [![Coverage status](https://codecov.io/gh/jcrodriguez1989/rco/branch/master/graph/badge.svg)](https://codecov.io/github/jcrodriguez1989/rco?branch=master) @@ -17,7 +17,7 @@ Make your R code run faster! The `rco` project, from its start to version 1.0.0, was made possible by a [Google Summer of Code 2019 project](https://summerofcode.withgoogle.com/archive/2019/projects/6300906386096128/). -Thanks to the kind mentorship of [Dr. Yihui Xie](https://yihui.name/en/) and [Dr. Nicolås Wolovick](https://cs.famaf.unc.edu.ar/~nicolasw/). +Thanks to the kind mentorship of [Dr. Yihui Xie](https://yihui.org/en/) and [Dr. Nicolås Wolovick](https://cs.famaf.unc.edu.ar/~nicolasw/). ```{r echo=FALSE} library("rco") diff --git a/README.md b/README.md index 2a006f7..16b6362 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![CRAN status](https://www.r-pkg.org/badges/version/rco)](https://CRAN.R-project.org/package=rco) [![Lifecycle: -stable](https://img.shields.io/badge/lifecycle-stable-brightgreen.svg)](https://www.tidyverse.org/lifecycle/#stable) +stable](https://img.shields.io/badge/lifecycle-stable-brightgreen.svg)](https://lifecycle.r-lib.org/articles/stages.html#stable) [![Travis build status](https://travis-ci.org/jcrodriguez1989/rco.svg?branch=master)](https://travis-ci.org/jcrodriguez1989/rco) [![AppVeyor build @@ -15,7 +15,7 @@ status](https://ci.appveyor.com/api/projects/status/github/jcrodriguez1989/rco?b status](https://codecov.io/gh/jcrodriguez1989/rco/branch/master/graph/badge.svg)](https://codecov.io/github/jcrodriguez1989/rco?branch=master) -Make your R code run faster\! `rco` analyzes your code and applies +Make your R code run faster! `rco` analyzes your code and applies different optimization strategies that return an R code that runs faster. @@ -23,7 +23,7 @@ The `rco` project, from its start to version 1.0.0, was made possible by a [Google Summer of Code 2019 project](https://summerofcode.withgoogle.com/archive/2019/projects/6300906386096128/). -Thanks to the kind mentorship of [Dr. Yihui Xie](https://yihui.name/en/) +Thanks to the kind mentorship of [Dr. Yihui Xie](https://yihui.org/en/) and [Dr. Nicolås Wolovick](https://cs.famaf.unc.edu.ar/~nicolasw/). ## Installation @@ -46,39 +46,35 @@ Or install the development version from GitHub: `rco` can be used in three ways: - - Using the RStudio Addins - +- Using the RStudio Addins + 1. `Optimize active file`: Optimizes the file currently open in RStudio. It will apply the optimizers present in `all_optimizers`. - + 2. `Optimize selection`: Optimizes the code currently highlited in the RStudio Source Pane. It will apply the optimizers present in `all_optimizers`. - - Using the `shiny` GUIs - +- Using the `shiny` GUIs + 1. `rco_gui("code_optimizer")` opens a `shiny` interface in a browser. This GUI allows to easily optimize chunks of code. - + 2. `rco_gui("pkg_optimizer")` opens a `shiny` interface in a browser. This GUI allows to easily optimize R packages that are hosted at CRAN or GitHub. - - Using the R functions - +- Using the R functions + 1. Optimize some `.R` code files - - - + ``` r optimize_files(c("file_to_optimize_1.R", "file_to_optimize_2.R")) ``` - - 2. Optimize some code in a character vector - - - + + 1. Optimize some code in a character vector + ``` r code <- paste( "code_to_optimize <- 8 ^ 8 * 1918", @@ -87,11 +83,9 @@ Or install the development version from GitHub: ) optimize_text(code) ``` - - 3. Optimize all `.R` code files into a folder - - - + + 1. Optimize all `.R` code files into a folder + ``` r optimize_folder("~/myfolder_to_optimize", recursive = FALSE) ``` diff --git a/_pkgdown.yml b/_pkgdown.yml index 766647c..c0b0782 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -12,6 +12,8 @@ navbar: menu: - text: "Common Subexpression Elimination" href: articles/opt-common-subexpr.html + - text: "Conditional Threading" + href: articles/opt-cond-thread.html - text: "Constant Folding" href: articles/opt-constant-folding.html - text: "Constant Propagation" @@ -24,10 +26,14 @@ navbar: href: articles/opt-dead-store.html - text: "Loop-invariant Code Motion" href: articles/opt-loop-invariant.html + - text: "Memory Allocation" + href: articles/opt-memory-alloc.html - text: "Articles" menu: - text: "Contributing an Optimizer" href: articles/contributing-an-optimizer.html + - text: "Potential Optimizers" + href: articles/potential-optimizers.html - text: "Docker Image" href: articles/docker-readme.html - text: "News" diff --git a/docs/404.html b/docs/404.html index 691351c..35f1247 100644 --- a/docs/404.html +++ b/docs/404.html @@ -71,7 +71,7 @@ rco - 1.0.1 + 1.0.2 @@ -96,6 +96,9 @@
  • Common Subexpression Elimination
  • +
  • + Conditional Threading +
  • Constant Folding
  • @@ -114,6 +117,9 @@
  • Loop-invariant Code Motion
  • +
  • + Memory Allocation +
  • Contributing an Optimizer
  • +
  • + Potential Optimizers +
  • Docker Image
  • @@ -178,7 +187,7 @@

    Contents

    -

    Site built with pkgdown 1.5.1.

    +

    Site built with pkgdown 1.6.1.

    diff --git a/docs/CODE_OF_CONDUCT.html b/docs/CODE_OF_CONDUCT.html index 91b61ea..97f5771 100644 --- a/docs/CODE_OF_CONDUCT.html +++ b/docs/CODE_OF_CONDUCT.html @@ -71,7 +71,7 @@ rco - 1.0.1 + 1.0.2 @@ -96,6 +96,9 @@
  • Common Subexpression Elimination
  • +
  • + Conditional Threading +
  • Constant Folding
  • @@ -114,6 +117,9 @@
  • Loop-invariant Code Motion
  • +
  • + Memory Allocation +
  • Contributing an Optimizer
  • +
  • + Potential Optimizers +
  • Docker Image
  • @@ -228,7 +237,7 @@

    Contents

    -

    Site built with pkgdown 1.5.1.

    +

    Site built with pkgdown 1.6.1.

    diff --git a/docs/CONTRIBUTING.html b/docs/CONTRIBUTING.html index 8f21dd8..a00e315 100644 --- a/docs/CONTRIBUTING.html +++ b/docs/CONTRIBUTING.html @@ -71,7 +71,7 @@ rco - 1.0.1 + 1.0.2 @@ -96,6 +96,9 @@
  • Common Subexpression Elimination
  • +
  • + Conditional Threading +
  • Constant Folding
  • @@ -114,6 +117,9 @@
  • Loop-invariant Code Motion
  • +
  • + Memory Allocation +
  • Contributing an Optimizer
  • +
  • + Potential Optimizers +
  • Docker Image
  • @@ -217,7 +226,7 @@

    Contents

    -

    Site built with pkgdown 1.5.1.

    +

    Site built with pkgdown 1.6.1.

    diff --git a/docs/articles/contributing-an-optimizer.html b/docs/articles/contributing-an-optimizer.html index 44fea48..fa7dfcd 100644 --- a/docs/articles/contributing-an-optimizer.html +++ b/docs/articles/contributing-an-optimizer.html @@ -31,7 +31,7 @@ rco - 1.0.1 + 1.0.2 @@ -56,6 +56,9 @@
  • Common Subexpression Elimination
  • +
  • + Conditional Threading +
  • Constant Folding
  • @@ -74,6 +77,9 @@
  • Loop-invariant Code Motion
  • +
  • + Memory Allocation +
  • Contributing an Optimizer
  • +
  • + Potential Optimizers +
  • Docker Image
  • @@ -112,7 +121,8 @@ -
    + +
    @@ -174,39 +185,39 @@

    For testing, we use the testthat package. All the code for testing the new optimizer will go in a file named tests/testthat/test-opt_**optimizer_name**.R.

    Please create a test suite that covers a large percentage of possible use and border cases.

    As skeleton to use to develop the new optimizer’s test suite we propose to follow:

    -
    context("opt_**optimizer_name**")
    -
    -test_that("**test name**", {
    -  **...**
    -})
    -
    -**more test cases**
    +
    context("opt_**optimizer_name**")
    +
    +test_that("**test name**", {
    +  **...**
    +})
    +
    +**more test cases**

    Writing the vignette

    When developing a new optimizer, we create a vignette to explain it. This documentation will not only be a vignette of the package but will also be part of the rco website.

    For writing vignettes, we use the knitr and rmarkdown packages. The new vignette will go in a file named vignettes/opt-**optimizer-name**.Rmd, and must follow the skeleton:

    -
    ---
    -output: rmarkdown::html_vignette
    -title: **New Optimizer Name**
    -vignette: >
    -  %\VignetteIndexEntry{**New Optimizer Name**}
    -  %\VignetteEngine{knitr::rmarkdown}
    -  %\VignetteEncoding{UTF-8}
    ----
    -
    -# **New Optimizer Name**
    -
    -## Background
    -
    -## Example
    -
    -## Implementation
    -
    -## **Additional headings** (optional)
    -
    -## To-Do (optional)
    +
    ---
    +output: rmarkdown::html_vignette
    +title: **New Optimizer Name**
    +vignette: >
    +  %\VignetteIndexEntry{**New Optimizer Name**}
    +  %\VignetteEngine{knitr::rmarkdown}
    +  %\VignetteEncoding{UTF-8}
    +---
    +
    +# **New Optimizer Name**
    +
    +## Background
    +
    +## Example
    +
    +## Implementation
    +
    +## **Additional headings** (optional)
    +
    +## To-Do (optional)

    The Background section must introduce the reader to why this optimization provides improvements, and what it does. The Example section must give a real example to be optimized, and show the improvements it gave in terms of execution speed, memory usage, or others. The Implementation section must show the idea beneath the optimizer coding, this section intends to ease the understanding of the developed code if it is needed to be edited or improved. Then, as many sections as necessary can be included, where questions and challenges related to the optimizer are explained or commented. Finally, if a list of possible improvements for the optimizer were detailed, each of them should be discussed in the To-Do section.

    @@ -223,11 +234,11 @@

    NEWS.md

    A entry to the NEWS.md file should be added:

    -
    # rco **x.y.(z+1)**
    -
    -  - Adding **New Optimizer Name** optimizer.
    -
    -**...**
    +
    # rco **x.y.(z+1)**
    +
    +  - Adding **New Optimizer Name** optimizer.
    +
    +**...**

    @@ -260,7 +271,7 @@

    -

    Site built with pkgdown 1.5.1.

    +

    Site built with pkgdown 1.6.1.

    diff --git a/docs/articles/contributing-an-optimizer_files/anchor-sections-1.0/anchor-sections.css b/docs/articles/contributing-an-optimizer_files/anchor-sections-1.0/anchor-sections.css new file mode 100644 index 0000000..07aee5f --- /dev/null +++ b/docs/articles/contributing-an-optimizer_files/anchor-sections-1.0/anchor-sections.css @@ -0,0 +1,4 @@ +/* Styles for section anchors */ +a.anchor-section {margin-left: 10px; visibility: hidden; color: inherit;} +a.anchor-section::before {content: '#';} +.hasAnchor:hover a.anchor-section {visibility: visible;} diff --git a/docs/articles/contributing-an-optimizer_files/anchor-sections-1.0/anchor-sections.js b/docs/articles/contributing-an-optimizer_files/anchor-sections-1.0/anchor-sections.js new file mode 100644 index 0000000..570f99a --- /dev/null +++ b/docs/articles/contributing-an-optimizer_files/anchor-sections-1.0/anchor-sections.js @@ -0,0 +1,33 @@ +// Anchor sections v1.0 written by Atsushi Yasumoto on Oct 3rd, 2020. +document.addEventListener('DOMContentLoaded', function() { + // Do nothing if AnchorJS is used + if (typeof window.anchors === 'object' && anchors.hasOwnProperty('hasAnchorJSLink')) { + return; + } + + const h = document.querySelectorAll('h1, h2, h3, h4, h5, h6'); + + // Do nothing if sections are already anchored + if (Array.from(h).some(x => x.classList.contains('hasAnchor'))) { + return null; + } + + // Use section id when pandoc runs with --section-divs + const section_id = function(x) { + return ((x.classList.contains('section') || (x.tagName === 'SECTION')) + ? x.id : ''); + }; + + // Add anchors + h.forEach(function(x) { + const id = x.id || section_id(x.parentElement); + if (id === '') { + return null; + } + let anchor = document.createElement('a'); + anchor.href = '#' + id; + anchor.classList = ['anchor-section']; + x.classList.add('hasAnchor'); + x.appendChild(anchor); + }); +}); diff --git a/docs/articles/contributing-an-optimizer_files/header-attrs-2.5/header-attrs.js b/docs/articles/contributing-an-optimizer_files/header-attrs-2.5/header-attrs.js new file mode 100644 index 0000000..dd57d92 --- /dev/null +++ b/docs/articles/contributing-an-optimizer_files/header-attrs-2.5/header-attrs.js @@ -0,0 +1,12 @@ +// Pandoc 2.9 adds attributes on both header and div. We remove the former (to +// be compatible with the behavior of Pandoc < 2.8). +document.addEventListener('DOMContentLoaded', function(e) { + var hs = document.querySelectorAll("div.section[class*='level'] > :first-child"); + var i, h, a; + for (i = 0; i < hs.length; i++) { + h = hs[i]; + if (!/^h[1-6]$/i.test(h.tagName)) continue; // it should be a header h1-h6 + a = h.attributes; + while (a.length > 0) h.removeAttribute(a[0].name); + } +}); diff --git a/docs/articles/docker-readme.html b/docs/articles/docker-readme.html index 8f0f3b7..3b410dd 100644 --- a/docs/articles/docker-readme.html +++ b/docs/articles/docker-readme.html @@ -31,7 +31,7 @@ rco - 1.0.1 + 1.0.2
    @@ -56,6 +56,9 @@
  • Common Subexpression Elimination
  • +
  • + Conditional Threading +
  • Constant Folding
  • @@ -74,6 +77,9 @@
  • Loop-invariant Code Motion
  • +
  • + Memory Allocation +
  • Contributing an Optimizer
  • +
  • + Potential Optimizers +
  • Docker Image
  • @@ -112,7 +121,8 @@ -
    + +

    Usage

    The docker image basic usage will optimize a random CRAN package, this is done with:

    - +
    docker run jcrodriguez1989/rco

    We can also specify a package to optimize, using the RCO_PKG environment variable, for example, if we want to optimize the rflights package:

    - +
    docker run -e RCO_PKG=rflights jcrodriguez1989/rco

    Also, if we want to get the resulting optimized files we can set a docker shared folder, with:

    - +
    # Replace DEST_FOLDER path, with your desired output path
    +DEST_FOLDER=/tmp/rco_dock_res
    +docker run -v $DEST_FOLDER:/rco_results jcrodriguez1989/rco

    In summary, if we want to optimize the rflights package and save its results, we can do:

    - +
    docker run -e RCO_PKG=rflights -v $DEST_FOLDER:/rco_results jcrodriguez1989/rco

    And in the DEST_FOLDER we will have files as:

    - +
    ls $DEST_FOLDER
    +## rflights  rflights_0.1.0.tar.gz  rflights_opt
    @@ -170,7 +180,7 @@

    -

    Site built with pkgdown 1.5.1.

    +

    Site built with pkgdown 1.6.1.

    diff --git a/docs/articles/docker-readme_files/anchor-sections-1.0/anchor-sections.css b/docs/articles/docker-readme_files/anchor-sections-1.0/anchor-sections.css new file mode 100644 index 0000000..07aee5f --- /dev/null +++ b/docs/articles/docker-readme_files/anchor-sections-1.0/anchor-sections.css @@ -0,0 +1,4 @@ +/* Styles for section anchors */ +a.anchor-section {margin-left: 10px; visibility: hidden; color: inherit;} +a.anchor-section::before {content: '#';} +.hasAnchor:hover a.anchor-section {visibility: visible;} diff --git a/docs/articles/docker-readme_files/anchor-sections-1.0/anchor-sections.js b/docs/articles/docker-readme_files/anchor-sections-1.0/anchor-sections.js new file mode 100644 index 0000000..570f99a --- /dev/null +++ b/docs/articles/docker-readme_files/anchor-sections-1.0/anchor-sections.js @@ -0,0 +1,33 @@ +// Anchor sections v1.0 written by Atsushi Yasumoto on Oct 3rd, 2020. +document.addEventListener('DOMContentLoaded', function() { + // Do nothing if AnchorJS is used + if (typeof window.anchors === 'object' && anchors.hasOwnProperty('hasAnchorJSLink')) { + return; + } + + const h = document.querySelectorAll('h1, h2, h3, h4, h5, h6'); + + // Do nothing if sections are already anchored + if (Array.from(h).some(x => x.classList.contains('hasAnchor'))) { + return null; + } + + // Use section id when pandoc runs with --section-divs + const section_id = function(x) { + return ((x.classList.contains('section') || (x.tagName === 'SECTION')) + ? x.id : ''); + }; + + // Add anchors + h.forEach(function(x) { + const id = x.id || section_id(x.parentElement); + if (id === '') { + return null; + } + let anchor = document.createElement('a'); + anchor.href = '#' + id; + anchor.classList = ['anchor-section']; + x.classList.add('hasAnchor'); + x.appendChild(anchor); + }); +}); diff --git a/docs/articles/docker-readme_files/header-attrs-2.5/header-attrs.js b/docs/articles/docker-readme_files/header-attrs-2.5/header-attrs.js new file mode 100644 index 0000000..dd57d92 --- /dev/null +++ b/docs/articles/docker-readme_files/header-attrs-2.5/header-attrs.js @@ -0,0 +1,12 @@ +// Pandoc 2.9 adds attributes on both header and div. We remove the former (to +// be compatible with the behavior of Pandoc < 2.8). +document.addEventListener('DOMContentLoaded', function(e) { + var hs = document.querySelectorAll("div.section[class*='level'] > :first-child"); + var i, h, a; + for (i = 0; i < hs.length; i++) { + h = hs[i]; + if (!/^h[1-6]$/i.test(h.tagName)) continue; // it should be a header h1-h6 + a = h.attributes; + while (a.length > 0) h.removeAttribute(a[0].name); + } +}); diff --git a/docs/articles/gsoc2020-final-report.html b/docs/articles/gsoc2020-final-report.html new file mode 100644 index 0000000..a0555db --- /dev/null +++ b/docs/articles/gsoc2020-final-report.html @@ -0,0 +1,344 @@ + + + + + + + +GSoC 2020 Final Report ‱ rco + + + + + + + + + + +
    +
    + + + + +
    +
    +
    + + + + +
    +

    +My GSoC 2020 work in a nutshell

    +

    Phew
 So it’s already time for the end of GSoC 2020. Time sure flew by this summer. Now it’s the time for me to take a step back and ponder over my summer with The R Project for Statistical Computing. So, let’s touch upon my summer in a chronological fashion.

    +
    +

    +Humble Beginnings:

    +

    Around the time when The R Project for Statistical Computing announced their participation in GSoC 2020, a list of proposed projects were announced. The rco or the R Code Optimizer caught my attention at the very first moment and I didn’t look back since then.

    +

    Although the exsisting rco repository was extremely well maintained and documented, me being a newbie still faced quite a lot of difficulties and also, most probably gave my mentors, Dr. JC Rodriguez and Mauricio “Pachá” Vargas SepĂșlveda a hard time.

    +

    As part of the tests to be completed for being considered as a potential student developer for R with the rco project, I was required to build a completely functional and working optimizer.

    +

    I decided to work on a suggested optimizer that made column extraction faster and more efficient. After the benign guidance of my mentors, I was able to come up with an column extraction optimizer, that performed according to its expectation. This got me so excited that I worked on and built another value extraction optimizer without any explicit incentive :D

    + +
    +
    +

    +Post GSoC selection:

    +

    It was only a few days before my acceptance into the GSoC 2020 program, that my mentors gave their insights into both these optimizers and explained, how these optimizers can be broken, owing to the extreme flexibility that the R Language offers. Since, .subset2( ) is not a reserved keyword in the R Language, it could be over-written. Hence we can never be sure while optimizing, that the functions that are being called has not been re-defined by the user. However, this served as a great primer as to what to expect when GSoC officially began.

    +
    +
    +

    +Getting up to speed:

    +

    The next tasks that I were assigned were not too glorious/dramatic or blinge-y but they were essential steps for the upkeep of a package on CRAN. Firstly, I designed a vignette that enlisted all the future potential optimizers that can be implemented in the rco package. Next, I went on to collate the efforts put in the rco library by the Google Code-In students, by creating PRs that such that every student gets credit as a contributor and at the same time hand-picking the best of examples and explanations to include in the official rco documentation. It was a riveting experience for me, as I had to learn and implement some really advanced concepts of git and github to be able to pull this off, including, but not limited to, cherry-picking, resolving merge conficts, adding remotes, etc.

    + +
    +
    +

    +Unexpected Turn of Events:

    +

    As Dwight D. Eisenhower once said,

    +

    In preparing for battle I have always found that plans are useless, but planning is indispensable.

    +

    I had listed several optimization techniques that could have been implemented in rco in my GSoC proposal as well the potential optimizers vignette but when we got down to discussing the design of these optimizers, it became quite clear to me and my mentors that we were walking on thin ice. With the extreme constraints that are imposed due to R’s flexible nature, only a few of our optimization strategies seemed bullet-proof.

    +
    +

    +Conditional Threading Optimizer:

    +

    I went on ahead with the idea of Jump/Conditional Threading and started designing the optimizer. The objective was to replace if statments with else wherever possible and also to group together the code blocks of different if blocks if the if conditionals were same. This concept is succintly covered over here.

    +

    To see the speed-ups provided by this optimization strategy, have a look at this example:

    +
    +

    +Unoptimized Code

    +
    +cond_thread <- function(n) {
    +  evens <- 0
    +  evens_sum <- 0
    +  odds <- 0
    +  for (i in seq_len(n)) {
    +    if (i %% 2 == 0) { # same logical as next if condition (can be merged)
    +      evens <- evens + 1
    +    }
    +    if (i %% 2 == 0) {
    +      evens_sum <- evens_sum + i
    +    }
    +    if (!(i %% 2 == 0)) { # exact negation as previous if (can be an else)
    +      odds <- odds + 1
    +    }
    +  }
    +}
    +
    +
    +

    +Proposed Optimized Code

    +
    +cond_thread_opt <- function(n) {
    +  evens <- 0
    +  evens_sum <- 0
    +  odds <- 0
    +  for (i in seq_len(n)) {
    +    if (i %% 2 == 0) { # merged
    +      evens <- evens + 1
    +      evens_sum <- evens_sum + i
    +    } else { # converted to else
    +      odds <- odds + 1
    +    }
    +  }
    +}
    +
    +
    +

    +Benchmark

    +
    +n <- 100000
    +autoplot(microbenchmark(cond_thread(n), cond_thread_opt(n)))
    +

    + +
    +
    +
    +

    +Memory Allocation Optimizer:

    +

    Next, I set my eyes on the Memory Pre-Allocation optimization technique. The objective here was to save the R Programmers from the sin of growing a vector in loops. In our experience, we’ve seen that vector, lists, etc are initialized often with NULL or c() out of convenience. Bothering about the size of the vectors may seem trivial to the programmer, but it has a huge impact on the performance of the script.

    +

    To see the speed-ups provided by this optimization strategy, consider this example:

    +
    +

    +Unoptimized Code

    +
    +mem_alloc <- function(n) {
    +  vec <- NULL
    +  for (i in seq_len(n)) {
    +    vec[i] <- i
    +  }
    +}
    +
    +
    +

    +Proposed Optimized Code

    +
    +mem_alloc_opt <- function(n) {
    +  vec <- vector(length = n)
    +  for (i in seq_len(n)) {
    +    vec[i] <- i
    +  }
    +}
    +
    +
    +

    +Benchmark

    +
    +n <- 100000
    +autoplot(microbenchmark(mem_alloc(n), mem_alloc_opt(n)))
    +

    + +
    +
    +
    +
    +

    +Pivot and Push:

    +

    As mentioned in the last segment, we had pretty much exhausted all the possible optimization strategies, that could be implemented without breaking. Now, it was onto us to decide whether, to work on new optimizer, that had a non-zero chance of breaking or pivot and change objectives. Following the lead of my mentors, I decided to put on hold, the optimizers that could break and focus on further bolstering the rco package.

    +
    +

    +Fixing a critical issue:

    +

    Owing to the fantastic upkeep of the rco repository, it had almost no bugs in the optimizers, and that is no mean feat. However an issue reporting a bug was reported. The bug was that an optimizer, namely opt-dead-expr() did not function when the user used ;. Ideally, the optimizer shouldn’t have been affected by the usage of ;, but here it was, a bug, laying dormant for over 6 months.

    +

    I decided to tackle this bug head on. I created a separate branch for this bug and reproduced the bug. After lots of trials and tribulations and lots of reverse-engineering, I zeroed in one the problem. It was a problem that existed in very low-level R code. When a R script without the use of ; is parsed, then they get tokenized as something called expr but when ; was used, the parser tokenized them as an exprlist. The exsisting optimizer did not handle the case of exprlist, so I appended code that handled exprlist and then the optimizer started functioning normally irrespective of the usage of ;. This is an issue which could arise in other optimizers too, but given that, it has been solved in one optimizer, solving them in other optimizers would not prove challenging. Also, in the process of making the opt_dead_expr() optimizer work, I further bolstered the robustness of the optimizer by doubling the number of test cases that the optimizer must pass, including tests with ;.

    + +
    +
    +

    +Call for Actions:

    +

    To reduce dependencies on 3rd party applications and to promote homogeneity, we decided to go for an in-house solution for the CI/CD needs of our package, ie Github Actions. Earlier the popular choice for carrying out CI/CD operations were 3rd party applications such as Travis CI or codecov.io, but lately more and more developers and organizations had been migrating to Github Actions and we decided to follow suit. Github Marketplace wasn’t much help, as there were not much support for the R Language as compared to other languages such as Rust, Ruby, Typescript, etc. So, I went through several documentations and scarce examples and created a branch that renders a website and completes the testing that Travis used to do. We left out the code coverage as Github does not yet support badges that shows the percentage of coverage.

    +
    +
    +

    +Acting on users’ recommendations:

    +

    One of the users of rco opened an issue, with a feature request. The request that the user asked for, is as follows:

    +
    +

    I think it would be nifty to be able to create a report out of rco optimisers. The idea would be to analyse the whole code and return some kind of markdown / bookdown that will list all the results from the optimisers, without changing the > original code. It might be useful to analyse your own code and learn what you could do better, but also it could be use in the industry to analyse the quality of code.

    +
    +

    While actually implementing this idea, would have been a tall order, me and my mentors discussed that, as the first iteration we could work on creating a function, which could be called to check, exactly how many files from a folder or a group of files can be optimized and which optimizer is being used there. If interested, the user could run that specific file along with the optimizers named in the output, in the rco_gui() function and see the code difference.

    + +
    +
    +
    +

    +And, my GSoC ’20 journey comes to an end:

    +

    Well, I never thought that 3 months could pass in a jiffy, but these months when I was working under the R Language in this program, had been the golden months of my life. Never to be forgotten and to be re-lived time and again. I am proud and happy to state that, my GSoC experience and project was mostly a hit. Yes, we did face obstacles, but each and every time we emerged stronger. I have now become a life-long fan of open-source.

    +
    +

    Open source is simply put, magic. And, no one can ever get enough of magic, right >.<

    +
    +
    +
    +
    + + + +
    + + + +
    + +
    +

    Site built with pkgdown 1.6.1.

    +
    + +
    +
    + + + + + + diff --git a/docs/articles/gsoc2020-final-report_files/anchor-sections-1.0/anchor-sections.css b/docs/articles/gsoc2020-final-report_files/anchor-sections-1.0/anchor-sections.css new file mode 100644 index 0000000..07aee5f --- /dev/null +++ b/docs/articles/gsoc2020-final-report_files/anchor-sections-1.0/anchor-sections.css @@ -0,0 +1,4 @@ +/* Styles for section anchors */ +a.anchor-section {margin-left: 10px; visibility: hidden; color: inherit;} +a.anchor-section::before {content: '#';} +.hasAnchor:hover a.anchor-section {visibility: visible;} diff --git a/docs/articles/gsoc2020-final-report_files/anchor-sections-1.0/anchor-sections.js b/docs/articles/gsoc2020-final-report_files/anchor-sections-1.0/anchor-sections.js new file mode 100644 index 0000000..570f99a --- /dev/null +++ b/docs/articles/gsoc2020-final-report_files/anchor-sections-1.0/anchor-sections.js @@ -0,0 +1,33 @@ +// Anchor sections v1.0 written by Atsushi Yasumoto on Oct 3rd, 2020. +document.addEventListener('DOMContentLoaded', function() { + // Do nothing if AnchorJS is used + if (typeof window.anchors === 'object' && anchors.hasOwnProperty('hasAnchorJSLink')) { + return; + } + + const h = document.querySelectorAll('h1, h2, h3, h4, h5, h6'); + + // Do nothing if sections are already anchored + if (Array.from(h).some(x => x.classList.contains('hasAnchor'))) { + return null; + } + + // Use section id when pandoc runs with --section-divs + const section_id = function(x) { + return ((x.classList.contains('section') || (x.tagName === 'SECTION')) + ? x.id : ''); + }; + + // Add anchors + h.forEach(function(x) { + const id = x.id || section_id(x.parentElement); + if (id === '') { + return null; + } + let anchor = document.createElement('a'); + anchor.href = '#' + id; + anchor.classList = ['anchor-section']; + x.classList.add('hasAnchor'); + x.appendChild(anchor); + }); +}); diff --git a/docs/articles/gsoc2020-final-report_files/figure-html/cond_thread_thread_benchmark-1.png b/docs/articles/gsoc2020-final-report_files/figure-html/cond_thread_thread_benchmark-1.png new file mode 100644 index 0000000000000000000000000000000000000000..5fce67c999341aecf5faa7be12447766d8aabc8a GIT binary patch literal 54612 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 literal 0 HcmV?d00001 diff --git a/docs/articles/gsoc2020-final-report_files/figure-html/pre_allocate_benchmark-1.png b/docs/articles/gsoc2020-final-report_files/figure-html/pre_allocate_benchmark-1.png new file mode 100644 index 0000000000000000000000000000000000000000..5d3196a6fe9bcb42838608564eb7d04fbce408d7 GIT binary patch literal 50907 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 :first-child"); + var i, h, a; + for (i = 0; i < hs.length; i++) { + h = hs[i]; + if (!/^h[1-6]$/i.test(h.tagName)) continue; // it should be a header h1-h6 + a = h.attributes; + while (a.length > 0) h.removeAttribute(a[0].name); + } +}); diff --git a/docs/articles/index.html b/docs/articles/index.html index 6021931..1427a0a 100644 --- a/docs/articles/index.html +++ b/docs/articles/index.html @@ -71,7 +71,7 @@ rco - 1.0.1 + 1.0.2
    @@ -96,6 +96,9 @@
  • Common Subexpression Elimination
  • +
  • + Conditional Threading +
  • Constant Folding
  • @@ -114,6 +117,9 @@
  • Loop-invariant Code Motion
  • +
  • + Memory Allocation +
  • Contributing an Optimizer
  • +
  • + Potential Optimizers +
  • Docker Image
  • @@ -167,8 +176,12 @@

    All vignettes

    Docker file
    +
    GSoC 2020 Final Report
    +
    Common Subexpression Elimination
    +
    Conditional Threading Optimizer
    +
    Constant Folding
    Constant Propagation
    @@ -181,6 +194,10 @@

    All vignettes

    Loop-invariant Code Motion
    +
    Memory Allocation Optimizer
    +
    +
    Potential Optimizers
    +
    @@ -193,7 +210,7 @@

    All vignettes

    -

    Site built with pkgdown 1.5.1.

    +

    Site built with pkgdown 1.6.1.

    diff --git a/docs/articles/opt-common-subexpr.html b/docs/articles/opt-common-subexpr.html index 89745e8..bd61d76 100644 --- a/docs/articles/opt-common-subexpr.html +++ b/docs/articles/opt-common-subexpr.html @@ -31,7 +31,7 @@ rco - 1.0.1 + 1.0.2 @@ -56,6 +56,9 @@
  • Common Subexpression Elimination
  • +
  • + Conditional Threading +
  • Constant Folding
  • @@ -74,6 +77,9 @@
  • Loop-invariant Code Motion
  • +
  • + Memory Allocation +
  • Contributing an Optimizer
  • +
  • + Potential Optimizers +
  • Docker Image
  • @@ -112,7 +121,8 @@ -
    + +