[PATCH] D116465: [SPIRV 6/6] Add 2 essential passes and the simplest tests

Renato Golin via Phabricator via llvm-commits llvm-commits at lists.llvm.org
Thu Feb 3 04:25:34 PST 2022


rengolin added a comment.

In D116465#3293156 <https://reviews.llvm.org/D116465#3293156>, @iliya-diyachkov wrote:

> And as far as I understand your suggestion this de-duplicated variant will break the verifier (after generation):
>
>   func1
>     %0 = OpTypeInt 32
>     %1 = OpTypeInt 64
>     ...
>     %4 = OpIAdd %1 %2 %3
>     ...
>   func2
>     // (no definition of %1)
>     ... 
>     %4 = OpIMul %1 %2 %3
>     ...

No, I'm suggestion you emit on every function (as is functionally correct) but reuse the same ID, and keep that same ID on `global_function`.

The singleton is just caching the VReg numbers, not the actual `MNode`s.

Code emission will be the same as what you have today after that.

> I agree that this is a natural desire to use some caching in this situation. But I join Alexander in the question how the proposed caching would work at module (cross-function) level? As I understand we can define %type1 register in func1 and refer to the register inside func1, but it's illegal to refer %type1 in func2 without a local definition of %type1 (let me remind that in SPIR-V the type definition is a regular MIR instruction which produces %type register).

By just re-declaring each used type/constant in each subsequent function with the same VReg ID.

In your example:

> After generation:
>
>   func1
>     %0 = OpTypeInt 32
>     %1 = OpTypeInt 64
>     ...
>     %4 = OpIAdd %1 %2 %3
>     ...
>   func2
>     %0 = OpTypeInt 32
>     %1 = OpTypeInt 64
>     ...
>     %4 = OpIMul %1 %2 %3
>     ...
>
> After GlobalTypesAndRegNumPass:
>
>   global_func:
>     %0 = OpTypeInt 32
>     %1 = OpTypeInt 64
>   ...
>   func1
>     %0 = OpTypeInt 32
>     %1 = OpTypeInt 64
>     ...
>     %4 = OpIAdd %1 %2 %3
>     ...
>   func2
>     %0 = OpTypeInt 32
>     %1 = OpTypeInt 64
>     ...
>     %4 = OpIMul %1 %2 %3
>     ...

To have the same MIR in your second example, straight from generation, you have to follow these steps:

- First time you see, generate a new type (`OpTypeInt 32`) with a new auto-increment ID, cache the ID for the original type, emit on `global_func` and *also* on the function you're generating.
- Every other time you see, get the cached VReg ID and re-emit (a new MNode) using that same ID.

In your example:

- Inside `func1` you generate a new type (`OpTypeInt 32`) and call it `%0`, cache `%0 for i32`, emit on `global_func` and *also* on `func1`.
- Inside `func2` you get the cached version (of i32 as %0) and generate it *again* only on `func2` (on a new Node, using %0). `global_func` already have it's the same VReg number on all previous functions.

If you use a global auto-increment `next_id()`, there's no way VReg IDs will clash with previously defined types and constants (in previous functions).

The VReg numbers on the later functions will be (numerically) large, but the number of different IDs in each function will still be the same, so code generation should be equally efficient.


CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D116465/new/

https://reviews.llvm.org/D116465



More information about the llvm-commits mailing list