Skip to content

Move iOS, watchOS, and tvOS queues to latest releases#12

Closed
huskyroll wants to merge 1 commit into
WebKit:mainfrom
huskyroll:eng/230837-ios15-update
Closed

Move iOS, watchOS, and tvOS queues to latest releases#12
huskyroll wants to merge 1 commit into
WebKit:mainfrom
huskyroll:eng/230837-ios15-update

Conversation

@huskyroll

@huskyroll huskyroll commented Sep 27, 2021

Copy link
Copy Markdown
Contributor

95507e3

Move iOS, watchOS, and tvOS queues to latest releases
https://bugs.webkit.org/show_bug.cgi?id=230837

Reviewed by NOBODY (OOPS!).

* CISupport/build-webkit-org/config.json:
* CISupport/build-webkit-org/factories_unittest.py:
(TestExpectedBuildSteps):
* CISupport/build-webkit-org/public_html/dashboard/Scripts/WebKitBuildbot.js:
(WebKitBuildbot):
* CISupport/build-webkit-org/public_html/dashboard/Styles/Main.css:
(table.queue-grid tr.platform.ios-simulator-14 img.logo): Deleted.
(table.queue-grid tr.platform.ios-14 img.logo): Deleted.
(table.queue-grid tr.platform.tvos-simulator-14 img.logo): Deleted.
(table.queue-grid tr.platform.tvos-14 img.logo): Deleted.
(table.queue-grid tr.platform.watchos-simulator-7 img.logo): Deleted.
(table.queue-grid tr.platform.watchos-7 img.logo): Deleted.

@ryanhaddad

Copy link
Copy Markdown
Contributor

You need to update the schedulers for the updated queues in config.json

@huskyroll
huskyroll force-pushed the eng/230837-ios15-update branch 2 times, most recently from eb0e7d0 to a2e0ed6 Compare September 27, 2021 18:43
@huskyroll huskyroll changed the title 2021-09-27 Matt Lewis <[email protected]> Move iOS, watchOS, and tvOS queues to latest releases. Sep 27, 2021
@JonWBedard
JonWBedard self-requested a review September 27, 2021 19:16
@huskyroll
huskyroll force-pushed the eng/230837-ios15-update branch 3 times, most recently from 29d0efe to 6b7fa37 Compare September 27, 2021 20:04

@JonWBedard JonWBedard left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Needs to land via bugzilla still, though

@huskyroll
huskyroll force-pushed the eng/230837-ios15-update branch from 6b7fa37 to 8ea6942 Compare September 27, 2021 20:26
@huskyroll huskyroll changed the title Move iOS, watchOS, and tvOS queues to latest releases. Move iOS, watchOS, and tvOS queues to latest releases Sep 27, 2021
https://bugs.webkit.org/show_bug.cgi?id=230837

Reviewed by NOBODY (OOPS!).

* CISupport/build-webkit-org/config.json:
* CISupport/build-webkit-org/factories_unittest.py:
(TestExpectedBuildSteps):
* CISupport/build-webkit-org/public_html/dashboard/Scripts/WebKitBuildbot.js:
(WebKitBuildbot):
* CISupport/build-webkit-org/public_html/dashboard/Styles/Main.css:
(table.queue-grid tr.platform.ios-simulator-14 img.logo): Deleted.
(table.queue-grid tr.platform.ios-14 img.logo): Deleted.
(table.queue-grid tr.platform.tvos-simulator-14 img.logo): Deleted.
(table.queue-grid tr.platform.tvos-14 img.logo): Deleted.
(table.queue-grid tr.platform.watchos-simulator-7 img.logo): Deleted.
(table.queue-grid tr.platform.watchos-7 img.logo): Deleted.
@huskyroll
huskyroll force-pushed the eng/230837-ios15-update branch from 8ea6942 to 95507e3 Compare September 27, 2021 20:56
@JonWBedard

Copy link
Copy Markdown
Member

Played with this enough, we can close it now:
Landed: https://commits.webkit.org/242225@main (6571649)

@JonWBedard JonWBedard closed this Oct 18, 2021
@huskyroll
huskyroll deleted the eng/230837-ios15-update branch June 21, 2022 04:35
webkit-commit-queue pushed a commit to Constellation/WebKit that referenced this pull request Jan 24, 2023
https://bugs.webkit.org/show_bug.cgi?id=251063
rdar://104585575

Reviewed by Mark Lam and Justin Michaud.

This patch enhances CallFrame::dump to support wasm frames in btjs stacktrace.
The example is as follows.

    frame #0: 0x00000001035fca78 JavaScriptCore`JSC::functionBreakpoint(globalObject=0x000000012f410068, callFrame=0x000000016fdfa9d0) at JSDollarVM.cpp:2273:9 [opt]
    frame WebKit#1: 0x000000010ec44204 0x10eccc5dc
    frame WebKit#2: 0x000000010eccc5dc callback#Dwaxn6 [Baseline bc#50](Undefined)
    frame WebKit#3: 0x000000010ec4ca84 wasm-stub [WasmToJS](Wasm::Instance: 0x10d29da40)
    frame WebKit#4: 0x000000010ed0c060 <?>.wasm-function[1] [OMG](Wasm::Instance: 0x10d29da40)
    frame WebKit#5: 0x000000010ed100d0 jsToWasm#CWTx6k [FTL bc#22](Cell[JSModuleEnvironment]: 0x12f524540, Cell[WebAssemblyFunction]: 0x10d06a3a8, 1, 2, 3)
    frame WebKit#6: 0x000000010ec881b0 #D5ymZE [Baseline bc#733](Undefined, Cell[Generator]: 0x12f55c180, 1, Cell[Object]: 0x12f69dfc0, 0, Cell[JSLexicalEnvironment]: 0x12f52cee0)
    frame WebKit#7: 0x000000010ec3c008 asyncFunctionResume#A4ayYg [LLInt bc#49](Undefined, Cell[Generator]: 0x12f55c180, Cell[Object]: 0x12f69dfc0, 0)
    frame WebKit#8: 0x000000010ec3c008 promiseReactionJobWithoutPromise#D0yDF1 [LLInt bc#25](Undefined, Cell[Function]: 0x12f44f3c0, Cell[Object]: 0x12f69dfc0, Cell[Generator]: 0x12f55c180)
    frame WebKit#9: 0x000000010ec80ec0 promiseReactionJob#EdShZz [Baseline bc#74](Undefined, Undefined, Cell[Function]: 0x12f44f3c0, Cell[Object]: 0x12f69dfc0, Cell[Generator]: 0x12f55c180)
    frame WebKit#10: 0x000000010ec3c728
    frame WebKit#11: 0x0000000103137560 JavaScriptCore`JSC::Interpreter::executeCall(JSC::JSGlobalObject*, JSC::JSObject*, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) [inlined] JSC::JITCode::execute(this=<unavailable>, vm=<unavailable>, protoCallFrame=<unavailable>) at JITCodeInlines.h:42:38 [opt]
    frame WebKit#12: 0x0000000103137524 JavaScriptCore`JSC::Interpreter::executeCall(this=<unavailable>, lexicalGlobalObject=<unavailable>, function=<unavailable>, callData=<unavailable>, thisValue=<unavailable>, args=<unavailable>) at Interpreter.cpp:1093:27 [opt]
    frame WebKit#13: 0x000000010349d6d0 JavaScriptCore`JSC::runJSMicrotask(globalObject=0x000000012f410068, identifier=(m_identifier = 81), job=JSValue @ x22, argument0=JSValue @ x26, argument1=JSValue @ x25, argument2=<unavailable>, argument3=<unavailable>) at JSMicrotask.cpp:98:9 [opt]
    frame WebKit#14: 0x00000001039dfc54 JavaScriptCore`JSC::VM::drainMicrotasks() (.cold.1) at VM.cpp:0:9 [opt]
    frame WebKit#15: 0x00000001035e58a4 JavaScriptCore`JSC::VM::drainMicrotasks() [inlined] JSC::MicrotaskQueue::dequeue(this=<unavailable>) at VM.cpp:0:9 [opt]
    frame WebKit#16: 0x00000001035e5894 JavaScriptCore`JSC::VM::drainMicrotasks(this=0x000000012f000000) at VM.cpp:1255:46 [opt]
    ...

* Source/JavaScriptCore/interpreter/CallFrame.cpp:
(JSC::CallFrame::dump const):

Canonical link: https://commits.webkit.org/259262@main
CGQAQ pushed a commit to CGQAQ/WebKit that referenced this pull request Aug 25, 2023
Implement AsyncLocalStorage context tracking.
WebKit-Jenner pushed a commit that referenced this pull request May 14, 2024
…g LICM to miscompile

https://bugs.webkit.org/show_bug.cgi?id=271435
rdar://124506508

Reviewed by Yusuke Suzuki.

Consider the following example:

============================================================================================================
FIRST SLEEP (before performCFA)

     D@80:< 10:->	JSConstant(JS|PureNum|NeedsNegZero|NeedsNaNOrInfinity|UseAsOther, Final, Weak:Object: 0x13a0e8140 with butterfly 0x0(base=0xfffffffffffffff8) (Structure %AJ:Object), StructureID: 40640, bc#0, ExitValid)

     D@126:<!0:->	FilterGetByStatus(Check:Untyped:D@80, MustGen, (Simple, <id='uid:(x)', [0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){x:0, toJSON:1}, NonArray, Proto:0x1180348d8]], [], offset = 0>, seenInJIT = true), W:SideState, bc#112, ExitValid)
     D@128:<!0:->	CheckStructure(Cell:D@80, MustGen, [%AJ:Object], R:JSCell_structureID, Exits, bc#112, ExitValid)
     D@133:<!0:->	FilterGetByStatus(Check:Untyped:D@80, MustGen, (Simple, <id='uid:(toJSON),cell:(String (atomic),8Bit:(1),length:(6): toJSON, StructureID: 16976)', [0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){x:0, toJSON:1}, NonArray, Proto:0x1180348d8]], [], offset = 1>, seenInJIT = true), W:SideState, bc#118, ExitValid)
     D@136:< 4:->	GetByOffset(KnownCell:D@80, KnownCell:D@80, JS|PureNum|NeedsNaNOrInfinity|UseAsOther|ReallyWantsInt, BoolInt32, id6{toJSON}, 1, R:NamedProperties(6), bc#118, ExitValid)  predicting BoolInt32
     D@138:<!0:->	Check(Check:Int32:D@136, MustGen, Exits, bc#118, exit: bc#124, ExitValid)
     D@140:<!0:->	Branch(Boolean:D@35, MustGen, T:#9/w:10.000000, F:#12/w:10.000000, W:SideState, bc#124, ExitValid)

     D@4:< 1:->	GetButterfly(Cell:D@104, Storage|PureInt, R:JSObject_butterfly, bc#127, ExitValid)
     D@1:<!1:->	CheckInBounds(Int32:D@136, KnownInt32:D@151, JS|MustGen|PureInt, Int32, Exits, bc#127, ExitValid)
     D@143:< 3:->	GetByVal(KnownCell:D@104, Int32:Kill:D@136, Check:Untyped:Kill:D@4, Check:Untyped:Kill:D@1, JS|VarArgs|PureNum|NeedsNegZero|NeedsNaNOrInfinity|UseAsOther, StringIdent, Contiguous+OriginalCopyOnWriteArray+InBoundsSaneChain+AsIs+Read, R:Butterfly_publicLength,IndexedContiguousProperties, Exits, bc#127, ExitValid)  predicting StringIdent

     %AJ:Object                                  = 0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){x:0, toJSON:1}, NonArray, Proto:0x1180348d8]

Execution:
     AI GetByOffset D@136 AI says (BoolInt32, Int32: 0, none:StructuresAreClobbered) base: (Final, NonArray, [0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){x:0, toJSON:1}, NonArray, Proto:0x1180348d8]], Object: 0x13a0e8140 with butterfly 0x0(base=0xfffffffffffffff8) (Structure 0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){x:0, toJSON:1}, NonArray, Proto:0x1180348d8]), StructureID: 40640, 1:StructuresAreWatched) state StructuresAreWatched
     AI CheckInBounds D@1 AI says left Int32:D@136 is Int32: 0

SECOND SLEEP (after performCFA, before performConstantFolding)

Note that the jsconstant has a structure transition at this point.

     D@80:< 10:->	JSConstant(JS|PureNum|NeedsNegZero|NeedsNaNOrInfinity|UseAsOther, Final, Weak:Object: 0x13a0e8140 with butterfly 0x8014002388(base=0x8014002380) (Structure %AR:Object), StructureID: 40976, bc#0, ExitValid)

     D@126:<!0:->	FilterGetByStatus(Check:Untyped:D@80, MustGen, (Simple, <id='uid:(x)', [0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){toJSON:1, x:0}, NonArray, Proto:0x1180348d8]], [], offset = 0>, seenInJIT = true), W:SideState, bc#112, ExitValid)
     D@128:<!0:->	CheckStructure(Cell:D@80, MustGen, [%AR:Object], R:JSCell_structureID, Exits, bc#112, ExitValid)
     D@133:<!0:->	FilterGetByStatus(Check:Untyped:D@80, MustGen, (Simple, <id='uid:(toJSON),cell:(String (atomic),8Bit:(1),length:(6): toJSON, StructureID: 16976)', [0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){toJSON:1, x:0}, NonArray, Proto:0x1180348d8]], [], offset = 1>, seenInJIT = true), W:SideState, bc#118, ExitValid)
     D@136:< 4:->	GetByOffset(KnownCell:D@80, KnownCell:D@80, JS|PureNum|NeedsNaNOrInfinity|UseAsOther|ReallyWantsInt, BoolInt32, id6{toJSON}, 1, R:NamedProperties(6), bc#118, ExitValid)  predicting BoolInt32

     D@4:< 1:->	GetButterfly(Cell:D@104, Storage|PureInt, R:JSObject_butterfly, bc#127, ExitValid)
     D@1:<!1:->	CheckInBounds(Int32:D@136, KnownInt32:D@151, JS|MustGen|PureInt, Int32, Exits, bc#127, ExitValid)
     D@143:< 3:->	GetByVal(KnownCell:D@104, Int32:Kill:D@136, Check:Untyped:Kill:D@4, Check:Untyped:Kill:D@1, JS|VarArgs|PureNum|NeedsNegZero|NeedsNaNOrInfinity|UseAsOther, StringIdent, Contiguous+OriginalCopyOnWriteArray+InBoundsSaneChain+AsIs+Read, R:Butterfly_publicLength,IndexedContiguousProperties, Exits, bc#127, ExitValid)  predicting StringIdent

     %AR:Object                                  = 0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){toJSON:1, x:0}, NonArray, Proto:0x1180348d8]
     %B6:Object                                  = 0x30000a010:[0xa010/40976, Object, (2/2, 1/4){y:64, toJSON:1, x:0}, NonArray, Proto:0x1180348d8, Leaf (Watched)]

Execution:
     AI GetByOffset D@136 AI says (HeapTop, TOP, TOP, none:StructuresAreClobbered) base: (Final, NonArray, [0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){toJSON:1, x:0}, NonArray, Proto:0x1180348d8]], Object: 0x13a0e8140 with butterfly 0x8014002388(base=0x8014002360) (Structure 0x30000a010:[0xa010/40976, Object, (2/2, 1/4){y:64, toJSON:1, x:0}, NonArray, Proto:0x1180348d8, Leaf (Watched)]), StructureID: 40976, 1:StructuresAreWatched) state StructuresAreWatched
     GetByOffset D@136 AI says (HeapTop, TOP, TOP, 1:StructuresAreWatched)
     CheckInBounds D@1 AI says left Int32:D@136 is Int32: 0
     AI GetByOffset D@136 AI says (HeapTop, TOP, TOP, none:StructuresAreClobbered) base: (Final, NonArray, [0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){toJSON:1, x:0}, NonArray, Proto:0x1180348d8]], Object: 0x13a0e8140 with butterfly 0x8014002388(base=0x8014002360) (Structure 0x30000a010:[0xa010/40976, Object, (2/2, 1/4){y:64, toJSON:1, x:0}, NonArray, Proto:0x1180348d8, Leaf (Watched)]), StructureID: 40976, 1:StructuresAreWatched) state StructuresAreWatched

SLEEP DONE
============================================================================================================

The constant folding phase chooses to fold the CheckInBounds, but not the GetByOffset. At this point, this is still correct (although sub-optimal).

1) Why does AI disagree in these two places?

The constant folding phase doesn't re-run AI. It runs it from top to bottom on certain blocks only.
In this example, The CheckInBounds AI proof is read directly from the block, but the GetByOffset
has its value computed.

1) Why can the JSConstant's structure change without triggering a watchpoint?

The constant remains constant. We never used the fact that that it had a certain
structure anywhere, our proofs stem from the fact that we have a CheckStructure.

1) Why does the re-run AI pass in performConstantFolding not predict the GetByOffset to be constant?

The structure change causes GetPropertyConcurrently to fail to get the value concurrently.
We must assume that it is always safe to produce a more conservative result in this phase.

Note though that if the phase returned the same value as the first time around, that would still have been
correct! The answer to this question didn't change, we just lost the ability to compute it.

============================================================================================================
Why this is a problem

This is a classic example of a broad class of bugs affecting the JIT. Different passes can see different values as the mutator
changes the object graph, even for the same pass. Normally this is fine, because the compiler is always narrowing its assumptions.

Specifically, with each pass we assume more and more detailed things about the code, and guard against these assumptions
being wrong either with watchpoints or runtime checks.

In this example, we see that we CheckStructure. Then, as a result, we can elide nodes that are dominated by that check (like the
GetByOffset or the CheckInBounds). As long as we never loosen that assumption again, we are fine.

In this example, our CFA pass assumes that the GetByOffset is constant. The Constant Folding phase then assumes sometimes that it is constant,
and sometimes that it is not. This puts us in opposition to another principle, that is the idea that we should always
be able to answer any question asked of us conservatively and be safe. Up until this point, both of these ideas are holding true.

Unfortunately, we also need LICM. LICM needs to run after many assumptions have already been made, and it dramatically loosens
assumptions. In this example, LICM comes along and hoists the GetByVal(GetByOffset()) above the CheckStructure.

If we had indeed constant folded the GetByOffset too, we would be fine to do.

We should always be able to avoid constant folding safely.

LICM should be able to hoist constant values safely.

============================================================================================================
How to fix this generally

1) If AI says something is constant, just make it constant then.

This is the simplest solution, and should just work. This makes sure that what AI says is true, even if LICM moves stuff around.

This would require some re-work of the AI phase though.

1) LCIM should see that this isn't safe to move

The effects here are super specific. If LICM asked the question "If I move this, is this still safe to execute?" it would
have answered "no" in this case (without the structure check). Of course, if we hadn't removed the CheckInBounds, the answer
would be "yes," which is also fine.

One could imagine that this analysis would be pretty difficult.

1) Always run the constant folder on each block.

```
// This method is evil - it causes a huge maintenance headache and there is a gross amount of
// code devoted to it. It would be much nicer to just always run the constant folder on each
// block. But, the last time we did it, it was a 1% SunSpider regression:
// https://bugs.webkit.org/show_bug.cgi?id=133947
// So, we should probably keep this method.
void setShouldTryConstantFolding(bool tryConstantFolding) { m_shouldTryConstantFolding = tryConstantFolding; }
```

This would fix the issue though, as a failure to prove something at any point in time would not permit
that proof to be used later on.

This patch chooses the third option.

This appears to be perf-neutral on modern hardware on JS2/3 and SP2/3.

* JSTests/stress/get-by-val-hoist-above-structure.js: Added.
(opt):
(createObjectOfS1):
(createObjectOfS2):
(main):
* Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp:
(JSC::DFG::ConstantFoldingPhase::foldConstants):

Canonical link: https://commits.webkit.org/272448.796@safari-7618-branch
aperezdc pushed a commit that referenced this pull request May 15, 2024
…kit.org/show_bug.cgi?id=271435

    DFG Constant Folding phase can see inconsistent view of world, causing LICM to miscompile
    https://bugs.webkit.org/show_bug.cgi?id=271435
    rdar://124506508

    Reviewed by Yusuke Suzuki.

    Consider the following example:

    ============================================================================================================
    FIRST SLEEP (before performCFA)

         D@80:< 10:->	JSConstant(JS|PureNum|NeedsNegZero|NeedsNaNOrInfinity|UseAsOther, Final, Weak:Object: 0x13a0e8140 with butterfly 0x0(base=0xfffffffffffffff8) (Structure %AJ:Object), StructureID: 40640, bc#0, ExitValid)

         D@126:<!0:->	FilterGetByStatus(Check:Untyped:D@80, MustGen, (Simple, <id='uid:(x)', [0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){x:0, toJSON:1}, NonArray, Proto:0x1180348d8]], [], offset = 0>, seenInJIT = true), W:SideState, bc#112, ExitValid)
         D@128:<!0:->	CheckStructure(Cell:D@80, MustGen, [%AJ:Object], R:JSCell_structureID, Exits, bc#112, ExitValid)
         D@133:<!0:->	FilterGetByStatus(Check:Untyped:D@80, MustGen, (Simple, <id='uid:(toJSON),cell:(String (atomic),8Bit:(1),length:(6): toJSON, StructureID: 16976)', [0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){x:0, toJSON:1}, NonArray, Proto:0x1180348d8]], [], offset = 1>, seenInJIT = true), W:SideState, bc#118, ExitValid)
         D@136:< 4:->	GetByOffset(KnownCell:D@80, KnownCell:D@80, JS|PureNum|NeedsNaNOrInfinity|UseAsOther|ReallyWantsInt, BoolInt32, id6{toJSON}, 1, R:NamedProperties(6), bc#118, ExitValid)  predicting BoolInt32
         D@138:<!0:->	Check(Check:Int32:D@136, MustGen, Exits, bc#118, exit: bc#124, ExitValid)
         D@140:<!0:->	Branch(Boolean:D@35, MustGen, T:#9/w:10.000000, F:#12/w:10.000000, W:SideState, bc#124, ExitValid)

         D@4:< 1:->	GetButterfly(Cell:D@104, Storage|PureInt, R:JSObject_butterfly, bc#127, ExitValid)
         D@1:<!1:->	CheckInBounds(Int32:D@136, KnownInt32:D@151, JS|MustGen|PureInt, Int32, Exits, bc#127, ExitValid)
         D@143:< 3:->	GetByVal(KnownCell:D@104, Int32:Kill:D@136, Check:Untyped:Kill:D@4, Check:Untyped:Kill:D@1, JS|VarArgs|PureNum|NeedsNegZero|NeedsNaNOrInfinity|UseAsOther, StringIdent, Contiguous+OriginalCopyOnWriteArray+InBoundsSaneChain+AsIs+Read, R:Butterfly_publicLength,IndexedContiguousProperties, Exits, bc#127, ExitValid)  predicting StringIdent

         %AJ:Object                                  = 0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){x:0, toJSON:1}, NonArray, Proto:0x1180348d8]

    Execution:
         AI GetByOffset D@136 AI says (BoolInt32, Int32: 0, none:StructuresAreClobbered) base: (Final, NonArray, [0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){x:0, toJSON:1}, NonArray, Proto:0x1180348d8]], Object: 0x13a0e8140 with butterfly 0x0(base=0xfffffffffffffff8) (Structure 0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){x:0, toJSON:1}, NonArray, Proto:0x1180348d8]), StructureID: 40640, 1:StructuresAreWatched) state StructuresAreWatched
         AI CheckInBounds D@1 AI says left Int32:D@136 is Int32: 0

    SECOND SLEEP (after performCFA, before performConstantFolding)

    Note that the jsconstant has a structure transition at this point.

         D@80:< 10:->	JSConstant(JS|PureNum|NeedsNegZero|NeedsNaNOrInfinity|UseAsOther, Final, Weak:Object: 0x13a0e8140 with butterfly 0x8014002388(base=0x8014002380) (Structure %AR:Object), StructureID: 40976, bc#0, ExitValid)

         D@126:<!0:->	FilterGetByStatus(Check:Untyped:D@80, MustGen, (Simple, <id='uid:(x)', [0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){toJSON:1, x:0}, NonArray, Proto:0x1180348d8]], [], offset = 0>, seenInJIT = true), W:SideState, bc#112, ExitValid)
         D@128:<!0:->	CheckStructure(Cell:D@80, MustGen, [%AR:Object], R:JSCell_structureID, Exits, bc#112, ExitValid)
         D@133:<!0:->	FilterGetByStatus(Check:Untyped:D@80, MustGen, (Simple, <id='uid:(toJSON),cell:(String (atomic),8Bit:(1),length:(6): toJSON, StructureID: 16976)', [0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){toJSON:1, x:0}, NonArray, Proto:0x1180348d8]], [], offset = 1>, seenInJIT = true), W:SideState, bc#118, ExitValid)
         D@136:< 4:->	GetByOffset(KnownCell:D@80, KnownCell:D@80, JS|PureNum|NeedsNaNOrInfinity|UseAsOther|ReallyWantsInt, BoolInt32, id6{toJSON}, 1, R:NamedProperties(6), bc#118, ExitValid)  predicting BoolInt32

         D@4:< 1:->	GetButterfly(Cell:D@104, Storage|PureInt, R:JSObject_butterfly, bc#127, ExitValid)
         D@1:<!1:->	CheckInBounds(Int32:D@136, KnownInt32:D@151, JS|MustGen|PureInt, Int32, Exits, bc#127, ExitValid)
         D@143:< 3:->	GetByVal(KnownCell:D@104, Int32:Kill:D@136, Check:Untyped:Kill:D@4, Check:Untyped:Kill:D@1, JS|VarArgs|PureNum|NeedsNegZero|NeedsNaNOrInfinity|UseAsOther, StringIdent, Contiguous+OriginalCopyOnWriteArray+InBoundsSaneChain+AsIs+Read, R:Butterfly_publicLength,IndexedContiguousProperties, Exits, bc#127, ExitValid)  predicting StringIdent

         %AR:Object                                  = 0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){toJSON:1, x:0}, NonArray, Proto:0x1180348d8]
         %B6:Object                                  = 0x30000a010:[0xa010/40976, Object, (2/2, 1/4){y:64, toJSON:1, x:0}, NonArray, Proto:0x1180348d8, Leaf (Watched)]

    Execution:
         AI GetByOffset D@136 AI says (HeapTop, TOP, TOP, none:StructuresAreClobbered) base: (Final, NonArray, [0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){toJSON:1, x:0}, NonArray, Proto:0x1180348d8]], Object: 0x13a0e8140 with butterfly 0x8014002388(base=0x8014002360) (Structure 0x30000a010:[0xa010/40976, Object, (2/2, 1/4){y:64, toJSON:1, x:0}, NonArray, Proto:0x1180348d8, Leaf (Watched)]), StructureID: 40976, 1:StructuresAreWatched) state StructuresAreWatched
         GetByOffset D@136 AI says (HeapTop, TOP, TOP, 1:StructuresAreWatched)
         CheckInBounds D@1 AI says left Int32:D@136 is Int32: 0
         AI GetByOffset D@136 AI says (HeapTop, TOP, TOP, none:StructuresAreClobbered) base: (Final, NonArray, [0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){toJSON:1, x:0}, NonArray, Proto:0x1180348d8]], Object: 0x13a0e8140 with butterfly 0x8014002388(base=0x8014002360) (Structure 0x30000a010:[0xa010/40976, Object, (2/2, 1/4){y:64, toJSON:1, x:0}, NonArray, Proto:0x1180348d8, Leaf (Watched)]), StructureID: 40976, 1:StructuresAreWatched) state StructuresAreWatched

    SLEEP DONE
    ============================================================================================================

    The constant folding phase chooses to fold the CheckInBounds, but not the GetByOffset. At this point, this is still correct (although sub-optimal).

    1) Why does AI disagree in these two places?

    The constant folding phase doesn't re-run AI. It runs it from top to bottom on certain blocks only.
    In this example, The CheckInBounds AI proof is read directly from the block, but the GetByOffset
    has its value computed.

    1) Why can the JSConstant's structure change without triggering a watchpoint?

    The constant remains constant. We never used the fact that that it had a certain
    structure anywhere, our proofs stem from the fact that we have a CheckStructure.

    1) Why does the re-run AI pass in performConstantFolding not predict the GetByOffset to be constant?

    The structure change causes GetPropertyConcurrently to fail to get the value concurrently.
    We must assume that it is always safe to produce a more conservative result in this phase.

    Note though that if the phase returned the same value as the first time around, that would still have been
    correct! The answer to this question didn't change, we just lost the ability to compute it.

    ============================================================================================================
    Why this is a problem

    This is a classic example of a broad class of bugs affecting the JIT. Different passes can see different values as the mutator
    changes the object graph, even for the same pass. Normally this is fine, because the compiler is always narrowing its assumptions.

    Specifically, with each pass we assume more and more detailed things about the code, and guard against these assumptions
    being wrong either with watchpoints or runtime checks.

    In this example, we see that we CheckStructure. Then, as a result, we can elide nodes that are dominated by that check (like the
    GetByOffset or the CheckInBounds). As long as we never loosen that assumption again, we are fine.

    In this example, our CFA pass assumes that the GetByOffset is constant. The Constant Folding phase then assumes sometimes that it is constant,
    and sometimes that it is not. This puts us in opposition to another principle, that is the idea that we should always
    be able to answer any question asked of us conservatively and be safe. Up until this point, both of these ideas are holding true.

    Unfortunately, we also need LICM. LICM needs to run after many assumptions have already been made, and it dramatically loosens
    assumptions. In this example, LICM comes along and hoists the GetByVal(GetByOffset()) above the CheckStructure.

    If we had indeed constant folded the GetByOffset too, we would be fine to do.

    We should always be able to avoid constant folding safely.

    LICM should be able to hoist constant values safely.

    ============================================================================================================
    How to fix this generally

    1) If AI says something is constant, just make it constant then.

    This is the simplest solution, and should just work. This makes sure that what AI says is true, even if LICM moves stuff around.

    This would require some re-work of the AI phase though.

    1) LCIM should see that this isn't safe to move

    The effects here are super specific. If LICM asked the question "If I move this, is this still safe to execute?" it would
    have answered "no" in this case (without the structure check). Of course, if we hadn't removed the CheckInBounds, the answer
    would be "yes," which is also fine.

    One could imagine that this analysis would be pretty difficult.

    1) Always run the constant folder on each block.

    ```
    // This method is evil - it causes a huge maintenance headache and there is a gross amount of
    // code devoted to it. It would be much nicer to just always run the constant folder on each
    // block. But, the last time we did it, it was a 1% SunSpider regression:
    // https://bugs.webkit.org/show_bug.cgi?id=133947
    // So, we should probably keep this method.
    void setShouldTryConstantFolding(bool tryConstantFolding) { m_shouldTryConstantFolding = tryConstantFolding; }
    ```

    This would fix the issue though, as a failure to prove something at any point in time would not permit
    that proof to be used later on.

    This patch chooses the third option.

    This appears to be perf-neutral on modern hardware on JS2/3 and SP2/3.

    * JSTests/stress/get-by-val-hoist-above-structure.js: Added.
    (opt):
    (createObjectOfS1):
    (createObjectOfS2):
    (main):
    * Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp:
    (JSC::DFG::ConstantFoldingPhase::foldConstants):

    Canonical link: https://commits.webkit.org/272448.796@safari-7618-branch

Canonical link: https://commits.webkit.org/274313.239@webkitglib/2.44
webkit-commit-queue pushed a commit to robert-jenner/WebKit that referenced this pull request May 16, 2024
…g LICM to miscompile

https://bugs.webkit.org/show_bug.cgi?id=271435
rdar://124506508

Reviewed by Yusuke Suzuki.

Consider the following example:

============================================================================================================
FIRST SLEEP (before performCFA)

     D@80:< 10:->	JSConstant(JS|PureNum|NeedsNegZero|NeedsNaNOrInfinity|UseAsOther, Final, Weak:Object: 0x13a0e8140 with butterfly 0x0(base=0xfffffffffffffff8) (Structure %AJ:Object), StructureID: 40640, bc#0, ExitValid)

     D@126:<!0:->	FilterGetByStatus(Check:Untyped:D@80, MustGen, (Simple, <id='uid:(x)', [0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){x:0, toJSON:1}, NonArray, Proto:0x1180348d8]], [], offset = 0>, seenInJIT = true), W:SideState, bc#112, ExitValid)
     D@128:<!0:->	CheckStructure(Cell:D@80, MustGen, [%AJ:Object], R:JSCell_structureID, Exits, bc#112, ExitValid)
     D@133:<!0:->	FilterGetByStatus(Check:Untyped:D@80, MustGen, (Simple, <id='uid:(toJSON),cell:(String (atomic),8Bit:(1),length:(6): toJSON, StructureID: 16976)', [0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){x:0, toJSON:1}, NonArray, Proto:0x1180348d8]], [], offset = 1>, seenInJIT = true), W:SideState, bc#118, ExitValid)
     D@136:< 4:->	GetByOffset(KnownCell:D@80, KnownCell:D@80, JS|PureNum|NeedsNaNOrInfinity|UseAsOther|ReallyWantsInt, BoolInt32, id6{toJSON}, 1, R:NamedProperties(6), bc#118, ExitValid)  predicting BoolInt32
     D@138:<!0:->	Check(Check:Int32:D@136, MustGen, Exits, bc#118, exit: bc#124, ExitValid)
     D@140:<!0:->	Branch(Boolean:D@35, MustGen, T:WebKit#9/w:10.000000, F:WebKit#12/w:10.000000, W:SideState, bc#124, ExitValid)

     D@4:< 1:->	GetButterfly(Cell:D@104, Storage|PureInt, R:JSObject_butterfly, bc#127, ExitValid)
     D@1:<!1:->	CheckInBounds(Int32:D@136, KnownInt32:D@151, JS|MustGen|PureInt, Int32, Exits, bc#127, ExitValid)
     D@143:< 3:->	GetByVal(KnownCell:D@104, Int32:Kill:D@136, Check:Untyped:Kill:D@4, Check:Untyped:Kill:D@1, JS|VarArgs|PureNum|NeedsNegZero|NeedsNaNOrInfinity|UseAsOther, StringIdent, Contiguous+OriginalCopyOnWriteArray+InBoundsSaneChain+AsIs+Read, R:Butterfly_publicLength,IndexedContiguousProperties, Exits, bc#127, ExitValid)  predicting StringIdent

     %AJ:Object                                  = 0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){x:0, toJSON:1}, NonArray, Proto:0x1180348d8]

Execution:
     AI GetByOffset D@136 AI says (BoolInt32, Int32: 0, none:StructuresAreClobbered) base: (Final, NonArray, [0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){x:0, toJSON:1}, NonArray, Proto:0x1180348d8]], Object: 0x13a0e8140 with butterfly 0x0(base=0xfffffffffffffff8) (Structure 0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){x:0, toJSON:1}, NonArray, Proto:0x1180348d8]), StructureID: 40640, 1:StructuresAreWatched) state StructuresAreWatched
     AI CheckInBounds D@1 AI says left Int32:D@136 is Int32: 0

SECOND SLEEP (after performCFA, before performConstantFolding)

Note that the jsconstant has a structure transition at this point.

     D@80:< 10:->	JSConstant(JS|PureNum|NeedsNegZero|NeedsNaNOrInfinity|UseAsOther, Final, Weak:Object: 0x13a0e8140 with butterfly 0x8014002388(base=0x8014002380) (Structure %AR:Object), StructureID: 40976, bc#0, ExitValid)

     D@126:<!0:->	FilterGetByStatus(Check:Untyped:D@80, MustGen, (Simple, <id='uid:(x)', [0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){toJSON:1, x:0}, NonArray, Proto:0x1180348d8]], [], offset = 0>, seenInJIT = true), W:SideState, bc#112, ExitValid)
     D@128:<!0:->	CheckStructure(Cell:D@80, MustGen, [%AR:Object], R:JSCell_structureID, Exits, bc#112, ExitValid)
     D@133:<!0:->	FilterGetByStatus(Check:Untyped:D@80, MustGen, (Simple, <id='uid:(toJSON),cell:(String (atomic),8Bit:(1),length:(6): toJSON, StructureID: 16976)', [0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){toJSON:1, x:0}, NonArray, Proto:0x1180348d8]], [], offset = 1>, seenInJIT = true), W:SideState, bc#118, ExitValid)
     D@136:< 4:->	GetByOffset(KnownCell:D@80, KnownCell:D@80, JS|PureNum|NeedsNaNOrInfinity|UseAsOther|ReallyWantsInt, BoolInt32, id6{toJSON}, 1, R:NamedProperties(6), bc#118, ExitValid)  predicting BoolInt32

     D@4:< 1:->	GetButterfly(Cell:D@104, Storage|PureInt, R:JSObject_butterfly, bc#127, ExitValid)
     D@1:<!1:->	CheckInBounds(Int32:D@136, KnownInt32:D@151, JS|MustGen|PureInt, Int32, Exits, bc#127, ExitValid)
     D@143:< 3:->	GetByVal(KnownCell:D@104, Int32:Kill:D@136, Check:Untyped:Kill:D@4, Check:Untyped:Kill:D@1, JS|VarArgs|PureNum|NeedsNegZero|NeedsNaNOrInfinity|UseAsOther, StringIdent, Contiguous+OriginalCopyOnWriteArray+InBoundsSaneChain+AsIs+Read, R:Butterfly_publicLength,IndexedContiguousProperties, Exits, bc#127, ExitValid)  predicting StringIdent

     %AR:Object                                  = 0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){toJSON:1, x:0}, NonArray, Proto:0x1180348d8]
     %B6:Object                                  = 0x30000a010:[0xa010/40976, Object, (2/2, 1/4){y:64, toJSON:1, x:0}, NonArray, Proto:0x1180348d8, Leaf (Watched)]

Execution:
     AI GetByOffset D@136 AI says (HeapTop, TOP, TOP, none:StructuresAreClobbered) base: (Final, NonArray, [0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){toJSON:1, x:0}, NonArray, Proto:0x1180348d8]], Object: 0x13a0e8140 with butterfly 0x8014002388(base=0x8014002360) (Structure 0x30000a010:[0xa010/40976, Object, (2/2, 1/4){y:64, toJSON:1, x:0}, NonArray, Proto:0x1180348d8, Leaf (Watched)]), StructureID: 40976, 1:StructuresAreWatched) state StructuresAreWatched
     GetByOffset D@136 AI says (HeapTop, TOP, TOP, 1:StructuresAreWatched)
     CheckInBounds D@1 AI says left Int32:D@136 is Int32: 0
     AI GetByOffset D@136 AI says (HeapTop, TOP, TOP, none:StructuresAreClobbered) base: (Final, NonArray, [0x300009ec0:[0x9ec0/40640, Object, (2/2, 0/0){toJSON:1, x:0}, NonArray, Proto:0x1180348d8]], Object: 0x13a0e8140 with butterfly 0x8014002388(base=0x8014002360) (Structure 0x30000a010:[0xa010/40976, Object, (2/2, 1/4){y:64, toJSON:1, x:0}, NonArray, Proto:0x1180348d8, Leaf (Watched)]), StructureID: 40976, 1:StructuresAreWatched) state StructuresAreWatched

SLEEP DONE
============================================================================================================

The constant folding phase chooses to fold the CheckInBounds, but not the GetByOffset. At this point, this is still correct (although sub-optimal).

1) Why does AI disagree in these two places?

The constant folding phase doesn't re-run AI. It runs it from top to bottom on certain blocks only.
In this example, The CheckInBounds AI proof is read directly from the block, but the GetByOffset
has its value computed.

1) Why can the JSConstant's structure change without triggering a watchpoint?

The constant remains constant. We never used the fact that that it had a certain
structure anywhere, our proofs stem from the fact that we have a CheckStructure.

1) Why does the re-run AI pass in performConstantFolding not predict the GetByOffset to be constant?

The structure change causes GetPropertyConcurrently to fail to get the value concurrently.
We must assume that it is always safe to produce a more conservative result in this phase.

Note though that if the phase returned the same value as the first time around, that would still have been
correct! The answer to this question didn't change, we just lost the ability to compute it.

============================================================================================================
Why this is a problem

This is a classic example of a broad class of bugs affecting the JIT. Different passes can see different values as the mutator
changes the object graph, even for the same pass. Normally this is fine, because the compiler is always narrowing its assumptions.

Specifically, with each pass we assume more and more detailed things about the code, and guard against these assumptions
being wrong either with watchpoints or runtime checks.

In this example, we see that we CheckStructure. Then, as a result, we can elide nodes that are dominated by that check (like the
GetByOffset or the CheckInBounds). As long as we never loosen that assumption again, we are fine.

In this example, our CFA pass assumes that the GetByOffset is constant. The Constant Folding phase then assumes sometimes that it is constant,
and sometimes that it is not. This puts us in opposition to another principle, that is the idea that we should always
be able to answer any question asked of us conservatively and be safe. Up until this point, both of these ideas are holding true.

Unfortunately, we also need LICM. LICM needs to run after many assumptions have already been made, and it dramatically loosens
assumptions. In this example, LICM comes along and hoists the GetByVal(GetByOffset()) above the CheckStructure.

If we had indeed constant folded the GetByOffset too, we would be fine to do.

We should always be able to avoid constant folding safely.

LICM should be able to hoist constant values safely.

============================================================================================================
How to fix this generally

1) If AI says something is constant, just make it constant then.

This is the simplest solution, and should just work. This makes sure that what AI says is true, even if LICM moves stuff around.

This would require some re-work of the AI phase though.

1) LCIM should see that this isn't safe to move

The effects here are super specific. If LICM asked the question "If I move this, is this still safe to execute?" it would
have answered "no" in this case (without the structure check). Of course, if we hadn't removed the CheckInBounds, the answer
would be "yes," which is also fine.

One could imagine that this analysis would be pretty difficult.

1) Always run the constant folder on each block.

```
// This method is evil - it causes a huge maintenance headache and there is a gross amount of
// code devoted to it. It would be much nicer to just always run the constant folder on each
// block. But, the last time we did it, it was a 1% SunSpider regression:
// https://bugs.webkit.org/show_bug.cgi?id=133947
// So, we should probably keep this method.
void setShouldTryConstantFolding(bool tryConstantFolding) { m_shouldTryConstantFolding = tryConstantFolding; }
```

This would fix the issue though, as a failure to prove something at any point in time would not permit
that proof to be used later on.

This patch chooses the third option.

This appears to be perf-neutral on modern hardware on JS2/3 and SP2/3.

* JSTests/stress/get-by-val-hoist-above-structure.js: Added.
(opt):
(createObjectOfS1):
(createObjectOfS2):
(main):
* Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp:
(JSC::DFG::ConstantFoldingPhase::foldConstants):

Originally-landed-as: 272448.796@safari-7618-branch (8d5ba1e). rdar://128088091
Canonical link: https://commits.webkit.org/278842@main
webkit-commit-queue pushed a commit that referenced this pull request Jul 8, 2024
https://bugs.webkit.org/show_bug.cgi?id=276291

Reviewed by Antti Koivisto.

In 162878@main we went from isPercent() to isPercentOrCalculated() without adding support
for the "calculated" part.
(FIXME: we should find a common place for this)

https://drafts.csswg.org/css-sizing-3/#replaced-percentage-min-contribution
"If the box is replaced, a cyclic percentage in the value of any max size property or preferred size property (width/max-width/height/max-height), is resolved against zero when calculating the min-content contribution in the corresponding axis"

and

https://drafts.csswg.org/css-sizing-3/#min-content-zero
"In addition to the replaced elements listed in HTML 14.4 [HTML], the following HTML elements are also considered to be replaced elements...select, textarea, progress, meter..."

* LayoutTests/imported/w3c/web-platform-tests/css/css-flexbox/flex-item-compressible-001-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/css/css-flexbox/flex-item-compressible-002-expected.txt:
* Source/WebCore/rendering/RenderFileUploadControl.cpp:
(WebCore::RenderFileUploadControl::computeIntrinsicLogicalWidths const):
* Source/WebCore/rendering/RenderListBox.cpp:
(WebCore::RenderListBox::computeIntrinsicLogicalWidths const):
* Source/WebCore/rendering/RenderMenuList.cpp:
(RenderMenuList::computeIntrinsicLogicalWidths const):
* Source/WebCore/rendering/RenderSlider.cpp:
(WebCore::RenderSlider::computeIntrinsicLogicalWidths const):
* Source/WebCore/rendering/RenderTextControl.cpp:
(WebCore::RenderTextControl::computeIntrinsicLogicalWidths const):

Canonical link: https://commits.webkit.org/280722@main
Wowfunhappy referenced this pull request in Wowfunhappy/WebKit Jun 16, 2026
…u repopulate hack

Safari 7's WebProcess injected bundle calls WKBundleHitTestResultGetFrame/GetTargetFrame to put the hit Frame into the context-menu userData. Upstream 882f278 ("Reduce InjectedBundle SPI surface area") gutted those to `return nullptr`. Safari's V3 UIProcess context-menu callback (BrowserPageContextMenuClient::getContextMenuFromProposedMenu) reads that Frame, finds it null, and bails -> empty menu. The 85f38e2 backport hack in WebContextMenuProxyMac::useContextMenuItems papered over this by repopulating an empty menu from the proposed items, which made Safari's menu work (#12) but also overrode the App Store's deliberate empty-array suppression (#73).

Restore the pre-882f278 bodies (matching the file's existing protect() idiom, used by all sibling functions) so Safari builds its own menu; delete the repopulate hack so the App Store suppresses correctly. Verified via right-click: Safari shows its rich native menu, App Store shows none, preventDefault honored, github/cnn render.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
webkit-commit-queue pushed a commit that referenced this pull request Jul 11, 2026
…, not free space

https://bugs.webkit.org/show_bug.cgi?id=318497

Reviewed by Antti Koivisto.

computeMainSizeForFlexItems resolved the flexible lengths and then returned the
per-line remaining main-axis free space. FlexLayout::layout instead returns the
used main size of each item and computes the free space later, in
handleMainAxisAlignment (#12). Match that: computeMainSizeForFlexItems now
returns a per-item Vector<LayoutUnit>, and handleMainAxisAlignment derives each
line's remaining free space from it.

Behavior is unchanged. handleMainAxisAlignment rebuilds an item's used outer main
size as mainSize + mainAxisBorderAndPadding + mainAxisMargin, which is exactly
flexedMarginBoxSize(); the container inner main size and gap handling are the
same, and the item margins are stable between sizing and this point (auto-margin
distribution happens inside placeFlexItems, after the per-line read). The item
still carries flexedContentSize for layout and placement, so the returned list
duplicates it for now -- dropping it is follow-on work.

* Source/WebCore/rendering/RenderFlexibleBox.h:
* Source/WebCore/rendering/RenderFlexibleBox.cpp:
(WebCore::RenderFlexibleBox::performFlexLayout):
(WebCore::RenderFlexibleBox::computeMainSizeForFlexItems):
(WebCore::RenderFlexibleBox::handleMainAxisAlignment):

Canonical link: https://commits.webkit.org/316959@main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants