summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorTim Foley <tfoley@nvidia.com>2020-06-17 14:55:46 -0700
committerTim Foley <tfoley@nvidia.com>2020-06-18 16:00:40 -0700
commit0eddf45f36f3948c18b8a45ef52b980f2a2b0cc9 (patch)
treec88d71dec8bd403d0d2330e930019ee025d8bac9 /tests
parentaa6aca498cd1f7fbbdb143e72dd48b1d714c8fbb (diff)
Work on struct inheritance and interfaces
The main new feature that works here is that a derived `struct` type can satisfy one or more interface requirements using methods it inherited from a base `struct` type: ```hlsl interface ICounter { [mutating] void increment(); } struct CounterBase { int val; [mutating] void increment() { val++; } } struct ResetableCounter : CounterBase, ICounter { [mutating] void reset() { val = 0; } } ``` Here the derived `ResetableCounter` type is satisfying the `increment()` requirement from `ICounter` using the inherited `CounterBase` method instead of one defined on `ResetableCounter`. The crux of the problem here was that after lowering to HLSL/GLSL, the above code looks something like: ```hlsl struct CounterBase { int val; }; void CounterBase_increment(in out CounterBase this) { this.val++; } struct ResetableCounter { CounterBase base; } void ResetableCounter_reset(in out ResetableCounter this) { this.base.val = 0; } ``` The central problem is that `CounterBase_increment` here is not type-compatible what we expect to find in the witness table for `ResetableCounter : ICounter`: the `this` parameter has the wrong type! The basic solution strategy here is to intercept the search for a witness to sastify an interface requirement in `findWitnessForInterfaceRequirement` (those witnesses get collected into a witness table). The revised logic first looks for an exact match, which will only consider members introduced for the type itself, and not those introduced by base types. If an exact match for a method requirement is not found, the semantic checker then tries to *synthesize* a witness for the requirement, which more or less amounts to generating a function like: ```hlsl [mutating] void ResetableCounter::synthesized_increment() { this.increment(); } ``` The body of that synthesized method will type-check just fine in this case (because it desugars into `this.base.increment()`, more or less), and thus the synthesized method declaration can be used as the actual witness that drives downstream code generation. Details: * I added some options to lookup to allow us to explicitly skip member lookup through base interfaces; this should make sure that we don't accidentally satisfy an interface requirement using a member of the same or another interface (since such members are conceptually `abstract`). * As it originally stood, the semantic checker was allowing `CounterBase.increment()` to satisfy the `increment()` requirement of `ResetableCounter` directly, with the result that we got invalid HLSL/GLSL code as output. In order to avoid this and other bad cases, I made sure that the "exact match" case of requirement satisfaction ignores members that included any "breadcrumbs" in the lookup result item (since the breadcrumbs would all indicate transformations that needed to be applied to `this` to find the right member). * If we eventually have targets where `this` is passed by pointer/reference in all cases, then all of this work is not needed for the common case of single inheritance, and the base-type method should be usable as a witness directly. I don't see any easy way to handle that special case without producing target-dependent code in the front-end. It might be that we need an IR pass that can detect functions that are trivial "forwarding" functions and replace them with the function they forward to. * This change includes a test case that should have come along with the original PR that started adding struct inheritance Caveats: * The comments in this change talk about things like allowing a method with a default parameter to satisfy a requirement without that parameter. That scenario won't actually work at present because we still have an enormous hack in our logic for checking methods against requirements: we don't actually consider their signatures! I couldn't fold a fix for that issue into this change because there are subtle corner cases around associated types that we need to handle correctly (which were part of the reason why the checking is as hacked as it is) * This change does *not* try to test or address the case where we want to have a `Derived` type conform to `ISomething` because it inherits from `Base` and `Base : ISomething`. That case has its own details that need to be worked out, but ideally can follow a similar implementation strategy when it comes to re-using methods from `Base` to satisfy requirement on `Derived`.
Diffstat (limited to 'tests')
-rw-r--r--tests/language-feature/inheritance/struct-inherit-interface-requirement.slang69
-rw-r--r--tests/language-feature/inheritance/struct-inherit-interface-requirement.slang.expected.txt0
-rw-r--r--tests/language-feature/inheritance/struct-inheritance.slang58
-rw-r--r--tests/language-feature/inheritance/struct-inheritance.slang.expected.txt4
4 files changed, 131 insertions, 0 deletions
diff --git a/tests/language-feature/inheritance/struct-inherit-interface-requirement.slang b/tests/language-feature/inheritance/struct-inherit-interface-requirement.slang
new file mode 100644
index 000000000..e18695737
--- /dev/null
+++ b/tests/language-feature/inheritance/struct-inherit-interface-requirement.slang
@@ -0,0 +1,69 @@
+// struct-inherit-interface-requirement.slang
+
+//TEST(compute):COMPARE_COMPUTE:
+
+// Test that a `struct` type can use an inherited
+// member to satisfy an interface requirement.
+
+interface ITweak
+{
+ int tweak(int val);
+ int twiddle(int val);
+}
+
+// Note: `Base` intentionally doesn't inherit from `ITweak`,
+// but it *does* provide a method that could satisfy one
+// of the interface requirements.
+//
+struct Base
+{
+ int a;
+
+ int tweak(int val) { return val ^ a; }
+}
+
+struct Derived : Base, ITweak
+{
+ // Note: it is important for this type to have an additional
+ // field beyond the one in `Base`, because it ensures that
+ // the two types `Base` and `Derived` aren't structurally
+ // equivalent when compiled through HLSL (which silently allows
+ // certain type mismatches so long as there is a memberwise
+ // structural match).
+ int b;
+
+ int twiddle(int val)
+ {
+ return val + b;
+ }
+}
+
+int tweakAndTwiddle<T : ITweak>(T tweaker, int val)
+{
+ int tmp = val;
+ tmp = tweaker.tweak(val);
+ tmp = tweaker.twiddle(val);
+ return val;
+}
+
+
+int test(int val)
+{
+ Derived d;
+ d.a = 0xFF;
+ d.b = 1;
+
+ return tweakAndTwiddle(d, val);
+}
+
+//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):out,name=outputBuffer
+RWStructuredBuffer<int> outputBuffer;
+
+[numthreads(4, 1, 1)]
+void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
+{
+ uint tid = dispatchThreadID.x;
+ int inVal = tid;
+ int outVal = test(inVal);
+ outputBuffer[tid] = outVal;
+}
diff --git a/tests/language-feature/inheritance/struct-inherit-interface-requirement.slang.expected.txt b/tests/language-feature/inheritance/struct-inherit-interface-requirement.slang.expected.txt
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/language-feature/inheritance/struct-inherit-interface-requirement.slang.expected.txt
diff --git a/tests/language-feature/inheritance/struct-inheritance.slang b/tests/language-feature/inheritance/struct-inheritance.slang
new file mode 100644
index 000000000..e2cfaf25b
--- /dev/null
+++ b/tests/language-feature/inheritance/struct-inheritance.slang
@@ -0,0 +1,58 @@
+// struct-inheritance.slang
+
+//TEST(compute):COMPARE_COMPUTE:
+
+// Test that we can define a `struct` type
+// that inherits from another `struct`.
+
+struct Base
+{
+ int a;
+
+ int tweakBase(int val) { return val ^ a; }
+}
+
+struct Derived : Base
+{
+ int b;
+
+ int tweakDerived(int val) { return tweakBase(val) + b; }
+}
+
+int tweak(Base b, int v)
+{
+ return b.tweakBase(v);
+}
+
+//TEST_INPUT:cbuffer(data=[1 2]):name=C
+cbuffer C
+{
+ int x;
+ int y;
+}
+
+int test(int val)
+{
+ Derived d;// = { x, y };
+ d.a = x;
+ d.b = y;
+
+ int result = 0;
+ result = result*16 + d.a;
+ result = result*16 + d.tweakBase(val);
+ result = result*16 + tweak(d, val);
+ result = result*16 + d.tweakDerived(val);
+ return result;
+}
+
+//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):out,name=outputBuffer
+RWStructuredBuffer<int> outputBuffer;
+
+[numthreads(4, 1, 1)]
+void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
+{
+ uint tid = dispatchThreadID.x;
+ int inVal = tid;
+ int outVal = test(inVal);
+ outputBuffer[tid] = outVal;
+}
diff --git a/tests/language-feature/inheritance/struct-inheritance.slang.expected.txt b/tests/language-feature/inheritance/struct-inheritance.slang.expected.txt
new file mode 100644
index 000000000..a17826230
--- /dev/null
+++ b/tests/language-feature/inheritance/struct-inheritance.slang.expected.txt
@@ -0,0 +1,4 @@
+1113
+1002
+1335
+1224