Skip to content

Conversation

@skiminki-nv
Copy link
Contributor

@skiminki-nv skiminki-nv commented Nov 24, 2025

Function rewriteValueUsesToAddrUses() translates
IRFieldExtract/IRGetElement/IRStore instructions that are affected by the change of the function parameter type from value to pointer. However, the implementation failed to consider the case where:

  • The base of translated IRGetElement() is IRFieldExtract(); and
  • The index of translated IRGetElement() is IRFieldExtract() or another IRGetElement()
    In the problem case, the index would be passed a pointer instead of a dereferenced pointer.

To fix this, rewrite the function with a slightly different strategy:

  • Start by injecting IRLoad() to all uses of the transformed function parameter.
  • Then, follow up with the injected IRLoad() ops:
    • Transform IRFieldExtract(IRLoad(ptr), ...) and IRGetElement(IRLoad(ptr), ...) patterns to IRLoad(IRFieldAddress(ptr, ...)) and IRLoad(IRGetElementPtr(ptr, ...)), and keep following up with the newly injected IRLoad() ops.
    • Transform also IRStore(ptr, load(x))+IRCall(func, ptr, ...) to IRCall(func, x, ...) where ptr is a single use temporary previously added by updateCallSites()

Add also a minimal repro for the aforementioned case as a regression test.

Issue #9073

Function rewriteValueUsesToAddrUses() translates
IRFieldExtract/IRGetElement/IRStore instructions that are affected by
the change of the function parameter type from value to
pointer. However, the implementation failed to consider the case where
the index of IRGetElement() is IRFieldExtract() in which case the
translated IRGetElementPtr() would be passed a pointer as the index.

Add a second phase in this function that fixes this issue by adding a
pointer load.

Add also a minimal repro for this case as a regression test.

Issue shader-slang#9073
@skiminki-nv skiminki-nv requested a review from a team as a code owner November 24, 2025 19:18
@skiminki-nv skiminki-nv added the pr: non-breaking PRs without breaking changes label Nov 24, 2025
@skiminki-nv
Copy link
Contributor Author

/format

@slangbot
Copy link
Contributor

🌈 Formatted, please merge the changes from this PR

@csyonghe
Copy link
Collaborator

I don't quite understand why the problem will occur. In slang-ir-transform-params-to-constref.cpp:84, the isUseBaseAddrOperand() check will fail if the use is from the index operand of a GetElement, and therefore it will break and use the default handling logic, which is inserting a load. Why isn't the load being inserted in this case?

@csyonghe
Copy link
Collaborator

Adding a "fixup" phase seems like a bandaid, since the main traverseUses loop is intended to handle proper insertion of loads, and if it is not doing that we should fix it.

Copy link
Contributor Author

@skiminki-nv skiminki-nv left a comment

Choose a reason for hiding this comment

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

I don't quite understand why the problem will occur. In slang-ir-transform-params-to-constref.cpp:84, the isUseBaseAddrOperand() check will fail if the use is from the index operand of a GetElement ... Why isn't the load being inserted in this case?

It's not inserted when the GetElement op is already transformed because of the base operand.

the main traverseUses loop is intended to handle proper insertion of loads, and if it is not doing that we should fix it.

This would require more logic in the traverseUses loop. If the order of base/index use happened to be different (input-dependent), then I suppose we would inject the load but not transform the respective getelement.

To fix the traverseUses loop without adding the second phase, we'd probably have to take a different approach. If I'd rewrite this function, I'd use an approach that only uses semantics-preserving transformations. This would be provably correct.

  1. First, inject the load to uses of the function parameter now turned to pointer. (This itself is a fixup to that the parameter was changed from value to pointer.)
  2. Then, look for getelement(load(ptr), ...) and fieldextract(load(ptr), ...) patterns and transform these to load(getelementptr(ptr, ...)) and load(fieldaddr(ptr, ...)).
  3. Repeat step 2 until done.

The provability comes, because the individual transformations never change the semantics of the IR.

Adding a "fixup" phase seems like a bandaid, since ...

If the word "fixup" is bothersome, I'll change it to something else. But such fixups are typical in compilers where later passes clean-up after previous passes.

[&](IRUse* use)
{
auto user = use->getUser();
if (workListSet.contains(user))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I suppose this prevents inserting the missing load.

Update the commentary in transformation code
@skiminki-nv skiminki-nv force-pushed the 9073-spirv-pointer-to-pointer-fix branch from 9f09f6e to fec5416 Compare November 25, 2025 15:23
@skiminki-nv
Copy link
Contributor Author

Updated the patch to avoid words like fix or fixup.

Added also another case in the test.

@skiminki-nv skiminki-nv changed the title Add post-fixup phase in rewriteValueUsesToAddrUses + regression test Add second phase in rewriteValueUsesToAddrUses + regression test Nov 25, 2025
@csyonghe
Copy link
Collaborator

I guess I still don't quite understand why it fails to work if a user getElement got translated first. In that case it becomes a getElementAddr, which is not any opcode we handle in the travseseUses switch, and therefore we should insert a load before the use.

Let's not have any code that puts the IR in an invalid state if we can avoid it, so I would prefer us go with your proposed algorithm if there isn't a simple non-fixup fix to the existing code.

@skiminki-nv
Copy link
Contributor Author

Fair enough. I'll rewrite the function.

@skiminki-nv skiminki-nv force-pushed the 9073-spirv-pointer-to-pointer-fix branch from 4530af9 to 421d1a4 Compare November 27, 2025 21:57
@skiminki-nv skiminki-nv changed the title Add second phase in rewriteValueUsesToAddrUses + regression test Fix rewriteValueUsesToAddrUses and add regression test Nov 28, 2025
auto dest = as<IRStore>(user)->getPtr();
if (dest->findDecorationImpl(kIROp_TempCallArgImmutableVarDecoration))
// ==> IRCall(func, x, ...)

Copy link
Collaborator

Choose a reason for hiding this comment

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

We should check that the use is store.value, not store.destPtr before applying the change, similar to the check for "isUseBaseAddrOperand" above.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right. I just reorganized this piece of existing code (from commit a6deb5e). I'll see if this can be improved a bit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, I'm not 100% sure I understand your suggestion.

Here we are eliminating a temporary variable that stores the parameter value, since the parameter was turned to a pointer to that value. When being turned to pointer, the ptr to the temp variable was tagged with the decoration in a previous pass, for the reason which I thought was that then we don't need to do a deeper analysis here, i.e., go through the use chain. We also know that the use is IRLoad(), since that's the worklist type.

I also did check when refactoring this code that the IR-level transformation is still done correctly for the store op, before and after my patch.

Double-checking again: (slangc -target spirv tests/language-feature/lambda/lambda-2.slang -dump-ir, dump right after calling this function)

Without this transformation (i.e., code commented out):

func %createFuncx5Fx5Fslangx5FLambdax5FcreateFuncx5F2x5Fx24x5Fx5Fsynx5Fx28x29	: Func(Float, BorrowInParam(%createFuncx5Fx5Fslangx5FLambdax5FcreateFuncx5F2_, 1 : UInt64, 2147483647 : UInt64), Float)
{
block %28(
		[nameHint("this")]
		param %this1	: BorrowInParam(%createFuncx5Fx5Fslangx5FLambdax5FcreateFuncx5F2_, 1 : UInt64, 2147483647 : UInt64),
		param %29	: Float):
	[TempCallArgImmutableVar]
	let  %30	: Ptr(%createFuncx5Fx5Fslangx5FLambdax5FcreateFuncx5F2_)	= var
	let  %31	: %createFuncx5Fx5Fslangx5FLambdax5FcreateFuncx5F2_	= load(%this1)
	store(%30, %31)
	let  %32	: Float	= call %createFuncx5Fx5Fslangx5FLambdax5FcreateFuncx5F2x5Fx28x29(%30, %29)
	return_val(%32)
}

With this transformation (i.e., code as it is in this patch):

func %createFuncx5Fx5Fslangx5FLambdax5FcreateFuncx5F2x5Fx24x5Fx5Fsynx5Fx28x29	: Func(Float, BorrowInParam(%createFuncx5Fx5Fslangx5FLambdax5FcreateFuncx5F2_, 1 : UInt64, 2147483647 : UInt64), Float)
{
block %28(
		[nameHint("this")]
		param %this1	: BorrowInParam(%createFuncx5Fx5Fslangx5FLambdax5FcreateFuncx5F2_, 1 : UInt64, 2147483647 : UInt64),
		param %29	: Float):
	let  %30	: Float	= call %createFuncx5Fx5Fslangx5FLambdax5FcreateFuncx5F2x5Fx28x29(%this1, %29)
	return_val(%30)
}

In other words, what this is doing, roughly speaking:

void func(const Type *this)
{
    const Type tmp = *this; // temporary variable to use after const Type this ==> const Type *this
    somefunction(&tmp); // this function wants this *, because its parameter type was also pointerized
}

==>

void func(const Type *this)
{
    somefunction(this);
}

Are you suggesting that the previous logic wasn't quite sound and it needs fixing and you want to make this transformation more robust? Or more generic, so that we don't depend on the decoration? Could you elaborate a bit, and also, would it be ok to do this improvement in a separate PR?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I am just saying we condition/guard the transform for OpStore only when the use is the store's val operand, not the store's dest operand, just to be safe. I would turn your assert into a condition of the if at 155. Does that make sense to you?

Copy link
Collaborator

Choose a reason for hiding this comment

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

that is: if (as<IRStore>(userInst)->getValUse() == use && <remaining conditions>) { apply transforms }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, so you're concerned that we might be accidentally doing this transformation based on the ptr use? I think that's not supposed to be possible, but probably doesn't hurt to be defensive here. I'll update this logic.

Copy link
Collaborator

@csyonghe csyonghe left a comment

Choose a reason for hiding this comment

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

Suggested one change to make the logic more robust, otherwise looks good to me.

@skiminki-nv skiminki-nv enabled auto-merge December 4, 2025 12:26
@skiminki-nv skiminki-nv added this pull request to the merge queue Dec 4, 2025
Merged via the queue into shader-slang:master with commit 9536d2f Dec 4, 2025
37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr: non-breaking PRs without breaking changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants