Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[JSC] Add enumerator_put_by_val
https://bugs.webkit.org/show_bug.cgi?id=255542
rdar://108153724

Reviewed by Justin Michaud.

We found that for-in + put_by_val is common pattern, like

    for (var key in object)
        object[key] = object[key] + 42;

Previously, we handle `object[key]` as normal put_by_val. But since we can propagate offset information from JSPropertyNameEnumerator,
we can make `object[key]` super fast as the same way to `enumerator_get_by_val`. This patch adds op_enumerator_put_by_val, which is
handled almost the same way to op_enumerator_get_by_val.

1. We add op_enumerator_put_by_val, very similar to op_enumerator_get_by_val. And we add corresponding DFG / FTL node, EnumeratorPutByVal.
1. We add didWatchReplacement bit to Structure since it needs watchpoint invalidation. So we cannot do super fast replace for that.

* JSTests/stress/for-in-put-by-val.js: Added.
(shouldBe):
(test):
* Source/JavaScriptCore/bytecode/BytecodeList.rb:
* Source/JavaScriptCore/bytecode/BytecodeUseDef.cpp:
(JSC::computeUsesForBytecodeIndexImpl):
(JSC::computeDefsForBytecodeIndexImpl):
* Source/JavaScriptCore/bytecode/CodeBlock.cpp:
(JSC::CodeBlock::finishCreation):
* Source/JavaScriptCore/bytecode/Opcode.h:
* Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp:
(JSC::BytecodeGenerator::emitPutByVal):
(JSC::BytecodeGenerator::emitEnumeratorPutByVal):
(JSC::ForInContext::finalize):
* Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h:
(JSC::ForInContext::addPutInst):
* Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp:
(JSC::AssignBracketNode::emitBytecode):
* Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h:
(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
* Source/JavaScriptCore/dfg/DFGBackwardsPropagationPhase.cpp:
(JSC::DFG::BackwardsPropagationPhase::propagate):
* Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::parseBlock):
* Source/JavaScriptCore/dfg/DFGClobberize.h:
(JSC::DFG::clobberize):
* Source/JavaScriptCore/dfg/DFGDoesGC.cpp:
(JSC::DFG::doesGC):
* Source/JavaScriptCore/dfg/DFGFixupPhase.cpp:
(JSC::DFG::FixupPhase::fixupNode):
* Source/JavaScriptCore/dfg/DFGNode.h:
(JSC::DFG::Node::hasStorageChild const):
(JSC::DFG::Node::storageChildIndex):
(JSC::DFG::Node::hasArrayMode):
(JSC::DFG::Node::hasECMAMode):
(JSC::DFG::Node::ecmaMode):
* Source/JavaScriptCore/dfg/DFGNodeType.h:
* Source/JavaScriptCore/dfg/DFGOperations.cpp:
(JSC::DFG::JSC_DEFINE_JIT_OPERATION):
* Source/JavaScriptCore/dfg/DFGOperations.h:
* Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp:
* Source/JavaScriptCore/dfg/DFGSSALoweringPhase.cpp:
(JSC::DFG::SSALoweringPhase::handleNode):
* Source/JavaScriptCore/dfg/DFGSafeToExecute.h:
(JSC::DFG::safeToExecute):
* Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h:
* Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
(JSC::DFG::SpeculativeJIT::compileEnumeratorPutByVal):
* Source/JavaScriptCore/dfg/DFGStoreBarrierInsertionPhase.cpp:
* Source/JavaScriptCore/dfg/DFGTypeCheckHoistingPhase.cpp:
(JSC::DFG::TypeCheckHoistingPhase::identifyRedundantStructureChecks):
(JSC::DFG::TypeCheckHoistingPhase::identifyRedundantArrayChecks):
* Source/JavaScriptCore/ftl/FTLAbstractHeapRepository.h:
* Source/JavaScriptCore/ftl/FTLCapabilities.cpp:
(JSC::FTL::canCompile):
* Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp:
(JSC::FTL::DFG::LowerDFGToB3::compileNode):
(JSC::FTL::DFG::LowerDFGToB3::compileCompareStrictEq):
* Source/JavaScriptCore/jit/BaselineJITRegisters.h:
* Source/JavaScriptCore/jit/JIT.cpp:
(JSC::JIT::privateCompileMainPass):
(JSC::JIT::privateCompileSlowCases):
* Source/JavaScriptCore/jit/JIT.h:
* Source/JavaScriptCore/jit/JITPropertyAccess.cpp:
(JSC::JIT::generatePutByValSlowCase):
(JSC::JIT::emitSlow_op_put_by_val):
(JSC::JIT::emit_op_enumerator_put_by_val):
(JSC::JIT::emitSlow_op_enumerator_put_by_val):
* Source/JavaScriptCore/llint/LowLevelInterpreter32_64.asm:
* Source/JavaScriptCore/llint/LowLevelInterpreter64.asm:
* Source/JavaScriptCore/runtime/CommonSlowPaths.cpp:
(JSC::JSC_DEFINE_COMMON_SLOW_PATH):
* Source/JavaScriptCore/runtime/CommonSlowPaths.h:
* Source/JavaScriptCore/runtime/CommonSlowPathsInlines.h:
(JSC::CommonSlowPaths::opEnumeratorPutByVal):
* Source/JavaScriptCore/runtime/JSObject.cpp:
(JSC::JSObject::putOwnDataPropertyBatching):
* Source/JavaScriptCore/runtime/PropertySlot.h:
* Source/JavaScriptCore/runtime/Structure.cpp:
(JSC::Structure::ensurePropertyReplacementWatchpointSet):
* Source/JavaScriptCore/runtime/Structure.h:
(JSC::Structure::bitFieldOffset):
* Source/JavaScriptCore/runtime/StructureInlines.h:
(JSC::Structure::didReplaceProperty):
* Source/JavaScriptCore/runtime/StructureTransitionTable.h:

Canonical link: https://commits.webkit.org/263056@main
  • Loading branch information
Constellation committed Apr 18, 2023
1 parent f209607 commit b96c7b6
Show file tree
Hide file tree
Showing 45 changed files with 846 additions and 10 deletions.
19 changes: 19 additions & 0 deletions JSTests/stress/enumerator-put-by-val-fallback.js
@@ -0,0 +1,19 @@
function shouldBe(actual, expected) {
if (actual !== expected)
throw new Error('bad value: ' + actual);
}

function test(src, dest) {
for (var key in src)
dest[key] = src[key] + 42;
}

var source = {
a: 0, b: 1, c: 2, d: 3, e: 4
};

for (var i = 0; i < 1e6; ++i) {
var dest = { };
test(source, dest);
shouldBe(JSON.stringify(dest), `{"a":42,"b":43,"c":44,"d":45,"e":46}`);
}
33 changes: 33 additions & 0 deletions JSTests/stress/for-in-put-by-val.js
@@ -0,0 +1,33 @@
function shouldBe(actual, expected) {
if (actual !== expected)
throw new Error('bad value: ' + actual);
}

function test() {
var object = {
a: 42,
b: 43,
c: 44,
d: 45,
e: 46,
f: 47,
g: 48,
h: 49,
};

for (var i in object) {
var value = object[i] + 20;
object[i] = value;
}
return object;
}
noInline(test);

for (var i = 0; i < 1e6; ++i) {
var object = test();
if (i & 0x100) {
var k = 62;
for (var i in object)
shouldBe(object[i], k++);
}
}
15 changes: 15 additions & 0 deletions Source/JavaScriptCore/bytecode/BytecodeList.rb
Expand Up @@ -792,6 +792,21 @@
enumeratorMetadata: EnumeratorMetadata,
}

op :enumerator_put_by_val,
args: {
base: VirtualRegister,
mode: VirtualRegister,
propertyName: VirtualRegister,
index: VirtualRegister,
enumerator: VirtualRegister,
value: VirtualRegister,
ecmaMode: ECMAMode,
},
metadata: {
arrayProfile: ArrayProfile,
enumeratorMetadata: EnumeratorMetadata,
}

# Alignment: 1
op :jneq_ptr,
args: {
Expand Down
2 changes: 2 additions & 0 deletions Source/JavaScriptCore/bytecode/BytecodeUseDef.cpp
Expand Up @@ -287,6 +287,7 @@ void computeUsesForBytecodeIndexImpl(const JSInstruction* instruction, Checkpoin
USES(OpEnumeratorNext, mode, index, base, enumerator)
USES(OpEnumeratorGetByVal, base, mode, propertyName, index, enumerator)
USES(OpEnumeratorInByVal, base, mode, propertyName, index, enumerator)
USES(OpEnumeratorPutByVal, base, mode, propertyName, index, enumerator, value)
USES(OpEnumeratorHasOwnProperty, base, mode, propertyName, index, enumerator)

case op_iterator_open: {
Expand Down Expand Up @@ -399,6 +400,7 @@ void computeDefsForBytecodeIndexImpl(unsigned numVars, const JSInstruction* inst
case op_put_setter_by_val:
case op_put_by_val:
case op_put_by_val_direct:
case op_enumerator_put_by_val:
case op_put_private_name:
case op_set_private_brand:
case op_check_private_brand:
Expand Down
1 change: 1 addition & 0 deletions Source/JavaScriptCore/bytecode/CodeBlock.cpp
Expand Up @@ -507,6 +507,7 @@ bool CodeBlock::finishCreation(VM& vm, ScriptExecutable* ownerExecutable, Unlink
LINK(OpInByVal)
LINK(OpPutByVal)
LINK(OpPutByValDirect)
LINK(OpEnumeratorPutByVal)
LINK(OpPutPrivateName)

LINK(OpSetPrivateBrand)
Expand Down
1 change: 1 addition & 0 deletions Source/JavaScriptCore/bytecode/Opcode.h
Expand Up @@ -152,6 +152,7 @@ static constexpr unsigned bitWidthForMaxBytecodeStructLength = WTF::getMSBSetCon
macro(OpEnumeratorNext) \
macro(OpEnumeratorGetByVal) \
macro(OpEnumeratorInByVal) \
macro(OpEnumeratorPutByVal) \
macro(OpEnumeratorHasOwnProperty) \
macro(OpNewArrayWithSpecies) \
FOR_EACH_OPCODE_WITH_CALL_LINK_INFO(macro) \
Expand Down
42 changes: 42 additions & 0 deletions Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp
Expand Up @@ -2786,6 +2786,13 @@ RegisterID* BytecodeGenerator::emitGetPrototypeOf(RegisterID* dst, RegisterID* v

RegisterID* BytecodeGenerator::emitPutByVal(RegisterID* base, RegisterID* property, RegisterID* value)
{
for (size_t i = m_forInContextStack.size(); i--; ) {
ForInContext& context = m_forInContextStack[i].get();
if (context.local() != property)
continue;
return emitEnumeratorPutByVal(context, base, property, value);
}

OpPutByVal::emit(this, base, property, value, ecmaMode());
return value;
}
Expand All @@ -2802,6 +2809,20 @@ RegisterID* BytecodeGenerator::emitPutByValWithECMAMode(RegisterID* base, Regist
return value;
}

RegisterID* BytecodeGenerator::emitEnumeratorPutByVal(ForInContext& context, RegisterID* base, RegisterID* property, RegisterID* value)
{
#if USE(JSVALUE64)
// FIXME: We should have a better bytecode rewriter that can resize chunks.
OpEnumeratorPutByVal::emit<OpcodeSize::Wide32>(this, base, context.mode(), property, context.propertyOffset(), context.enumerator(), value, ecmaMode());
context.addPutInst(m_lastInstruction.offset(), property->index());
return value;
#else
UNUSED_PARAM(context);
OpPutByVal::emit(this, base, property, value, ecmaMode());
return value;
#endif
}

RegisterID* BytecodeGenerator::emitGetPrivateName(RegisterID* dst, RegisterID* base, RegisterID* property)
{
OpGetPrivateName::emit(this, dst, base, property);
Expand Down Expand Up @@ -5414,6 +5435,27 @@ void ForInContext::finalize(BytecodeGenerator& generator, UnlinkedCodeBlockGener
for (const auto& instTuple : m_inInsts)
rewriteOp<OpEnumeratorInByVal, OpInByVal>(generator, instTuple);

for (const auto& instTuple : m_putInsts) {
unsigned instIndex = std::get<0>(instTuple);
int propertyRegIndex = std::get<1>(instTuple);
auto instruction = generator.m_writer.ref(instIndex);
auto end = instIndex + instruction->size();
ASSERT(instruction->isWide32());

generator.m_writer.seek(instIndex);

auto bytecode = instruction->as<OpEnumeratorPutByVal>();

generator.disablePeepholeOptimization();

static_assert(sizeof(OpPutByVal) <= sizeof(OpEnumeratorPutByVal));
OpPutByVal::emit(&generator, bytecode.m_base, VirtualRegister(propertyRegIndex), bytecode.m_value, bytecode.m_ecmaMode);

// 4. nop out the remaining bytes
while (generator.m_writer.position() < end)
OpNop::emit<OpcodeSize::Narrow>(&generator);
}

for (const auto& hasOwnPropertyTuple : m_hasOwnPropertyJumpInsts) {
static_assert(sizeof(OpJmp) <= sizeof(OpJneqPtr));
unsigned branchInstIndex = std::get<0>(hasOwnPropertyTuple);
Expand Down
8 changes: 8 additions & 0 deletions Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h
Expand Up @@ -256,6 +256,7 @@ namespace JSC {
WTF_MAKE_NONCOPYABLE(ForInContext);
public:
using GetInst = std::tuple<unsigned, int>;
using PutInst = GetInst;
using InInst = GetInst;
using HasOwnPropertyJumpInst = std::tuple<unsigned, unsigned>;

Expand All @@ -274,6 +275,11 @@ namespace JSC {
m_getInsts.append(GetInst { instIndex, propertyRegIndex });
}

void addPutInst(unsigned instIndex, int propertyRegIndex)
{
m_putInsts.append(PutInst { instIndex, propertyRegIndex });
}

void addInInst(unsigned instIndex, int propertyRegIndex)
{
m_inInsts.append(InInst { instIndex, propertyRegIndex });
Expand Down Expand Up @@ -309,6 +315,7 @@ namespace JSC {
unsigned m_bodyBytecodeStartOffset;
Vector<InInst> m_inInsts;
Vector<GetInst> m_getInsts;
Vector<PutInst> m_putInsts;
Vector<HasOwnPropertyJumpInst> m_hasOwnPropertyJumpInsts;
};

Expand Down Expand Up @@ -763,6 +770,7 @@ namespace JSC {
RegisterID* emitPutByVal(RegisterID* base, RegisterID* property, RegisterID* value);
RegisterID* emitPutByVal(RegisterID* base, RegisterID* thisValue, RegisterID* property, RegisterID* value);
RegisterID* emitPutByValWithECMAMode(RegisterID* base, RegisterID* thisValue, RegisterID* property, RegisterID* value, ECMAMode);
RegisterID* emitEnumeratorPutByVal(ForInContext&, RegisterID* base, RegisterID* property, RegisterID* value);
RegisterID* emitDirectPutByVal(RegisterID* base, RegisterID* property, RegisterID* value);
RegisterID* emitDeleteByVal(RegisterID* dst, RegisterID* base, RegisterID* property);

Expand Down
17 changes: 15 additions & 2 deletions Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
Expand Up @@ -3828,6 +3828,15 @@ RegisterID* AssignErrorNode::emitBytecode(BytecodeGenerator& generator, Register

RegisterID* AssignBracketNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
{
ForInContext* context = nullptr;
if (m_subscript->isResolveNode()) {
Variable argumentVariable = generator.variable(static_cast<ResolveNode*>(m_subscript)->identifier());
if (argumentVariable.isLocal()) {
RegisterID* property = argumentVariable.local();
context = generator.findForInContext(property);
}
}

RefPtr<RegisterID> base = generator.emitNodeForLeftHandSide(m_base, m_subscriptHasAssignments || m_rightHasAssignments, m_subscript->isPure(generator) && m_right->isPure(generator));
RefPtr<RegisterID> property = generator.emitNodeForLeftHandSideForProperty(m_subscript, m_rightHasAssignments, m_right->isPure(generator));
RefPtr<RegisterID> value = generator.destinationForAssignResult(dst);
Expand All @@ -3846,8 +3855,12 @@ RegisterID* AssignBracketNode::emitBytecode(BytecodeGenerator& generator, Regist
if (m_base->isSuperNode()) {
RefPtr<RegisterID> thisValue = generator.ensureThis();
generator.emitPutByVal(base.get(), thisValue.get(), property.get(), forwardResult);
} else
generator.emitPutByVal(base.get(), property.get(), forwardResult);
} else {
if (context)
generator.emitEnumeratorPutByVal(*context, base.get(), property.get(), forwardResult);
else
generator.emitPutByVal(base.get(), property.get(), forwardResult);
}
}

generator.emitProfileType(forwardResult, divotStart(), divotEnd());
Expand Down
6 changes: 6 additions & 0 deletions Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
Expand Up @@ -2589,6 +2589,12 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
}
break;
}

case EnumeratorPutByVal: {
clobberWorld();
break;
}


case ArrayPush:
switch (node->arrayMode().type()) {
Expand Down
1 change: 1 addition & 0 deletions Source/JavaScriptCore/dfg/DFGBackwardsPropagationPhase.cpp
Expand Up @@ -494,6 +494,7 @@ class BackwardsPropagationPhase {
break;
}

case EnumeratorPutByVal:
case PutByValDirect:
case PutByVal: {
m_graph.varArgChild(node, 0)->mergeFlags(NodeBytecodeUsesAsValue);
Expand Down
68 changes: 68 additions & 0 deletions Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
Expand Up @@ -9042,6 +9042,74 @@ void ByteCodeParser::parseBlock(unsigned limit)
NEXT_OPCODE(op_enumerator_has_own_property);
}

case op_enumerator_put_by_val: {
auto bytecode = currentInstruction->as<OpEnumeratorPutByVal>();
auto& metadata = bytecode.metadata(codeBlock);
ArrayMode arrayMode = getArrayMode(metadata.m_arrayProfile, Array::Write);

Node* base = get(bytecode.m_base);
Node* propertyName = get(bytecode.m_propertyName);
Node* value = get(bytecode.m_value);
Node* index = get(bytecode.m_index);
Node* mode = get(bytecode.m_mode);
Node* enumerator = get(bytecode.m_enumerator);

auto seenModes = OptionSet<JSPropertyNameEnumerator::Flag>::fromRaw(metadata.m_enumeratorMetadata);
if (!seenModes)
addToGraph(ForceOSRExit);

if (seenModes == JSPropertyNameEnumerator::IndexedMode) {
Node* badMode = addToGraph(ArithBitAnd, mode, jsConstant(jsNumber(JSPropertyNameEnumerator::GenericMode | JSPropertyNameEnumerator::OwnStructureMode)));

// We know the ArithBitAnd cannot have effects so it's ok to exit here.
m_exitOK = true;
addToGraph(ExitOK);

addToGraph(CheckIsConstant, OpInfo(m_graph.freezeStrong(jsNumber(0))), badMode);

addVarArgChild(base);
addVarArgChild(index); // Use index so we'll use the normal indexed optimizations.
addVarArgChild(value);
addVarArgChild(nullptr); // Leave room for property storage.
addVarArgChild(nullptr); // Leave room for length.
addToGraph(Node::VarArg, PutByVal, OpInfo(arrayMode.asWord()), OpInfo(bytecode.m_ecmaMode));

addToGraph(Phantom, propertyName);
addToGraph(Phantom, enumerator);
NEXT_OPCODE(op_enumerator_put_by_val);
}

// FIXME: Checking for a BadConstantValue causes us to always use the Generic variant if we switched from IndexedMode -> IndexedMode + OwnStructureMode even though that might be fine.
if (!seenModes.containsAny({ JSPropertyNameEnumerator::GenericMode, JSPropertyNameEnumerator::HasSeenOwnStructureModeStructureMismatch })
&& !m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadConstantValue)) {
Node* modeTest = addToGraph(SameValue, mode, jsConstant(jsNumber(static_cast<uint8_t>(JSPropertyNameEnumerator::GenericMode))));
addToGraph(CheckIsConstant, OpInfo(m_graph.freezeStrong(jsBoolean(false))), modeTest);

addVarArgChild(base);
addVarArgChild(index); // Use index so we'll use the normal indexed optimizations.
addVarArgChild(value);
addVarArgChild(nullptr); // For property storage to match PutByVal.
addVarArgChild(index);
addVarArgChild(mode);
addVarArgChild(enumerator);
addToGraph(Node::VarArg, EnumeratorPutByVal, OpInfo(arrayMode.asWord()), OpInfo(bytecode.m_ecmaMode));

addToGraph(Phantom, propertyName);
NEXT_OPCODE(op_enumerator_put_by_val);
}

addVarArgChild(base);
addVarArgChild(propertyName);
addVarArgChild(value);
addVarArgChild(nullptr); // For property storage to match PutByVal.
addVarArgChild(index);
addVarArgChild(mode);
addVarArgChild(enumerator);
addToGraph(Node::VarArg, EnumeratorPutByVal, OpInfo(arrayMode.asWord()), OpInfo(bytecode.m_ecmaMode));

NEXT_OPCODE(op_enumerator_put_by_val);
}

case op_get_internal_field: {
auto bytecode = currentInstruction->as<OpGetInternalField>();
set(bytecode.m_dst, addToGraph(GetInternalField, OpInfo(bytecode.m_index), OpInfo(getPrediction()), get(bytecode.m_base)));
Expand Down
8 changes: 7 additions & 1 deletion Source/JavaScriptCore/dfg/DFGClobberize.h
Expand Up @@ -149,6 +149,7 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu
break;
case EnumeratorNextUpdateIndexAndMode:
case EnumeratorGetByVal:
case EnumeratorPutByVal:
case EnumeratorInByVal:
case EnumeratorHasOwnProperty:
case GetIndexedPropertyStorage:
Expand Down Expand Up @@ -1220,7 +1221,12 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu
RELEASE_ASSERT_NOT_REACHED();
return;
}


case EnumeratorPutByVal: {
clobberTop();
return;
}

case CheckStructureOrEmpty:
case CheckStructure:
read(JSCell_structureID);
Expand Down
3 changes: 3 additions & 0 deletions Source/JavaScriptCore/dfg/DFGDoesGC.cpp
Expand Up @@ -541,6 +541,9 @@ bool doesGC(Graph& graph, Node* node)
}
return false;

case EnumeratorPutByVal:
return true;

case MapHash:
switch (node->child1().useKind()) {
case BooleanUse:
Expand Down
8 changes: 8 additions & 0 deletions Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
Expand Up @@ -1195,6 +1195,14 @@ class FixupPhase : public Phase {
break;
}

case EnumeratorPutByVal: {
fixEdge<CellUse>(m_graph.varArgChild(node, 0));
fixEdge<KnownInt32Use>(m_graph.varArgChild(node, 4));
fixEdge<KnownInt32Use>(m_graph.varArgChild(node, 5));
fixEdge<KnownCellUse>(m_graph.varArgChild(node, 6));
break;
}

case PutByValDirect:
case PutByVal:
case PutByValAlias: {
Expand Down

0 comments on commit b96c7b6

Please sign in to comment.