Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deduplicate witness table while specializing generic functions #6363

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 72 additions & 3 deletions source/slang/slang-ir-specialize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,29 @@ struct SpecializationContext
SpecializationOptions options;
bool changed = false;

// Keep the record of cloned IRs and avoid duplication.
struct WitnessTableKey
{
IRType* witnessTableType;
IRType* concreteType;

WitnessTableKey(IRType* wtt, IRType* ct)
: witnessTableType(wtt), concreteType(ct)
{
}

bool operator==(const WitnessTableKey& other) const
{
return witnessTableType == other.witnessTableType && concreteType == other.concreteType;
}

HashCode getHashCode() const
{
return combineHash(HashCode(witnessTableType), HashCode(concreteType));
}
};
Dictionary<WitnessTableKey, IRInst*> mapClonedWitnessTable;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This dictionary needs to be populated upon initialization of SpecializationContext, otherwise, if a specialization will result in a witness table that already exists in the global scope, we will not be able to dedup.

The current code can only dedup witness tables created during a single specialization pass. Note that the specialization pass is invoked in a loop, so it can run more than once, each time specializing a couple more insts.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I still feel like the cleanest way to solve this is to make IRWitnessTable a HOISTABLE inst so it can be deduplicated by the existing global value deduplication mechanism in our IR.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I will make changes based on the first comment.

For the second comment, I can give another try but when I tried, it was crashing.
I think WitnessTableEntry will have to become HOISTABLE as well in order to make WitnessTable HOISTABLE.
Because WitenessTable gets WitnessTableEntry as children.
I will try to make both of them HOISTABLE and see what happens.

Copy link
Collaborator

Choose a reason for hiding this comment

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

We definitely don’t want witness table entry to be hoistable, since they doesn’t matter for deduplication purpose. Deduplication uses only type and operand as key, and that is exactly what we need here.

I am not saying turning the witness table inst itself as hoistable is the only required change, we need to deal with the consequences. For example, hoistable insts requires knowing its type and operands upon creation, you can’t create an hoistable inst and then modify its type or operands afterwards.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I see. I will not touch the witness table entry.



SpecializationContext(IRModule* inModule, TargetProgram* target, SpecializationOptions options)
: workList(*inModule->getContainerPool().getList<IRInst>())
Expand Down Expand Up @@ -3093,10 +3116,56 @@ IRInst* specializeGenericImpl(
return specializedVal;
}

// For any instruction other than a `return`, we will
// simply clone it completely into the global scope.
IRInst* clonedInst;

// Deduplicate witness tables.
// As long as its type and the concrete type are same, they shouldn't be cloned.
//
IRInst* clonedInst = cloneInst(&env, builder, ii);
if (auto witnessTable = as<IRWitnessTable>(ii))
{
// Clone type and concreteType to use as cache-key
auto witnessTableType = witnessTable->getFullType();
IRType* clonedWitnessTableType =
as<IRType>(cloneInst(&env, builder, witnessTableType));

auto concreteType = witnessTable->getConcreteType();
IRType* clonedConcreteType = as<IRType>(cloneInst(&env, builder, concreteType));

SpecializationContext::WitnessTableKey cacheKey(
clonedWitnessTableType,
clonedConcreteType);

if (!context->mapClonedWitnessTable.tryGetValue(cacheKey, clonedInst))
{
// It is not found from cache and we need to create a new one.
clonedInst = builder->emitIntrinsicInst(
clonedWitnessTableType,
kIROp_WitnessTable,
1,
(IRInst* const*)&clonedConcreteType);

clonedInst->sourceLoc = ii->sourceLoc;

// It seems that the children are same when the witnessTableType and the
// concrete type are same.
// If it changes in the future, we will need to clone the witness-table-entries
// from the children and use them as the cacheKey as well.
cloneInstDecorationsAndChildren(&env, builder->getModule(), ii, clonedInst);

context->mapClonedWitnessTable.add(cacheKey, clonedInst);
}

env.mapOldValToNew[ii] = clonedInst;
}
else
{
// For any instruction other than a `return`, we will
// simply clone it completely into the global scope.
//
clonedInst = cloneInst(&env, builder, ii);
}

SLANG_ASSERT(clonedInst);

// Any new instructions we create during cloning were
// not present when we initially built our work list,
Expand Down
Loading