From f27b0da5f4bb11689bbaedc5089ebdd3a6c9bc22 Mon Sep 17 00:00:00 2001 From: Markus Bointner Date: Sat, 21 Sep 2024 21:05:39 +0200 Subject: [PATCH] add alternative reduction execution order --- README.md | 7 ++++ cmd/root.go | 43 ++++++++++++--------- collection/filter.go | 11 ++++++ reduction/candidate/util.go | 14 ++++++- reduction/context/alg_context.go | 23 +++++++++++ reduction/context/run_context.go | 38 +++++++++++++++++- reduction/entry.go | 6 ++- reduction/reduction_loop.go | 66 ++++++++++++++++++++++++++++---- 8 files changed, 178 insertions(+), 30 deletions(-) create mode 100644 collection/filter.go create mode 100644 reduction/context/alg_context.go diff --git a/README.md b/README.md index 45b44e4..4b1aa6d 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,13 @@ SeRu currently supports: 2. `-t ` _Required_ Test script checking if the reduced file still kept the required property A test script **must return 0** when the property was kept and 1 (or any code) if the property was lost + 3. `-l ` + Programming language of the input file. Will be inferred from the file extension if omitted. + 4. `-s` + Use strategy isolation. + This mode will apply only one semantic strategy and try to reduce all returned candidates using the syntactic reducer. + Default mode: strategy combination + In strategy combination, all strategies are applied and combined to one "best candidate". Then this one candidate will be reduced by the syntactic reducer. # Future plans diff --git a/cmd/root.go b/cmd/root.go index 1b362bc..938c49e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -8,27 +8,32 @@ import ( "os" ) -var ( - inputFile, testScript, givenLanguage string - rootCmd = &cobra.Command{ - Use: "seru", - Short: "A tool to reduce a program while maintaining a property", - // TODO - Long: `TODO`, - Run: func(cmd *cobra.Command, args []string) { - err := reduction.StartReductionProcess(inputFile, testScript, givenLanguage) - if err != nil { - log.Fatal(err) - } - }, - Version: version.Version, - } -) +type Flags struct { + InputFile, TestScript, GivenLanguage string + UseStrategyIsolation bool +} + +var flags Flags + +var rootCmd = &cobra.Command{ + Use: "seru", + Short: "A tool to reduce a program while maintaining a property", + // TODO + Long: `TODO`, + Run: func(cmd *cobra.Command, args []string) { + err := reduction.StartReductionProcess(flags.InputFile, flags.TestScript, flags.GivenLanguage, flags.UseStrategyIsolation) + if err != nil { + log.Fatal(err) + } + }, + Version: version.Version, +} func init() { - rootCmd.PersistentFlags().StringVarP(&inputFile, "input", "i", "", "-i ") - rootCmd.PersistentFlags().StringVarP(&testScript, "test", "t", "", "-i ") - rootCmd.PersistentFlags().StringVarP(&givenLanguage, "lang", "l", "", "-l ") + rootCmd.PersistentFlags().StringVarP(&flags.InputFile, "input", "i", "", "-i ") + rootCmd.PersistentFlags().StringVarP(&flags.TestScript, "test", "t", "", "-i ") + rootCmd.PersistentFlags().StringVarP(&flags.GivenLanguage, "lang", "l", "", "-l ") + rootCmd.PersistentFlags().BoolVarP(&flags.UseStrategyIsolation, "strategy-isolation", "s", false, "") _ = rootCmd.MarkPersistentFlagRequired("input") _ = rootCmd.MarkPersistentFlagRequired("test") diff --git a/collection/filter.go b/collection/filter.go new file mode 100644 index 0000000..2885bef --- /dev/null +++ b/collection/filter.go @@ -0,0 +1,11 @@ +package collection + +func FilterSlice[T any](slice []T, predicate func(it T) bool) []T { + var result []T + for _, v := range slice { + if predicate(v) { + result = append(result, v) + } + } + return result +} diff --git a/reduction/candidate/util.go b/reduction/candidate/util.go index 17a74d8..c058ae3 100644 --- a/reduction/candidate/util.go +++ b/reduction/candidate/util.go @@ -1,9 +1,21 @@ package candidate -import "slices" +import ( + "github.com/mandoway/seru/collection" + "slices" +) func MinCandidate(candidates []CandidateWithSize) CandidateWithSize { return slices.MinFunc(candidates, func(a, b CandidateWithSize) int { return b.Size - a.Size }) } + +func MinCandidateP(candidates []*CandidateWithSize) *CandidateWithSize { + nonNilCandidates := collection.FilterSlice(candidates, func(it *CandidateWithSize) bool { + return it != nil + }) + return slices.MinFunc(nonNilCandidates, func(a, b *CandidateWithSize) int { + return b.Size - a.Size + }) +} diff --git a/reduction/context/alg_context.go b/reduction/context/alg_context.go new file mode 100644 index 0000000..4fdb525 --- /dev/null +++ b/reduction/context/alg_context.go @@ -0,0 +1,23 @@ +package context + +type SemanticApplicationMethod string + +const ( + ApplyFirstOnly SemanticApplicationMethod = "ApplyFirstOnly" + ApplyAllCombined SemanticApplicationMethod = "ApplyAllCombined" +) + +type AlgorithmContext struct { + applicationMethod SemanticApplicationMethod +} + +func NewAlgorithmContext(useIsolation bool) *AlgorithmContext { + var application SemanticApplicationMethod + if useIsolation { + application = ApplyFirstOnly + } else { + application = ApplyAllCombined + } + + return &AlgorithmContext{applicationMethod: application} +} diff --git a/reduction/context/run_context.go b/reduction/context/run_context.go index e20eb8d..1c9fb9c 100644 --- a/reduction/context/run_context.go +++ b/reduction/context/run_context.go @@ -1,6 +1,7 @@ package context import ( + "errors" "fmt" "github.com/mandoway/seru/files" "github.com/mandoway/seru/reduction/candidate" @@ -27,6 +28,9 @@ type RunContext struct { currentSemanticStrategy int semanticStrategiesTotal int + algorithmContext AlgorithmContext + forceExhaustedSemanticStrategies bool + bestResult *candidate.CandidateWithSize countTokens plugin.TokenCountFunction @@ -56,6 +60,28 @@ func (ctx *RunContext) SemanticStrategiesTotal() int { return ctx.semanticStrategiesTotal } +func (ctx *RunContext) SemanticApplicationMethod() SemanticApplicationMethod { + return ctx.algorithmContext.applicationMethod +} + +func (ctx *RunContext) SetExhaustedSemanticStrategies() { + if ctx.algorithmContext.applicationMethod == ApplyFirstOnly { + panic("Forcing strategy exhaustion is only supported with combined strategies") + } + ctx.forceExhaustedSemanticStrategies = true +} + +func (ctx *RunContext) ExhaustedSemanticStrategies() bool { + switch ctx.algorithmContext.applicationMethod { + case ApplyFirstOnly: + return ctx.currentSemanticStrategy >= ctx.semanticStrategiesTotal + case ApplyAllCombined: + return ctx.forceExhaustedSemanticStrategies + } + + panic(fmt.Sprintf("unknown algorithm method: %s", ctx.algorithmContext.applicationMethod)) +} + func (ctx *RunContext) CountTokens(bytes []byte) int { return ctx.countTokens(bytes) } @@ -68,6 +94,14 @@ func (ctx *RunContext) SemanticReduce(bytes []byte) ([][]byte, error) { return ctx.semanticReducer(bytes, ctx.currentSemanticStrategy) } +func (ctx *RunContext) SemanticReduceWithStrategy(bytes []byte, strategyIndex int) ([][]byte, error) { + if strategyIndex >= ctx.semanticStrategiesTotal { + return [][]byte{}, errors.New(fmt.Sprintf("no strategy available at index %d", strategyIndex)) + } + + return ctx.semanticReducer(bytes, strategyIndex) +} + func (ctx *RunContext) Language() string { return ctx.language } @@ -123,7 +157,7 @@ func (ctx *RunContext) saveCurrent() error { return nil } -func NewRunContext(givenLanguage, inputFilePath, testScriptPath string) (*RunContext, error) { +func NewRunContext(givenLanguage, inputFilePath, testScriptPath string, algoContext AlgorithmContext) (*RunContext, error) { // Copy input files reductionDir := fmt.Sprintf("%s%s", RunContextFolderPrefix, time.Now().Format(time.RFC3339)) err := os.Mkdir(reductionDir, 0750) @@ -181,6 +215,8 @@ func NewRunContext(givenLanguage, inputFilePath, testScriptPath string) (*RunCon semanticStrategiesTotal: semanticStrategiesSize, currentSemanticStrategy: 0, + algorithmContext: algoContext, + bestResult: bestCandidate, syntacticReducer: syntacticFunctions, diff --git a/reduction/entry.go b/reduction/entry.go index fde33d1..e4122ac 100644 --- a/reduction/entry.go +++ b/reduction/entry.go @@ -5,12 +5,14 @@ import ( "log" ) -func StartReductionProcess(inputFile, testScript, givenLanguage string) error { +func StartReductionProcess(inputFile, testScript, givenLanguage string, isolation bool) error { log.Println("SeRu - Syntactic & Semantic Reduction") log.Println() log.Printf("Creating new run context with (input=%s, test=%s, lang=%s)\n", inputFile, testScript, givenLanguage) + algorithmContext := context.NewAlgorithmContext(isolation) + log.Printf("Running algorithm with context %v\n", algorithmContext) - runCtx, err := context.NewRunContext(givenLanguage, inputFile, testScript) + runCtx, err := context.NewRunContext(givenLanguage, inputFile, testScript, *algorithmContext) if err != nil { return err } diff --git a/reduction/reduction_loop.go b/reduction/reduction_loop.go index e0caa29..aa93ea3 100644 --- a/reduction/reduction_loop.go +++ b/reduction/reduction_loop.go @@ -18,15 +18,13 @@ func RunMainReductionLoop(ctx *context.RunContext) error { // TODO wrap errors candidates := []*candidate.CandidateWithSize{ctx.BestResult()} - for ctx.CurrentSemanticStrategy() < ctx.SemanticStrategiesTotal() { + for !ctx.ExhaustedSemanticStrategies() { err := reduceSyntacticallyAndSaveResultIfBetter(ctx, candidates) if err != nil { return err } - // TODO apply all semantic strategies and combine the results before feeding the output to the syntactic reducer - // just keep the best result per strategy as it will be processed again and there is a chance to modify the next instance in the next iteration candidates, err = getCandidatesFromSemanticReduction(ctx) if err != nil { return err @@ -88,7 +86,7 @@ func reduceSyntacticallyAndSaveResultIfBetter(ctx *context.RunContext, reduction } func getCandidatesFromSemanticReduction(ctx *context.RunContext) ([]*candidate.CandidateWithSize, error) { - if ctx.CurrentSemanticStrategy() >= ctx.SemanticStrategiesTotal() { + if ctx.ExhaustedSemanticStrategies() { logging.LogSemantic("Exhausted all semantic strategies, aborting") return nil, nil } @@ -99,7 +97,17 @@ func getCandidatesFromSemanticReduction(ctx *context.RunContext) ([]*candidate.C return nil, err } - validCandidates, err := trySemanticStrategiesToFindValidCandidates(ctx, currentBytes) + var validCandidates []*candidate.CandidateWithSize + switch ctx.SemanticApplicationMethod() { + case context.ApplyAllCombined: + validCandidates, err = applySemanticStrategiesCombined(ctx, currentBytes) + break + case context.ApplyFirstOnly: + validCandidates, err = applyFirstSemanticStrategy(ctx, currentBytes) + break + default: + panic("Unknown application method") + } if err != nil { return nil, err } @@ -111,9 +119,10 @@ func getCandidatesFromSemanticReduction(ctx *context.RunContext) ([]*candidate.C return validCandidates, nil } -func trySemanticStrategiesToFindValidCandidates(ctx *context.RunContext, currentBytes []byte) ([]*candidate.CandidateWithSize, error) { +func applyFirstSemanticStrategy(ctx *context.RunContext, currentBytes []byte) ([]*candidate.CandidateWithSize, error) { + logging.LogSemantic("Trying strategies one by one") var validCandidates []*candidate.CandidateWithSize - for len(validCandidates) == 0 && ctx.CurrentSemanticStrategy() < ctx.SemanticStrategiesTotal() { + for len(validCandidates) == 0 && !ctx.ExhaustedSemanticStrategies() { logging.LogSemantic("Trying strategy", ctx.CurrentSemanticStrategy()+1, "of", ctx.SemanticStrategiesTotal()) candidates, err := ctx.SemanticReduce(currentBytes) if err != nil { @@ -132,3 +141,46 @@ func trySemanticStrategiesToFindValidCandidates(ctx *context.RunContext, current } return validCandidates, nil } + +func applySemanticStrategiesCombined(ctx *context.RunContext, currentBytes []byte) ([]*candidate.CandidateWithSize, error) { + logging.LogSemantic("Trying strategies and combine results") + var bestCandidate *candidate.CandidateWithSize + currentStrategy := 0 + + for currentStrategy < ctx.SemanticStrategiesTotal() { + logging.LogSemantic("Trying strategy", currentStrategy+1, "of", ctx.SemanticStrategiesTotal()) + var bytesToReduce []byte + var err error + if bestCandidate == nil { + bytesToReduce = currentBytes + } else { + bytesToReduce, err = os.ReadFile(bestCandidate.InputPath) + if err != nil { + return nil, err + } + } + candidates, err := ctx.SemanticReduceWithStrategy(bytesToReduce, currentStrategy) + if err != nil { + return nil, err + } + logging.LogSemantic("Found candidates:", len(candidates)) + + validCandidates := persistance.CheckAndKeepValidCandidates(candidates, ctx) + + if len(validCandidates) > 0 { + logging.LogSemantic("Valid candidates:", len(validCandidates)) + logging.LogSemantic("Setting minimum as new intermediate best") + bestCandidate = candidate.MinCandidateP(validCandidates) + } else { + logging.LogSemantic("No valid candidates left after check, try next strategy") + } + currentStrategy++ + } + + if bestCandidate == nil { + ctx.SetExhaustedSemanticStrategies() + return []*candidate.CandidateWithSize{}, nil + } + + return []*candidate.CandidateWithSize{bestCandidate}, nil +}