From ac3b0a8f86e1df403419560c044d1cfc247ca127 Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Fri, 11 Nov 2022 16:11:50 -0500 Subject: [PATCH 01/15] Very experimental and slow negation idea --- share/prolog/oorules/guess.pl | 27 +++++++++++++++++++++++++++ share/prolog/oorules/setup.pl | 5 ++++- share/prolog/oorules/util.pl | 27 +++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/share/prolog/oorules/guess.pl b/share/prolog/oorules/guess.pl index 60781d2c..a481e19f 100644 --- a/share/prolog/oorules/guess.pl +++ b/share/prolog/oorules/guess.pl @@ -1664,6 +1664,33 @@ not(factNOTRealDestructor(RealDestructor)), true. +tryNegation(G) :- + loginfoln('Guessing ~Q.', negation_commit(G)), + try_assert(negation_commit(G)), + abolish_all_tables. + +tryNOTNegation(G) :- + loginfoln('Guessing ~Q.', negation_fail(G)), + try_assert(negation_fail(G)). + +tryOrNOTNegation(G) :- + %likelyDeletingDestructor(Method, _RealDestructor), + doNotGuessHelper(negation_commit(G), + negation_fail(G)), + ( + tryNegation(G); + tryNOTNegation(G); + logwarnln('Something is wrong upstream: ~Q.', negation(Method)), + fail + ). + +guessNegation(Out) :- + reportFirstSeen('guessNegation'), + retract(negation_queue(G)), + % XXX What happens if G is no longer true? + G, + Out = tryOrNOTNegation(G). + %% Local Variables: %% mode: prolog %% End: diff --git a/share/prolog/oorules/setup.pl b/share/prolog/oorules/setup.pl index d6fdb22a..9484a084 100644 --- a/share/prolog/oorules/setup.pl +++ b/share/prolog/oorules/setup.pl @@ -579,7 +579,10 @@ % class that we have not identified a base class for. guessCommitClassHasNoBase(Out); % Same thing for Derived classes - guessCommitClassHasNoDerived(Out) + guessCommitClassHasNoDerived(Out); + + % Commit to a negation + guessNegation(Out) )), diff --git a/share/prolog/oorules/util.pl b/share/prolog/oorules/util.pl index e77fda83..21c41437 100644 --- a/share/prolog/oorules/util.pl +++ b/share/prolog/oorules/util.pl @@ -5,6 +5,33 @@ :- use_module(library(lists), [append/3, nth1/4, list_to_set/2]). +:- dynamic negation_queue/1 as incremental. +:- dynamic negation_fail/1 as incremental. +:- dynamic negation_commit/1 as incremental. + +:- table negation_helper/1 as opaque. + +negation_helper(G) :- + var(G) -> throw_with_backtrace(user_error(negation_helper)). + +% We've already committed to G +negation_helper(G) :- + negation_commit(G), + logdebugln('Already committed ~Q', G), + !. + +% G isn't true right now. +negation_helper(G) :- + not(G), !, fail. + +% Queue G and fail. +negation_helper(G) :- + !, + negation_queue(G) + -> fail + ; logdebugln('I am queueing negation ~Q', G), assert(negation_queue(G)), logdebugln('done'), fail. + + sort_tuple((A,B), (C,D)) :- (A < B -> (C=A, D=B); (C=B, D=A)). From 11bfa78f038ec71910819c5bb4aab23efacba570 Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Tue, 15 Nov 2022 15:50:32 -0500 Subject: [PATCH 02/15] Improve tabling --- share/prolog/oorules/util.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/prolog/oorules/util.pl b/share/prolog/oorules/util.pl index 21c41437..c04eae5e 100644 --- a/share/prolog/oorules/util.pl +++ b/share/prolog/oorules/util.pl @@ -5,7 +5,7 @@ :- use_module(library(lists), [append/3, nth1/4, list_to_set/2]). -:- dynamic negation_queue/1 as incremental. +:- dynamic negation_queue/1. :- dynamic negation_fail/1 as incremental. :- dynamic negation_commit/1 as incremental. From 77dd0bb4782cfe2522ce6f5c2080c899483a6c10 Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Tue, 15 Nov 2022 16:04:01 -0500 Subject: [PATCH 03/15] Add a few negation helpers --- share/prolog/oorules/rules.pl | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/share/prolog/oorules/rules.pl b/share/prolog/oorules/rules.pl index d3707ea6..e577682d 100644 --- a/share/prolog/oorules/rules.pl +++ b/share/prolog/oorules/rules.pl @@ -758,7 +758,7 @@ % There is some offset for which there is a single vftable write and a thiscall to the same % offset factVFTableWrite(WriteAddr, Method, Offset, VFTable), - not((factVFTableWrite(_, Method, Offset, VFTable2), iso_dif(VFTable, VFTable2))), + negation_helper(not((factVFTableWrite(_, Method, Offset, VFTable2), iso_dif(VFTable, VFTable2)))), methodCallAtOffset(CallAddr, Method, Callee, Offset), % ejs 1/08/21: I believe that Offset can not be negative for a constructor or destructor @@ -996,7 +996,7 @@ % directly instantiated, because the "no other class trying to install this vftable" will % be trivially true, and without this clause, the vftable will simply belong to an % arbitrary method that installs it. - not(factVFTableOverwrite(Method, VFTable, _OverwriteVFTable, Offset)), + negation_helper(not(factVFTableOverwrite(Method, VFTable, _OverwriteVFTable, Offset))), % Constructors may inline embedded constructors. If non-offset % zero, we must make sure that there is an inherited class at this @@ -1029,7 +1029,7 @@ % embedded objects at offset 0, then we must own the vftable. (Offset = 0, (factNOTEmbeddedObject(Class, _AnyClass, 0); % Not ideal... - not(factEmbeddedObject(Class, _AnyClass2, 0))))), + negation_helper(not(factEmbeddedObject(Class, _AnyClass2, 0)))))), % VFTables from a base class can be reused in a derived class. If this happens, we know % that the VFTable does not belong to the derived class. @@ -1059,6 +1059,7 @@ % Alternatively, if we are a destructor, make sure there is no other class trying to % install this vftable % XXX: Should Offset = Offset2? + % Use negation_helper? (forall(factVFTableWrite(_Insn5, Method2, Offset2, VFTable), % It is ok to ignore overwritten vftables (factVFTableOverwrite(Method2, VFTable, _OtherVFTable, Offset2); @@ -1095,7 +1096,7 @@ % directly instantiated, because the "no other class trying to install this vftable" will % be trivially true, and without this clause, the vftable will simply belong to an % arbitrary method that installs it. - not(factVFTableOverwrite(Method, VFTable, _OverwriteVFTable, Offset)), + negation_helper(not(factVFTableOverwrite(Method, VFTable, _OverwriteVFTable, Offset))), % Constructors may inline embedded constructors. If non-offset % zero, we must make sure that there is an inherited class at this @@ -1128,7 +1129,7 @@ % embedded objects at offset 0, then we must own the vftable. (Offset = 0, (factNOTEmbeddedObject(Class, _AnyClass, 0); % Not ideal... - not(factEmbeddedObject(Class, _AnyClass2, 0))))), + negation_helper(not(factEmbeddedObject(Class, _AnyClass2, 0)))))), % VFTables from a base class can be reused in a derived class. If this happens, we know % that the VFTable does not belong to the derived class. From cb43b866c3d08a3888c392374ea1eafe7e795ebd Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Tue, 15 Nov 2022 16:10:45 -0500 Subject: [PATCH 04/15] A few more negation helpers --- share/prolog/oorules/rules.pl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/share/prolog/oorules/rules.pl b/share/prolog/oorules/rules.pl index e577682d..83796221 100644 --- a/share/prolog/oorules/rules.pl +++ b/share/prolog/oorules/rules.pl @@ -1546,7 +1546,7 @@ % Prevent grand ancestors from being decalred object in object. See commentary below. % It's unclear of this constraint is really required in cases where Offset is non-zero. - not(reasonClassRelationship(OuterClass, InnerClass)), + negation_helper(not(reasonClassRelationship(OuterClass, InnerClass))), % Debugging logtraceln('~@~Q.', [not(factObjectInObject(OuterClass, InnerClass, Offset)), @@ -1577,7 +1577,8 @@ % same class. And there doesn't appear be any downside either since there's already an % ObjectInObject at the appropriate offset, and we can reach the right conclusions through % those later class merges. - not(factObjectInObject(OuterClass, _, Offset)), + % negation: hmm, this rule sounds weird. + negation_helper(not(factObjectInObject(OuterClass, _, Offset))), factConstructor(InnerConstructor), iso_dif(InnerConstructor, OuterConstructor), @@ -1778,11 +1779,11 @@ factVFTableWrite(_Insn1, DerivedConstructor, ObjectOffset, DerivedVFTable), % No one overwrites the vftable - not(factVFTableOverwrite(DerivedConstructor, DerivedVFTable, _OverwrittenDerivedVFTable, ObjectOffset)), + negation_helper(not(factVFTableOverwrite(DerivedConstructor, DerivedVFTable, _OverwrittenDerivedVFTable, ObjectOffset))), ((factVFTableWrite(_Insn2, BaseConstructor, 0, BaseVFTable), % No one overwrites the vftable - not(factVFTableOverwrite(BaseConstructor, BaseVFTable, _OverwrittenBaseVFTable, 0)), + negation_helper(not(factVFTableOverwrite(BaseConstructor, BaseVFTable, _OverwrittenBaseVFTable, 0))), % And the vtables values written were different iso_dif(DerivedVFTable, BaseVFTable)); % Right now we assume that if a class inherits from an imported class, the base class is @@ -1797,7 +1798,7 @@ find(BaseConstructor, BaseClass), % There's not already a relationship. (Prevent grand ancestors) - not(reasonClassRelationship(DerivedClass, BaseClass)), + negation_helper(not(reasonClassRelationship(DerivedClass, BaseClass))), % Debugging logtraceln('~@DEBUG Derived VFTable: ~Q~n Base VFTable: ~Q~n Derived Constructor: ~Q~n Base Constructor: ~Q', @@ -1946,7 +1947,7 @@ factConstructor(DerivedConstructor), % The derived constructor does not write a vftable at offset 0 - not(factVFTableWrite(_Insn, DerivedConstructor, 0, _DVFTable)), + negation_helper(not(factVFTableWrite(_Insn, DerivedConstructor, 0, _DVFTable))), % The base class has a primary vftable find(BaseConstructor, BaseClass), From 0a6ce7a7f1c7370471c10d212a1b5ea84fbacc64 Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Tue, 15 Nov 2022 16:16:45 -0500 Subject: [PATCH 05/15] more negation --- share/prolog/oorules/rules.pl | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/share/prolog/oorules/rules.pl b/share/prolog/oorules/rules.pl index 83796221..3f473fd0 100644 --- a/share/prolog/oorules/rules.pl +++ b/share/prolog/oorules/rules.pl @@ -2302,10 +2302,10 @@ % ejs 2/12/21 Adding a more conservative (but possibly unnecessary) check for ANY object at % offset 0 (instead of Class1). - not((find(Function, FunctionClass), factObjectInObject(FunctionClass, _InnerClass1, 0))), + negation_helper(not((find(Function, FunctionClass), factObjectInObject(FunctionClass, _InnerClass1, 0)))), % We also need to verify that Class2 has no object at 0. - not((factObjectInObject(Class2, _InnerClass2, 0))), + negation_helper(not((factObjectInObject(Class2, _InnerClass2, 0)))), % Functions that are methods can call base methods @@ -2513,10 +2513,10 @@ find(VFTable1, Class1), find(VFTable2, Class2), iso_dif(Class1, Class2), - not(( + negation_helper(not(( reasonClassRelationship(Class1, Class2); reasonClassRelationship(Class2, Class1) - )), + ))), logtraceln('~@~Q.', [ not(factReusedImplementation(Method)), @@ -2536,10 +2536,10 @@ %possiblyReused(Method), factMethodInVFTable(VFTable1, _Offset1, Method), factMethodInVFTable(VFTable2, _Offset2, Method), - not(( + negation_helper(not(( reasonClassRelationship(Class1, Class2); reasonClassRelationship(Class2, Class1) - )), + ))), logtraceln('~@~Q.', [ not(factReusedImplementation(Method)), @@ -2649,7 +2649,7 @@ % Which has a Method factMethodInVFTable(BaseVFTable, _Offset, Method), not(purecall(Method)), - not(factReusedImplementation(Method)), + negation_helper(not(factReusedImplementation(Method))), % We don't have to check purecall because factMethodInVFTable does already @@ -2675,14 +2675,14 @@ % If we have no bases, it can't be on a base class. factClassHasNoBase(Class), % And if there's no object (embedded or base?) at offset zero... - not(factObjectInObject(Class, _0InnerClass, 0)), + negation_helper(not(factObjectInObject(Class, _0InnerClass, 0))), find(Method, ExistingClass), iso_dif(Class, ExistingClass), % Confusingly, the method's class must also have no base and no object at offset zero, % because the method being called could actually be the base class method... factClassHasNoBase(ExistingClass), - not(factObjectInObject(ExistingClass, _0InnerClass, 0)), + negation_helper(not(factObjectInObject(ExistingClass, _0InnerClass, 0))), % Debugging logtraceln('~@~Q.', [not(find(Class, ExistingClass)), @@ -2751,7 +2751,7 @@ % need to sum the sizes of the base class vftables, be confident in their layout order, and % no that there aren't any other complexities involving multiple inheritance. In the mean % time, just disable this rule where there's more than one base class. - not((factDerivedClass(DerivedClass, OtherBase, _OtherOffset), iso_dif(OtherBase, BaseClass))), + negation_helper(not((factDerivedClass(DerivedClass, OtherBase, _OtherOffset), iso_dif(OtherBase, BaseClass)))), findVFTable(DerivedVFTable, 0, DerivedClass), findVFTable(BaseVFTable, 0, BaseClass), @@ -2760,7 +2760,7 @@ % There's an entry in the derived vftable that's to big to be in the base vftable. factVFTableEntry(DerivedVFTable, VOffset, Method), not(purecall(Method)), - not(factReusedImplementation(Method)), + negation_helper(not(factReusedImplementation(Method))), VOffset > BaseSize, find(Method, MethodClass), iso_dif(DerivedClass, MethodClass), @@ -2995,8 +2995,8 @@ % But one counter example that we need to protect against is the inlining of base class % VFTable writes. The intention here is very similar to factVFTableOverwrite, but without % the complications of caring which value overwrote which other value. - not((factVFTableWrite(_Insn3, Method1, 0, VFTable2))), - not((factVFTableWrite(_Insn4, Method2, 0, VFTable1))), + negation_helper(not((factVFTableWrite(_Insn3, Method1, 0, VFTable2)))), + negation_helper(not((factVFTableWrite(_Insn4, Method2, 0, VFTable1)))), % Debugging logtraceln('~@~Q.', [not(dynFactNOTMergeClasses(Class1, Class2)), reasonNOTMergeClasses_E(Class1, Class2)]). @@ -3176,6 +3176,7 @@ factMethod(Method), iso_dif(Method, Caller), (factConstructor(Method); + % XXX negation_helper here? (factDeletingDestructor(Method), not(factRealDestructor(Caller))); factRealDestructor(Method)), % Handle symmetry @@ -3447,7 +3448,7 @@ % Ideally we'd want a stronger statement here to ensure there is no embedded object. But % at least make sure we don't currently have one. - not(factObjectInObject(_Derived, Class, 0)), + negation_helper(not(factObjectInObject(_Derived, Class, 0))), Debug=nobaseorderived. From ecff8f150ff2490cd35fff1bbb090a41384690e5 Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Wed, 16 Nov 2022 09:22:50 -0500 Subject: [PATCH 06/15] Delay negations in guessing --- share/prolog/oorules/guess.pl | 44 ++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/share/prolog/oorules/guess.pl b/share/prolog/oorules/guess.pl index a481e19f..98926e4a 100644 --- a/share/prolog/oorules/guess.pl +++ b/share/prolog/oorules/guess.pl @@ -136,7 +136,7 @@ reportFirstSeen('guessVirtualFunctionCall'), minof((Insn, Constructor, OOffset, VFTable, VOffset), (likelyVirtualFunctionCall(Insn, Constructor, OOffset, VFTable, VOffset), - not(factNOTConstructor(Constructor)), + negation_helper(not(factNOTConstructor(Constructor))), doNotGuessHelper( factVirtualFunctionCall(Insn, Constructor, OOffset, VFTable, VOffset), factNOTVirtualFunctionCall(Insn, Constructor, OOffset, VFTable, VOffset)))), @@ -183,7 +183,7 @@ % ordering). osetof(VFTable, (possibleVFTable(VFTable), - not(factNOTVFTable(VFTable)), + negation_helper(not(factNOTVFTable(VFTable))), doNotGuessHelper(factVFTable(VFTable), factNOTVFTable(VFTable))), VFTableSet), @@ -701,13 +701,13 @@ guessClassHasNoDerivedA(Class) :- factConstructor(Constructor), - not(( + negation_helper(not(( factConstructor(OtherConstructor), validMethodCallAtOffset(_Insn, OtherConstructor, Constructor, _AnyOffset) - )), + ))), find(Constructor, Class), - not(factDerivedClass(_DerivedClass, Class, _SomeOffset)), + negation_helper(not(factDerivedClass(_DerivedClass, Class, _SomeOffset))), doNotGuessHelper(factClassHasNoDerived(Class), factClassHasUnknownDerived(Class)). @@ -736,13 +736,13 @@ factConstructor(Constructor), factVFTableWrite(_Insn1, Constructor, 0, VFTable), - not(( + negation_helper(not(( factVFTableWrite(_Insn2, Constructor, _Offset1, OtherVFTable), iso_dif(VFTable, OtherVFTable) - )), + ))), find(Constructor, Class), - not(factDerivedClass(Class, _BaseClass, _Offset2)), + negation_helper(not(factDerivedClass(Class, _BaseClass, _Offset2))), doNotGuessHelper(factClassHasNoBase(Class), factClassHasUnknownBase(Class)). @@ -786,7 +786,7 @@ find(_, Class), % Class does not have any base classes - not(factDerivedClass(Class, _Base, _Offset)), + negation_helper(not(factDerivedClass(Class, _Base, _Offset))), % XXX: If we're at the end of reasoning and there is an unknown base, is that OK? Should % we leave it as is? Try really hard to make a guess? Or treat it as a failure? @@ -809,7 +809,7 @@ find(_, Class), % Class does not have any derived classes - not(factDerivedClass(_DerivedClass, Class, _Offset)), + negation_helper(not(factDerivedClass(_DerivedClass, Class, _Offset))), % XXX: If we're at the end of reasoning and there is an unknown derived class, is that OK? % Should we leave it as is? Try really hard to make a guess? Or treat it as a failure? @@ -847,7 +847,8 @@ factMethodInVFTable(VFTable, _Offset1, Method), % One of the methods is in a class all by itself right now. - is_singleton(Method), + % XXX: Is this monotonic? + negation_helper(is_singleton(Method)), findVFTable(VFTable, Class), @@ -858,7 +859,7 @@ % If a method is in the vftable of a derived and base class, we should give priority to the % base class. - not(factDerivedClass(Class, _BaseClass, _Offset)). + negation_helper(not(factDerivedClass(Class, _BaseClass, _Offset))). guessLateMergeClasses(Out) :- reportFirstSeen('guessLateMergeClassesF1'), @@ -1111,10 +1112,10 @@ find(CalledMethod, CalledClass), % The called method does NOT install any vftables that are on the base class. - not(( + negation_helper(not(( find(BaseVFTable, BaseClass), factVFTableWrite(_Insn, CalledMethod, Offset, BaseVFTable) - )), + ))), % There does NOT exist a distinct class that also calls the called method. If this % happens, there is ambiguity about which derived class the CalledMethod should be placed % on, so it should probably go on the base. @@ -1267,10 +1268,10 @@ % Also ensure that the two methods not in a class relationship already. Merging them would % ultimately result in merging a class with it's own ancestor. - not(( + negation_helper(not(( reasonClassRelationship(Class1, Class2); reasonClassRelationship(Class2, Class1) - )), + ))), checkMergeClasses(Class1, Class2), @@ -1314,7 +1315,7 @@ setof(Class, Insn2^Offset2^Method2^( factVFTableWrite(Insn2, Method2, Offset2, VFTable), - not(factVFTableOverwrite(Method2, _OtherVFTable, VFTable, Offset2)), + negation_helper(not(factVFTableOverwrite(Method2, _OtherVFTable, VFTable, Offset2))), find(Method2, Class), iso_dif(Class1, Class)), ClassSet), @@ -1365,6 +1366,7 @@ factNOTMergeClasses(Class2, Class1)), % XXX: Check factMergeClasses? % Now relationships between the classes are not allowed either. + % XXX: negation_helper? not(reasonClassRelationship(Class1, Class2)), not(reasonClassRelationship(Class2, Class1)). @@ -1564,10 +1566,10 @@ % guessDeletingDestructor requires a call to a real destructor. This rule relaxes that a % bit, by ensuring we don't call any non-real destructors. So calling a real destructor % will trigger this rule, but it is not necessary. - not(( + negation_helper(not(( callTarget(_Insn1, Method, Called), not(factRealDestructor(Called)) - )), + ))), !, Out = tryOrNOTDeletingDestructor(Method). @@ -1632,7 +1634,7 @@ % This indicates that the method met some basic criteria in C++. possibleDestructor(DeletingDestructor), % That's not already certain to NOT be a deleting destructor. - not(factNOTDeletingDestructor(DeletingDestructor)), + negation_helper(not(factNOTDeletingDestructor(DeletingDestructor))), % And the deleting destructor must also call delete (we think), since that's what makes it % deleting. Using this instead of the more complicated rule below led toa very slight % improvement in the fast test suite F=0.43 -> F=0.44. @@ -1661,7 +1663,7 @@ % And while it's premature to require the real destructor to be certain, it shouldn't be % disproven. possibleDestructor(RealDestructor), - not(factNOTRealDestructor(RealDestructor)), + negation_helper(not(factNOTRealDestructor(RealDestructor))), true. tryNegation(G) :- From 78dbb84fb02ee7a7738c825b6f5f01f9ed39ca49 Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Wed, 16 Nov 2022 09:44:47 -0500 Subject: [PATCH 07/15] Delay the merge checks. Otherwise a late merge is not delayed which is bad. --- share/prolog/oorules/guess.pl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/prolog/oorules/guess.pl b/share/prolog/oorules/guess.pl index 98926e4a..9efdc9eb 100644 --- a/share/prolog/oorules/guess.pl +++ b/share/prolog/oorules/guess.pl @@ -1367,8 +1367,8 @@ % XXX: Check factMergeClasses? % Now relationships between the classes are not allowed either. % XXX: negation_helper? - not(reasonClassRelationship(Class1, Class2)), - not(reasonClassRelationship(Class2, Class1)). + negation_helper(not(reasonClassRelationship(Class1, Class2))), + negation_helper(not(reasonClassRelationship(Class2, Class1))). tryMergeClasses((Method1, Method2)) :- tryMergeClasses(Method1, Method2). % If we are merging classes that have already been merged, just ignore it. From de55ef086e9cefa04aba3f626c2a1d87b0e90c59 Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Wed, 16 Nov 2022 09:49:27 -0500 Subject: [PATCH 08/15] Do not log so much --- share/prolog/oorules/util.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/prolog/oorules/util.pl b/share/prolog/oorules/util.pl index c04eae5e..61cac9d3 100644 --- a/share/prolog/oorules/util.pl +++ b/share/prolog/oorules/util.pl @@ -17,7 +17,7 @@ % We've already committed to G negation_helper(G) :- negation_commit(G), - logdebugln('Already committed ~Q', G), + logtraceln('Already committed ~Q', G), !. % G isn't true right now. From 4012c9de0d68bdd34831b24188a1a9b729b56763 Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Wed, 16 Nov 2022 10:07:35 -0500 Subject: [PATCH 09/15] Handle negation_failure --- share/prolog/oorules/util.pl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/share/prolog/oorules/util.pl b/share/prolog/oorules/util.pl index 61cac9d3..37e7bf0b 100644 --- a/share/prolog/oorules/util.pl +++ b/share/prolog/oorules/util.pl @@ -6,7 +6,7 @@ :- use_module(library(lists), [append/3, nth1/4, list_to_set/2]). :- dynamic negation_queue/1. -:- dynamic negation_fail/1 as incremental. +:- dynamic negation_fail/1. :- dynamic negation_commit/1 as incremental. :- table negation_helper/1 as opaque. @@ -20,6 +20,10 @@ logtraceln('Already committed ~Q', G), !. +% G results in a sanit yfailure +negation_helper(G) :- + negation_fail(G), !, fail. + % G isn't true right now. negation_helper(G) :- not(G), !, fail. From fd392bda643f7e525493e664e7d3a5531ccf19b2 Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Wed, 16 Nov 2022 10:11:05 -0500 Subject: [PATCH 10/15] Don't abolish tables! --- share/prolog/oorules/guess.pl | 3 +-- share/prolog/oorules/util.pl | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/share/prolog/oorules/guess.pl b/share/prolog/oorules/guess.pl index 9efdc9eb..f21a3add 100644 --- a/share/prolog/oorules/guess.pl +++ b/share/prolog/oorules/guess.pl @@ -1668,8 +1668,7 @@ tryNegation(G) :- loginfoln('Guessing ~Q.', negation_commit(G)), - try_assert(negation_commit(G)), - abolish_all_tables. + try_assert(negation_commit(G)). tryNOTNegation(G) :- loginfoln('Guessing ~Q.', negation_fail(G)), diff --git a/share/prolog/oorules/util.pl b/share/prolog/oorules/util.pl index 37e7bf0b..05c1953b 100644 --- a/share/prolog/oorules/util.pl +++ b/share/prolog/oorules/util.pl @@ -9,8 +9,6 @@ :- dynamic negation_fail/1. :- dynamic negation_commit/1 as incremental. -:- table negation_helper/1 as opaque. - negation_helper(G) :- var(G) -> throw_with_backtrace(user_error(negation_helper)). @@ -20,7 +18,7 @@ logtraceln('Already committed ~Q', G), !. -% G results in a sanit yfailure +% G results in a sanity failure negation_helper(G) :- negation_fail(G), !, fail. From b9e9df4591b25543d314b87db98a6318e34ce207 Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Wed, 16 Nov 2022 15:30:54 -0500 Subject: [PATCH 11/15] Add priorities to the queued negations --- share/prolog/oorules/guess.pl | 18 +++++++++++++----- share/prolog/oorules/util.pl | 18 ++++++++++-------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/share/prolog/oorules/guess.pl b/share/prolog/oorules/guess.pl index f21a3add..a2146db4 100644 --- a/share/prolog/oorules/guess.pl +++ b/share/prolog/oorules/guess.pl @@ -786,7 +786,8 @@ find(_, Class), % Class does not have any base classes - negation_helper(not(factDerivedClass(Class, _Base, _Offset))), + % -10: Very low priority negation! + negation_helper(not(factDerivedClass(Class, _Base, _Offset)), -10), % XXX: If we're at the end of reasoning and there is an unknown base, is that OK? Should % we leave it as is? Try really hard to make a guess? Or treat it as a failure? @@ -1687,10 +1688,17 @@ guessNegation(Out) :- reportFirstSeen('guessNegation'), - retract(negation_queue(G)), - % XXX What happens if G is no longer true? - G, - Out = tryOrNOTNegation(G). + + %setof(P, negation_queue(_, P), Pset), + %max_list(Pset, Pmax), + + % Sorting the queue takes a while. We should really use a priority queue... + % but this hack works ok for now. + member(Pmax, [0, -10, _]), + + retract(negation_queue(G, Pmax)), + !, + (G, not(negation_commit(G)), not(negation_fail(G)) -> Out = tryOrNOTNegation(G); guessNegation(Out)). %% Local Variables: %% mode: prolog diff --git a/share/prolog/oorules/util.pl b/share/prolog/oorules/util.pl index 05c1953b..78244899 100644 --- a/share/prolog/oorules/util.pl +++ b/share/prolog/oorules/util.pl @@ -5,33 +5,35 @@ :- use_module(library(lists), [append/3, nth1/4, list_to_set/2]). -:- dynamic negation_queue/1. +:- dynamic negation_queue/2. :- dynamic negation_fail/1. :- dynamic negation_commit/1 as incremental. -negation_helper(G) :- +negation_helper(G) :- negation_helper(G, 0). + +negation_helper(G, _) :- var(G) -> throw_with_backtrace(user_error(negation_helper)). % We've already committed to G -negation_helper(G) :- +negation_helper(G, _) :- negation_commit(G), logtraceln('Already committed ~Q', G), !. % G results in a sanity failure -negation_helper(G) :- +negation_helper(G, _) :- negation_fail(G), !, fail. % G isn't true right now. -negation_helper(G) :- +negation_helper(G, _) :- not(G), !, fail. % Queue G and fail. -negation_helper(G) :- +negation_helper(G, P) :- !, - negation_queue(G) + (negation_queue(G, OldP), P > OldP) -> fail - ; logdebugln('I am queueing negation ~Q', G), assert(negation_queue(G)), logdebugln('done'), fail. + ; logdebugln('I am queueing negation ~Q', G), assert(negation_queue(G, P)), logdebugln('done'), fail. sort_tuple((A,B), (C,D)) :- From 9064f05f2917b3c1f4ba96cce679360ce55a9442 Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Wed, 16 Nov 2022 15:49:50 -0500 Subject: [PATCH 12/15] Fix priority check --- share/prolog/oorules/util.pl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/share/prolog/oorules/util.pl b/share/prolog/oorules/util.pl index 78244899..6654cf45 100644 --- a/share/prolog/oorules/util.pl +++ b/share/prolog/oorules/util.pl @@ -31,7 +31,8 @@ % Queue G and fail. negation_helper(G, P) :- !, - (negation_queue(G, OldP), P > OldP) + (negation_queue(G, OldP), P >= OldP) + % There is a better or same priority queued element -> fail ; logdebugln('I am queueing negation ~Q', G), assert(negation_queue(G, P)), logdebugln('done'), fail. From 6276128df339950bde6a57771175a83711cb112d Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Wed, 16 Nov 2022 15:54:18 -0500 Subject: [PATCH 13/15] Disable trigger rule, since it's not compatible with negation delay. Temporarily disable MC_E too. --- share/prolog/oorules/rules.pl | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/share/prolog/oorules/rules.pl b/share/prolog/oorules/rules.pl index 3f473fd0..065d3569 100644 --- a/share/prolog/oorules/rules.pl +++ b/share/prolog/oorules/rules.pl @@ -2253,13 +2253,14 @@ % information about the direction of that relationship. :- table reasonClassRelatedMethod/2 as incremental. :- table reasonClassRelatedMethod_A/2 as incremental. -%:- table reasonClassRelatedMethod_B/2 as incremental. +:- table reasonClassRelatedMethod_B/2 as incremental. :- table reasonClassRelatedMethod_C/2 as incremental. reasonClassRelatedMethod(Class, Method) :- reasonClassRelatedMethod_A(Class, Method); % _B is now a trigger rule - %reasonClassRelatedMethod_B(Class, Method); + % Re-enabled (temporarily) for negation branch + reasonClassRelatedMethod_B(Class, Method); reasonClassRelatedMethod_C(Class, Method). % ClassCallsMethod => ClassRelatedMethod @@ -2270,6 +2271,9 @@ thisPtrUsage(Function, ThisPtr, Method) :- thisPtrUsage(_, Function, ThisPtr, Method). +reasonClassRelatedMethod_B(Class1, Method2) :- + reasonClassRelatedMethod_B(Class1, _, _, Method2). + % Because two methods are called on the same this-pointer in the same function. This rule is % NOT direction safe, because it simply observes two methods being called on the same object % pointer, and does not account for inheritance relationships. @@ -2695,6 +2699,7 @@ % inverse rule under reasonNOTMergeClasses. % PAPER: Merging-4 reasonMergeClasses_E(Class1, Class2) :- + fail, factDerivedClass(DerivedClass, Class1, ObjectOffset), factDerivedClass(DerivedClass, Class2, ObjectOffset), iso_dif(Class1, Class2), From d6df61d7c3869be23128300ae4cc909cdcfd6b9b Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Sat, 19 Nov 2022 19:40:24 -0500 Subject: [PATCH 14/15] Add a sanity check for delay negation --- share/prolog/oorules/insanity.pl | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/share/prolog/oorules/insanity.pl b/share/prolog/oorules/insanity.pl index b99cef92..64ee623c 100644 --- a/share/prolog/oorules/insanity.pl +++ b/share/prolog/oorules/insanity.pl @@ -328,6 +328,18 @@ factEmbeddedObject(A, B, C)) ). +% We committed to a condition being true (or false). Make sure that it stays +% that way. +:- table insanityNegation/1 as incremental. +insanityNegation(Out) :- + negation_commit(G), + + not(G), + + Out = ( + logwarnln('Consistency checks failed.~nThe condition "~Q" was committed to, but has become false.~n', [G]) + ). + :- table sanityChecks/1 as incremental. sanityChecks(Out) :- insanityNoBaseConsistency(Out); @@ -346,7 +358,8 @@ insanityInheritanceLoop(Out); insanityContradictoryMerges(Out); insanityContradictoryNOTConstructor(Out); - insanityTwoRealDestructorsOnClass(Out). + insanityTwoRealDestructorsOnClass(Out); + insanityNegation(Out). sanityChecks :- sanityChecks(Out) From 741f729d0e9b787d779fa4d8328cfe8f386d2e78 Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Mon, 28 Nov 2022 15:38:56 -0500 Subject: [PATCH 15/15] Large changes to negation code 1. Change in naming 2. Multiple guesses at once 3. Distinction between delaying and committing to a condition Note: This commit does not change the rules yet --- share/prolog/oorules/guess.pl | 75 +++++++++++++++++++++++--------- share/prolog/oorules/insanity.pl | 2 +- share/prolog/oorules/util.pl | 50 ++++++++++++++------- 3 files changed, 89 insertions(+), 38 deletions(-) diff --git a/share/prolog/oorules/guess.pl b/share/prolog/oorules/guess.pl index a2146db4..fd992d3d 100644 --- a/share/prolog/oorules/guess.pl +++ b/share/prolog/oorules/guess.pl @@ -1667,38 +1667,71 @@ negation_helper(not(factNOTRealDestructor(RealDestructor))), true. -tryNegation(G) :- - loginfoln('Guessing ~Q.', negation_commit(G)), - try_assert(negation_commit(G)). +tryDelay((G,P,Commit)) :- + try_retract(delay_queue(G, P, Commit)), -tryNOTNegation(G) :- - loginfoln('Guessing ~Q.', negation_fail(G)), - try_assert(negation_fail(G)). + % Do nothing if we've already committed one way or the other + %% (not(G) -> true; + %% delay_goal(G, _) -> true; + %% delay_fail(G) -> true; + + loginfoln('Guessing ~Q.', delay_goal(G, Commit)), + try_assert(delay_goal(G, Commit)). + +tryNOTDelay((G,P)) :- + loginfoln('tryNOTDelay'), + try_retract(delay_queue(G, P, Commit)), + + % Do nothing if we've already committed one way or the other + %% (not(G) -> true; + %% delay_goal(G, _) -> true; + %% delay_fail(G) -> true; + + loginfoln('Guessing ~Q.', delay_fail(G)), + try_assert(delay_fail(G)). + +tryOrNOTDelay((G, P)) :- + % XXX Optimization for not(G)... + + try_retract(delay_queue(G, P)), + + % Do nothing if we've already committed one way or the other + delay_goal(G, _) -> true; + delay_fail(G) -> true; -tryOrNOTNegation(G) :- - %likelyDeletingDestructor(Method, _RealDestructor), - doNotGuessHelper(negation_commit(G), - negation_fail(G)), ( - tryNegation(G); - tryNOTNegation(G); - logwarnln('Something is wrong upstream: ~Q.', negation(Method)), + tryDelay(G); + tryNOTDelay(G); + logwarnln('Something is wrong upstream: ~Q.', delay(Method)), fail ). -guessNegation(Out) :- - reportFirstSeen('guessNegation'), +guessDelay(Out) :- + reportFirstSeen('guessDelay'), + + % Remove each false or already committed to delay + forall(delay_queue(G, P, Commit), + ((not(G); + delay_goal(G, _); + delay_fail(G)) + -> (logdebugln('Removing outdated delay goal ~Q', [G]), + retract(delay_queue(G, P, Commit))) + ; true)), + + !, - %setof(P, negation_queue(_, P), Pset), - %max_list(Pset, Pmax), + setof(P, delay_queue(_, P, _), Pset), + max_list(Pset, Pmax), % Sorting the queue takes a while. We should really use a priority queue... % but this hack works ok for now. - member(Pmax, [0, -10, _]), + %member(Pmax, [0, -10, _]), - retract(negation_queue(G, Pmax)), - !, - (G, not(negation_commit(G)), not(negation_fail(G)) -> Out = tryOrNOTNegation(G); guessNegation(Out)). + setof((G, Pmax, Commit), + delay_queue(G, Pmax, Commit), + Gset), + + Out = tryBinarySearch(tryDelay, tryNOTDelay, Gset). %% Local Variables: %% mode: prolog diff --git a/share/prolog/oorules/insanity.pl b/share/prolog/oorules/insanity.pl index 64ee623c..a9abdaf9 100644 --- a/share/prolog/oorules/insanity.pl +++ b/share/prolog/oorules/insanity.pl @@ -332,7 +332,7 @@ % that way. :- table insanityNegation/1 as incremental. insanityNegation(Out) :- - negation_commit(G), + delay_goal(G, true), not(G), diff --git a/share/prolog/oorules/util.pl b/share/prolog/oorules/util.pl index 6654cf45..596043cd 100644 --- a/share/prolog/oorules/util.pl +++ b/share/prolog/oorules/util.pl @@ -5,36 +5,54 @@ :- use_module(library(lists), [append/3, nth1/4, list_to_set/2]). -:- dynamic negation_queue/2. -:- dynamic negation_fail/1. -:- dynamic negation_commit/1 as incremental. +% Delayed and committed negations. The following code is used to indicate a delayed check, +% which is how it sounds. A delayed check may optionally be committed to, which ensures that +% the check can not become false in the future. -negation_helper(G) :- negation_helper(G, 0). +:- dynamic delay_queue/3. -negation_helper(G, _) :- - var(G) -> throw_with_backtrace(user_error(negation_helper)). +:- dynamic delay_fail/1. -% We've already committed to G -negation_helper(G, _) :- - negation_commit(G), - logtraceln('Already committed ~Q', G), +%:- dynamic delay_commit/1 as incremental. + +% delayed_goal(Goal, Commit). Commit=true iff the goal should never become false in the +% future. +:- dynamic delay_goal/2 as incremental. + +%delay_helper(G) :- delay_helper(G, 0, false). + +delay(G) :- delay_helper(G, 0, false). + +delay_and_commit(G) :- delay_and_commit(G, 0). + +delay_and_commit(G, P) :- delay_helper(G, P, true). + +delay_helper(G, _, _) :- + var(G) -> throw_with_backtrace(user_error(delay_helper)). + +% We've already delayed for G +delay_helper(G, _, _) :- + delay_goal(G, _), + logtraceln('Already delayed ~Q', G), !. % G results in a sanity failure -negation_helper(G, _) :- - negation_fail(G), !, fail. +delay_helper(G, _, _) :- + delay_fail(G), !, fail. % G isn't true right now. -negation_helper(G, _) :- +delay_helper(G, _, _) :- not(G), !, fail. % Queue G and fail. -negation_helper(G, P) :- +delay_helper(G, P, Commit) :- !, - (negation_queue(G, OldP), P >= OldP) + (delay_queue(G, OldP, Commit), P >= OldP) % There is a better or same priority queued element -> fail - ; logdebugln('I am queueing negation ~Q', G), assert(negation_queue(G, P)), logdebugln('done'), fail. + ; logdebugln('I am queueing delay ~Q', G), + assert(delay_queue(G, P, Commit)), + fail. sort_tuple((A,B), (C,D)) :-