hlsl: Pointer support

Add `transform::InlinePointerLets` - a Transform that moves all usage of function-scope  `let` statements of a pointer type into their places of usage.

Make the HLSL writer transform pointer parameters to `inout`.

Fixed: tint:183
Change-Id: I0a7552fa6cd31c7b7691e64feae3170a81cc6c49
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/51281
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: David Neto <dneto@google.com>
This commit is contained in:
Ben Clayton 2021-05-19 19:16:32 +00:00 committed by Tint LUCI CQ
parent c9dc951e9e
commit 06aa88aa97
20 changed files with 834 additions and 22 deletions

View File

@ -513,6 +513,8 @@ libtint_source_set("libtint_core_all_src") {
"transform/external_texture_transform.h",
"transform/first_index_offset.cc",
"transform/first_index_offset.h",
"transform/inline_pointer_lets.cc",
"transform/inline_pointer_lets.h",
"transform/manager.cc",
"transform/manager.h",
"transform/renamer.cc",

View File

@ -277,6 +277,8 @@ set(TINT_LIB_SRCS
transform/external_texture_transform.h
transform/first_index_offset.cc
transform/first_index_offset.h
transform/inline_pointer_lets.cc
transform/inline_pointer_lets.h
transform/manager.cc
transform/manager.h
transform/renamer.cc
@ -804,6 +806,7 @@ if(${TINT_BUILD_TESTS})
transform/decompose_storage_access_test.cc
transform/external_texture_transform_test.cc
transform/first_index_offset_test.cc
transform/inline_pointer_lets_test.cc
transform/renamer_test.cc
transform/single_entry_point.cc
transform/test_helper.h

View File

@ -27,6 +27,7 @@
#include "src/transform/canonicalize_entry_point_io.h"
#include "src/transform/decompose_storage_access.h"
#include "src/transform/external_texture_transform.h"
#include "src/transform/inline_pointer_lets.h"
#include "src/transform/manager.h"
namespace tint {
@ -41,6 +42,7 @@ Output Hlsl::Run(const Program* in, const DataMap& data) {
manager.Add<DecomposeStorageAccess>();
manager.Add<CalculateArrayLength>();
manager.Add<ExternalTextureTransform>();
manager.Add<InlinePointerLets>();
auto out = manager.Run(in, data);
if (!out.program.IsValid()) {
return out;

View File

@ -0,0 +1,191 @@
// Copyright 2021 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/transform/inline_pointer_lets.h"
#include <memory>
#include <unordered_map>
#include <utility>
#include "src/program_builder.h"
#include "src/sem/block_statement.h"
#include "src/sem/function.h"
#include "src/sem/statement.h"
#include "src/sem/variable.h"
#include "src/utils/scoped_assignment.h"
namespace tint {
namespace transform {
namespace {
/// Traverses the expression `expr` looking for non-literal array indexing
/// expressions that would affect the computed address of a pointer expression.
/// The function-like argument `cb` is called for each found.
/// @param program the program that owns all the expression nodes
/// @param expr the expression to traverse
/// @param cb a function-like object with the signature
/// `void(const ast::Expression*)`, which is called for each array index
/// expression
template <typename F>
void CollectSavedArrayIndices(const Program* program,
ast::Expression* expr,
F&& cb) {
if (auto* a = expr->As<ast::ArrayAccessorExpression>()) {
CollectSavedArrayIndices(program, a->array(), cb);
if (!a->idx_expr()->Is<ast::ScalarConstructorExpression>()) {
cb(a->idx_expr());
}
return;
}
if (auto* m = expr->As<ast::MemberAccessorExpression>()) {
CollectSavedArrayIndices(program, m->structure(), cb);
return;
}
if (auto* u = expr->As<ast::UnaryOpExpression>()) {
CollectSavedArrayIndices(program, u->expr(), cb);
return;
}
// Note: Other ast::Expression types can be safely ignored as they cannot be
// used to generate a reference or pointer.
// See https://gpuweb.github.io/gpuweb/wgsl/#forming-references-and-pointers
}
// PtrLet represents a `let` declaration of a pointer type.
struct PtrLet {
// A map of ptr-let initializer sub-expression to the name of generated
// variable that holds the saved value of this sub-expression, when resolved
// at the point of the ptr-let declaration.
std::unordered_map<const ast::Expression*, Symbol> saved_vars;
};
} // namespace
InlinePointerLets::InlinePointerLets() = default;
InlinePointerLets::~InlinePointerLets() = default;
Output InlinePointerLets::Run(const Program* in, const DataMap&) {
ProgramBuilder out;
CloneContext ctx(&out, in);
// If not null, current_ptr_let is the current PtrLet being operated on.
PtrLet* current_ptr_let = nullptr;
// A map of the AST `let` variable to the PtrLet
std::unordered_map<const ast::Variable*, std::unique_ptr<PtrLet>> ptr_lets;
// Register the ast::Expression transform handler.
// This performs two different transformations:
// * Identifiers that resolve to the pointer-typed `let` declarations are
// replaced with the inlined (and recursively transformed) initializer
// expression for the `let` declaration.
// * Sub-expressions inside the pointer-typed `let` initializer expression
// that have been hoisted to a saved variable are replaced with the saved
// variable identifier.
ctx.ReplaceAll([&](ast::Expression* expr) -> ast::Expression* {
if (current_ptr_let) {
// We're currently processing the initializer expression of a
// pointer-typed `let` declaration. Look to see if we need to swap this
// Expression with a saved variable.
auto it = current_ptr_let->saved_vars.find(expr);
if (it != current_ptr_let->saved_vars.end()) {
return ctx.dst->Expr(it->second);
}
}
if (auto* ident = expr->As<ast::IdentifierExpression>()) {
if (auto* vu = in->Sem().Get<sem::VariableUser>(ident)) {
auto* var = vu->Variable()->Declaration();
auto it = ptr_lets.find(var);
if (it != ptr_lets.end()) {
// We've found an identifier that resolves to a `let` declaration.
// We need to replace this identifier with the initializer expression
// of the `let` declaration. Clone the initializer expression to make
// a copy. Note that this will call back into this ReplaceAll()
// handler for sub-expressions of the initializer.
auto* ptr_let = it->second.get();
// TINT_SCOPED_ASSIGNMENT provides a stack of PtrLet*, this is
// required to handle the 'chaining' of inlined `let`s.
TINT_SCOPED_ASSIGNMENT(current_ptr_let, ptr_let);
return ctx.Clone(var->constructor());
}
}
}
return nullptr;
});
// Find all the pointer-typed `let` declarations.
// Note that these must be function-scoped, as module-scoped `let`s are not
// permitted.
for (auto* node : in->ASTNodes().Objects()) {
if (auto* let = node->As<ast::VariableDeclStatement>()) {
if (!let->variable()->is_const()) {
continue; // Not a `let` declaration. Ignore.
}
auto* var = in->Sem().Get(let->variable());
if (!var->Type()->Is<sem::Pointer>()) {
continue; // Not a pointer type. Ignore.
}
// We're dealing with a pointer-typed `let` declaration.
auto ptr_let = std::make_unique<PtrLet>();
TINT_SCOPED_ASSIGNMENT(current_ptr_let, ptr_let.get());
auto* block = ctx.src->Sem().Get(let)->Block()->Declaration();
// Scan the initializer expression for array index expressions that need
// to be hoist to temporary "saved" variables.
CollectSavedArrayIndices(
ctx.src, var->Declaration()->constructor(),
[&](ast::Expression* idx_expr) {
// We have a sub-expression that needs to be saved.
// Create a new variable
auto saved_name = ctx.dst->Symbols().New(
ctx.src->Symbols().NameFor(var->Declaration()->symbol()) +
"_save");
auto* saved = ctx.dst->Decl(
ctx.dst->Const(saved_name, nullptr, ctx.Clone(idx_expr)));
// Place this variable after the pointer typed let. Order here is
// important as order-of-operations needs to be preserved.
// CollectSavedArrayIndices() visits the LHS of an array accessor
// before the index expression.
// Note that repeated calls to InsertAfter() with the same `after`
// argument will result in nodes to inserted in the order the calls
// are made (last call is inserted last).
ctx.InsertAfter(block->statements(), let, saved);
// Record the substitution of `idx_expr` to the saved variable with
// the symbol `saved_name`. This will be used by the ReplaceAll()
// handler above.
ptr_let->saved_vars.emplace(idx_expr, saved_name);
});
// Record the pointer-typed `let` declaration.
// This will be used by the ReplaceAll() handler above.
ptr_lets.emplace(let->variable(), std::move(ptr_let));
// As the original `let` declaration will be fully inlined, there's no
// need for the original declaration to exist. Remove it.
ctx.Remove(block->statements(), let);
}
}
ctx.Clone();
return Output(Program(std::move(out)));
}
} // namespace transform
} // namespace tint

View File

@ -0,0 +1,52 @@
// Copyright 2021 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SRC_TRANSFORM_INLINE_POINTER_LETS_H_
#define SRC_TRANSFORM_INLINE_POINTER_LETS_H_
#include <string>
#include <unordered_map>
#include "src/transform/transform.h"
namespace tint {
namespace transform {
/// InlinePointerLets is a Transform that moves all usage of function-scope
/// `let` statements of a pointer type into their places of usage.
///
/// Parameters of a pointer type are not adjusted.
///
/// Note: InlinePointerLets does not operate on module-scope `let`s, as these
/// cannot be pointers: https://gpuweb.github.io/gpuweb/wgsl/#module-constants
/// `A module-scope let-declared constant must be of atomic-free plain type.`
class InlinePointerLets : public Transform {
public:
/// Constructor
InlinePointerLets();
/// Destructor
~InlinePointerLets() override;
/// Runs the transform on `program`, returning the transformation result.
/// @param program the source program to transform
/// @param data optional extra transform-specific input data
/// @returns the transformation result
Output Run(const Program* program, const DataMap& data = {}) override;
};
} // namespace transform
} // namespace tint
#endif // SRC_TRANSFORM_INLINE_POINTER_LETS_H_

View File

@ -0,0 +1,323 @@
// Copyright 2021 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/transform/inline_pointer_lets.h"
#include <memory>
#include <utility>
#include <vector>
#include "src/transform/test_helper.h"
namespace tint {
namespace transform {
namespace {
using InlinePointerLetsTest = TransformTest;
TEST_F(InlinePointerLetsTest, EmptyModule) {
auto* src = "";
auto* expect = "";
auto got = Run<InlinePointerLets>(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(InlinePointerLetsTest, Basic) {
auto* src = R"(
fn f() {
var v : i32;
let p : ptr<function, i32> = &v;
let x : i32 = *p;
}
)";
auto* expect = R"(
fn f() {
var v : i32;
let x : i32 = *(&(v));
}
)";
auto got = Run<InlinePointerLets>(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(InlinePointerLetsTest, ComplexChain) {
auto* src = R"(
fn f() {
var m : mat4x4<f32>;
let mp : ptr<function, mat4x4<f32>> = &m;
let vp : ptr<function, vec4<f32>> = &(*mp)[2];
let fp : ptr<function, f32> = &(*vp)[1];
let f : f32 = *fp;
}
)";
auto* expect = R"(
fn f() {
var m : mat4x4<f32>;
let f : f32 = *(&(*(&(*(&(m))[2]))[1]));
}
)";
auto got = Run<InlinePointerLets>(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(InlinePointerLetsTest, Param) {
auto* src = R"(
fn x(p : ptr<function, i32>) -> i32 {
return *p;
}
fn f() {
var v : i32;
let p : ptr<function, i32> = &v;
var r : i32 = x(p);
}
)";
auto* expect = R"(
fn x(p : ptr<function, i32>) -> i32 {
return *(p);
}
fn f() {
var v : i32;
var r : i32 = x(&(v));
}
)";
auto got = Run<InlinePointerLets>(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(InlinePointerLetsTest, SavedVars) {
auto* src = R"(
struct S {
i : i32;
};
fn arr() {
var a : array<S, 2>;
var i : i32 = 0;
var j : i32 = 0;
let p : ptr<function, i32> = &a[i + j].i;
i = 2;
*p = 4;
}
fn vec() {
var v : vec3<f32>;
var i : i32 = 0;
var j : i32 = 0;
let p : ptr<function, f32> = &v[i + j];
i = 2;
*p = 4.0;
}
fn mat() {
var m : mat3x3<f32>;
var i : i32 = 0;
var j : i32 = 0;
let p : ptr<function, vec3<f32>> = &m[i + j];
i = 2;
*p = vec3<f32>(4.0, 5.0, 6.0);
}
)";
auto* expect = R"(
struct S {
i : i32;
};
fn arr() {
var a : array<S, 2>;
var i : i32 = 0;
var j : i32 = 0;
let p_save = (i + j);
i = 2;
*(&(a[p_save].i)) = 4;
}
fn vec() {
var v : vec3<f32>;
var i : i32 = 0;
var j : i32 = 0;
let p_save_1 = (i + j);
i = 2;
*(&(v[p_save_1])) = 4.0;
}
fn mat() {
var m : mat3x3<f32>;
var i : i32 = 0;
var j : i32 = 0;
let p_save_2 = (i + j);
i = 2;
*(&(m[p_save_2])) = vec3<f32>(4.0, 5.0, 6.0);
}
)";
auto got = Run<InlinePointerLets>(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(InlinePointerLetsTest, DontSaveLiterals) {
auto* src = R"(
fn f() {
var arr : array<i32, 2>;
let p1 : ptr<function, i32> = &arr[1];
*p1 = 4;
}
)";
auto* expect = R"(
fn f() {
var arr : array<i32, 2>;
*(&(arr[1])) = 4;
}
)";
auto got = Run<InlinePointerLets>(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(InlinePointerLetsTest, SavedVarsChain) {
auto* src = R"(
fn f() {
var arr : array<array<i32, 2>, 2>;
let i : i32 = 0;
let j : i32 = 1;
let p : ptr<function, array<i32, 2>> = &arr[i];
let q : ptr<function, i32> = &(*p)[j];
*q = 12;
}
)";
auto* expect = R"(
fn f() {
var arr : array<array<i32, 2>, 2>;
let i : i32 = 0;
let j : i32 = 1;
let p_save = i;
let q_save = j;
*(&(*(&(arr[p_save]))[q_save])) = 12;
}
)";
auto got = Run<InlinePointerLets>(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(InlinePointerLetsTest, MultiSavedVarsInSinglePtrLetExpr) {
auto* src = R"(
fn x() -> i32 {
return 1;
}
fn y() -> i32 {
return 1;
}
fn z() -> i32 {
return 1;
}
struct Inner {
a : array<i32, 2>;
};
struct Outer {
a : array<Inner, 2>;
};
fn f() {
var arr : array<Outer, 2>;
let p : ptr<function, i32> = &arr[x()].a[y()].a[z()];
*p = 1;
*p = 2;
}
)";
auto* expect = R"(
fn x() -> i32 {
return 1;
}
fn y() -> i32 {
return 1;
}
fn z() -> i32 {
return 1;
}
struct Inner {
a : array<i32, 2>;
};
struct Outer {
a : array<Inner, 2>;
};
fn f() {
var arr : array<Outer, 2>;
let p_save = x();
let p_save_1 = y();
let p_save_2 = z();
*(&(arr[p_save].a[p_save_1].a[p_save_2])) = 1;
*(&(arr[p_save].a[p_save_1].a[p_save_2])) = 2;
}
)";
auto got = Run<InlinePointerLets>(src);
EXPECT_EQ(expect, str(got));
}
// TODO(crbug.com/tint/819): Enable when we support inter-scope shadowing.
TEST_F(InlinePointerLetsTest, DISABLED_ModificationAfterInline) {
auto* src = R"(
fn x(p : ptr<function, i32>) -> i32 {
return *p;
}
fn f() {
var i : i32 = 1;
let p : ptr<function, i32> = &i;
if (true) {
var i : i32 = 2;
x(p);
}
}
)";
auto* expect = R"(<TODO>)";
auto got = Run<InlinePointerLets>(src);
EXPECT_EQ(expect, str(got));
}
} // namespace
} // namespace transform
} // namespace tint

View File

@ -1625,7 +1625,16 @@ bool GeneratorImpl::EmitFunctionInternal(std::ostream& out,
}
first = false;
auto* type = v->Type();
auto const* type = v->Type();
if (auto* ptr = type->As<sem::Pointer>()) {
// Transform pointer parameters in to `inout` parameters.
// The WGSL spec is highly restrictive in what can be passed in pointer
// parameters, which allows for this transformation. See:
// https://gpuweb.github.io/gpuweb/wgsl/#function-restriction
out << "inout ";
type = ptr->StoreType();
}
// Note: WGSL only allows for StorageClass::kNone on parameters, however the
// sanitizer transforms generates load / store functions for storage
@ -2458,9 +2467,9 @@ bool GeneratorImpl::EmitType(std::ostream& out,
// https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-per-component-math#matrix-ordering
out << mat->columns() << "x" << mat->rows();
} else if (type->Is<sem::Pointer>()) {
// TODO(dsinclair): What do we do with pointers in HLSL?
// https://bugs.chromium.org/p/tint/issues/detail?id=183
diagnostics_.add_error("pointers not supported in HLSL");
TINT_ICE(diagnostics_)
<< "Attempting to emit pointer type. These should have been removed "
"with the InlinePointerLets transform";
return false;
} else if (auto* sampler = type->As<sem::Sampler>()) {
out << "Sampler";
@ -2634,7 +2643,6 @@ bool GeneratorImpl::EmitUnaryOp(std::ostream& pre,
switch (expr->op()) {
case ast::UnaryOp::kIndirection:
case ast::UnaryOp::kAddressOf:
// TODO(crbug.com/tint/183) - support pointers
return EmitExpression(pre, out, expr->expr());
case ast::UnaryOp::kNot:
out << "!";

View File

@ -100,6 +100,25 @@ TEST_F(HlslGeneratorImplTest_Function,
Validate();
}
TEST_F(HlslGeneratorImplTest_Function, PtrParameter) {
// fn f(foo : ptr<function, f32>) -> f32 {
// return *foo;
// }
Func("f", {Param("foo", ty.pointer<f32>(ast::StorageClass::kFunction))},
ty.f32(), {Return(Deref("foo"))});
GeneratorImpl& gen = SanitizeAndBuild();
ASSERT_TRUE(gen.Generate(out)) << gen.error();
EXPECT_THAT(result(), HasSubstr(R"(float f(inout float foo) {
return foo;
}
)"));
Validate();
}
TEST_F(HlslGeneratorImplTest_Function,
Emit_Decoration_EntryPoint_WithInOutVars) {
// fn frag_main([[location(0)]] foo : f32) -> [[location(1)]] f32 {

View File

@ -36,17 +36,17 @@ TEST_F(HlslSanitizerTest, ArrayLength) {
auto* ac_ty = ty.access(ast::AccessControl::kReadOnly, sb_ty);
Global("sb", ac_ty, ast::StorageClass::kStorage, nullptr,
ast::DecorationList{
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(1),
});
Func("main", ast::VariableList{}, ty.void_(),
ast::StatementList{
{
Decl(Var("len", ty.u32(), ast::StorageClass::kNone,
Call("arrayLength", MemberAccessor("sb", "arr")))),
},
ast::DecorationList{
{
Stage(ast::PipelineStage::kFragment),
});
@ -76,10 +76,10 @@ TEST_F(HlslSanitizerTest, PromoteArrayInitializerToConstVar) {
auto* pos = Var("pos", ty.i32(), ast::StorageClass::kNone, array_index);
Func("main", ast::VariableList{}, ty.void_(),
ast::StatementList{
{
Decl(pos),
},
ast::DecorationList{
{
Stage(ast::PipelineStage::kFragment),
});
@ -110,10 +110,10 @@ TEST_F(HlslSanitizerTest, PromoteStructInitializerToConstVar) {
Var("pos", ty.vec3<f32>(), ast::StorageClass::kNone, struct_access);
Func("main", ast::VariableList{}, ty.void_(),
ast::StatementList{
{
Decl(pos),
},
ast::DecorationList{
{
Stage(ast::PipelineStage::kFragment),
});
@ -137,6 +137,134 @@ void main() {
)";
EXPECT_EQ(expect, got);
}
TEST_F(HlslSanitizerTest, InlinePtrLetsBasic) {
// var v : i32;
// let p : ptr<function, i32> = &v;
// let x : i32 = *p;
auto* v = Var("v", ty.i32());
auto* p =
Const("p", ty.pointer<i32>(ast::StorageClass::kFunction), AddressOf(v));
auto* x = Var("x", ty.i32(), ast::StorageClass::kNone, Deref(p));
Func("main", ast::VariableList{}, ty.void_(),
{
Decl(v),
Decl(p),
Decl(x),
},
{
Stage(ast::PipelineStage::kFragment),
});
GeneratorImpl& gen = SanitizeAndBuild();
ASSERT_TRUE(gen.Generate(out)) << gen.error();
auto got = result();
auto* expect = R"(void main() {
int v = 0;
int x = v;
return;
}
)";
EXPECT_EQ(expect, got);
}
TEST_F(HlslSanitizerTest, InlinePtrLetsComplexChain) {
// var m : mat4x4<f32>;
// let mp : ptr<function, mat4x4<f32>> = &m;
// let vp : ptr<function, vec4<f32>> = &(*mp)[2];
// let fp : ptr<function, f32> = &(*vp)[1];
// let f : f32 = *fp;
auto* m = Var("m", ty.mat4x4<f32>());
auto* mp =
Const("mp", ty.pointer(ty.mat4x4<f32>(), ast::StorageClass::kFunction),
AddressOf(m));
auto* vp =
Const("vp", ty.pointer(ty.vec4<f32>(), ast::StorageClass::kFunction),
AddressOf(IndexAccessor(Deref(mp), 2)));
auto* fp = Const("fp", ty.pointer<f32>(ast::StorageClass::kFunction),
AddressOf(IndexAccessor(Deref(vp), 1)));
auto* f = Var("f", ty.f32(), ast::StorageClass::kNone, Deref(fp));
Func("main", ast::VariableList{}, ty.void_(),
{
Decl(m),
Decl(mp),
Decl(vp),
Decl(fp),
Decl(f),
},
{
Stage(ast::PipelineStage::kFragment),
});
GeneratorImpl& gen = SanitizeAndBuild();
ASSERT_TRUE(gen.Generate(out)) << gen.error();
auto got = result();
auto* expect = R"(void main() {
float4x4 m = float4x4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
float f = m[2][1];
return;
}
)";
EXPECT_EQ(expect, got);
}
TEST_F(HlslSanitizerTest, InlineParam) {
// fn x(p : ptr<function, i32>) -> i32 {
// return *p;
// }
//
// [[stage(fragment)]]
// fn main() {
// var v : i32;
// let p : ptr<function, i32> = &v;
// var r : i32 = x(p);
// }
Func("x", {Param("p", ty.pointer<i32>(ast::StorageClass::kFunction))},
ty.i32(), {Return(Deref("p"))});
auto* v = Var("v", ty.i32());
auto* p = Const("p", ty.pointer(ty.i32(), ast::StorageClass::kFunction),
AddressOf(v));
auto* r = Var("r", ty.i32(), ast::StorageClass::kNone, Call("x", p));
Func("main", ast::VariableList{}, ty.void_(),
{
Decl(v),
Decl(p),
Decl(r),
},
{
Stage(ast::PipelineStage::kFragment),
});
GeneratorImpl& gen = SanitizeAndBuild();
ASSERT_TRUE(gen.Generate(out)) << gen.error();
auto got = result();
auto* expect = R"(int x(inout int p) {
return p;
}
void main() {
int v = 0;
int r = x(v);
return;
}
)";
EXPECT_EQ(expect, got);
}
} // namespace
} // namespace hlsl
} // namespace writer

View File

@ -301,6 +301,7 @@ tint_unittests_source_set("tint_unittests_core_src") {
"../src/transform/decompose_storage_access_test.cc",
"../src/transform/external_texture_transform_test.cc",
"../src/transform/first_index_offset_test.cc",
"../src/transform/inline_pointer_lets_test.cc",
"../src/transform/renamer_test.cc",
"../src/transform/single_entry_point_test.cc",
"../src/transform/transform_test.cc",

View File

@ -1 +1,8 @@
SKIP: Failed to generate: error: pointers not supported in HLSL
[numthreads(1, 1, 1)]
void main() {
float3x3 m = float3x3(float3(0.0f, 0.0f, 0.0f), float3(0.0f, 0.0f, 0.0f), float3(0.0f, 0.0f, 0.0f));
m = float3x3(float3(1.0f, 2.0f, 3.0f), float3(4.0f, 5.0f, 6.0f), float3(7.0f, 8.0f, 9.0f));
m[1] = float3(5.0f, 5.0f, 5.0f);
return;
}

View File

@ -1 +1,7 @@
SKIP: Failed to generate: error: pointers not supported in HLSL
[numthreads(1, 1, 1)]
void main() {
float3x3 m = float3x3(float3(1.0f, 2.0f, 3.0f), float3(4.0f, 5.0f, 6.0f), float3(7.0f, 8.0f, 9.0f));
m[1] = float3(5.0f, 5.0f, 5.0f);
return;
}

View File

@ -1 +1,8 @@
SKIP: Failed to generate: error: pointers not supported in HLSL
[numthreads(1, 1, 1)]
void main() {
float3 v = float3(0.0f, 0.0f, 0.0f);
v = float3(1.0f, 2.0f, 3.0f);
v.y = 5.0f;
return;
}

View File

@ -1 +1,7 @@
SKIP: Failed to generate: error: pointers not supported in HLSL
[numthreads(1, 1, 1)]
void main() {
float3 v = float3(1.0f, 2.0f, 3.0f);
v.y = 5.0f;
return;
}

View File

@ -1 +1,6 @@
SKIP: error: pointers not supported in HLSL
[numthreads(1, 1, 1)]
void main() {
uint x_10 = 0u;
return;
}

View File

@ -1 +1,14 @@
SKIP: error: pointers not supported in HLSL
int func(int value, inout int pointer) {
const int x_9 = pointer;
return (value + x_9);
}
[numthreads(1, 1, 1)]
void main() {
int i = 0;
i = 123;
const int x_19 = i;
const int x_18 = func(x_19, i);
return;
}

View File

@ -1 +1,11 @@
SKIP: error: pointers not supported in HLSL
int func(int value, inout int pointer) {
return (value + pointer);
}
[numthreads(1, 1, 1)]
void main() {
int i = 123;
const int r = func(i, i);
return;
}

View File

@ -1 +1,8 @@
SKIP: Failed to generate: error: pointers not supported in HLSL
[numthreads(1, 1, 1)]
void main() {
int i = 123;
i = 123;
i = ((100 + 20) + 3);
return;
}

View File

@ -1 +1,13 @@
SKIP: error: pointers not supported in HLSL
void func(int value, inout int pointer) {
pointer = value;
return;
}
[numthreads(1, 1, 1)]
void main() {
int i = 0;
i = 123;
func(123, i);
return;
}

View File

@ -1 +1,11 @@
SKIP: error: pointers not supported in HLSL
void func(int value, inout int pointer) {
pointer = value;
}
[numthreads(1, 1, 1)]
void main() {
int i = 123;
func(123, i);
return;
}