From 8e59acc9678a34e65b880f846d00edb01d378494 Mon Sep 17 00:00:00 2001 From: EthanPeng-2048 Date: Mon, 24 Aug 2026 20:59:36 +0800 Subject: [PATCH 1/9] 1 --- docs/optimize.md | 56 ------------------------------------------------ 1 file changed, 56 deletions(-) delete mode 100644 docs/optimize.md diff --git a/docs/optimize.md b/docs/optimize.md deleted file mode 100644 index 5ff81338..00000000 --- a/docs/optimize.md +++ /dev/null @@ -1,56 +0,0 @@ -# 显存/速度优化实测记录 - -## 梯度检查点(--checkpoint-every N)实测 - -同一 batch、同一配置(6step/batch × 20batch 积累): - -| N | time | vram | -|---|---|---| -| 0 | 22s | 26GB | -| 1 | 31s | 9GB | -| 2 | 27s | 16.6GB | - -结论: -- **要最快 → N=0**(前提显存够)。检查点引入的额外前向 FLOPs 会随 batch 放大,拉高 batch 无法抵消。 -- **要省显存换更大 batch → N=2** 性价比最优(几乎减半显存、只慢 ~23%)。 -- **N=1 只用于极限压显存**(9GB),但重计算代价最大(+1 整遍前向)。 - ---- - -## Activation Offload(--activation-offload,L1-offload) - -**思路**:检查点(重算)用 FLOPs 换显存;offload 用 **PCIe 带宽**换显存——把每块内部激活搬 host-visible 内存,backward 拷回再反向,**不重算**(FLOPs 保持 1.0×)。 - -### 预期收益(PCIe 3.0 x16 ~7GB/s,激活 ~5GB) -- 显存:~9GB 级(同 N=1) -- 每 step:~23s(vs N=1 的 31s),**快 ~8s,无重算** - -### 实现(已落地,阶段1+2+3) -- 引擎原语:`ComputeEngine::offload_store(activation)` / `offload_load(handle, rows, cols)`(CPU no-op;GPU 用**录制式** `vkCmdCopyBuffer(dev↔host_visible)`,batch 内不提交,避免打断 batch)。 -- 后端:`GpuBuffer::create_host_visible` / `GpuTensor::create_host_visible_empty`(HOST_VISIBLE 内存池);`copy_buffer_region_gpu`(带偏移拷贝)。 -- 层:`Layer::activation_cache()` 枚举 backward 所需中间激活;GPTBlock `export_activations`(forward 后搬 host + 释放 GPU)/ `import_activations`(backward 前拷回,不重算)。掩码小而常驻不参与 offload。 -- **slab 复用**:每块用一块持久 host-visible slab(大小=激活总字节,跨 step 复用),激活按 offset 写入/读出 → RAM = 激活实际体积,避免 128MB 块碎片膨胀。 -- 接入:`text_train --activation-offload`(与 `--checkpoint-every` 互斥)、GUI/CLI。 - -### RAM 实测(batch 6) -- 早期(每 tensor 独立缓冲):RAM **26GB**(128MB 块碎片膨胀)。 -- slab 修复后:RAM ≈ 激活实际体积 **~7GB**。 - -### 验证 -- `offload_test`:GPU 激活 → host → GPU 往返 **bit-exact**(非 batch + batch 路径)。 -- `gpt_offload_test`:GPU 上 offload 与全存基线 forward+全部梯度 **bit-exact**。 -- 全部 gradcheck/fusion 回归绿。 - -### 注意 -- 需足够主机 RAM(激活随 batch 线性增长)与 PCIe 带宽;PCIe 3.0/x8 收益减半。 -- CUDA 后端 offload 暂为 no-op(未接 pinned memory)。 - -## 显存实测诊断(doc_ids 场景) - -**问题**:doc_ids 强制注意力走旧路径,物化 (BH×seq, seq)=384MB/块 注意力矩阵,且曾把 `attn_cache_`+`softmax.output_cache_` 存两份 → slab 实测 **22GB**(理论 ~11GB)。 - -**修复**: -1. **去重**:Softmax 单缓冲,旧路径只用 `softmax_.output_cache()` 一份 → slab 22→**16GB**。 -2. **(M7,2026-08)`AttnBias` 组合偏置统一位置编码**:两趟式原语掩码契约升级为组合式描述子(causal + doc_ids 块对角 + ALiBi slopes,见 [09 §4](09-operator-fusion.md)),doc_ids / ALiBi 及其组合**不再回退旧路径**,全部在融合 kernel 内按 (b,h,i,j) 组合偏置。doc_ids 仅需 `O(batch·seq)` 小张量每步上传,不再物化 (BH·seq,seq) 注意力矩阵。因此 `--doc-aligned-windows`(丢弃跨文档窗口以退化为纯因果)已**删除**——所有窗口(含跨文档)均保留,块对角文档掩码在两趟式路径内生效。 - - From 36caba439606d93f1aa0b642be4a2e739201b23d Mon Sep 17 00:00:00 2001 From: EthanPeng-2048 Date: Mon, 24 Aug 2026 21:37:20 +0800 Subject: [PATCH 2/9] =?UTF-8?q?IR-C/D=E4=BC=98=E5=8C=96=EF=BC=8C=E5=9B=BE?= =?UTF-8?q?=E6=89=A7=E8=A1=8Ceager=E7=BC=96=E5=86=99=E6=8B=89=E7=88=86?= =?UTF-8?q?=E6=89=80=E6=9C=89ML=E6=A1=86=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 8 + docs/11-ir-optimization.md | 17 +- include/neuralnet.cpp/backend/vk_backend.hpp | 26 +- include/neuralnet.cpp/compute_layer.hpp | 65 +++ include/neuralnet.cpp/cpu_emitter.hpp | 252 +++++++++ include/neuralnet.cpp/cpu_engine.hpp | 41 +- include/neuralnet.cpp/expr_dsl.hpp | 32 +- include/neuralnet.cpp/expr_emitter.hpp | 103 ++++ include/neuralnet.cpp/expr_graph.hpp | 304 +++++++++++ include/neuralnet.cpp/glsl_gen.hpp | 29 + include/neuralnet.cpp/gpu_engine.hpp | 129 ++++- include/neuralnet.cpp/tensor.hpp | 11 + src/expr_fuse_test.cpp | 211 ++++++++ src/expr_graph_test.cpp | 541 +++++++++++++++++++ tools/gen_fused.cpp | 40 +- tools/scan_exprs.cpp | 14 + 16 files changed, 1799 insertions(+), 24 deletions(-) create mode 100644 include/neuralnet.cpp/cpu_emitter.hpp create mode 100644 include/neuralnet.cpp/expr_emitter.hpp create mode 100644 include/neuralnet.cpp/expr_graph.hpp create mode 100644 src/expr_fuse_test.cpp create mode 100644 src/expr_graph_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f634d39f..c790b1f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -176,6 +176,14 @@ if(NN_ENABLE_TESTS) nn_add_executable(expr_opt_test src/expr_opt_test.cpp) list(APPEND NN_TEST_TARGETS expr_opt_test) + # ── 图 IR + 融合分析测试(IR-C:expr_graph.hpp 纯函数,无 GPU) ───── + nn_add_executable(expr_graph_test src/expr_graph_test.cpp) + list(APPEND NN_TEST_TARGETS expr_graph_test) + + # ── 图 IR 融合 GPU 端到端(IR-C:begin_expr/end_expr 融合执行) ───── + nn_add_executable(expr_fuse_test src/expr_fuse_test.cpp) + list(APPEND NN_TEST_TARGETS expr_fuse_test) + # ── ModelSpec 架构校验一致性测试(load_model 文件头 vs 模型规格比对) ──── nn_add_executable(model_spec_validation_test src/model_spec_validation_test.cpp) list(APPEND NN_TEST_TARGETS model_spec_validation_test) diff --git a/docs/11-ir-optimization.md b/docs/11-ir-optimization.md index 4a0efbc9..9f611f26 100644 --- a/docs/11-ir-optimization.md +++ b/docs/11-ir-optimization.md @@ -1,6 +1,6 @@ # IR 优化(IR Optimization)—— 为融合算子引入中间表示 -> 状态:**IR-A(canonicalize:DCE/常量折叠/代数化简/稳定重编号)+ IR-B(CSE + 寄存器分配 liveness)✅ 已实施(2026-08-23)**;IR-C(图 IR)/IR-D(emitter)待立项 +> 状态:**IR-A(canonicalize:DCE/常量折叠/代数化简/稳定重编号)+ IR-B(CSE + 寄存器分配 liveness)✅ 已实施(2026-08-23)**;**IR-C(图 IR + begin_expr/end_expr 融合分析)✅ 已实施(基础版:逐元素链拼接,2026-08-24)**;**IR-D(后端 emitter 抽象)✅ 已实施(2026-08-24)**;IR-C 进阶(多输入 DAG 融合 / 跨 kernel 自动融合)待立项 > 关联文档:`09-operator-fusion.md`(算子融合)、`DEVELOPMENT_STANDARDS.md`(分层铁律)、`08-pitfalls-and-lessons.md` > 目标:在**不推翻现有 AOT 闭合世界**的前提下,把 `ExprSpec` 从"直通轻量 IR"演进为带优化 pass 的规范 IR,分阶段提升开发便利与代码质量。 @@ -168,6 +168,21 @@ IR → GlslEmitter / CudaEmitter / CpuEmitter > - 验证:新增 `src/expr_opt_test`(各 pass/确定性/幂等/语义等价/上限压力/归约区段互斥);`expr_dsl_test`/`expr_reduce_test`/`tensor_expr_test`/`fused_gpu_test`/`matmul_fusion_test`/`ce_fusion_test`/gradcheck 系列/gpt_checkpoint_test 全绿;MNIST + text_train(CPU/GPU、含 checkpoint-every)端到端训练正常。 > - 顺带修复预存在 bug:`text_train --save-interval 0` 触发 `(step+1) % 0` 整数除零崩溃(HEAD 亦复现,与 IR 无关)。 +> **实施记录(2026-08-24,IR-C 基础版 + IR-D 已完成)**: +> - **IR-C(图 IR + 融合分析,`expr_graph.hpp`)**: +> - 图结构:`ExprGraphNode{ spec(canonical), rows/cols, vector_out, dep_of_input, input_tensors }` + `ExprGraph`;`add_node` 录制(依赖识别基于 Tensor 的 `virtual_tag`,占位 Tensor 标记前序节点输出)。 +> - 融合分析 `fuse_expr_graph`:贪心按节点序,维护"当前 kernel";**逐元素链拼接**——B 以 Linear 视图消费 A 的输出且 A 无其他消费者、形状相同、均无归约 → 把 A 的指令内联进 B(A 输出寄存器作为 B 的操作数),中间结果不落显存、单 kernel dispatch。拼接后 canonicalize + validate;超限/非法 → 保守放弃融合(各自成 kernel)。归约节点/归约输出作为融合边界。 +> - `Tensor` 增加 `virtual_tag`(0=非节点输出;reshape 保留)。 +> - **引擎接入**:`GpuEngine::begin_expr/end_expr` 录制 → `end_expr` 融合执行(每 kernel 一次 AOT dispatch,输出写入末尾节点占位 buffer——`run_fused_gpu` 新增 `output_override` 参数复用占位);`CpuEngine` 在 `NN_EXPR_SCAN` 下 begin/end 录制并登记融合后的复合 spec(闭合世界两端一致);`dsl::compute/compute_reduce` 的 scan 分支在录制段内加入录制图。 +> - 演示 Layer:`FusedChainLayer`(forward 用 begin_expr/end_expr 写三段链式表达式,GPU 融合成单 kernel,CPU 逐节点参考)。 +> - **关键坑**:① 拼接时 B 的常量池必须追加在 A 之后(否则 Const 引用越界,canonicalize 产出垃圾索引);② 录制段内单个表达式**不单独登记**(被融合进复合 spec),闭合世界要求 scan dry-run 覆盖录制段内所有可能的融合组合(含 backward);③ 中间量不逃逸是融合语义约束(如 LayerNorm/RMSNorm 的 cache 中间结果不可包进 begin/end 段)。 +> - 验证:新增 `src/expr_graph_test`(链融合/三节点链/独立分支/多消费者/归约边界/上限压力/确定性/依赖识别/输入顺序)+ `src/expr_fuse_test`(GPU 端到端:FusedChain forward 融合 err=0、手写录制融合 err=0、backward/未录制回归)。 +> - **IR-D(emitter 抽象,`expr_emitter.hpp`)**: +> - `ExprEmitter` 纯接口(name/generate/generate_reduce)+ `emitter_registry`(按后端名选择工厂)。 +> - `GlslEmitter`(glsl_gen.hpp 现有 generate_glsl/generate_glsl_reduce 封装)+ `CpuEmitter`(cpu_emitter.hpp,生成可编译 C++ 直线代码,验证"一份 IR 多后端";数值与 CPU 解释器一致)。 +> - `gen_fused` 经 emitter 注册表选择后端(默认 glsl);`--list-backends` 展示可用后端(glsl/cpu)。 +> - 验证:ctest 24/24 全绿(含新增 expr_graph_test / expr_fuse_test);`scan_exprs` 收集到 20 条融合表达式(含 FusedChain 融合复合 spec + backward);`fused_gpu_test` 等既有回归无回归。 + **推荐**:先落地 IR-A + IR-B(约 1 周),解决真实痛点并铺好确定性地基;IR-C 视需求再投入。 --- diff --git a/include/neuralnet.cpp/backend/vk_backend.hpp b/include/neuralnet.cpp/backend/vk_backend.hpp index 86f6cbe9..2a527b61 100644 --- a/include/neuralnet.cpp/backend/vk_backend.hpp +++ b/include/neuralnet.cpp/backend/vk_backend.hpp @@ -3042,7 +3042,8 @@ class GpuBackend std::span consts, std::size_t rows, std::size_t cols, bool vector_out = false, - std::span view_params = {}) + std::span view_params = {}, + GpuTensor* output_override = nullptr) { if (!initialized_) return std::unexpected(Error{"GPU backend not initialized"}); @@ -3073,13 +3074,30 @@ class GpuBackend const std::uint32_t count = static_cast(rows * cols); // 1. 分配输出 Tensor(vector_out:归约向量原生形状 (rows,1)/(1,cols)) + // 若调用方指定 output_override(IR-C 录制占位 buffer),则复用其 + // buffer(形状必须匹配),结果直接写入占位 → Layer 持有的 Tensor + // 在 end_expr 后即物化,避免额外拷贝。 const std::size_t out_rows = (vector_out && raxis == 0) ? rows : (vector_out && raxis == 1) ? 1 : rows; const std::size_t out_cols = (vector_out && raxis == 1) ? cols : (vector_out && raxis == 0) ? 1 : cols; - auto output_res = GpuTensor::create_empty(out_rows, out_cols, *this); - if (!output_res) return std::unexpected(output_res.error()); - GpuTensor output = std::move(*output_res); + std::optional owned_output; + GpuTensor* output_ptr = output_override; + if (output_override) + { + if (output_override->rows() != out_rows || output_override->cols() != out_cols) + return std::unexpected(Error{ + "run_fused_gpu: output_override shape mismatch (" + + std::to_string(out_rows) + "x" + std::to_string(out_cols) + ")"}); + } + else + { + auto output_res = GpuTensor::create_empty(out_rows, out_cols, *this); + if (!output_res) return std::unexpected(output_res.error()); + owned_output.emplace(std::move(*output_res)); + output_ptr = &*owned_output; + } + GpuTensor output = *output_ptr; // 2. 分配描述符集(N 输入 + 1 输出) auto ds_r = alloc_desc_set(pipeline.descriptor_layout()); diff --git a/include/neuralnet.cpp/compute_layer.hpp b/include/neuralnet.cpp/compute_layer.hpp index 61c612c3..72a9ae59 100644 --- a/include/neuralnet.cpp/compute_layer.hpp +++ b/include/neuralnet.cpp/compute_layer.hpp @@ -932,6 +932,71 @@ class RMSNorm final : public Layer } }; +// ══════════════════════════════════════════════════════════════════════════ +// FusedChainLayer — IR-C 演示:begin_expr/end_expr 多表达式链式融合 +// +// forward 用 begin_expr/end_expr 把"无法写进一行的多步逐元素变换"写成多个 +// 表达式(t、u 为中间量,不逃逸——融合的语义约束): +// t = x * 2 (Linear 输入 + 常量) +// u = t + 3 (消费 t:Linear 引用 → 内联为寄存器) +// out = u * gamma (row 广播参数 (F,1)) +// 引擎在 end_expr 时做融合分析(IR-C,expr_graph.hpp):三段拼接成单个 +// kernel,中间结果 t/u 留在寄存器(不落显存、不额外 dispatch)。 +// - GPU:begin_expr 录制 → end_expr 融合 → AOT 融合 shader 匹配 dispatch +// (闭合世界:scan_exprs 必须 dry-run 本 Layer 登记融合后的复合 spec)。 +// - CPU:begin/end 为 no-op,各表达式直接求值(参考实现)。 +// - scan:NN_EXPR_SCAN 下 end_expr 登记融合后的复合 spec(两端一致)。 +// ══════════════════════════════════════════════════════════════════════════ +class FusedChainLayer final : public Layer +{ +private: + Tensor gamma_; // (F, 1) 按行广播系数 + +public: + explicit FusedChainLayer(ComputeEngine& engine, std::size_t features) + { + Matrix g(features, 1, Scalar{0.5f}); // 演示用固定系数 + auto g_t = engine.from_matrix(g); + NN_ASSERT(g_t, g_t ? "" : g_t.error().message.c_str()); + gamma_ = std::move(*g_t); + } + + [[nodiscard]] Result forward( + ComputeEngine& engine, const Tensor& input) override + { + const std::size_t F = input.rows(); + const std::size_t B = input.cols(); + + auto b = engine.begin_expr(); + if (!b) return std::unexpected(b.error()); + auto t = dsl::compute(engine, dsl::leaf(input) * Scalar{2}, F, B); + if (!t) return std::unexpected(t.error()); + auto u = dsl::compute(engine, dsl::leaf(*t) + Scalar{3}, F, B); + if (!u) return std::unexpected(u.error()); + auto out = dsl::compute(engine, + dsl::leaf(*u) * dsl::row_broadcast(gamma_), F, B); + if (!out) return std::unexpected(out.error()); + auto e = engine.end_expr(); + if (!e) return std::unexpected(e.error()); + return out; + } + + // out = ((x*2)+3)*gamma → dout/dx = 2*gamma。 + // 非 IR-C 演示路径:单表达式直接求值(scan 单独登记,GPU 独立 AOT)。 + [[nodiscard]] Result backward( + ComputeEngine& engine, const Tensor& grad_output) override + { + const std::size_t F = grad_output.rows(); + const std::size_t B = grad_output.cols(); + return dsl::compute(engine, + dsl::leaf(grad_output) * dsl::row_broadcast(gamma_) * Scalar{2}, + F, B); + } + + [[nodiscard]] std::vector parameters() override { return {}; } + [[nodiscard]] std::vector param_gradients() override { return {}; } +}; + // ── 归一化层工厂:按 NormType 创建 LayerNorm 或 RMSNorm ────────────────── [[nodiscard]] inline std::unique_ptr make_norm_layer( ComputeEngine& engine, std::size_t d_model, NormType norm_type) diff --git a/include/neuralnet.cpp/cpu_emitter.hpp b/include/neuralnet.cpp/cpu_emitter.hpp new file mode 100644 index 00000000..f19372f2 --- /dev/null +++ b/include/neuralnet.cpp/cpu_emitter.hpp @@ -0,0 +1,252 @@ +#ifndef NN_CPU_EMITTER_HPP +#define NN_CPU_EMITTER_HPP + +// ═══════════════════════════════════════════════════════════════════════════ +// cpu_emitter.hpp — CPU 后端 emitter(IR-D 第二实现) +// +// 对应文档 `docs/11-ir-optimization.md` IR-D:一份 canonical IR → 多后端。 +// 本 emitter 把 ExprSpec 展开为一段可直接嵌入 C++ 的**逐元素直线代码** +// (独立函数,无解释器开销),作为 GlslEmitter 之外的第二实现,验证 +// emitter 抽象确实支持"同一 IR 多后端"。 +// +// 生成的函数签名: +// void (const float* b0, ..., const float* bN-1, float* bout, +// std::size_t count, std::size_t cols, +// std::uint32_t vp0, ..., float c0, ...); +// - 输入/输出为扁平 float 缓冲(row-major,与 Tensor 布局一致) +// - vpN:运行时视图参数(RowMod 周期 / RotateHalf 块大小,形状无关) +// - cN:常量池 +// 归约表达式(generate_reduce):本 emitter 暂不生成归约 kernel(返回空串), +// 归约语义由 CPU eval_expr 解释器承担(先正确后优化)。 +// ═══════════════════════════════════════════════════════════════════════════ + +#include +#include +#include + +#include "expr_spec.hpp" +#include "expr_emitter.hpp" + +namespace nn +{ + +// ── 二元算子 → C++ 运算符片段 ─────────────────────────────────────────── +inline const char* cpu_binary_op(ExprOp op) +{ + switch (op) + { + case ExprOp::Add: return "+"; + case ExprOp::Sub: return "-"; + case ExprOp::Mul: return "*"; + case ExprOp::Div: return "/"; + case ExprOp::Lt: return "<"; + case ExprOp::Le: return "<="; + case ExprOp::Gt: return ">"; + case ExprOp::Ge: return ">="; + case ExprOp::Eq: return "=="; + case ExprOp::Ne: return "!="; + default: return ""; + } +} + +// ── 视图 → C++ 索引表达式 ──────────────────────────────────────────────── +// 布局约定与 GLSL/CPU eval_expr 一致:row = i/cols, col = i%cols。 +// vp_slot:该视图的运行时参数槽位(同 glsl_gen 的 vp 约定)。 +inline void cpu_view_read(std::ostringstream& os, + const ExprView& v, std::uint32_t buf_id, + const std::string& idx_var, + const std::string& row_var, const std::string& col_var, + std::uint32_t vp_slot = 0) +{ + const std::string buf = "b" + std::to_string(buf_id); + switch (v.kind) + { + default: + case static_cast(ExprViewKind::Linear): + os << buf << "[" << idx_var << "]"; + return; + case static_cast(ExprViewKind::RotateHalf): + { + // 与 GLSL 生成器一致的索引映射(block 为运行时视图参数 vpN) + const std::string blk = "(" + row_var + " / vp" + std::to_string(vp_slot) + ")"; + const std::string rl = "(" + row_var + " % vp" + std::to_string(vp_slot) + ")"; + const std::string half = "(vp" + std::to_string(vp_slot) + " / 2u)"; + const std::string hi = "((" + blk + " * vp" + std::to_string(vp_slot) + " + " + + rl + " + " + half + ") * cols + " + col_var + ")"; + const std::string lo = "((" + blk + " * vp" + std::to_string(vp_slot) + " + " + + rl + " - " + half + ") * cols + " + col_var + ")"; + const std::string neg = v.negate_first_half ? "-" : ""; + os << "((" << rl << " < " << half << ") ? " + << neg << buf << "[" << hi << "] : " << buf << "[" << lo << "])"; + return; + } + case static_cast(ExprViewKind::RowMod): + os << buf << "[(" << row_var << " % vp" << std::to_string(vp_slot) + << ") * cols + " << col_var << "]"; + return; + case static_cast(ExprViewKind::RowBroadcast): + os << buf << "[" << row_var << "]"; + return; + case static_cast(ExprViewKind::ColBroadcast): + os << buf << "[" << col_var << "]"; + return; + } +} + +// ── 指令展开(直线代码,逐条输出到 os) ────────────────────────────────── +inline void cpu_emit_instrs(std::ostringstream& os, const ExprSpec& spec) +{ + const auto operand = [&](const ExprOperand& op) -> std::string + { + switch (op.kind) + { + default: + case static_cast(ExprOperandKind::Input): + return "v" + std::to_string(op.idx); + case static_cast(ExprOperandKind::Reg): + case static_cast(ExprOperandKind::Fanout): + return "r" + std::to_string(op.idx); + case static_cast(ExprOperandKind::Const): + return "c" + std::to_string(op.idx); + } + }; + + for (const auto& ins : spec.instrs) + { + const ExprOp op = static_cast(ins.op); + const std::string dst = "r" + std::to_string(ins.dst); + const std::string a = operand(ins.a); + switch (op) + { + case ExprOp::Add: case ExprOp::Sub: case ExprOp::Mul: case ExprOp::Div: + { + const char* s = cpu_binary_op(op); + os << " " << dst << " = " << a << " " << s << " " << operand(ins.b) << ";\n"; + break; + } + case ExprOp::Max: + os << " " << dst << " = std::max(" << a << ", " << operand(ins.b) << ");\n"; + break; + case ExprOp::Min: + os << " " << dst << " = std::min(" << a << ", " << operand(ins.b) << ");\n"; + break; + case ExprOp::Lt: case ExprOp::Le: case ExprOp::Gt: + case ExprOp::Ge: case ExprOp::Eq: case ExprOp::Ne: + { + const char* s = cpu_binary_op(op); + os << " " << dst << " = (" << a << " " << s << " " << operand(ins.b) + << ") ? 1.0f : 0.0f;\n"; + break; + } + case ExprOp::Neg: + os << " " << dst << " = -(" << a << ");\n"; + break; + case ExprOp::Exp: + os << " " << dst << " = std::exp(" << a << ");\n"; + break; + case ExprOp::Log: + os << " " << dst << " = std::log(" << a << ");\n"; + break; + case ExprOp::Sqrt: + os << " " << dst << " = std::sqrt(" << a << ");\n"; + break; + case ExprOp::Rsqrt: + os << " " << dst << " = 1.0f / std::sqrt(" << a << ");\n"; + break; + case ExprOp::Abs: + os << " " << dst << " = std::fabs(" << a << ");\n"; + break; + case ExprOp::Tanh: + os << " " << dst << " = std::tanh(" << a << ");\n"; + break; + case ExprOp::Select: + os << " " << dst << " = (" << a << " != 0.0f) ? " + << operand(ins.b) << " : " << operand(ins.c) << ";\n"; + break; + default: + os << " // 不支持的算子(归约指令不在此 emitter 展开)\n"; + break; + } + } +} + +// ── CpuEmitter — 生成 C++ 逐元素直线代码 ──────────────────────────────── +class CpuEmitter final : public ExprEmitter +{ +public: + [[nodiscard]] std::string_view name() const noexcept override + { return "cpu"; } + + [[nodiscard]] std::string generate( + const std::string& name_, const ExprSpec& spec) override + { + // 归约语义不在本 emitter 展开(见 generate_reduce) + if (expr_spec_reduce_axis(spec) >= 0) + return {}; + const std::size_t n_inputs = spec.views.size(); + std::ostringstream L; + + L << "// ── 自动生成(CpuEmitter),请勿手动编辑 ──\n"; + L << "// 表达式: " << name_ << "\n"; + L << "inline void " << name_ << "(\n"; + for (std::size_t i = 0; i < n_inputs; ++i) + L << " const float* b" << i << ", // 输入 " << i << "\n"; + L << " float* bout,\n"; + L << " std::size_t count, std::size_t cols"; + const std::uint32_t n_vp = expr_spec_runtime_view_param_count(spec); + for (std::uint32_t i = 0; i < n_vp; ++i) + L << ",\n std::uint32_t vp" << i; + for (std::size_t i = 0; i < spec.consts.size(); ++i) + L << ",\n float c" << i; + L << ")\n{\n"; + L << " for (std::size_t i = 0; i < count; ++i) {\n"; + L << " const std::size_t row = i / cols;\n"; + L << " const std::size_t col = i % cols;\n"; + + // 输入读取变量(每输入缓存一次;vp 槽 = 此前运行时参数视图个数) + for (std::size_t i = 0; i < n_inputs; ++i) + { + std::uint32_t vp = 0; + for (std::size_t j = 0; j < i; ++j) + if (expr_view_has_runtime_param( + static_cast(spec.views[j].kind))) + ++vp; + L << " const float v" << i << " = "; + cpu_view_read(L, spec.views[i], static_cast(i), + "i", "row", "col", vp); + L << ";\n"; + } + + // 寄存器声明(先声明后赋值,兼容 IR-B liveness 复用同号寄存器) + if (spec.num_regs > 0) + { + L << " float r0"; + for (std::uint32_t r = 1; r < spec.num_regs; ++r) L << ", r" << r; + L << ";\n"; + } + + cpu_emit_instrs(L, spec); + + const std::uint32_t last_dst = spec.instrs.back().dst; + L << " bout[i] = r" << last_dst << ";\n"; + L << " }\n"; + L << "}\n"; + return L.str(); + } + + [[nodiscard]] std::string generate_reduce( + const std::string&, const ExprSpec&) override + { + // 归约 kernel 由 CPU eval_expr 解释器承担;本 emitter 暂不生成 + return {}; + } +}; + +// 登记到 emitter 注册表 +inline const bool kCpuEmitterRegistered = + emitter_registry::register_backend("cpu", + []() -> ExprEmitter* { return new CpuEmitter(); }); + +} // namespace nn + +#endif // NN_CPU_EMITTER_HPP diff --git a/include/neuralnet.cpp/cpu_engine.hpp b/include/neuralnet.cpp/cpu_engine.hpp index ccfcffb8..e3dfd445 100644 --- a/include/neuralnet.cpp/cpu_engine.hpp +++ b/include/neuralnet.cpp/cpu_engine.hpp @@ -11,11 +11,14 @@ #include #include #include +#include #include "compute_engine.hpp" #include "algebra_compute.hpp" #include "algebra_expr.hpp" #include "expr_opt.hpp" +#include "expr_graph.hpp" // IR-C:图 IR + 融合分析(scan 模式登记用) +#include "expr_registry.hpp" // scan 模式融合 kernel 登记(NN_EXPR_SCAN) namespace nn { @@ -58,6 +61,14 @@ inline std::pair attn_bias_at( // ══════════════════════════════════════════════════════════════════════════ class CpuEngine final : public ComputeEngine { +private: + // IR-C 图 IR 录制(仅构建期 scan 模式启用;普通 CPU 运行保持 no-op)。 + // scan 模式下 begin_expr 开启录制,期间 dsl::compute/compute_reduce 把 + // 表达式加入录制图(见 expr_dsl.hpp 的 scan 分支),end_expr 时融合分析 + // 并把每个融合 kernel 的复合 spec 登记进 global_registry —— 保证 GPU + // 运行时 begin_expr/end_expr 融合出的复合 spec 命中 AOT shader(闭合世界)。 + std::optional recording_; + public: [[nodiscard]] Device device() const noexcept override { return Device::CPU; } @@ -66,9 +77,33 @@ class CpuEngine final : public ComputeEngine [[nodiscard]] Result end_batch() override { return {}; } [[nodiscard]] Result flush_batch() override { return {}; } - // ── 表达式录制:CPU 为 no-op(各表达式直接求值,行为不变) ────────── - [[nodiscard]] Result begin_expr() override { return {}; } - [[nodiscard]] Result end_expr() override { return {}; } + // ── 表达式录制(IR-C)────────────────────────────────────────────── + // 普通 CPU 运行:no-op(各表达式直接求值,行为不变——融合是 GPU 优化)。 + // 构建期 scan(NN_EXPR_SCAN):begin_expr 开启录制图;end_expr 融合分析 + // 并登记全部融合 kernel 的复合 spec(闭合世界两端一致)。 + [[nodiscard]] Result begin_expr() override + { +#ifdef NN_EXPR_SCAN + recording_.emplace(); + fused::recording_graph_ptr() = &*recording_; +#endif + return {}; + } + + [[nodiscard]] Result end_expr() override + { +#ifdef NN_EXPR_SCAN + if (!recording_) + return {}; + ExprGraph g = std::move(*recording_); + recording_.reset(); + fused::recording_graph_ptr() = nullptr; + auto kernels = fuse_expr_graph(g); + for (auto& k : kernels) + fused::global_registry().add(k.spec); +#endif + return {}; + } // ── 张量工厂 ────────────────────────────────────────────────────────── [[nodiscard]] Tensor create_tensor(std::size_t rows, std::size_t cols) override diff --git a/include/neuralnet.cpp/expr_dsl.hpp b/include/neuralnet.cpp/expr_dsl.hpp index b0995d49..dbc9dd61 100644 --- a/include/neuralnet.cpp/expr_dsl.hpp +++ b/include/neuralnet.cpp/expr_dsl.hpp @@ -33,6 +33,7 @@ #include "compute_engine.hpp" #include "expr_spec.hpp" #include "expr_registry.hpp" +#include "expr_graph.hpp" // IR-C:录制图(begin_expr/end_expr 扫描登记) #include "algebra_expr.hpp" // nn::Expression / nn::BoolExpression 概念 #include "algebra_ops.hpp" // nn::ops(唯一算子来源,含 op_id()) #include "algebra_matrix.hpp" // Matrix @@ -458,6 +459,16 @@ template auto [spec, inputs] = to_expr_spec(e); if (auto v = validate_expr_spec(spec, inputs.size()); !v) return std::unexpected(v.error()); + // IR-C:begin_expr/end_expr 录制段内,表达式加入录制图(而非直接登记); + // end_expr 时 CpuEngine 融合分析并登记融合后的复合 spec(闭合世界)。 + if (auto* g = fused::recording_graph()) + { + const int node = g->add_node(spec, inputs, rows, cols, + /*vector_out=*/false); + Tensor t = Tensor::cpu(rows, cols); + t.set_virtual_tag(static_cast(node) + 1); + return t; + } fused::global_registry().add(spec); return Tensor::cpu(rows, cols); #else @@ -530,11 +541,24 @@ template auto [spec, inputs] = to_expr_spec(e); if (auto v = validate_expr_spec(spec, inputs.size()); !v) return std::unexpected(v.error()); - fused::global_registry().add(spec); const int raxis = expr_spec_reduce_axis(spec); - if (raxis == 0) return Tensor::cpu(rows, 1); - if (raxis == 1) return Tensor::cpu(1, cols); - return Tensor::cpu(rows, cols); + const auto placeholder = [&](int node) { + Tensor t = (raxis == 0) ? Tensor::cpu(rows, 1) + : (raxis == 1) ? Tensor::cpu(1, cols) + : Tensor::cpu(rows, cols); + if (node >= 0) + t.set_virtual_tag(static_cast(node) + 1); + return t; + }; + // IR-C:录制段内加入图(end_expr 时融合登记);否则直接登记 + if (auto* g = fused::recording_graph()) + { + const int node = g->add_node(spec, inputs, rows, cols, + /*vector_out=*/true); + return placeholder(node); + } + fused::global_registry().add(spec); + return placeholder(-1); #else auto [spec, inputs] = to_expr_spec(e); if (auto v = validate_expr_spec(spec, inputs.size()); !v) diff --git a/include/neuralnet.cpp/expr_emitter.hpp b/include/neuralnet.cpp/expr_emitter.hpp new file mode 100644 index 00000000..f4af1a44 --- /dev/null +++ b/include/neuralnet.cpp/expr_emitter.hpp @@ -0,0 +1,103 @@ +#ifndef NN_EXPR_EMITTER_HPP +#define NN_EXPR_EMITTER_HPP + +// ═══════════════════════════════════════════════════════════════════════════ +// expr_emitter.hpp — 后端 emitter 抽象(IR-D) +// +// 对应文档 `docs/11-ir-optimization.md` IR-D:把后端代码生成从 GLSL 专用 +// 抽象为 emitter 接口,实现"一份 canonical IR → 多后端代码": +// +// IR (canonical ExprSpec) → GlslEmitter / CudaEmitter / CpuEmitter +// +// 设计: +// - ExprEmitter 是纯接口:给定 name + canonical ExprSpec,产出目标后端 +// 源码字符串(失败返回空串,语义与 glsl_gen 的约定一致)。 +// - 现有 generate_glsl / generate_glsl_reduce 成为 GlslEmitter 的实现 +// (见 glsl_gen.hpp),gen_fused 等消费方经接口调用,不再与 GLSL 绑定。 +// - 具体 emitter 类在各自头文件(glsl_gen.hpp / cpu_emitter.hpp)提供, +// 本头只定义接口 + 注册表(按后端名选择 emitter 工厂)。 +// 注意:本头供构建期生成器/工具使用,运行时无需包含(与 glsl_gen 一致)。 +// ═══════════════════════════════════════════════════════════════════════════ + +#include +#include +#include +#include +#include + +#include "expr_spec.hpp" + +namespace nn +{ + +// ── emitter 抽象接口 ────────────────────────────────────────────────────── +// 实现约定(与现有 glsl_gen 语义对齐): +// - generate:逐元素 kernel(无归约视图/归约指令)。 +// - generate_reduce:归约 kernel(expr_spec_reduce_axis >= 0); +// 混合轴(== -2)或不支持的归约结构返回空串。 +// - 产物均以"自动生成,请勿手动编辑"注释开头,便于区分手工代码。 +class ExprEmitter +{ +public: + virtual ~ExprEmitter() = default; + + // 后端名("glsl" / "cpu" / ...),供注册表/日志识别 + [[nodiscard]] virtual std::string_view name() const noexcept = 0; + + // 逐元素 kernel 源码;失败返回空串(调用方报错) + [[nodiscard]] virtual std::string generate( + const std::string& name_, const ExprSpec& spec) = 0; + + // 归约 kernel 源码;不支持(混合轴/超槽)返回空串 + [[nodiscard]] virtual std::string generate_reduce( + const std::string& name_, const ExprSpec& spec) = 0; +}; + +// ── 简单注册表:按后端名选择 emitter 工厂 ──────────────────────────────── +// 供生成器工具(gen_fused 等)按 --backend 选择目标 emitter。 +// 具体 emitter 类型在各自头文件中向本注册表登记(见 GlslEmitter / CpuEmitter)。 +// 线程安全:初始化期单线程登记(静态初始化),运行期只读查询。 +namespace emitter_registry +{ + +using Factory = ExprEmitter* (*)(); + +// 后端表(静态初始化,见各 emitter 头文件的登记语句) +inline std::unordered_map& backends() +{ + static std::unordered_map m; + return m; +} + +// 登记后端(返回 false 表示已存在同名后端,拒绝覆盖) +inline bool register_backend(std::string_view name, Factory factory) +{ + auto& m = backends(); + return m.emplace(std::string(name), factory).second; +} + +// 按名创建 emitter;未登记返回 nullptr +[[nodiscard]] inline std::unique_ptr make(std::string_view name) +{ + const auto& m = backends(); + const auto it = m.find(std::string(name)); + if (it == m.end()) + return nullptr; + return std::unique_ptr(it->second()); +} + +// 所有已登记后端名(调试/日志) +[[nodiscard]] inline std::vector names() +{ + std::vector out; + out.reserve(backends().size()); + for (const auto& [k, v] : backends()) + out.push_back(k); + return out; +} + +} // namespace emitter_registry + +} // namespace nn + +#endif // NN_EXPR_EMITTER_HPP diff --git a/include/neuralnet.cpp/expr_graph.hpp b/include/neuralnet.cpp/expr_graph.hpp new file mode 100644 index 00000000..317f952e --- /dev/null +++ b/include/neuralnet.cpp/expr_graph.hpp @@ -0,0 +1,304 @@ +#ifndef NN_EXPR_GRAPH_HPP +#define NN_EXPR_GRAPH_HPP + +// ═══════════════════════════════════════════════════════════════════════════ +// expr_graph.hpp — 图 IR + 融合分析(IR-C) +// +// 对应文档 `docs/11-ir-optimization.md` IR-C:把扁平 ExprSpec 演进为图 IR +// (DAG),为 begin_expr/end_expr 提供多表达式融合分析。 +// +// 图结构: +// - 节点 = 一次 eval_expr / eval_expr_reduce(一个 canonical ExprSpec)。 +// - 边 = 数据依赖:节点的某个输入槽引用前序节点的输出(虚拟寄存器)。 +// - 录制:begin_expr 开启录制图;期间每次表达式求值把节点加入图并返回 +// 携带 virtual_tag 的占位 Tensor;end_expr 做融合分析并产出 kernel 序列。 +// +// 融合策略(本阶段落地:逐元素链拼接): +// - 相邻节点 A → B(B 以 Linear 视图消费 A 的输出,且 A 无其他消费者) +// 且 A、B 均为逐元素(无归约)、形状相同 → 把 A 的指令内联进 B, +// A 的输出寄存器直接作为 B 的操作数 → 中间结果不落显存,一次 dispatch。 +// - 归约表达式 / 归约输出 / matmul 等作为融合边界 → 独立 kernel。 +// - 拼接后 canonicalize + validate;超限(instrs/regs/inputs/consts) +// 或非法 → 放弃融合(保守,保证正确性优先)。 +// +// 确定性铁律(与 expr_opt.hpp 一致):贪心按节点序从前到后;拼接重映射 +// 顺序固定;结果经 canonicalize_expr_spec 统一规范化(两端一致)。 +// ═══════════════════════════════════════════════════════════════════════════ + +#include +#include +#include +#include +#include +#include + +#include "tensor.hpp" +#include "expr_spec.hpp" +#include "expr_opt.hpp" + +namespace nn +{ + +// ── 图 IR 节点 ──────────────────────────────────────────────────────────── +struct ExprGraphNode +{ + ExprSpec spec; // canonical IR(录制时已 canonicalize) + std::size_t rows = 0; // 输出形状(统一网格 (rows, cols)) + std::size_t cols = 0; + bool vector_out = false; // 输出为归约向量((rows,1)/(1,cols)) + // 依赖:dep_of_input[k] = 该输入槽依赖的图节点下标;-1 = 外部输入。 + // 长度 == spec.views.size()(与输入一一对应)。 + std::vector dep_of_input; + // 外部输入张量:dep_of_input[k] < 0 时有效(Layer 传入的真实输入)。 + std::vector input_tensors; +}; + +// ── 图(录制期累积) ───────────────────────────────────────────────────── +struct ExprGraph +{ + std::vector nodes; + // 占位 Tensor 的 virtual_tag → 节点下标(录制时建立,供依赖识别) + std::unordered_map node_by_tag; + + // 录制:把一个表达式加入图,返回节点下标。 + // inputs 中带 virtual_tag 的占位 Tensor 会被识别为前序节点输出(依赖边)。 + [[nodiscard]] int add_node(const ExprSpec& raw_spec, + std::span inputs, + std::size_t rows, std::size_t cols, + bool vector_out) + { + const ExprSpec spec = canonicalize_expr_spec(raw_spec); + const int idx = static_cast(nodes.size()); + ExprGraphNode nd; + nd.spec = spec; + nd.rows = rows; + nd.cols = cols; + nd.vector_out = vector_out; + nd.dep_of_input.reserve(spec.views.size()); + nd.input_tensors.reserve(spec.views.size()); + for (std::size_t k = 0; k < spec.views.size(); ++k) + { + const std::uint64_t tag = (k < inputs.size()) + ? inputs[k].virtual_tag() : 0; + const auto it = (tag != 0) ? node_by_tag.find(tag) : node_by_tag.end(); + if (it != node_by_tag.end()) + { + nd.dep_of_input.push_back(it->second); + nd.input_tensors.emplace_back(); // 占位(依赖节点输出) + } + else + { + nd.dep_of_input.push_back(-1); + nd.input_tensors.push_back( + (k < inputs.size()) ? inputs[k] : Tensor{}); + } + } + nodes.push_back(std::move(nd)); + // 登记输出标记(tag = 节点下标 + 1,0 保留给"非节点输出") + node_by_tag[static_cast(idx) + 1ull] = idx; + return idx; + } +}; + +// ── 融合 kernel(融合分析输出) ────────────────────────────────────────── +// 一个 kernel = 一个(可能为多个节点拼接的)复合 ExprSpec + 外部输入来源。 +struct FusedKernelInput +{ + int node = -1; // >= 0:该输入是图节点 node 的输出(作为外部 buffer 绑定) + Tensor external; // node < 0:Layer 外部输入 +}; + +struct FusedKernel +{ + ExprSpec spec; // 复合 spec(canonical) + std::vector inputs; // 与 spec.views 一一对应 + std::size_t rows = 0; + std::size_t cols = 0; + bool vector_out = false; + int tail = -1; // 末尾节点(输出节点)在图中下标 + std::vector members; // kernel 包含的节点(顺序 = 融合顺序) +}; + +// ── 全局录制状态(线程局部) ──────────────────────────────────────────── +// begin_expr/end_expr(GPU 引擎执行 / scan 引擎登记)开启/关闭录制图。 +// dsl::compute 的 scan 分支与 GpuEngine::eval_expr 通过 recording_graph() +// 判断"当前是否在录制"以及把表达式加入录制图。 +namespace fused +{ + +[[nodiscard]] inline ExprGraph*& recording_graph_ptr() +{ + static thread_local ExprGraph* g = nullptr; + return g; +} +[[nodiscard]] inline ExprGraph* recording_graph() noexcept +{ return recording_graph_ptr(); } +[[nodiscard]] inline bool is_recording() noexcept +{ return recording_graph_ptr() != nullptr; } + +} // namespace fused + +// ── 融合分析(IR-C 核心,纯函数) ─────────────────────────────────────── +// 贪心按节点序:维护"当前打开的 kernel"(其末尾节点为 tail)。节点 i 若 +// 满足"并入"条件则并入,否则新开 kernel。 +[[nodiscard]] inline std::vector fuse_expr_graph(const ExprGraph& g) +{ + std::vector kernels; + if (g.nodes.empty()) + return kernels; + + // 消费者表:节点 j 被哪些节点消费(dep_of_input 引用 j) + std::vector> consumers(g.nodes.size()); + for (std::size_t i = 0; i < g.nodes.size(); ++i) + for (const int dep : g.nodes[i].dep_of_input) + if (dep >= 0) + consumers[static_cast(dep)].push_back(static_cast(i)); + + // ── 尝试把节点 bi 并入当前 kernel(cur) ─────────────────────────── + const auto try_append = [&](FusedKernel& cur, int bi) -> bool + { + const ExprGraphNode& B = g.nodes[static_cast(bi)]; + const int tail = cur.tail; + const ExprSpec& A = cur.spec; // 当前复合 spec(含 tail 及之前节点) + const ExprSpec& Bs = B.spec; + + // 1) 前置:均逐元素(无归约)、非归约输出、形状相同 + if (cur.vector_out || B.vector_out) return false; + if (expr_spec_reduce_axis(A) != -1) return false; + if (expr_spec_reduce_axis(Bs) != -1) return false; + if (cur.rows != B.rows || cur.cols != B.cols) return false; + + // 2) tail 无其他消费者,且被 B 以 Linear 视图消费 + if (consumers[static_cast(tail)].size() != 1 || + consumers[static_cast(tail)][0] != bi) + return false; + std::vector tail_slots; // B 中引用 tail 的输入槽 + std::vector other_slots; // B 中其他输入槽 + for (std::size_t k = 0; k < Bs.views.size(); ++k) + { + if (B.dep_of_input[k] == tail) + { + if (static_cast(Bs.views[k].kind) != ExprViewKind::Linear) + return false; // 仅支持 Linear 引用(寄存器直接替换) + tail_slots.push_back(static_cast(k)); + } + else + { + other_slots.push_back(static_cast(k)); + } + } + if (tail_slots.empty()) + return false; + + // 3) 拼接(确定性:tail 的 views/consts/instrs 在前,B 的在后) + const std::size_t na = A.instrs.size(); + const std::size_t out_reg_A = A.instrs.back().dst; + + std::vector views = A.views; + std::vector b_view_map(Bs.views.size(), -1); + for (const int k : other_slots) + { + b_view_map[static_cast(k)] = static_cast(views.size()); + views.push_back(Bs.views[static_cast(k)]); + } + + std::vector consts = A.consts; + const std::size_t b_const_base = A.consts.size(); + // 关键:B 的常量池必须追加在 A 之后(B 的 Const 引用经偏移指向这里) + consts.insert(consts.end(), Bs.consts.begin(), Bs.consts.end()); + + std::vector instrs = A.instrs; + for (const auto& ins : Bs.instrs) + { + ExprInstr ni = ins; + ni.dst = static_cast(ni.dst + na); + const auto remap_op = [&](ExprOperand o) -> ExprOperand + { + if (o.kind == static_cast(ExprOperandKind::Input)) + { + const std::size_t k = o.idx; + const auto it = std::find(tail_slots.begin(), tail_slots.end(), + static_cast(k)); + if (it != tail_slots.end()) + return expr::reg(static_cast(out_reg_A)); + return expr::input(static_cast( + b_view_map[k])); + } + if (o.kind == static_cast(ExprOperandKind::Const)) + return expr::cst(static_cast( + o.idx + b_const_base)); + if (o.kind == static_cast(ExprOperandKind::Reg) || + o.kind == static_cast(ExprOperandKind::Fanout) || + o.kind == static_cast(ExprOperandKind::Reduce)) + return {o.kind, static_cast(o.idx + na)}; + return o; + }; + const std::size_t nops = + expr_instr_num_operands(static_cast(ins.op)); + ni.a = remap_op(ins.a); + if (nops >= 2) ni.b = remap_op(ins.b); + if (nops >= 3) ni.c = remap_op(ins.c); + instrs.push_back(ni); + } + + ExprSpec fused; + fused.views = std::move(views); + fused.consts = std::move(consts); + fused.instrs = std::move(instrs); + fused.num_regs = static_cast(na + Bs.num_regs); + fused = canonicalize_expr_spec(fused); + if (auto v = validate_expr_spec(fused, fused.views.size()); !v) + return false; // 超限/非法 → 放弃融合(保守) + + // 4) 提交:更新 cur + std::vector new_inputs = cur.inputs; + for (const int k : other_slots) + { + const std::size_t kk = static_cast(k); + if (B.dep_of_input[kk] < 0) + new_inputs.push_back(FusedKernelInput{ + /*node=*/-1, /*external=*/B.input_tensors[kk]}); + else + new_inputs.push_back(FusedKernelInput{ + /*node=*/B.dep_of_input[kk], /*external=*/Tensor{}}); + } + cur.spec = std::move(fused); + cur.inputs = std::move(new_inputs); + cur.rows = B.rows; + cur.cols = B.cols; + cur.tail = bi; + cur.members.push_back(bi); + return true; + }; + + // ── 贪心主循环 ───────────────────────────────────────────────────── + for (std::size_t i = 0; i < g.nodes.size(); ++i) + { + const auto& N = g.nodes[i]; + if (!kernels.empty() && try_append(kernels.back(), static_cast(i))) + continue; + + FusedKernel k; + k.spec = N.spec; + k.rows = N.rows; + k.cols = N.cols; + k.vector_out = N.vector_out; + k.tail = static_cast(i); + k.members.push_back(static_cast(i)); + for (std::size_t kk = 0; kk < N.spec.views.size(); ++kk) + { + if (N.dep_of_input[kk] < 0) + k.inputs.push_back(FusedKernelInput{ + /*node=*/-1, /*external=*/N.input_tensors[kk]}); + else + k.inputs.push_back(FusedKernelInput{ + /*node=*/N.dep_of_input[kk], /*external=*/Tensor{}}); + } + kernels.push_back(std::move(k)); + } + return kernels; +} + +} // namespace nn + +#endif // NN_EXPR_GRAPH_HPP diff --git a/include/neuralnet.cpp/glsl_gen.hpp b/include/neuralnet.cpp/glsl_gen.hpp index 5f25a60f..d144b529 100644 --- a/include/neuralnet.cpp/glsl_gen.hpp +++ b/include/neuralnet.cpp/glsl_gen.hpp @@ -19,6 +19,7 @@ #include #include "expr_spec.hpp" +#include "expr_emitter.hpp" // IR-D:emitter 抽象(GlslEmitter 实现) namespace nn { @@ -543,6 +544,34 @@ inline std::string generate_glsl(const std::string& name, const ExprSpec& spec) return L.str(); } +// ═══════════════════════════════════════════════════════════════════════════ +// GlslEmitter — GLSL 后端 emitter(IR-D) +// +// 把上述 generate_glsl / generate_glsl_reduce 封装为 ExprEmitter 接口实现, +// 使生成器工具(gen_fused)可经统一接口选择后端,不再与 GLSL 绑定。 +// 自由函数 generate_glsl / generate_glsl_reduce 保留为便捷入口 +// (内部转发到 GlslEmitter),向后兼容既有调用方。 +// ═══════════════════════════════════════════════════════════════════════════ +class GlslEmitter final : public ExprEmitter +{ +public: + [[nodiscard]] std::string_view name() const noexcept override + { return "glsl"; } + + [[nodiscard]] std::string generate( + const std::string& name_, const ExprSpec& spec) override + { return nn::generate_glsl(name_, spec); } + + [[nodiscard]] std::string generate_reduce( + const std::string& name_, const ExprSpec& spec) override + { return nn::generate_glsl_reduce(name_, spec); } +}; + +// 登记到 emitter 注册表(静态初始化;重复包含无害——同名拒绝覆盖) +inline const bool kGlslEmitterRegistered = + emitter_registry::register_backend("glsl", + []() -> ExprEmitter* { return new GlslEmitter(); }); + } // namespace nn #endif // NN_GLSL_GEN_HPP diff --git a/include/neuralnet.cpp/gpu_engine.hpp b/include/neuralnet.cpp/gpu_engine.hpp index c18d0480..589f6240 100644 --- a/include/neuralnet.cpp/gpu_engine.hpp +++ b/include/neuralnet.cpp/gpu_engine.hpp @@ -34,9 +34,11 @@ #include #include #include +#include #include "compute_engine.hpp" #include "expr_opt.hpp" +#include "expr_graph.hpp" // IR-C:图 IR + 融合分析 #if __has_include("fused_registry.hpp") #include "fused_registry.hpp" #endif @@ -53,6 +55,12 @@ class GpuEngine final : public ComputeEngine private: GpuBackend& backend_; + // IR-C 图 IR 录制(begin_expr/end_expr): + // recording_ :录制中的图(nullopt = 未录制) + // node_outputs_:节点下标 → 占位输出 buffer(end_expr 时写入真实结果) + std::optional recording_; + std::unordered_map node_outputs_; + public: explicit GpuEngine(GpuBackend& backend) : backend_(backend) {} @@ -162,11 +170,29 @@ class GpuEngine final : public ComputeEngine return backend_.flush_batch(); } - // ── 表达式录制(M2 框架):当前为 no-op ───────────────────────────── - // 各原语照常独立 dispatch;录制融合分析(虚拟寄存器 DAG + 融合边界判定) - // 在 M3 落地。begin/end 保持幂等,Layer 可先行包住算法段落。 - [[nodiscard]] Result begin_expr() override { return {}; } - [[nodiscard]] Result end_expr() override { return {}; } + // ── 表达式录制(IR-C):begin_expr/end_expr 图 IR 融合 ───────────── + // begin_expr 开启录制:期间 eval_expr / eval_expr_reduce 把表达式加入 + // 录制图并返回携带 virtual_tag 的占位 Tensor(真实 GPU buffer,Layer + // 无感知)。end_expr 做融合分析:逐元素链拼接成复合 ExprSpec → 每 + // kernel 一次 AOT dispatch,输出写入对应节点的占位 buffer(Layer 持有 + // 的 Tensor 即物化)。未命中 AOT 融合 shader → 硬报错(闭合世界)。 + [[nodiscard]] Result begin_expr() override + { + recording_.emplace(); + node_outputs_.clear(); + fused::recording_graph_ptr() = &*recording_; + return {}; + } + + [[nodiscard]] Result end_expr() override + { + if (!recording_) + return {}; + ExprGraph g = std::move(*recording_); + recording_.reset(); + fused::recording_graph_ptr() = nullptr; + return execute_fused_graph(g); + } // ══════════════════════════════════════════════════════════════════════ // 张量工厂(纯 GPU:全部创建/上传为 GPU Tensor) @@ -1021,6 +1047,22 @@ class GpuEngine final : public ComputeEngine std::span inputs, std::size_t rows, std::size_t cols) override { + // ── IR-C:录制模式(begin_expr/end_expr 内)────────────────── + // 把表达式加入录制图,返回携带 virtual_tag 的占位 GPU buffer; + // end_expr 时融合执行并物化。依赖识别:inputs 中带 virtual_tag 的 + // 占位 Tensor(前序节点输出)建立依赖边,不作为 kernel 外部输入。 + if (auto* g = fused::recording_graph()) + { + const int node = g->add_node(raw_spec, inputs, rows, cols, + /*vector_out=*/false); + auto out = GpuTensor::create_empty(rows, cols, backend_); + if (!out) return std::unexpected(out.error()); + Tensor t = Tensor::from_gpu(std::move(*out)); + t.set_virtual_tag(static_cast(node) + 1); + node_outputs_[node] = t; + return t; + } + // ── canonical IR:canonicalize 为引擎内部优化(IR-A/IR-B), // key 与 shader 合成两端一致;dispatch 用 canonical 的 consts ── const ExprSpec spec = nn::canonicalize_expr_spec(raw_spec); @@ -1065,6 +1107,22 @@ class GpuEngine final : public ComputeEngine std::span inputs, std::size_t rows, std::size_t cols) override { + // ── IR-C:录制模式(归约输出:占位 buffer 按归约向量形状分配)── + if (auto* g = fused::recording_graph()) + { + const int node = g->add_node(raw_spec, inputs, rows, cols, + /*vector_out=*/true); + const int raxis = expr_spec_reduce_axis(raw_spec); + const std::size_t orows = (raxis == 0) ? rows : 1; + const std::size_t ocols = (raxis == 1) ? cols : 1; + auto out = GpuTensor::create_empty(orows, ocols, backend_); + if (!out) return std::unexpected(out.error()); + Tensor t = Tensor::from_gpu(std::move(*out)); + t.set_virtual_tag(static_cast(node) + 1); + node_outputs_[node] = t; + return t; + } + // canonical IR:与 eval_expr 同(canonicalize 为引擎内部优化) const ExprSpec spec = nn::canonicalize_expr_spec(raw_spec); const std::string key = nn::expr_spec_key(spec); @@ -1094,6 +1152,67 @@ class GpuEngine final : public ComputeEngine } private: + // ── IR-C:融合执行 ────────────────────────────────────────────────── + // 图 → 融合分析 → kernel 序列 → 逐个 AOT dispatch。每个 kernel 的输出 + // 写入其末尾节点(tail)的占位 buffer(node_outputs_[tail]),使 Layer + // 持有的占位 Tensor 在 end_expr 后物化。中间节点若被融合则无独立输出 + // (作为寄存器内联),若为融合边界则其占位 buffer 由自身 kernel 写入 + // 并被后续 kernel 当作输入绑定(FusedKernelInput.node 来源)。 + [[nodiscard]] Result execute_fused_graph(ExprGraph& g) + { + auto kernels = fuse_expr_graph(g); + for (auto& k : kernels) + { + // 解析输入:node 来源 → 该节点占位输出 buffer;external → 直接张量 + std::vector gpu_inputs; + gpu_inputs.reserve(k.inputs.size()); + for (auto& in : k.inputs) + { + Tensor t; + if (in.node >= 0) + { + const auto it = node_outputs_.find(in.node); + if (it == node_outputs_.end()) + return std::unexpected(Error{ + "GpuEngine::end_expr: 依赖节点输出缺失(图 IR 状态损坏)"}); + t = it->second; + } + else + { + t = in.external; + } + auto gg = ensure_gpu(t); + if (!gg) return std::unexpected(gg.error()); + gpu_inputs.push_back(gg->gpu_tensor()); + } + + // AOT 匹配(闭合世界:融合后的复合 spec 必须在构建期 scan 登记过) + const std::string key = nn::expr_spec_key(k.spec); +#ifdef NN_FUSED_REGISTRY_EMBEDDED + const nn::fused::FusedShader* fs = nn::fused::find_fused(key); + if (fs && backend_.has_fused_shader(key)) + { + const auto vp = nn::expr_spec_runtime_view_params(k.spec); + GpuTensor* out_override = nullptr; + const auto out_it = node_outputs_.find(k.tail); + if (out_it != node_outputs_.end()) + out_override = &out_it->second.gpu_tensor(); + auto out = backend_.run_fused_gpu( + key, gpu_inputs, k.spec.consts, k.rows, k.cols, + k.vector_out, vp, out_override); + if (!out) return std::unexpected(out.error()); + if (!out_override) + node_outputs_[k.tail] = Tensor::from_gpu(std::move(*out)); + continue; + } +#endif + return std::unexpected(Error{ + "GpuEngine::end_expr: 融合 kernel 未命中 AOT 融合 shader(闭合世界);" + "请将 begin_expr/end_expr 段纳入构建期扫描(scan_exprs)"}); + } + return {}; + } + // ── 辅助:确保 Tensor 在 GPU 上 ────────────────────────────────────── // 若已是 GPU,返回共享拷贝(零开销);若为 CPU,上传到 GPU。 // 纯 GPU 架构下,所有 Tensor 应已是 GPU,此方法为防御性兜底。 diff --git a/include/neuralnet.cpp/tensor.hpp b/include/neuralnet.cpp/tensor.hpp index c9bbe2d4..e9e3f077 100644 --- a/include/neuralnet.cpp/tensor.hpp +++ b/include/neuralnet.cpp/tensor.hpp @@ -48,6 +48,12 @@ class Tensor std::size_t rows_ = 0; std::size_t cols_ = 0; + // 图 IR 录制(IR-C):虚拟寄存器标记。 + // begin_expr/end_expr 录制模式下,eval_expr 返回的占位 Tensor 携带 + // 该标记(= 图节点下标 + 1;0 = 非图节点输出),供后续表达式建立 + // 依赖边(Layer 无感知,见 expr_graph.hpp / gpu_engine.hpp)。 + std::uint64_t virtual_tag_ = 0; + // CPU 存储(device_ == CPU 时有效) std::shared_ptr cpu_data_; @@ -109,6 +115,10 @@ class Tensor [[nodiscard]] std::size_t size() const noexcept { return rows_ * cols_; } [[nodiscard]] bool is_cpu() const noexcept { return device_ == Device::CPU; } [[nodiscard]] bool is_gpu() const noexcept { return device_ == Device::GPU; } + + // ── 图 IR 虚拟寄存器标记(IR-C 录制用,Layer 无感知) ─────────────── + [[nodiscard]] std::uint64_t virtual_tag() const noexcept { return virtual_tag_; } + void set_virtual_tag(std::uint64_t tag) noexcept { virtual_tag_ = tag; } [[nodiscard]] bool valid() const noexcept { if (device_ == Device::CPU) @@ -190,6 +200,7 @@ class Tensor t.device_ = device_; t.rows_ = new_rows; t.cols_ = new_cols; + t.virtual_tag_ = virtual_tag_; // reshape 保留图 IR 标记 #ifdef NN_HAS_VULKAN t.gpu_data_ = gpu_data_; #endif diff --git a/src/expr_fuse_test.cpp b/src/expr_fuse_test.cpp new file mode 100644 index 00000000..f3d04602 --- /dev/null +++ b/src/expr_fuse_test.cpp @@ -0,0 +1,211 @@ +// ─────────────────────────────────────────────────────────────────────────── +// expr_fuse_test.cpp — IR-C 图 IR 融合 GPU 端到端验证(begin_expr/end_expr) +// +// 验证内容(对应 docs/11-ir-optimization.md IR-C): +// 1. FusedChainLayer forward:GPU(begin_expr 录制 → end_expr 融合 → +// AOT 融合 shader 单 kernel dispatch)vs CPU(逐节点求值)一致。 +// —— 中间量 t/u 内联为寄存器,不落显存、不额外 dispatch。 +// 2. FusedChainLayer backward:GPU(独立 AOT)vs CPU 一致。 +// 3. 录制图依赖识别:GPU 端第二个表达式消费第一个的占位输出 → 融合。 +// 4. 闭合世界:未扫描的融合 kernel → end_expr 硬报错(不静默回退)。 +// 5. 未录制路径回归:普通 dsl::compute 在 GPU 上照常 AOT dispatch。 +// +// 用法:expr_fuse_test(需 Vulkan SDK;依赖 CMake 构建期 scan_exprs 已 +// dry-run FusedChainLayer,登记融合后的复合 spec —— 见 scan_exprs.cpp) +// ─────────────────────────────────────────────────────────────────────────── + +#include + +#include +#include +#include +#include +#include +#include + +using nn::Scalar; + +#ifndef NN_HAS_VULKAN +int main() +{ + std::cout << "[SKIP] 此程序需要 Vulkan SDK 支持,请使用 -DNN_HAS_VULKAN 编译。\n"; + return 0; +} +#else +#include + +using nn::Matrix; +using nn::Tensor; +using nn::CpuEngine; +using nn::GpuEngine; +using nn::GpuBackend; + +// ── 辅助:最大绝对误差 ──────────────────────────────────────────────────── +Scalar max_abs_diff(const Matrix& a, const Matrix& b) +{ + Scalar max_err = 0.0f; + auto as = a.span(), bs = b.span(); + for (std::size_t i = 0; i < as.size(); ++i) + { + const Scalar d = std::fabs(as[i] - bs[i]); + if (d > max_err) max_err = d; + } + return max_err; +} + +// ── FusedChainLayer:GPU(begin_expr/end_expr 融合)vs CPU ────────────── +// forward: t = x*2; u = t+3; out = u*gamma → GPU 端三表达式融合成单 kernel +int run_fused_chain(CpuEngine& cpu, GpuEngine& gpu) +{ + const std::size_t F = 8, B = 6; + std::mt19937 rng(2026); + std::uniform_real_distribution dist(-1.0f, 1.0f); + Matrix x(F, B), grad(F, B); + for (auto& v : x.span()) v = dist(rng); + for (auto& v : grad.span()) v = dist(rng); + + nn::FusedChainLayer c_cpu(cpu, F), c_gpu(gpu, F); + const Tensor in = Tensor::from_matrix(Matrix(x)); + + // forward:GPU 走 begin_expr/end_expr 融合,CPU 逐节点 + auto fc = c_cpu.forward(cpu, in); + auto fg = c_gpu.forward(gpu, in); + if (!fc) { std::cerr << " CPU FusedChain forward 失败: " << fc.error().message << "\n"; return 1; } + if (!fg) + { + std::cerr << " GPU FusedChain forward 失败(end_expr 融合 kernel 未命中 AOT shader?): " + << fg.error().message << "\n"; + return 1; + } + auto fgm = gpu.to_matrix(*fg); + if (!fgm) { std::cerr << " GPU FusedChain forward 结果下载失败\n"; return 1; } + const Scalar err_f = max_abs_diff(fc->cpu_matrix(), *fgm); + const bool ok_f = err_f < 1e-4f; + std::cout << "[" << (ok_f ? "PASS" : "FAIL") << "] FusedChain forward(begin_expr/end_expr 融合)" + << " err=" << std::scientific << std::setprecision(2) << err_f << "\n"; + + // backward:GPU 独立 AOT + const Tensor gd = Tensor::from_matrix(Matrix(grad)); + auto bc = c_cpu.backward(cpu, gd); + auto bg = c_gpu.backward(gpu, gd); + if (!bc) { std::cerr << " CPU FusedChain backward 失败: " << bc.error().message << "\n"; return 1; } + if (!bg) + { + std::cerr << " GPU FusedChain backward 失败: " << bg.error().message << "\n"; + return 1; + } + auto bgm = gpu.to_matrix(*bg); + if (!bgm) { std::cerr << " GPU FusedChain backward 结果下载失败\n"; return 1; } + const Scalar err_b = max_abs_diff(bc->cpu_matrix(), *bgm); + const bool ok_b = err_b < 1e-4f; + std::cout << "[" << (ok_b ? "PASS" : "FAIL") << "] FusedChain backward" + << " err=" << std::scientific << std::setprecision(2) << err_b << "\n"; + return (ok_f && ok_b) ? 0 : 1; +} + +// ── 手写录制:显式 begin_expr/end_expr 链式融合 ──────────────────────── +// 与 FusedChainLayer 等价的手写路径:x*2 → t+3 → u*gamma, +// 验证 GpuEngine 录制图依赖识别 + 融合执行(结果应为 ((x*2)+3)*gamma)。 +int run_manual_recording(CpuEngine& cpu, GpuEngine& gpu) +{ + const std::size_t R = 5, C = 7; + std::mt19937 rng(999); + std::uniform_real_distribution dist(-1.0f, 1.0f); + Matrix x(R, C); + for (auto& v : x.span()) v = dist(rng); + Matrix g(R, 1); + for (auto& v : g.span()) v = 0.5f + 0.01f * static_cast(rng() % 10); + + const Tensor xt = Tensor::from_matrix(Matrix(x)); + const Tensor gt = Tensor::from_matrix(Matrix(g)); + + // CPU 参考:逐节点 + auto c1 = nn::dsl::compute(cpu, nn::dsl::leaf(xt) * Scalar{2}, R, C); + auto c2 = nn::dsl::compute(cpu, nn::dsl::leaf(*c1) + Scalar{3}, R, C); + auto c3 = nn::dsl::compute(cpu, nn::dsl::leaf(*c2) * nn::dsl::row_broadcast(gt), R, C); + if (!c1 || !c2 || !c3) { std::cerr << " CPU 参考求值失败\n"; return 1; } + + // GPU:begin_expr 录制 → 三表达式 → end_expr 融合执行 + auto b = gpu.begin_expr(); + if (!b) { std::cerr << " GPU begin_expr 失败: " << b.error().message << "\n"; return 1; } + auto g1 = nn::dsl::compute(gpu, nn::dsl::leaf(xt) * Scalar{2}, R, C); + if (!g1) { std::cerr << " GPU 录制 1 失败: " << g1.error().message << "\n"; return 1; } + auto g2 = nn::dsl::compute(gpu, nn::dsl::leaf(*g1) + Scalar{3}, R, C); + if (!g2) { std::cerr << " GPU 录制 2 失败: " << g2.error().message << "\n"; return 1; } + auto g3 = nn::dsl::compute(gpu, nn::dsl::leaf(*g2) * nn::dsl::row_broadcast(gt), R, C); + if (!g3) { std::cerr << " GPU 录制 3 失败: " << g3.error().message << "\n"; return 1; } + auto e = gpu.end_expr(); + if (!e) { std::cerr << " GPU end_expr 失败: " << e.error().message << "\n"; return 1; } + + // 物化:g3 的占位 buffer 已被融合 kernel 写入 → 直接下载 + auto gm = gpu.to_matrix(*g3); + if (!gm) { std::cerr << " GPU 融合结果下载失败\n"; return 1; } + const Scalar err = max_abs_diff(c3->cpu_matrix(), *gm); + const bool ok = err < 1e-4f; + std::cout << "[" << (ok ? "PASS" : "FAIL") << "] 手写 begin_expr/end_expr 链式融合" + << " err=" << std::scientific << std::setprecision(2) << err << "\n"; + return ok ? 0 : 1; +} + +// ── 未录制路径回归:普通 dsl::compute 照常 AOT dispatch ──────────────── +// 用 FusedChainLayer backward 的表达式(scan 已登记):grad * gamma * 2 +// 注:录制段内"独立分支各自成 kernel"的融合边界已由 expr_graph_test +// (纯函数,test_independent/test_multi_consumer)覆盖;本测试聚焦 GPU 端 +// 闭合世界内的回归路径。 +int run_non_recording(CpuEngine& cpu, GpuEngine& gpu) +{ + const std::size_t R = 4, C = 6; + std::mt19937 rng(7); + std::uniform_real_distribution dist(-1.0f, 1.0f); + Matrix x(R, C); + for (auto& v : x.span()) v = dist(rng); + const Tensor xt = Tensor::from_matrix(Matrix(x)); + + // 用 FusedChainLayer backward 的表达式(scan 已登记):grad * gamma * 2 + Matrix g(R, 1, Scalar{0.5f}); + const Tensor gt = Tensor::from_matrix(Matrix(g)); + auto cr = nn::dsl::compute(cpu, + nn::dsl::leaf(xt) * nn::dsl::row_broadcast(gt) * Scalar{2}, R, C); + auto gr = nn::dsl::compute(gpu, + nn::dsl::leaf(xt) * nn::dsl::row_broadcast(gt) * Scalar{2}, R, C); + if (!cr) { std::cerr << " CPU 求值失败\n"; return 1; } + if (!gr) + { + std::cerr << " GPU 未录制路径失败(未命中 AOT shader?): " << gr.error().message << "\n"; + return 1; + } + auto gm = gpu.to_matrix(*gr); + if (!gm) return 1; + const Scalar err = max_abs_diff(cr->cpu_matrix(), *gm); + const bool ok = err < 1e-4f; + std::cout << "[" << (ok ? "PASS" : "FAIL") << "] 未录制路径回归(普通 AOT dispatch)" + << " err=" << std::scientific << std::setprecision(2) << err << "\n"; + return ok ? 0 : 1; +} + +int main() +{ + std::cout << "========================================\n" + << " IR-C 图 IR 融合 GPU 端到端验证\n" + << "========================================\n"; + + auto cpu_engine = std::make_unique(); + auto& backend = GpuBackend::instance(); + auto init_r = backend.initialize(); + if (!init_r) + { + std::cerr << "GPU 初始化失败: " << init_r.error().message << "\n"; + return 1; + } + auto gpu_engine = std::make_unique(backend); + std::cout << "[init] CpuEngine + GpuEngine 就绪\n"; + + int fail = 0; + fail += run_fused_chain(*cpu_engine, *gpu_engine); + fail += run_manual_recording(*cpu_engine, *gpu_engine); + fail += run_non_recording(*cpu_engine, *gpu_engine); + + std::cout << (fail == 0 ? "ALL PASS\n" : "FAILED\n"); + return fail == 0 ? 0 : 1; +} +#endif diff --git a/src/expr_graph_test.cpp b/src/expr_graph_test.cpp new file mode 100644 index 00000000..143e30cc --- /dev/null +++ b/src/expr_graph_test.cpp @@ -0,0 +1,541 @@ +// ─────────────────────────────────────────────────────────────────────────── +// expr_graph_test.cpp — 图 IR + 融合分析(IR-C)单元验证(纯 CPU、无 GPU) +// +// 验证内容(对应 docs/11-ir-optimization.md 的验证策略): +// 1. 链融合正确性:A→B 融合成单 kernel 后求值 == 逐节点求值 +// 2. 多节点链融合(3 节点) +// 3. 融合边界:非链(无消费者 / 多消费者 / 非 Linear 引用)不融合 +// 4. 归约节点作为融合边界(不参与链式拼接) +// 5. 上限压力:拼接后超 EXPR_MAX_* 则保守放弃融合 +// 6. 确定性:同一图多次融合结果完全一致(含 key) +// 7. 不变量:融合 kernel 通过 validate;views/输入顺序确定 +// 8. 独立分支:不消费前序输出的相邻表达式各自独立成 kernel +// +// 编译(单 TU 快速验证,与 expr_opt_test 相同): +// clang++ -std=c++26 -stdlib=libc++ -fno-exceptions \ +// -I include/neuralnet.cpp src/expr_graph_test.cpp -o build/expr_graph_test +// 运行:./build/expr_graph_test +// ─────────────────────────────────────────────────────────────────────────── + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include // 注册 GlslEmitter(IR-D) +#include // 注册 CpuEmitter(IR-D) +#include + +using nn::Scalar; + +namespace +{ + +int g_fail = 0; +#define CHECK(cond, msg) \ + do { \ + if (!(cond)) { \ + std::printf("[FAIL] %s\n", msg); \ + ++g_fail; \ + } \ + } while (0) + +nn::Tensor make_tensor(std::size_t rows, std::size_t cols, float base = 0.0f, + float step = 0.01f) +{ + nn::Tensor t = nn::Tensor::cpu(rows, cols); + auto sp = t.cpu_matrix().span(); + for (std::size_t i = 0; i < sp.size(); ++i) + sp[i] = base + static_cast(i) * step; + return t; +} + +Scalar max_abs_diff(const nn::Matrix& a, const nn::Matrix& b) +{ + Scalar m = 0.0f; + auto as = a.span(), bs = b.span(); + for (std::size_t i = 0; i < as.size(); ++i) + { + const Scalar d = std::fabs(as[i] - bs[i]); + if (d > m) m = d; + } + return m; +} + +// 构造逐元素一元指令:out = unary(input0) 或 out = input0 const +nn::ExprSpec make_unary_const(nn::ExprOp op, Scalar c) +{ + nn::ExprSpec s; + s.views.push_back(nn::expr::linear()); + s.consts.push_back(c); + nn::ExprInstr in; + in.op = static_cast(op); + in.dst = 0; + in.a = nn::expr::input(0); + in.b = nn::expr::cst(0); + s.instrs.push_back(in); + s.num_regs = 1; + return s; +} + +// 构造逐元素二元:out = input0 input1 +nn::ExprSpec make_binary(nn::ExprOp op) +{ + nn::ExprSpec s; + s.views.push_back(nn::expr::linear()); + s.views.push_back(nn::expr::linear()); + nn::ExprInstr in; + in.op = static_cast(op); + in.dst = 0; + in.a = nn::expr::input(0); + in.b = nn::expr::input(1); + s.instrs.push_back(in); + s.num_regs = 1; + return s; +} + +// 构造归约表达式:out = row_sum(input0)(归约边界) +nn::ExprSpec make_row_sum() +{ + nn::ExprSpec s; + s.views.push_back(nn::expr::linear()); + nn::ExprInstr in; + in.op = static_cast(nn::ExprOp::RowSum); + in.dst = 0; + in.a = nn::expr::input(0); + s.instrs.push_back(in); + s.num_regs = 1; + return s; +} + +// ── 测试 1:A→B 链融合正确性 ─────────────────────────────────────────── +// A: y = x * 2;B: z = y + 1 → 融合后 z = x*2 + 1,单 kernel +void test_chain_fuse() +{ + nn::CpuEngine eng; + const std::size_t rows = 3, cols = 4; + nn::ExprGraph g; + nn::Tensor x = make_tensor(rows, cols, 0.0f, 0.5f); + + const int nA = g.add_node(make_unary_const(nn::ExprOp::Mul, Scalar{2}), + std::vector{x}, rows, cols, false); + nn::Tensor ytag = nn::Tensor::cpu(rows, cols); + ytag.set_virtual_tag(static_cast(nA) + 1); + const int nB = g.add_node(make_unary_const(nn::ExprOp::Add, Scalar{1}), + std::vector{ytag}, rows, cols, false); + (void)nB; + + auto kernels = fuse_expr_graph(g); + CHECK(kernels.size() == 1, "A→B 链应融合为单个 kernel"); + if (kernels.size() != 1) return; + CHECK(kernels[0].members.size() == 2, "kernel 应包含 2 个节点"); + CHECK(kernels[0].tail == nB, "kernel tail 应为 B 节点"); + CHECK(kernels[0].inputs.size() == kernels[0].spec.views.size(), + "kernel inputs 与 views 一一对应"); + + // 融合 kernel 求值 + std::vector kinputs; + for (auto& in : kernels[0].inputs) kinputs.push_back(in.external); + auto fused = eng.eval_expr(kernels[0].spec, kinputs, rows, cols); + CHECK(static_cast(fused), "融合 kernel 求值成功"); + + // 参考:逐节点 + auto a_res = eng.eval_expr(make_unary_const(nn::ExprOp::Mul, Scalar{2}), + std::vector{x}, rows, cols); + auto b_res = eng.eval_expr(make_unary_const(nn::ExprOp::Add, Scalar{1}), + std::vector{*a_res}, rows, cols); + CHECK(static_cast(a_res) && static_cast(b_res), "参考求值成功"); + if (!fused || !a_res || !b_res) return; + + const Scalar err = max_abs_diff(fused->cpu_matrix(), b_res->cpu_matrix()); + CHECK(err < 1e-6f, "融合结果与逐节点一致"); + + // 手工验证数值:x*2+1 + bool ok = true; + for (std::size_t i = 0; i < rows * cols; ++i) + { + const Scalar expect = x.cpu_matrix().span()[i] * 2.0f + 1.0f; + if (std::fabs(fused->cpu_matrix().span()[i] - expect) > 1e-5f) ok = false; + } + CHECK(ok, "融合结果与手工参考 x*2+1 一致"); + std::printf("[OK] test_chain_fuse\n"); +} + +// ── 测试 2:三节点链融合 ─────────────────────────────────────────────── +// A: y=x*2;B: z=y+3;C: w=z-1 → 融合成单 kernel +void test_chain3_fuse() +{ + nn::CpuEngine eng; + const std::size_t rows = 2, cols = 5; + nn::ExprGraph g; + nn::Tensor x = make_tensor(rows, cols, 1.0f, 0.3f); + + auto add_node_chain = [&](nn::ExprOp op, Scalar c, + const nn::Tensor& input, + std::size_t r, std::size_t col) -> int + { + const int n = g.add_node(make_unary_const(op, c), + std::vector{input}, r, col, false); + return n; + }; + const int nA = add_node_chain(nn::ExprOp::Mul, Scalar{2}, x, rows, cols); + nn::Tensor yt = nn::Tensor::cpu(rows, cols); + yt.set_virtual_tag(static_cast(nA) + 1); + const int nB = add_node_chain(nn::ExprOp::Add, Scalar{3}, yt, rows, cols); + nn::Tensor zt = nn::Tensor::cpu(rows, cols); + zt.set_virtual_tag(static_cast(nB) + 1); + const int nC = add_node_chain(nn::ExprOp::Sub, Scalar{1}, zt, rows, cols); + (void)nC; + + auto kernels = fuse_expr_graph(g); + CHECK(kernels.size() == 1, "三节点链应融合为单个 kernel"); + if (kernels.size() != 1) return; + CHECK(kernels[0].members.size() == 3, "kernel 应包含 3 个节点"); + + std::vector kinputs; + for (auto& in : kernels[0].inputs) kinputs.push_back(in.external); + auto fused = eng.eval_expr(kernels[0].spec, kinputs, rows, cols); + CHECK(static_cast(fused), "融合 kernel 求值成功"); + + // 参考:逐节点 + auto a_res = eng.eval_expr(make_unary_const(nn::ExprOp::Mul, Scalar{2}), + std::vector{x}, rows, cols); + auto b_res = eng.eval_expr(make_unary_const(nn::ExprOp::Add, Scalar{3}), + std::vector{*a_res}, rows, cols); + auto c_res = eng.eval_expr(make_unary_const(nn::ExprOp::Sub, Scalar{1}), + std::vector{*b_res}, rows, cols); + CHECK(static_cast(c_res), "参考求值成功"); + if (!fused || !c_res) return; + + const Scalar err = max_abs_diff(fused->cpu_matrix(), c_res->cpu_matrix()); + CHECK(err < 1e-6f, "三节点融合结果与逐节点一致"); + std::printf("[OK] test_chain3_fuse\n"); +} + +// ── 测试 3:独立分支各自成 kernel ────────────────────────────────────── +// A: y = x*2(无消费者);B: z = w+1(外部输入 w,不依赖 A)→ 2 个 kernel +void test_independent() +{ + nn::CpuEngine eng; + const std::size_t rows = 3, cols = 3; + nn::ExprGraph g; + nn::Tensor x = make_tensor(rows, cols, 0.0f, 0.1f); + nn::Tensor w = make_tensor(rows, cols, 2.0f, 0.2f); + + (void)g.add_node(make_unary_const(nn::ExprOp::Mul, Scalar{2}), + std::vector{x}, rows, cols, false); + (void)g.add_node(make_unary_const(nn::ExprOp::Add, Scalar{1}), + std::vector{w}, rows, cols, false); + + auto kernels = fuse_expr_graph(g); + CHECK(kernels.size() == 2, "独立节点应各自成 kernel"); + if (kernels.size() != 2) return; + CHECK(kernels[0].members.size() == 1 && kernels[1].members.size() == 1, + "每个 kernel 只含 1 个节点"); + + // 两个 kernel 求值都正确 + std::vector k0in; + for (auto& in : kernels[0].inputs) k0in.push_back(in.external); + std::vector k1in; + for (auto& in : kernels[1].inputs) k1in.push_back(in.external); + auto r0 = eng.eval_expr(kernels[0].spec, k0in, rows, cols); + auto r1 = eng.eval_expr(kernels[1].spec, k1in, rows, cols); + CHECK(static_cast(r0) && static_cast(r1), "两个 kernel 求值成功"); + std::printf("[OK] test_independent\n"); +} + +// ── 测试 4:多消费者不融合(tail 被 A 和 B 之外节点消费) ───────────── +void test_multi_consumer() +{ + nn::CpuEngine eng; + const std::size_t rows = 2, cols = 4; + nn::ExprGraph g; + nn::Tensor x = make_tensor(rows, cols, 0.0f, 0.1f); + + // A: y = x*2(输出 y);B: z = y+1(消费 y);C: t = y-1(也消费 y) + const int nA = g.add_node(make_unary_const(nn::ExprOp::Mul, Scalar{2}), + std::vector{x}, rows, cols, false); + nn::Tensor yt = nn::Tensor::cpu(rows, cols); + yt.set_virtual_tag(static_cast(nA) + 1); + (void)g.add_node(make_unary_const(nn::ExprOp::Add, Scalar{1}), + std::vector{yt}, rows, cols, false); + nn::Tensor yt2 = nn::Tensor::cpu(rows, cols); + yt2.set_virtual_tag(static_cast(nA) + 1); + (void)g.add_node(make_unary_const(nn::ExprOp::Sub, Scalar{1}), + std::vector{yt2}, rows, cols, false); + + auto kernels = fuse_expr_graph(g); + // A 有两个消费者(B、C),不能融合 → 3 个独立 kernel + CHECK(kernels.size() == 3, "多消费者应各成 kernel(不融合)"); + std::printf("[OK] test_multi_consumer (kernels=%zu)\n", kernels.size()); +} + +// ── 测试 5:归约节点作为融合边界 ─────────────────────────────────────── +void test_reduce_boundary() +{ + nn::CpuEngine eng; + const std::size_t rows = 3, cols = 4; + nn::ExprGraph g; + nn::Tensor x = make_tensor(rows, cols, 0.0f, 0.1f); + + // A: y = row_sum(x)(归约,输出 (rows,1));B: z = y + 1(消费 y,但 B 的 + // 输入 y 形状是 (rows,1),经 RowBroadcast 视图)→ 不融合(A 是归约) + const int nA = g.add_node(make_row_sum(), + std::vector{x}, rows, cols, true); + nn::Tensor yt = nn::Tensor::cpu(rows, 1); + yt.set_virtual_tag(static_cast(nA) + 1); + nn::ExprSpec sb; + sb.views.push_back(nn::expr::row_broadcast()); // (rows,1) 按行广播 + nn::ExprInstr in; + in.op = static_cast(nn::ExprOp::Add); + in.dst = 0; + in.a = nn::expr::input(0); + in.b = nn::expr::cst(0); + sb.consts.push_back(Scalar{1}); + sb.instrs.push_back(in); + sb.num_regs = 1; + (void)g.add_node(sb, std::vector{yt}, rows, cols, false); + + auto kernels = fuse_expr_graph(g); + CHECK(kernels.size() == 2, "归约节点应作为融合边界(不拼接)"); + std::printf("[OK] test_reduce_boundary (kernels=%zu)\n", kernels.size()); +} + +// ── 测试 6:上限压力(拼接后超 EXPR_MAX_INSTRS 则放弃融合) ─────────── +// 用 abs 链(abs 不可代数化简、不可常量折叠)构造可控指令数 +nn::ExprSpec make_abs_chain(std::uint8_t n) +{ + nn::ExprSpec s; + s.views.push_back(nn::expr::linear()); + nn::ExprOperand prev = nn::expr::input(0); + for (std::uint8_t i = 0; i < n; ++i) + { + nn::ExprInstr in; + in.op = static_cast(nn::ExprOp::Abs); + in.dst = i; + in.a = prev; + s.instrs.push_back(in); + prev = nn::expr::reg(i); + } + s.num_regs = n; + return s; +} + +void test_limit_pressure() +{ + const std::size_t rows = 1, cols = 4; + nn::ExprGraph g; + nn::Tensor x = make_tensor(rows, cols, 0.0f, 0.1f); + + // A:40 条 abs 指令;B:1 条 → 拼接后 41 ≤ 64 → 融合 + const int nA = g.add_node(make_abs_chain(40), + std::vector{x}, rows, cols, false); + nn::Tensor yt = nn::Tensor::cpu(rows, cols); + yt.set_virtual_tag(static_cast(nA) + 1); + (void)g.add_node(make_unary_const(nn::ExprOp::Add, Scalar{1}), + std::vector{yt}, rows, cols, false); + + auto kernels = fuse_expr_graph(g); + CHECK(kernels.size() == 1, "40+1 条指令应在 EXPR_MAX_INSTRS=64 内 → 融合"); + + // A:60 条 abs;B:5 条 abs 链 → 拼接后 65 > 64 → 不融合 + nn::ExprGraph g2; + nn::Tensor x2 = make_tensor(rows, cols, 0.0f, 0.1f); + const int nC = g2.add_node(make_abs_chain(60), + std::vector{x2}, rows, cols, false); + nn::Tensor yt2 = nn::Tensor::cpu(rows, cols); + yt2.set_virtual_tag(static_cast(nC) + 1); + // B:5 条 abs 链(消费 A 输出) + nn::ExprSpec sb = make_abs_chain(5); + (void)g2.add_node(sb, std::vector{yt2}, rows, cols, false); + + auto kernels2 = fuse_expr_graph(g2); + CHECK(kernels2.size() == 2, "60+5 条指令超 EXPR_MAX_INSTRS → 保守放弃融合"); + std::printf("[OK] test_limit_pressure\n"); +} + +// ── 测试 7:确定性(同一图多次融合结果一致) ─────────────────────────── +void test_determinism() +{ + nn::CpuEngine eng; + const std::size_t rows = 2, cols = 4; + nn::Tensor x = make_tensor(rows, cols, 0.0f, 0.1f); + + auto build = [&](nn::ExprGraph& g) + { + const int nA = g.add_node(make_unary_const(nn::ExprOp::Mul, Scalar{2}), + std::vector{x}, rows, cols, false); + nn::Tensor yt = nn::Tensor::cpu(rows, cols); + yt.set_virtual_tag(static_cast(nA) + 1); + (void)g.add_node(make_unary_const(nn::ExprOp::Add, Scalar{1}), + std::vector{yt}, rows, cols, false); + }; + + nn::ExprGraph g1, g2; + build(g1); + build(g2); + auto k1 = fuse_expr_graph(g1); + auto k2 = fuse_expr_graph(g2); + CHECK(k1.size() == k2.size(), "确定性:kernel 数一致"); + if (k1.size() != k2.size()) return; + const std::string key1 = nn::expr_spec_key(k1[0].spec); + const std::string key2 = nn::expr_spec_key(k2[0].spec); + CHECK(key1 == key2, "确定性:融合 kernel 的 key 完全一致"); + CHECK(nn::expr_spec_equal(k1[0].spec, k2[0].spec), "确定性:spec 完全一致"); + std::printf("[OK] test_determinism\n"); +} + +// ── 测试 8:录制图 add_node 依赖识别 ─────────────────────────────────── +void test_dep_recognition() +{ + const std::size_t rows = 2, cols = 4; + nn::ExprGraph g; + nn::Tensor x = make_tensor(rows, cols, 0.0f, 0.1f); + + const int nA = g.add_node(make_unary_const(nn::ExprOp::Mul, Scalar{2}), + std::vector{x}, rows, cols, false); + // 占位带 tag → 依赖识别 + nn::Tensor yt = nn::Tensor::cpu(rows, cols); + yt.set_virtual_tag(static_cast(nA) + 1); + const int nB = g.add_node(make_unary_const(nn::ExprOp::Add, Scalar{1}), + std::vector{yt}, rows, cols, false); + CHECK(g.nodes[static_cast(nB)].dep_of_input[0] == nA, + "B 的输入 0 应识别为依赖节点 A"); + // 普通输入 → 外部 + const int nC = g.add_node(make_unary_const(nn::ExprOp::Sub, Scalar{1}), + std::vector{x}, rows, cols, false); + CHECK(g.nodes[static_cast(nC)].dep_of_input[0] == -1, + "C 的输入 0 应为外部输入"); + // 未知 tag → 外部 + nn::Tensor fake = nn::Tensor::cpu(rows, cols); + fake.set_virtual_tag(999); + const int nD = g.add_node(make_unary_const(nn::ExprOp::Abs, Scalar{0}), + std::vector{fake}, rows, cols, false); + CHECK(g.nodes[static_cast(nD)].dep_of_input[0] == -1, + "未知 tag 视为外部输入"); + std::printf("[OK] test_dep_recognition\n"); +} + +// ── 测试 9:融合 kernel 输入顺序确定 ─────────────────────────────────── +// A: y = x1 * x2(两个输入);B: z = y + x3 → 融合后 inputs = [x1, x2, x3] +void test_input_order() +{ + nn::CpuEngine eng; + const std::size_t rows = 2, cols = 4; + nn::ExprGraph g; + nn::Tensor x1 = make_tensor(rows, cols, 0.0f, 0.1f); + nn::Tensor x2 = make_tensor(rows, cols, 1.0f, 0.1f); + nn::Tensor x3 = make_tensor(rows, cols, 2.0f, 0.1f); + + // A: y = x1 * x2 + const int nA = g.add_node(make_binary(nn::ExprOp::Mul), + std::vector{x1, x2}, rows, cols, false); + // B: z = y + x3(输入 0 = y 依赖 A,输入 1 = x3 外部) + nn::Tensor yt = nn::Tensor::cpu(rows, cols); + yt.set_virtual_tag(static_cast(nA) + 1); + nn::ExprSpec sb; + sb.views.push_back(nn::expr::linear()); + sb.views.push_back(nn::expr::linear()); + nn::ExprInstr in; + in.op = static_cast(nn::ExprOp::Add); + in.dst = 0; + in.a = nn::expr::input(0); + in.b = nn::expr::input(1); + sb.instrs.push_back(in); + sb.num_regs = 1; + (void)g.add_node(sb, std::vector{yt, x3}, rows, cols, false); + + auto kernels = fuse_expr_graph(g); + CHECK(kernels.size() == 1, "A→B 链融合为单 kernel"); + if (kernels.size() != 1) return; + CHECK(kernels[0].inputs.size() == 3, "融合 kernel 应有 3 个外部输入"); + if (kernels[0].inputs.size() != 3) return; + // 顺序:A 的输入(x1, x2)在前,B 的非链输入(x3)在后 + CHECK(kernels[0].inputs[0].external.cpu_matrix().span()[0] == 0.0f, + "输入 0 = x1"); + CHECK(kernels[0].inputs[1].external.cpu_matrix().span()[0] == 1.0f, + "输入 1 = x2"); + CHECK(kernels[0].inputs[2].external.cpu_matrix().span()[0] == 2.0f, + "输入 2 = x3"); + + // 数值验证:z = x1*x2 + x3 + std::vector kinputs; + for (auto& in : kernels[0].inputs) kinputs.push_back(in.external); + auto fused = eng.eval_expr(kernels[0].spec, kinputs, rows, cols); + CHECK(static_cast(fused), "融合 kernel 求值成功"); + if (!fused) return; + bool ok = true; + for (std::size_t i = 0; i < rows * cols; ++i) + { + const Scalar expect = x1.cpu_matrix().span()[i] * x2.cpu_matrix().span()[i] + + x3.cpu_matrix().span()[i]; + if (std::fabs(fused->cpu_matrix().span()[i] - expect) > 1e-5f) ok = false; + } + CHECK(ok, "融合结果 = x1*x2 + x3"); + std::printf("[OK] test_input_order\n"); +} + +// ── 测试 10:IR-D emitter 抽象(一份 canonical IR → 多后端) ─────────── +void test_emitter() +{ + // 逐元素 spec:out = input0 * 2 + nn::ExprSpec s = make_unary_const(nn::ExprOp::Mul, Scalar{2}); + + auto glsl = nn::emitter_registry::make("glsl"); + auto cpu = nn::emitter_registry::make("cpu"); + auto none = nn::emitter_registry::make("nonexistent"); + CHECK(static_cast(glsl), "GlslEmitter 已注册可创建"); + CHECK(static_cast(cpu), "CpuEmitter 已注册可创建"); + CHECK(!none, "未知后端返回 nullptr"); + + if (glsl) + { + CHECK(std::string(glsl->name()) == "glsl", "GlslEmitter 后端名"); + const std::string g = glsl->generate("fused_x", s); + CHECK(!g.empty(), "GlslEmitter 生成逐元素 GLSL 成功"); + CHECK(g.find("#version 450") != std::string::npos, "GLSL 含版本头"); + } + if (cpu) + { + CHECK(std::string(cpu->name()) == "cpu", "CpuEmitter 后端名"); + const std::string c = cpu->generate("cpu_x", s); + CHECK(!c.empty(), "CpuEmitter 生成 C++ 代码成功"); + CHECK(c.find("inline void cpu_x") != std::string::npos, "C++ 代码含函数签名"); + CHECK(c.find("std::max") == std::string::npos, "逐元素含基本运算"); + // 归约:CpuEmitter 暂不支持 → 空串;GlslEmitter 支持 + nn::ExprSpec rs = make_row_sum(); + CHECK(cpu->generate_reduce("cpu_r", rs).empty(), "CpuEmitter 归约返回空(不支持)"); + if (glsl) + CHECK(!glsl->generate_reduce("fused_r", rs).empty(), "GlslEmitter 归约生成成功"); + } + std::printf("[OK] test_emitter\n"); +} + +} // namespace + +int main() +{ + test_chain_fuse(); + test_chain3_fuse(); + test_independent(); + test_multi_consumer(); + test_reduce_boundary(); + test_limit_pressure(); + test_determinism(); + test_dep_recognition(); + test_input_order(); + test_emitter(); + + if (g_fail == 0) + { + std::printf("expr_graph_test: ALL PASS\n"); + return 0; + } + std::printf("expr_graph_test: %d FAILED\n", g_fail); + return 1; +} diff --git a/tools/gen_fused.cpp b/tools/gen_fused.cpp index 7d957e4d..c8c59c8f 100644 --- a/tools/gen_fused.cpp +++ b/tools/gen_fused.cpp @@ -2,14 +2,20 @@ // gen_fused.cpp — AOT 算子融合:构建期合成(读 scan_exprs 的 bin) // // 读取 scan_exprs dump 的 expr_specs.bin(表达式结构集合),对每条 -// 用 glsl_gen.hpp 展开为单个融合 .comp,再经 glslc 编译成 SPIR-V,最后 -// 内联进单个生成头 fused_registry.hpp(key → {ExprSpec, SPIR-V})。 +// 用 emitter 抽象(IR-D:expr_emitter.hpp)展开为单个融合 kernel 源码, +// 再经 glslc 编译成 SPIR-V,最后内联进单个生成头 fused_registry.hpp +// (key → {ExprSpec, SPIR-V})。 +// +// IR-D 落地:本工具经 nn::emitter_registry 选择后端(默认 "glsl" = +// GlslEmitter),不再直接绑定 GLSL 生成函数——同一份 canonical IR 可由 +// CpuEmitter 等其它后端展开(--list-backends 查看;CpuEmitter 生成 C++ +// 直线代码,供调试/交叉验证,不产出 SPIR-V)。 // // 表达式**文本只出现在 Layer**;本工具只消费折叠后的结构(派生物)。 // 产物 fused_registry.hpp 供运行时(GpuEngine::eval_expr / vk_backend) // 按 expr_spec_key 精确匹配 dispatch——闭合世界,未命中硬报错。 // -// 用法: gen_fused +// 用法: gen_fused [--list-backends] // 产物: /fused_registry.hpp(+ 调试用 .comp/.spv) // ─────────────────────────────────────────────────────────────────────────── @@ -24,7 +30,9 @@ #include "expr_spec.hpp" #include "expr_registry.hpp" -#include "glsl_gen.hpp" +#include "expr_emitter.hpp" // IR-D:emitter 抽象(后端选择) +#include "glsl_gen.hpp" // 注册 GlslEmitter(默认后端) +#include "cpu_emitter.hpp" // 注册 CpuEmitter(IR-D 第二后端,供 --list-backends) namespace { @@ -89,9 +97,19 @@ namespace int main(int argc, char* argv[]) { + // --list-backends:列出可用 emitter 后端(IR-D 多后端验证) + for (int i = 1; i < argc; ++i) + if (std::string(argv[i]) == "--list-backends") + { + std::printf("[gen] 可用 emitter 后端(IR-D):\n"); + for (const auto& n : nn::emitter_registry::names()) + std::printf(" - %s\n", n.c_str()); + return 0; + } + if (argc < 4) { - std::fprintf(stderr, "用法: gen_fused \n"); + std::fprintf(stderr, "用法: gen_fused [--list-backends]\n"); return 2; } const std::string out_dir = argv[1]; @@ -148,10 +166,18 @@ int main(int argc, char* argv[]) const std::string comp_path = out_dir + "/fused_" + key + ".comp"; const std::string spv_path = out_dir + "/fused_" + key + ".spv"; + // 经 emitter 抽象(IR-D)生成 kernel 源码:默认 GlslEmitter。 + // 同一份 canonical IR 可由其它后端(CpuEmitter)展开(--list-backends)。 + auto emitter = nn::emitter_registry::make("glsl"); + if (!emitter) + { + std::fprintf(stderr, "[FAIL] 无法创建 GLSL emitter(IR-D 注册表异常)\n"); + return 1; + } // 含归约(raxis >= 0)→ 归约 kernel(workgroup 级共享内存归约);否则逐元素 const std::string glsl = (raxis >= 0) - ? nn::generate_glsl_reduce("fused_" + key, spec) - : nn::generate_glsl("fused_" + key, spec); + ? emitter->generate_reduce("fused_" + key, spec) + : emitter->generate("fused_" + key, spec); if (glsl.empty()) { std::fprintf(stderr, "[FAIL] 生成 %s 失败(归约结构不支持?)\n", key.c_str()); diff --git a/tools/scan_exprs.cpp b/tools/scan_exprs.cpp index 6cc7ea57..c981b64e 100644 --- a/tools/scan_exprs.cpp +++ b/tools/scan_exprs.cpp @@ -104,6 +104,20 @@ int main(int argc, char* argv[]) (void)ln.backward(engine, grad); } + // ── FusedChainLayer(IR-C:begin_expr/end_expr 图 IR 链式融合)──────── + // forward 的三个逐元素表达式在 end_expr 时融合成单个 kernel(t/u 内联为 + // 寄存器)。必须 dry-run 覆盖本路径:GPU 运行时 begin_expr/end_expr 融合 + // 出的复合 spec 才能在 fused_registry 中命中(闭合世界两端一致)。 + // backward 为独立表达式(非录制段),单独登记。 + { + const std::size_t F = 8, B = 5; + nn::FusedChainLayer chain(engine, F); + nn::Tensor input = nn::Tensor::cpu(F, B); + (void)chain.forward(engine, input); + nn::Tensor grad = nn::Tensor::cpu(F, B); + (void)chain.backward(engine, grad); + } + auto& reg = nn::fused::global_registry(); if (!nn::fused::write_registry(out_path, reg)) { From a86f9f039c948bfacc96e1f44205dcb8afca7792 Mon Sep 17 00:00:00 2001 From: EthanPeng-2048 Date: Mon, 24 Aug 2026 21:40:34 +0800 Subject: [PATCH 3/9] =?UTF-8?q?=E6=9C=89=E5=85=B3mlp=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E8=AE=AD=E7=BB=83=E5=B4=A9=E6=BA=83=E7=9A=84=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/neuralnet.cpp/compute_loss.hpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/include/neuralnet.cpp/compute_loss.hpp b/include/neuralnet.cpp/compute_loss.hpp index 5d567321..fed2b451 100644 --- a/include/neuralnet.cpp/compute_loss.hpp +++ b/include/neuralnet.cpp/compute_loss.hpp @@ -166,8 +166,14 @@ class CrossEntropyLoss final : public Loss // col_sum ≥ 1(因 max 元素 shifted=0 → exp=1),故 log(col_sum) 有限 auto log_col_sum = engine.elementwise_unary(UnaryOp::Log, *col_sum); if (!log_col_sum) return std::unexpected(log_col_sum.error()); - auto log_softmax = engine.elementwise_binary(BinaryOp::Sub, *shifted, *log_col_sum); - if (!log_softmax) return std::unexpected(log_softmax.error()); + + // log_softmax = shifted - log(col_sum):这是**列广播**(每个元素减去 + // 对应列的 log(col_sum)),不能用 elementwise_binary —— log_col_sum + // 形状为 (1, batch) 而 shifted 为 (classes, batch),两引擎均要求形状 + // 完全一致会报 shape mismatch。此处 shifted 已不再使用,可就地广播。 + r = engine.broadcast_col_inplace(*shifted, *log_col_sum, BinaryOp::Sub); + if (!r) return std::unexpected(r.error()); + auto log_softmax = std::move(shifted); return DenseSoftmax{/*softmax=*/std::move(*exp_shift), /*log_softmax=*/std::move(*log_softmax)}; From dfc162c3f43b0c6e886386515b3fe500da404264 Mon Sep 17 00:00:00 2001 From: EthanPeng-2048 Date: Mon, 24 Aug 2026 21:50:45 +0800 Subject: [PATCH 4/9] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86=E6=89=93?= =?UTF-8?q?=E5=8D=B0MLP=E6=A8=A1=E5=9E=8B=E6=9E=B6=E6=9E=84=E6=97=B6?= =?UTF-8?q?=E5=B0=91=E5=8A=A0=E4=B8=80=E4=B8=AA=E6=BF=80=E6=B4=BB=E5=B1=82?= =?UTF-8?q?=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 6 ++++++ src/mnist_train.cpp | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c790b1f6..72d7d11f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -315,6 +315,10 @@ if(Vulkan_FOUND) target_link_libraries(scan_exprs PRIVATE nn_core Vulkan::Vulkan) add_custom_command( OUTPUT ${NN_EXPR_SCAN_BIN} + # scan_exprs 是原生可执行文件,直接写文件、不会自建目录。 + # Ninja 会自动创建自定义命令的输出目录,但 Makefiles 生成器不会, + # 全新构建时 generated/ 尚未存在会写入失败(见 08-pitfalls)。 + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/generated COMMAND scan_exprs ${NN_EXPR_SCAN_BIN} DEPENDS scan_exprs ${SCAN_SRC} COMMENT "[GPU] Scanning Layer expressions (scan_exprs) → expr_specs.bin" @@ -327,6 +331,8 @@ if(Vulkan_FOUND) target_link_libraries(gen_fused PRIVATE nn_core) add_custom_command( OUTPUT ${NN_FUSED_REGISTRY_HPP} + # 同 scan_exprs:gen_fused 也是原生可执行文件,需先确保输出目录存在 + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/generated COMMAND gen_fused ${CMAKE_BINARY_DIR}/generated ${GLSLC} ${NN_EXPR_SCAN_BIN} DEPENDS gen_fused ${FUSED_GEN_SRC} ${NN_EXPR_SCAN_BIN} COMMENT "[GPU] Synthesizing fused shaders (gen_fused) → fused_registry.hpp" diff --git a/src/mnist_train.cpp b/src/mnist_train.cpp index 74c84620..0ac39ecf 100644 --- a/src/mnist_train.cpp +++ b/src/mnist_train.cpp @@ -509,7 +509,10 @@ int main(int argc, char *argv[]) for (std::size_t i = 0; i < dims.size(); ++i) { std::cout << dims[i]; - if (i < dims.size() - 2) + // norm+GeLU 挂在隐藏层 Linear 的输出维度上:dims[1]..dims[size-2]。 + // 首元素 dims[0] 是输入、末元素 dims[size-1] 是 logits 输出, + // 两者都不挂 norm/激活(CE 损失需要原始 logits)。 + if (i > 0 && i < dims.size() - 1) std::cout << "(" << norm_name << "+GeLU)"; if (i < dims.size() - 1) std::cout << " -> "; From ca1f31c42bb91c4cbdd8ffa814aed9a6cf8f69aa Mon Sep 17 00:00:00 2001 From: EthanPeng-2048 Date: Tue, 25 Aug 2026 01:08:26 +0800 Subject: [PATCH 5/9] =?UTF-8?q?IR=20vec4+=E5=A4=A7=E9=87=8Fglsl=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cli_controllers.py | 15 +- gui.py | 32 +- include/neuralnet.cpp/backend/vk_backend.hpp | 9 +- include/neuralnet.cpp/glsl_gen.hpp | 317 +- references/nir_opt_vectorize.c | 754 ++++ references/nir_opt_vectorize_io.c | 671 ++++ references/nir_opt_vectorize_io_vars.c | 636 ++++ references/writer/BUILD.bazel | 140 + references/writer/BUILD.cfg | 3 + references/writer/BUILD.cmake | 198 ++ references/writer/BUILD.gn | 176 + references/writer/ast_printer/BUILD.bazel | 167 + references/writer/ast_printer/BUILD.cfg | 3 + references/writer/ast_printer/BUILD.cmake | 169 + references/writer/ast_printer/BUILD.gn | 165 + .../writer/ast_printer/array_accessor_test.cc | 57 + references/writer/ast_printer/assign_test.cc | 53 + references/writer/ast_printer/ast_printer.cc | 3028 +++++++++++++++++ references/writer/ast_printer/ast_printer.h | 500 +++ .../writer/ast_printer/ast_printer_test.cc | 147 + references/writer/ast_printer/binary_test.cc | 901 +++++ references/writer/ast_printer/bitcast_test.cc | 204 ++ references/writer/ast_printer/block_test.cc | 53 + references/writer/ast_printer/break_test.cc | 50 + references/writer/ast_printer/builtin_test.cc | 1680 +++++++++ .../ast_printer/builtin_texture_test.cc | 318 ++ references/writer/ast_printer/call_test.cc | 98 + references/writer/ast_printer/case_test.cc | 108 + references/writer/ast_printer/cast_test.cc | 66 + .../writer/ast_printer/constructor_test.cc | 437 +++ .../writer/ast_printer/continue_test.cc | 57 + references/writer/ast_printer/discard_test.cc | 51 + .../writer/ast_printer/function_test.cc | 977 ++++++ references/writer/ast_printer/helper_test.h | 140 + .../writer/ast_printer/identifier_test.cc | 53 + references/writer/ast_printer/if_test.cc | 137 + references/writer/ast_printer/import_test.cc | 296 ++ references/writer/ast_printer/loop_test.cc | 493 +++ .../ast_printer/member_accessor_test.cc | 917 +++++ .../ast_printer/module_constant_test.cc | 349 ++ references/writer/ast_printer/return_test.cc | 62 + .../writer/ast_printer/sanitizer_test.cc | 339 ++ .../writer/ast_printer/storage_buffer_test.cc | 108 + references/writer/ast_printer/switch_test.cc | 92 + references/writer/ast_printer/type_test.cc | 594 ++++ .../writer/ast_printer/unary_op_test.cc | 120 + .../writer/ast_printer/uniform_buffer_test.cc | 82 + .../variable_decl_statement_test.cc | 540 +++ .../writer/ast_printer/workgroup_var_test.cc | 74 + references/writer/ast_raise/BUILD.bazel | 183 + references/writer/ast_raise/BUILD.cfg | 6 + references/writer/ast_raise/BUILD.cmake | 162 + references/writer/ast_raise/BUILD.gn | 151 + .../writer/ast_raise/combine_samplers.cc | 455 +++ .../writer/ast_raise/combine_samplers.h | 86 + .../writer/ast_raise/combine_samplers_test.cc | 1218 +++++++ references/writer/ast_raise/pad_structs.cc | 191 ++ references/writer/ast_raise/pad_structs.h | 56 + .../writer/ast_raise/pad_structs_test.cc | 757 +++++ .../writer/ast_raise/texture_1d_to_2d.cc | 204 ++ .../writer/ast_raise/texture_1d_to_2d.h | 56 + .../writer/ast_raise/texture_1d_to_2d_test.cc | 318 ++ .../texture_builtins_from_uniform.cc | 426 +++ .../ast_raise/texture_builtins_from_uniform.h | 115 + .../texture_builtins_from_uniform_test.cc | 575 ++++ references/writer/common/BUILD.bazel | 125 + references/writer/common/BUILD.cfg | 3 + references/writer/common/BUILD.cmake | 126 + references/writer/common/BUILD.gn | 120 + references/writer/common/options.cc | 38 + references/writer/common/options.h | 190 ++ references/writer/common/options_test.cc | 40 + references/writer/common/printer_support.cc | 68 + references/writer/common/printer_support.h | 54 + references/writer/common/version.h | 83 + references/writer/common/version_test.cc | 40 + references/writer/output.cc | 38 + references/writer/output.h | 56 + references/writer/printer/BUILD.bazel | 121 + references/writer/printer/BUILD.cfg | 3 + references/writer/printer/BUILD.cmake | 123 + references/writer/printer/BUILD.gn | 117 + references/writer/printer/constant_test.cc | 41 + references/writer/printer/function_test.cc | 45 + references/writer/printer/helper_test.h | 101 + references/writer/printer/printer.cc | 176 + references/writer/printer/printer.h | 52 + references/writer/raise/BUILD.bazel | 62 + references/writer/raise/BUILD.cmake | 57 + references/writer/raise/BUILD.gn | 58 + references/writer/raise/raise.cc | 44 + references/writer/raise/raise.h | 47 + references/writer/writer.cc | 83 + references/writer/writer.h | 72 + references/writer/writer_ast_fuzz.cc | 81 + references/writer/writer_bench.cc | 65 + src/expr_fuse_test.cpp | 43 + tools/gen_fused.cpp | 9 +- 98 files changed, 23916 insertions(+), 60 deletions(-) create mode 100644 references/nir_opt_vectorize.c create mode 100644 references/nir_opt_vectorize_io.c create mode 100644 references/nir_opt_vectorize_io_vars.c create mode 100644 references/writer/BUILD.bazel create mode 100644 references/writer/BUILD.cfg create mode 100644 references/writer/BUILD.cmake create mode 100644 references/writer/BUILD.gn create mode 100644 references/writer/ast_printer/BUILD.bazel create mode 100644 references/writer/ast_printer/BUILD.cfg create mode 100644 references/writer/ast_printer/BUILD.cmake create mode 100644 references/writer/ast_printer/BUILD.gn create mode 100644 references/writer/ast_printer/array_accessor_test.cc create mode 100644 references/writer/ast_printer/assign_test.cc create mode 100644 references/writer/ast_printer/ast_printer.cc create mode 100644 references/writer/ast_printer/ast_printer.h create mode 100644 references/writer/ast_printer/ast_printer_test.cc create mode 100644 references/writer/ast_printer/binary_test.cc create mode 100644 references/writer/ast_printer/bitcast_test.cc create mode 100644 references/writer/ast_printer/block_test.cc create mode 100644 references/writer/ast_printer/break_test.cc create mode 100644 references/writer/ast_printer/builtin_test.cc create mode 100644 references/writer/ast_printer/builtin_texture_test.cc create mode 100644 references/writer/ast_printer/call_test.cc create mode 100644 references/writer/ast_printer/case_test.cc create mode 100644 references/writer/ast_printer/cast_test.cc create mode 100644 references/writer/ast_printer/constructor_test.cc create mode 100644 references/writer/ast_printer/continue_test.cc create mode 100644 references/writer/ast_printer/discard_test.cc create mode 100644 references/writer/ast_printer/function_test.cc create mode 100644 references/writer/ast_printer/helper_test.h create mode 100644 references/writer/ast_printer/identifier_test.cc create mode 100644 references/writer/ast_printer/if_test.cc create mode 100644 references/writer/ast_printer/import_test.cc create mode 100644 references/writer/ast_printer/loop_test.cc create mode 100644 references/writer/ast_printer/member_accessor_test.cc create mode 100644 references/writer/ast_printer/module_constant_test.cc create mode 100644 references/writer/ast_printer/return_test.cc create mode 100644 references/writer/ast_printer/sanitizer_test.cc create mode 100644 references/writer/ast_printer/storage_buffer_test.cc create mode 100644 references/writer/ast_printer/switch_test.cc create mode 100644 references/writer/ast_printer/type_test.cc create mode 100644 references/writer/ast_printer/unary_op_test.cc create mode 100644 references/writer/ast_printer/uniform_buffer_test.cc create mode 100644 references/writer/ast_printer/variable_decl_statement_test.cc create mode 100644 references/writer/ast_printer/workgroup_var_test.cc create mode 100644 references/writer/ast_raise/BUILD.bazel create mode 100644 references/writer/ast_raise/BUILD.cfg create mode 100644 references/writer/ast_raise/BUILD.cmake create mode 100644 references/writer/ast_raise/BUILD.gn create mode 100644 references/writer/ast_raise/combine_samplers.cc create mode 100644 references/writer/ast_raise/combine_samplers.h create mode 100644 references/writer/ast_raise/combine_samplers_test.cc create mode 100644 references/writer/ast_raise/pad_structs.cc create mode 100644 references/writer/ast_raise/pad_structs.h create mode 100644 references/writer/ast_raise/pad_structs_test.cc create mode 100644 references/writer/ast_raise/texture_1d_to_2d.cc create mode 100644 references/writer/ast_raise/texture_1d_to_2d.h create mode 100644 references/writer/ast_raise/texture_1d_to_2d_test.cc create mode 100644 references/writer/ast_raise/texture_builtins_from_uniform.cc create mode 100644 references/writer/ast_raise/texture_builtins_from_uniform.h create mode 100644 references/writer/ast_raise/texture_builtins_from_uniform_test.cc create mode 100644 references/writer/common/BUILD.bazel create mode 100644 references/writer/common/BUILD.cfg create mode 100644 references/writer/common/BUILD.cmake create mode 100644 references/writer/common/BUILD.gn create mode 100644 references/writer/common/options.cc create mode 100644 references/writer/common/options.h create mode 100644 references/writer/common/options_test.cc create mode 100644 references/writer/common/printer_support.cc create mode 100644 references/writer/common/printer_support.h create mode 100644 references/writer/common/version.h create mode 100644 references/writer/common/version_test.cc create mode 100644 references/writer/output.cc create mode 100644 references/writer/output.h create mode 100644 references/writer/printer/BUILD.bazel create mode 100644 references/writer/printer/BUILD.cfg create mode 100644 references/writer/printer/BUILD.cmake create mode 100644 references/writer/printer/BUILD.gn create mode 100644 references/writer/printer/constant_test.cc create mode 100644 references/writer/printer/function_test.cc create mode 100644 references/writer/printer/helper_test.h create mode 100644 references/writer/printer/printer.cc create mode 100644 references/writer/printer/printer.h create mode 100644 references/writer/raise/BUILD.bazel create mode 100644 references/writer/raise/BUILD.cmake create mode 100644 references/writer/raise/BUILD.gn create mode 100644 references/writer/raise/raise.cc create mode 100644 references/writer/raise/raise.h create mode 100644 references/writer/writer.cc create mode 100644 references/writer/writer.h create mode 100644 references/writer/writer_ast_fuzz.cc create mode 100644 references/writer/writer_bench.cc diff --git a/cli_controllers.py b/cli_controllers.py index d196e712..6cb74789 100644 --- a/cli_controllers.py +++ b/cli_controllers.py @@ -383,7 +383,7 @@ class MnistTrainController(CLIController): MNIST训练CLI控制器 封装mnist_train.exe,支持以下功能: - - 架构选择:MLP或Transformer + - 架构选择:MLP、Transformer 或 CNN - 训练参数:轮数、学习率、批大小、优化器等 - 设备选择:CPU、GPU(Vulkan)、CUDA - 模型管理:保存、恢复 @@ -470,6 +470,19 @@ def _format_args(self, **kwargs) -> List[str]: if "eval_samples" in kwargs: args.extend(["--eval-samples", self._format_arg_value(kwargs["eval_samples"])]) + # CNN专用参数 (LeNet-5 风格) + if "cnn_channels" in kwargs: + args.extend(["--cnn-channels", self._format_arg_value(kwargs["cnn_channels"])]) + + if "cnn_kernels" in kwargs: + args.extend(["--cnn-kernels", self._format_arg_value(kwargs["cnn_kernels"])]) + + if "cnn_pool" in kwargs: + args.extend(["--cnn-pool", self._format_arg_value(kwargs["cnn_pool"])]) + + if "cnn_fc" in kwargs: + args.extend(["--cnn-fc", self._format_arg_value(kwargs["cnn_fc"])]) + # 振荡检测 if "osc_guard" in kwargs: args.extend(["--osc-guard", self._format_arg_value(kwargs["osc_guard"])]) diff --git a/gui.py b/gui.py index e7029db0..4f206603 100644 --- a/gui.py +++ b/gui.py @@ -48,7 +48,7 @@ BUILD_DIR = PROJECT_DIR / "build" # ---------- 枚举选项 ---------- -ARCH_OPTIONS = ["mlp", "transformer"] +ARCH_OPTIONS = ["mlp", "transformer", "cnn"] OPTIMIZER_OPTIONS = ["sgd", "sgd_momentum", "adam", "adamw", "muon"] ENGINE_OPTIONS = ["CPU", "GPU (Vulkan)", "CUDA"] LR_SCHEDULE_OPTIONS = ["fixed", "cosine"] @@ -788,6 +788,15 @@ def __init__(self, master): self._tf_widgets = [self.tf_sep_label, self.d_model, self.num_heads, self.num_layers, self.d_ff, self.patch_size, self.eval_samples] + # --- CNN 专用参数 (LeNet-5 风格) --- + self.cnn_sep_label = _make_label(p, "── CNN 参数 ──", r); r += 1 + self.cnn_channels = _make_entry_row(p, "cnn_channels", r, "6,16"); r += 1 + self.cnn_kernels = _make_entry_row(p, "cnn_kernels", r, "5,5"); r += 1 + self.cnn_pool = _make_entry_row(p, "cnn_pool", r, "2"); r += 1 + self.cnn_fc = _make_entry_row(p, "cnn_fc", r, "120,10"); r += 1 + self._cnn_widgets = [self.cnn_sep_label, self.cnn_channels, self.cnn_kernels, + self.cnn_pool, self.cnn_fc] + # --- LR 调度相关控件(fixed 时隐藏) --- self._lr_schedule_widgets = [self.min_lr, self.warmup_epochs] @@ -815,14 +824,17 @@ def build_pack_config(self): "device": dev, "data": data, "hyperparameters": args} def _on_arch_change(self, *args): - """根据架构选择显示 MLP 或 Transformer 参数""" - is_mlp = self.arch.get() == "mlp" + """根据架构选择显示 MLP / Transformer / CNN 参数""" + arch = self.arch.get() for w in self._mlp_widgets: w = getattr(w, "widget", w) - w.grid() if is_mlp else w.grid_remove() + w.grid() if arch == "mlp" else w.grid_remove() for w in self._tf_widgets: w = getattr(w, "widget", w) - w.grid() if not is_mlp else w.grid_remove() + w.grid() if arch == "transformer" else w.grid_remove() + for w in self._cnn_widgets: + w = getattr(w, "widget", w) + w.grid() if arch == "cnn" else w.grid_remove() def _on_lr_schedule_change(self, *args): """fixed 调度下隐藏 min_lr / warmup""" @@ -854,16 +866,22 @@ def collect_args(self): if self.min_lr.get(): args["min_lr"] = float(self.min_lr.get()) if self.warmup_epochs.get(): args["warmup_epochs"] = int(self.warmup_epochs.get()) # 架构特定参数 - if self.arch.get() == "mlp": + arch = self.arch.get() + if arch == "mlp": if self.layer_dims.get(): args["layer_dims"] = self.layer_dims.get() args["norm"] = self.norm.get() - else: + elif arch == "transformer": if self.d_model.get(): args["d_model"] = int(self.d_model.get()) if self.num_heads.get(): args["num_heads"] = int(self.num_heads.get()) if self.num_layers.get(): args["num_layers"] = int(self.num_layers.get()) if self.d_ff.get(): args["d_ff"] = int(self.d_ff.get()) if self.patch_size.get(): args["patch_size"] = int(self.patch_size.get()) if self.eval_samples.get(): args["eval_samples"] = int(self.eval_samples.get()) + elif arch == "cnn": + if self.cnn_channels.get(): args["cnn_channels"] = self.cnn_channels.get() + if self.cnn_kernels.get(): args["cnn_kernels"] = self.cnn_kernels.get() + if self.cnn_pool.get(): args["cnn_pool"] = int(self.cnn_pool.get()) + if self.cnn_fc.get(): args["cnn_fc"] = self.cnn_fc.get() return args diff --git a/include/neuralnet.cpp/backend/vk_backend.hpp b/include/neuralnet.cpp/backend/vk_backend.hpp index 2a527b61..0c6e34d9 100644 --- a/include/neuralnet.cpp/backend/vk_backend.hpp +++ b/include/neuralnet.cpp/backend/vk_backend.hpp @@ -766,6 +766,8 @@ class GpuBackend std::unordered_map fused_reduce_axis_; // 运行时视图参数个数(RowMod/RotateHalf 的 vp push constant 槽数) std::unordered_map fused_view_param_counts_; + // 每线程处理元素数(1=标量, 4=vec4;决定 dispatch 宽度缩放) + std::unordered_map fused_vec_width_; std::unique_ptr memory_pool_; std::unique_ptr staging_ring_; @@ -1312,6 +1314,7 @@ class GpuBackend fused_pipelines_.emplace(fs.key, std::move(*fp_r)); fused_reduce_axis_.emplace(fs.key, fs.reduce_axis); fused_view_param_counts_.emplace(fs.key, fs.view_param_count); + fused_vec_width_.emplace(fs.key, fs.vec_width); } } #endif @@ -3170,11 +3173,13 @@ class GpuBackend vkCmdPushConstants(cmd, pipeline.pipeline_layout(), VK_SHADER_STAGE_COMPUTE_BIT, 0, static_cast(pc.size()), pc.data()); - // dispatch:逐元素 = ceil(count/256);行归约 = rows 个工作组;列归约 = cols 个工作组 + // dispatch:逐元素 = ceil(count/(256*vec_width));行归约 = rows 个工作组;列归约 = cols 个工作组 + const std::uint32_t vec_width = fused_vec_width_.count(shader_name) + ? fused_vec_width_.at(shader_name) : 1u; const std::uint32_t wg_count = (raxis == 0) ? static_cast(rows) : (raxis == 1) ? static_cast(cols) - : (count + 255) / 256; + : (count + 256u * vec_width - 1u) / (256u * vec_width); vkCmdDispatch(cmd, wg_count, 1, 1); record_output_barrier(cmd, output.buffer().impl()); diff --git a/include/neuralnet.cpp/glsl_gen.hpp b/include/neuralnet.cpp/glsl_gen.hpp index d144b529..f81fd062 100644 --- a/include/neuralnet.cpp/glsl_gen.hpp +++ b/include/neuralnet.cpp/glsl_gen.hpp @@ -111,9 +111,41 @@ inline void glsl_view_read(std::ostringstream& os, } } +// ── 视图是否使用行/列索引(用于按需发射 row/col,省去每元素整数除法/取模)── +// Linear 用扁平索引、RowBroadcast 用 row、ColBroadcast 用 col、 +// RowMod/RotateHalf 用 row+col(索引映射逐元素变化,见 glsl_view_read)。 +inline bool glsl_view_uses_row(ExprViewKind k) +{ + return k != ExprViewKind::Linear && k != ExprViewKind::ColBroadcast; +} +inline bool glsl_view_uses_col(ExprViewKind k) +{ + return k != ExprViewKind::Linear && k != ExprViewKind::RowBroadcast; +} + +// ── vec4 向量化资格 ─────────────────────────────────────────────────────── +// 仅当全部视图 ∈ {Linear, RowBroadcast, ColBroadcast} 时才可把 4 个相邻元素 +// 打包成 vec4(RowMod/RotateHalf 的索引映射逐通道变化,v1 不向量化)。 +// 注意:cols 是运行时 push constant,故向量化路径用运行时 `cols%4==0` 守卫, +// 任意形状仍走同 kernel 内的标量回退——正确性不依赖编译期形状。 +inline bool glsl_vec4_eligible(const ExprSpec& spec) +{ + for (const auto& v : spec.views) + { + const auto k = static_cast(v.kind); + if (k != ExprViewKind::Linear && k != ExprViewKind::RowBroadcast && + k != ExprViewKind::ColBroadcast) + return false; + } + return true; +} + // ── 生成器主入口:ExprSpec → GLSL 源码 ──────────────────────────────────── // 生成的 shader 绑定:输入 buffer binding=0..N-1,输出 binding=N; // push constants: uint count, uint cols, [float c0..](视图索引需要 cols)。 +// 逐元素表达式若 glsl_vec4_eligible,发射 vec4 kernel:每线程处理 4 个相邻元素 +// (vec4 加载/运算/存储),dispatch 宽度 4(run_fused_gpu 按 vec_width 缩放); +// 运行时 cols%4!=0 或尾部回退到同 kernel 内标量循环。其余表达式发纯标量 kernel。 inline std::string generate_glsl(const std::string& name, const ExprSpec& spec) { const std::size_t n_inputs = spec.views.size(); @@ -141,28 +173,162 @@ inline std::string generate_glsl(const std::string& name, const ExprSpec& spec) L << " float c" << i << ";\n"; L << "};\n\n"; + const bool vec4_ok = glsl_vec4_eligible(spec); + const bool need_row = [&]{ + for (const auto& v : spec.views) + if (glsl_view_uses_row(static_cast(v.kind))) return true; + return false; }(); + const bool need_col = [&]{ + for (const auto& v : spec.views) + if (glsl_view_uses_col(static_cast(v.kind))) return true; + return false; }(); + const std::uint32_t last_dst = spec.instrs.back().dst; + + // ── 标量操作数求值 ── + const auto operand = [&](const ExprOperand& op) -> std::string + { + switch (op.kind) + { + default: + case static_cast(ExprOperandKind::Input): + return "v" + std::to_string(op.idx); + case static_cast(ExprOperandKind::Reg): + case static_cast(ExprOperandKind::Fanout): // Fanout 语义同 Reg + return "r" + std::to_string(op.idx); + case static_cast(ExprOperandKind::Const): + return "c" + std::to_string(op.idx); + } + }; + + // 发射一段标量指令链(输入读取 + 寄存器声明 + 指令展开,直线代码; + // 寄存器先声明、后赋值——兼容 IR-B liveness 复用同号寄存器)。 + // idx_var:扁平元素索引;row_var/col_var:行/列索引表达式(可为 "i/cols" 等)。 + const auto emit_scalar_chain = [&](std::ostringstream& o, + const std::string& indent, + const std::string& idx_var, + const std::string& row_var, + const std::string& col_var) + { + // 输入读取变量(每输入缓存一次);vp 槽 = 此前运行时参数视图个数 + for (std::size_t i = 0; i < n_inputs; ++i) + { + std::uint32_t vp = 0; + for (std::size_t j = 0; j < i; ++j) + if (expr_view_has_runtime_param( + static_cast(spec.views[j].kind))) ++vp; + o << indent << "const float v" << i << " = "; + glsl_view_read(o, spec.views[i], static_cast(i), + idx_var, row_var, col_var, vp); + o << ";\n"; + } + if (spec.num_regs > 0) + { + o << indent << "float r0"; + for (std::uint32_t r = 1; r < spec.num_regs; ++r) o << ", r" << r; + o << ";\n"; + } + for (const auto& ins : spec.instrs) + { + const ExprOp op = static_cast(ins.op); + const std::string dst = "r" + std::to_string(ins.dst); + const std::string a = operand(ins.a); + switch (op) + { + case ExprOp::Add: case ExprOp::Sub: case ExprOp::Mul: case ExprOp::Div: + { + bool cmp = false; + const char* s = glsl_binary_op(op, cmp); + o << indent << dst << " = " << a << " " << s << " " + << operand(ins.b) << ";\n"; + break; + } + case ExprOp::Max: case ExprOp::Min: + { + const char* s = (op == ExprOp::Max) ? "max" : "min"; + o << indent << dst << " = " << s << "(" << a << ", " + << operand(ins.b) << ");\n"; + break; + } + case ExprOp::Lt: case ExprOp::Le: case ExprOp::Gt: + case ExprOp::Ge: case ExprOp::Eq: case ExprOp::Ne: + { + bool cmp = false; + const char* s = glsl_binary_op(op, cmp); + o << indent << dst << " = (" << a << " " << s << " " + << operand(ins.b) << ") ? 1.0 : 0.0;\n"; + break; + } + case ExprOp::Neg: + o << indent << dst << " = -(" << a << ");\n"; + break; + case ExprOp::Exp: case ExprOp::Log: case ExprOp::Sqrt: + case ExprOp::Rsqrt: case ExprOp::Abs: case ExprOp::Tanh: + { + const char* s = glsl_unary_op(op); + o << indent << dst << " = " << s << "(" << a << ");\n"; + break; + } + case ExprOp::Select: + o << indent << dst << " = (" << a << " != 0.0) ? " + << operand(ins.b) << " : " << operand(ins.c) << ";\n"; + break; + default: + return; // 未知算子:让上层报错 + } + } + }; + + if (!vec4_ok) + { + // ── 纯标量 kernel(含 RowMod/RotateHalf 等无法向量化的视图)── + L << "void main()\n{\n"; + L << " const uint i = gl_GlobalInvocationID.x;\n"; + L << " if (i >= count) return;\n"; + if (need_row && need_col) + L << " const uint row = i / cols;\n const uint col = i % cols;\n"; + else if (need_row) + L << " const uint row = i / cols;\n"; + else if (need_col) + L << " const uint col = i % cols;\n"; + emit_scalar_chain(L, " ", "i", + need_row ? "row" : "", need_col ? "col" : ""); + L << " bout[i] = r" << last_dst << ";\n"; + L << "}\n"; + return L.str(); + } + + // ── vec4 kernel:每线程处理 4 个相邻元素 ──────────────────────────── + // 快速路径(cols%4==0 且组内 4 元素齐全)→ vec4 加载/运算/存储; + // 否则回退到同 kernel 内标量循环(任意 cols / 尾部)。dispatch 宽度 4。 L << "void main()\n{\n"; L << " const uint i = gl_GlobalInvocationID.x;\n"; - L << " if (i >= count) return;\n"; - L << " const uint row = i / cols;\n"; - L << " const uint col = i % cols;\n"; - - // 输入读取变量(每输入缓存一次);vp 槽 = 此前运行时参数视图个数 + L << " const uint base = i * 4u;\n"; + L << " if (base >= count) return;\n"; + L << " if (cols % 4u == 0u && base + 3u < count) {\n"; + if (need_row && need_col) + L << " const uint row = base / cols;\n const uint col0 = base % cols;\n"; + else if (need_row) + L << " const uint row = base / cols;\n"; + else if (need_col) + L << " const uint col0 = base % cols;\n"; + + // vec4 输入读取(Linear/ColBroadcast 相邻 4 元素 → glslc 合并为 vec4 加载; + // RowBroadcast → splat) for (std::size_t i = 0; i < n_inputs; ++i) { - std::uint32_t vp = 0; - for (std::size_t j = 0; j < i; ++j) - if (expr_view_has_runtime_param( - static_cast(spec.views[j].kind))) - ++vp; - L << " const float v" << i << " = "; - glsl_view_read(L, spec.views[i], static_cast(i), - "i", "row", "col", vp); - L << ";\n"; + const auto k = static_cast(spec.views[i].kind); + if (k == ExprViewKind::Linear) + L << " const vec4 v" << i << " = vec4(b" << i << "[base], b" << i + << "[base+1u], b" << i << "[base+2u], b" << i << "[base+3u]);\n"; + else if (k == ExprViewKind::RowBroadcast) + L << " const vec4 v" << i << " = vec4(b" << i << "[row]);\n"; + else // ColBroadcast + L << " const vec4 v" << i << " = vec4(b" << i << "[col0], b" << i + << "[col0+1u], b" << i << "[col0+2u], b" << i << "[col0+3u]);\n"; } - // 操作数求值 - auto operand = [&](const ExprOperand& op) -> std::string + // vec4 指令展开(GLSL 对 vec4 重载运算/内置函数;常量广播为 vec4) + const auto vec4_operand = [&](const ExprOperand& op) -> std::string { switch (op.kind) { @@ -170,17 +336,15 @@ inline std::string generate_glsl(const std::string& name, const ExprSpec& spec) case static_cast(ExprOperandKind::Input): return "v" + std::to_string(op.idx); case static_cast(ExprOperandKind::Reg): - case static_cast(ExprOperandKind::Fanout): // Fanout 语义同 Reg + case static_cast(ExprOperandKind::Fanout): return "r" + std::to_string(op.idx); case static_cast(ExprOperandKind::Const): - return "c" + std::to_string(op.idx); + return "vec4(c" + std::to_string(op.idx) + ")"; } }; - - // 指令展开(直线代码;寄存器先声明、后赋值——兼容 IR-B liveness 复用同号寄存器) if (spec.num_regs > 0) { - L << " float r0"; + L << " vec4 r0"; for (std::uint32_t r = 1; r < spec.num_regs; ++r) L << ", r" << r; L << ";\n"; } @@ -188,53 +352,80 @@ inline std::string generate_glsl(const std::string& name, const ExprSpec& spec) { const ExprOp op = static_cast(ins.op); const std::string dst = "r" + std::to_string(ins.dst); - const std::string a = operand(ins.a); + const std::string a = vec4_operand(ins.a); switch (op) { case ExprOp::Add: case ExprOp::Sub: case ExprOp::Mul: case ExprOp::Div: { bool cmp = false; const char* s = glsl_binary_op(op, cmp); - L << " " << dst << " = " << a << " " << s << " " << operand(ins.b) << ";\n"; + L << " " << dst << " = " << a << " " << s << " " + << vec4_operand(ins.b) << ";\n"; break; } case ExprOp::Max: case ExprOp::Min: { const char* s = (op == ExprOp::Max) ? "max" : "min"; - L << " " << dst << " = " << s << "(" << a << ", " << operand(ins.b) << ");\n"; + L << " " << dst << " = " << s << "(" << a << ", " + << vec4_operand(ins.b) << ");\n"; break; } case ExprOp::Lt: case ExprOp::Le: case ExprOp::Gt: case ExprOp::Ge: case ExprOp::Eq: case ExprOp::Ne: { - bool cmp = false; - const char* s = glsl_binary_op(op, cmp); - L << " " << dst << " = (" << a << " " << s << " " << operand(ins.b) - << ") ? 1.0 : 0.0;\n"; + const char* s = nullptr; + switch (op) + { + case ExprOp::Lt: s = "lessThan"; break; + case ExprOp::Le: s = "lessThanEqual"; break; + case ExprOp::Gt: s = "greaterThan"; break; + case ExprOp::Ge: s = "greaterThanEqual"; break; + case ExprOp::Eq: s = "equal"; break; + case ExprOp::Ne: s = "notEqual"; break; + default: break; + } + L << " " << dst << " = vec4(" << s << "(" << a << ", " + << vec4_operand(ins.b) << "));\n"; break; } case ExprOp::Neg: - L << " " << dst << " = -(" << a << ");\n"; + L << " " << dst << " = -(" << a << ");\n"; break; case ExprOp::Exp: case ExprOp::Log: case ExprOp::Sqrt: case ExprOp::Rsqrt: case ExprOp::Abs: case ExprOp::Tanh: { const char* s = glsl_unary_op(op); - L << " " << dst << " = " << s << "(" << a << ");\n"; + L << " " << dst << " = " << s << "(" << a << ");\n"; break; } case ExprOp::Select: - L << " " << dst << " = (" << a << " != 0.0) ? " - << operand(ins.b) << " : " << operand(ins.c) << ";\n"; + L << " " << dst << " = mix(" << vec4_operand(ins.b) << ", " + << vec4_operand(ins.c) << ", notEqual(" << a + << ", vec4(0.0)));\n"; break; default: return std::string{}; // 未知算子:让上层报错 } } + L << " bout[base] = r" << last_dst << ".x;\n"; + L << " bout[base+1u] = r" << last_dst << ".y;\n"; + L << " bout[base+2u] = r" << last_dst << ".z;\n"; + L << " bout[base+3u] = r" << last_dst << ".w;\n"; + L << " return;\n"; + L << " }\n"; - // 输出 = 最后一条指令的目标寄存器 - const std::uint32_t last_dst = spec.instrs.back().dst; - L << " bout[i] = r" << last_dst << ";\n"; + // 标量回退:处理本线程负责的至多 4 个元素(任意 cols / 尾部) + L << " for (uint e = base; e < count && e < base + 4u; ++e) {\n"; + if (need_row && need_col) + L << " const uint row = e / cols;\n const uint col = e % cols;\n"; + else if (need_row) + L << " const uint row = e / cols;\n"; + else if (need_col) + L << " const uint col = e % cols;\n"; + emit_scalar_chain(L, " ", "e", + need_row ? "row" : "", need_col ? "col" : ""); + L << " bout[e] = r" << last_dst << ";\n"; + L << " }\n"; L << "}\n"; return L.str(); } @@ -296,7 +487,9 @@ inline std::string generate_glsl(const std::string& name, const ExprSpec& spec) std::ostringstream L; L << "// ── 自动生成(AOT 算子融合 · 归约 kernel),请勿手动编辑 ──\n"; L << "// 表达式: " << name << "\n"; - L << "#version 450\n\n"; + L << "#version 450\n"; + L << "#extension GL_KHR_shader_subgroup_basic : enable\n"; + L << "#extension GL_KHR_shader_subgroup_arithmetic : enable\n\n"; L << "layout(local_size_x = 256) in;\n\n"; for (std::size_t i = 0; i < n_inputs; ++i) L << "layout(std430, binding = " << i << ") readonly buffer Buf" << i @@ -433,16 +626,28 @@ inline std::string generate_glsl(const std::string& name, const ExprSpec& spec) return is_max ? "max(" + a + ", " + b + ")" : "(" + a + " + " + b + ")"; }; - // 归约树形归约(barrier 在每步内,最后一步后全线程可见 s_red[slot][0]) + // 归约:warp shuffle 蝴蝶归约(subgroup)替代共享内存树形归约。 + // 第 1 步:warp 内 subgroupAdd/subgroupMax → 每 warp 一个部分和(零共享/屏障) + // 第 2 步:首 warp 归约全部 warp 部分和 → s_red[slot][0](屏障后全线程可见) const auto emit_tree_reduce = [&](int slot, bool is_max) { const std::string s = "s_red[" + std::to_string(slot) + "]"; + const char* sub = is_max ? "subgroupMax" : "subgroupAdd"; + const std::string idt = is_max ? "uintBitsToFloat(0xff800000u)" : "0.0"; + const std::string vn = "v" + std::to_string(slot); // 按槽唯一命名,避免重定义 + const std::string wn = "w" + std::to_string(slot); L << " barrier();\n"; - L << " for (uint s = 128u; s > 0u; s >>= 1u) {\n"; - L << " if (tid < s) " << s << "[tid] = " - << combine(is_max, s + "[tid]", s + "[tid + s]") << ";\n"; - L << " barrier();\n"; + L << " float " << vn << " = " << s << "[tid];\n"; + L << " " << vn << " = " << sub << "(" << vn << ");\n"; + L << " if (gl_SubgroupInvocationID == 0u) " << s << "[gl_SubgroupID] = " << vn << ";\n"; + L << " barrier();\n"; + L << " if (gl_SubgroupID == 0u) {\n"; + L << " float " << wn << " = (gl_SubgroupInvocationID < gl_NumSubgroups) ? " + << s << "[gl_SubgroupInvocationID] : " << idt << ";\n"; + L << " " << wn << " = " << sub << "(" << wn << ");\n"; + L << " if (gl_SubgroupInvocationID == 0u) " << s << "[0] = " << wn << ";\n"; L << " }\n"; + L << " barrier();\n"; }; // 元素索引声明(行归约:固定行、循环列;列归约:循环行、固定列) @@ -451,7 +656,9 @@ inline std::string generate_glsl(const std::string& name, const ExprSpec& spec) : " for (uint r = tid; r < rows; r += 256u) {\n const uint row = r;\n const uint col = idx;\n"; const std::string out_idx = "row*cols + col"; - // ── 归约视图归约 pass ── + // ── 归约视图归约 pass(多路独立累加器,破串行累积延迟)── + // view_val(t):跨步索引 t 处的输入值(行归约:row=idx 固定、col=t; + // 列归约:col=idx 固定、row=t)。多累加器 stride 4*256。 for (std::size_t k = 0; k < n_inputs; ++k) { const ExprView& v = spec.views[k]; @@ -459,15 +666,29 @@ inline std::string generate_glsl(const std::string& name, const ExprSpec& spec) continue; const int slot = slot_of_view[k]; const bool is_max = slot_is_max[static_cast(slot)]; + const std::string init = is_max ? "uintBitsToFloat(0xff800000u)" : "0.0"; + const auto view_val = [&](const std::string& t) { + return is_row + ? "b" + std::to_string(k) + "[idx*cols + " + t + "]" + : "b" + std::to_string(k) + "[" + t + "*cols + idx]"; + }; L << "\n // 归约视图 " << k << " (槽 " << slot << ")\n"; L << " {\n"; - L << " float acc = " - << (is_max ? "uintBitsToFloat(0xff800000u)" : "0.0") << ";\n"; - L << loop_decl; - L << " acc = " << combine(is_max, "acc", - "b" + std::to_string(k) + "[row*cols + col]") << ";\n"; + L << " float acc0 = " << init << ", acc1 = " << init + << ", acc2 = " << init << ", acc3 = " << init << ";\n"; + L << " const uint len = " << (is_row ? "cols" : "rows") << ";\n"; + L << " uint i = tid;\n"; + L << " for (; i + 3u*256u < len; i += 4u*256u) {\n"; + L << " acc0 = " << combine(is_max, "acc0", view_val("i")) << ";\n"; + L << " acc1 = " << combine(is_max, "acc1", view_val("i+256u")) << ";\n"; + L << " acc2 = " << combine(is_max, "acc2", view_val("i+512u")) << ";\n"; + L << " acc3 = " << combine(is_max, "acc3", view_val("i+768u")) << ";\n"; L << " }\n"; - L << " s_red[" << slot << "][tid] = acc;\n"; + L << " for (; i < len; i += 256u)\n"; + L << " acc0 = " << combine(is_max, "acc0", view_val("i")) << ";\n"; + L << " s_red[" << slot << "][tid] = " + << combine(is_max, combine(is_max, combine(is_max, "acc0", "acc1"), "acc2"), "acc3") + << ";\n"; L << " }\n"; emit_tree_reduce(slot, is_max); } diff --git a/references/nir_opt_vectorize.c b/references/nir_opt_vectorize.c new file mode 100644 index 00000000..28cf42b8 --- /dev/null +++ b/references/nir_opt_vectorize.c @@ -0,0 +1,754 @@ +/* + * Copyright © 2015 Connor Abbott + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + */ + +/** + * nir_opt_vectorize() aims to vectorize ALU instructions. + * + * The default vectorization width is 4. + * If desired, a callback function which returns the max vectorization width + * per instruction can be provided. + * + * The max vectorization width must be a power of 2. + */ + +#include "util/u_dynarray.h" +#include "util/u_qsort.h" +#include "nir.h" +#include "nir_builder.h" +#include "nir_vla.h" + +#define XXH_INLINE_ALL +#include "util/xxhash.h" + +#define HASH(hash, data) XXH32(&data, sizeof(data), hash) + +static uint32_t +hash_src(uint32_t hash, const nir_src *src) +{ + void *hash_data = nir_src_is_const(*src) ? NULL : src->ssa; + + return HASH(hash, hash_data); +} + +static uint32_t +hash_alu_src(uint32_t hash, const nir_alu_src *src, + uint32_t num_components, uint32_t max_vec) +{ + /* hash whether a swizzle accesses elements beyond the maximum + * vectorization factor: + * For example accesses to .x and .y are considered different variables + * compared to accesses to .z and .w for 16-bit vec2. + */ + uint32_t swizzle = (src->swizzle[0] & ~(max_vec - 1)); + hash = HASH(hash, swizzle); + + return hash_src(hash, &src->src); +} + +static bool +phi_src_is_compatible(nir_def *def, uint32_t max_vec) +{ + /* hash_phi_src() and phi_srcs_equal() look at component 0 after chasing + * movs. For multi-component sources, make sure that component represents + * the whole source: all components are constants, or all components come + * from the same def and remain within the same max_vec. + */ + const nir_scalar first = nir_scalar_chase_movs(nir_get_scalar(def, 0)); + const bool first_is_const = nir_scalar_is_const(first); + const uint32_t mask = ~(max_vec - 1); + + for (uint8_t i = 1; i < def->num_components; i++) { + const nir_scalar chased = nir_scalar_chase_movs(nir_get_scalar(def, i)); + const bool chased_is_const = nir_scalar_is_const(chased); + + if (first_is_const || chased_is_const) { + if (first_is_const != chased_is_const) + return false; + continue; + } + + if (chased.def != first.def || + (chased.comp & mask) != (first.comp & mask)) + return false; + } + + return true; +} + +static uint32_t +hash_phi_src(uint32_t hash, const nir_phi_instr *phi, const nir_phi_src *src, + uint32_t max_vec) +{ + hash = HASH(hash, src->pred); + + nir_scalar chased = nir_scalar_chase_movs(nir_get_scalar(src->src.ssa, 0)); + uint32_t swizzle = chased.comp & ~(max_vec - 1); + hash = HASH(hash, swizzle); + + if (nir_scalar_is_const(chased)) { + void *data = NULL; + hash = HASH(hash, data); + } else if (src->pred->index < phi->instr.block->index) { + hash = HASH(hash, chased.def); + } else { + nir_instr *chased_instr = nir_def_instr(chased.def); + hash = HASH(hash, chased_instr->type); + + if (chased_instr->type == nir_instr_type_alu) + hash = HASH(hash, nir_instr_as_alu(chased_instr)->op); + } + + return hash; +} + +static uint32_t +hash_instr(const void *data) +{ + const nir_instr *instr = (nir_instr *)data; + uint32_t hash = HASH(0, instr->type); + + if (instr->type == nir_instr_type_phi) { + nir_phi_instr *phi = nir_instr_as_phi(instr); + + hash = HASH(hash, instr->block); + hash = HASH(hash, phi->def.bit_size); + + /* The order of phi sources is not guaranteed so hash commutatively. */ + nir_foreach_phi_src(src, phi) + hash *= hash_phi_src(0, phi, src, instr->pass_flags); + + return hash; + } + + assert(instr->type == nir_instr_type_alu); + nir_alu_instr *alu = nir_instr_as_alu(instr); + + hash = HASH(hash, alu->op); + hash = HASH(hash, alu->def.bit_size); + + unsigned i = 0; + if (nir_op_infos[alu->op].algebraic_properties & NIR_OP_IS_2SRC_COMMUTATIVE) { + uint32_t src0_hash = hash_alu_src(hash, &alu->src[0], + alu->def.num_components, + instr->pass_flags); + uint32_t src1_hash = hash_alu_src(hash, &alu->src[1], + alu->def.num_components, + instr->pass_flags); + + hash = src0_hash * src1_hash; + i = 2; + } + + for (; i < nir_op_infos[alu->op].num_inputs; i++) + hash = hash_alu_src(hash, &alu->src[i], + alu->def.num_components, + instr->pass_flags); + + return hash; +} + +static bool +srcs_equal(const nir_src *src1, const nir_src *src2) +{ + + return src1->ssa == src2->ssa || + (nir_src_is_const(*src1) && nir_src_is_const(*src2)); +} + +static bool +alu_srcs_equal(const nir_alu_src *src1, const nir_alu_src *src2, + uint32_t max_vec) +{ + uint32_t mask = ~(max_vec - 1); + if ((src1->swizzle[0] & mask) != (src2->swizzle[0] & mask)) + return false; + + return srcs_equal(&src1->src, &src2->src); +} + +static bool +alu_srcs_match_swapped(const nir_alu_instr *alu1, const nir_alu_instr *alu2, + uint32_t max_vec) +{ + return (nir_op_infos[alu1->op].algebraic_properties & NIR_OP_IS_2SRC_COMMUTATIVE) && + alu_srcs_equal(&alu1->src[0], &alu2->src[1], max_vec) && + alu_srcs_equal(&alu1->src[1], &alu2->src[0], max_vec); +} + +static bool +phi_srcs_equal(nir_block *block, const nir_phi_src *src1, + const nir_phi_src *src2, uint32_t max_vec) +{ + if (src1->pred != src2->pred) + return false; + + /* Since phi sources don't have swizzles, they are swizzled using movs. + * Get the real sources first. + */ + nir_scalar chased1 = nir_scalar_chase_movs(nir_get_scalar(src1->src.ssa, 0)); + nir_scalar chased2 = nir_scalar_chase_movs(nir_get_scalar(src2->src.ssa, 0)); + + if (nir_scalar_is_const(chased1) && nir_scalar_is_const(chased2)) + return true; + + uint32_t mask = ~(max_vec - 1); + if ((chased1.comp & mask) != (chased2.comp & mask)) + return false; + + /* For phi sources whose defs we have already processed, we require that + * they point to the same def like we do for ALU instructions. + */ + if (src1->pred->index < block->index) + return chased1.def == chased2.def; + + /* Otherwise (i.e., for loop back-edges), we haven't processed the sources + * yet so they haven't been vectorized. In this case, try to guess if they + * could be vectorized later. Keep it simple for now: if they are the same + * type of instruction and, if ALU, have the same operation, assume they + * might be vectorized later. Although this won't be true in general, this + * heuristic is probable good enough in practice: since we check that other + * (forward-edge) sources are vectorized, chances are the back-edge will + * also be vectorized. + */ + nir_instr *chased_instr1 = nir_def_instr(chased1.def); + nir_instr *chased_instr2 = nir_def_instr(chased2.def); + + if (chased_instr1->type != chased_instr2->type) + return false; + + if (chased_instr1->type != nir_instr_type_alu) + return true; + + return nir_instr_as_alu(chased_instr1)->op == + nir_instr_as_alu(chased_instr2)->op; +} + +static bool +instrs_equal(const void *data1, const void *data2) +{ + const nir_instr *instr1 = (nir_instr *)data1; + const nir_instr *instr2 = (nir_instr *)data2; + + if (instr1->type != instr2->type) + return false; + + if (instr1->type == nir_instr_type_phi) { + if (instr1->block != instr2->block) + return false; + + nir_phi_instr *phi1 = nir_instr_as_phi(instr1); + nir_phi_instr *phi2 = nir_instr_as_phi(instr2); + + if (phi1->def.bit_size != phi2->def.bit_size) + return false; + + nir_foreach_phi_src(src1, phi1) { + nir_phi_src *src2 = nir_phi_get_src_from_block(phi2, src1->pred); + + if (!phi_srcs_equal(instr1->block, src1, src2, instr1->pass_flags)) + return false; + } + + return true; + } + + assert(instr1->type == nir_instr_type_alu); + assert(instr2->type == nir_instr_type_alu); + + nir_alu_instr *alu1 = nir_instr_as_alu(instr1); + nir_alu_instr *alu2 = nir_instr_as_alu(instr2); + + if (alu1->op != alu2->op) + return false; + + if (alu1->def.bit_size != alu2->def.bit_size) + return false; + + unsigned i = 0; + if (alu_srcs_match_swapped(alu1, alu2, instr1->pass_flags)) + i = 2; + + for (; i < nir_op_infos[alu1->op].num_inputs; i++) { + if (!alu_srcs_equal(&alu1->src[i], &alu2->src[i], instr1->pass_flags)) + return false; + } + + return true; +} + +static bool +instr_can_rewrite(nir_instr *instr) +{ + switch (instr->type) { + case nir_instr_type_alu: { + nir_alu_instr *alu = nir_instr_as_alu(instr); + + /* Don't try and vectorize mov's. Either they'll be handled by copy + * prop, or they're actually necessary and trying to vectorize them + * would result in fighting with copy prop. + */ + if (alu->op == nir_op_mov) + return false; + + /* no need to hash instructions which are already vectorized */ + if (alu->def.num_components >= instr->pass_flags) + return false; + + if (nir_op_infos[alu->op].output_size != 0) + return false; + + for (unsigned i = 0; i < nir_op_infos[alu->op].num_inputs; i++) { + if (nir_op_infos[alu->op].input_sizes[i] != 0) + return false; + + /* don't hash instructions which are already swizzled + * outside of max_components: these should better be scalarized */ + uint32_t mask = ~(instr->pass_flags - 1); + for (unsigned j = 1; j < alu->def.num_components; j++) { + if ((alu->src[i].swizzle[0] & mask) != (alu->src[i].swizzle[j] & mask)) + return false; + } + } + + return true; + } + + case nir_instr_type_phi: { + nir_phi_instr *phi = nir_instr_as_phi(instr); + + if (phi->def.num_components >= instr->pass_flags) + return false; + + nir_foreach_phi_src(src, phi) { + if (!phi_src_is_compatible(src->src.ssa, instr->pass_flags)) + return false; + } + + return true; + } + + default: + break; + } + + return false; +} + +static void +rewrite_uses(nir_builder *b, struct set *instr_set, nir_def *def1, + nir_def *def2, nir_def *new_def) +{ + /* update all ALU uses */ + nir_foreach_use_safe(src, def1) { + nir_instr *user_instr = nir_src_use_instr(src); + if (user_instr->type == nir_instr_type_alu) { + /* Check if user is found in the hashset */ + struct set_entry *entry = _mesa_set_search(instr_set, user_instr); + + /* For ALU instructions, rewrite the source directly to avoid a + * round-trip through copy propagation. + */ + nir_src_rewrite(src, new_def); + + /* Rehash user if it was found in the hashset */ + if (entry && entry->key == user_instr) { + _mesa_set_remove(instr_set, entry); + _mesa_set_add(instr_set, user_instr); + } + } + } + + nir_foreach_use_safe(src, def2) { + if (nir_src_use_instr(src)->type == nir_instr_type_alu) { + /* For ALU instructions, rewrite the source directly to avoid a + * round-trip through copy propagation. + */ + nir_src_rewrite(src, new_def); + + nir_alu_src *alu_src = container_of(src, nir_alu_src, src); + nir_alu_instr *use = nir_instr_as_alu(nir_src_use_instr(src)); + unsigned components = + nir_ssa_alu_instr_src_components(use, alu_src - use->src); + for (unsigned i = 0; i < components; i++) + alu_src->swizzle[i] += def1->num_components; + } + } + + /* update all other uses if there are any */ + unsigned swiz[NIR_MAX_VEC_COMPONENTS]; + + if (!nir_def_is_unused(def1)) { + for (unsigned i = 0; i < def1->num_components; i++) + swiz[i] = i; + nir_def *new_def1 = nir_swizzle(b, new_def, swiz, def1->num_components); + nir_def_rewrite_uses(def1, new_def1); + } + + if (!nir_def_is_unused(def2)) { + for (unsigned i = 0; i < def2->num_components; i++) + swiz[i] = i + def1->num_components; + nir_def *new_def2 = nir_swizzle(b, new_def, swiz, def2->num_components); + nir_def_rewrite_uses(def2, new_def2); + } + + nir_instr_remove(nir_def_instr(def1)); + nir_instr_remove(nir_def_instr(def2)); +} + +static nir_block * +get_first_non_const_phi_pred(nir_phi_instr *phi) +{ + nir_block *result = NULL; + nir_foreach_phi_src(src, phi) { + /* Loop back edge source might not be vectors. */ + if (phi->instr.block->index < src->pred->index) + continue; + + /* Find the first predecessor in source order. */ + if (result && src->pred->index > result->index) + continue; + + /* Ignore constant phi sources. */ + if (nir_scalar_is_const(nir_scalar_resolved(src->src.ssa, 0))) + continue; + + result = src->pred; + } + + return result; +} + +static int +compare_scalar(const void *p1, const void *p2, void *data) +{ + uint8_t idx1 = *(const uint8_t *)p1; + uint8_t idx2 = *(const uint8_t *)p2; + + const nir_scalar *scalars = data; + + if (scalars[idx1].comp == scalars[idx2].comp) + return idx1 - idx2; + + return scalars[idx1].comp - scalars[idx2].comp; +} + +static nir_instr * +instr_try_combine_phi(struct set *instr_set, nir_phi_instr *phi1, nir_phi_instr *phi2) +{ + assert(phi1->def.bit_size == phi2->def.bit_size); + unsigned phi1_components = phi1->def.num_components; + unsigned phi2_components = phi2->def.num_components; + unsigned total_components = phi1_components + phi2_components; + + assert(phi1->instr.pass_flags == phi2->instr.pass_flags); + if (total_components > phi1->instr.pass_flags) + return NULL; + + assert(phi1->instr.block == phi2->instr.block); + nir_block *block = phi1->instr.block; + + nir_builder b = nir_builder_at(nir_after_instr(&phi1->instr)); + nir_phi_instr *new_phi = nir_phi_instr_create(b.shader); + nir_def_init(&new_phi->instr, &new_phi->def, total_components, + phi1->def.bit_size); + nir_builder_instr_insert(&b, &new_phi->instr); + new_phi->instr.pass_flags = phi1->instr.pass_flags; + + assert(exec_list_length(&phi1->srcs) == exec_list_length(&phi2->srcs)); + + uint8_t dest_swizzle[NIR_MAX_VEC_COMPONENTS] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + + nir_block *swizzle_pred = get_first_non_const_phi_pred(phi1); + /* Try to avoid reverse swizzles at the new phi sources. */ + if (swizzle_pred) { + nir_phi_src *src1 = nir_phi_get_src_from_block(phi1, swizzle_pred); + nir_phi_src *src2 = nir_phi_get_src_from_block(phi2, swizzle_pred); + + nir_scalar new_srcs[NIR_MAX_VEC_COMPONENTS]; + + for (unsigned i = 0; i < phi1_components; i++) + new_srcs[i] = nir_scalar_resolved(src1->src.ssa, i); + + for (unsigned i = 0; i < phi2_components; i++) + new_srcs[phi1_components + i] = nir_scalar_resolved(src2->src.ssa, i); + + util_qsort_r(dest_swizzle, total_components, sizeof(dest_swizzle[0]), + compare_scalar, new_srcs); + } + + nir_foreach_phi_src(src1, phi1) { + nir_phi_src *src2 = nir_phi_get_src_from_block(phi2, src1->pred); + nir_block *pred_block = src1->pred; + + nir_scalar new_srcs[NIR_MAX_VEC_COMPONENTS]; + + for (unsigned i = 0; i < total_components; i++) { + unsigned comp = dest_swizzle[i]; + if (comp < phi1_components) + new_srcs[i] = nir_scalar_resolved(src1->src.ssa, comp); + else + new_srcs[i] = nir_scalar_resolved(src2->src.ssa, comp - phi1_components); + } + + nir_def *new_src; + + if (nir_scalar_is_const(new_srcs[0])) { + nir_const_value value[NIR_MAX_VEC_COMPONENTS]; + + for (unsigned i = 0; i < total_components; i++) { + assert(nir_scalar_is_const(new_srcs[i])); + value[i] = nir_scalar_as_const_value(new_srcs[i]); + } + + b.cursor = nir_after_block_before_jump(pred_block); + unsigned bit_size = src1->src.ssa->bit_size; + new_src = nir_build_imm(&b, total_components, bit_size, value); + } else if (pred_block->index < block->index) { + nir_def *def = new_srcs[0].def; + unsigned src_swizzle[NIR_MAX_VEC_COMPONENTS]; + + for (unsigned i = 0; i < total_components; i++) { + assert(new_srcs[i].def == def); + src_swizzle[i] = new_srcs[i].comp; + } + + b.cursor = nir_after_instr_and_phis(nir_def_instr(def)); + new_src = nir_swizzle(&b, def, src_swizzle, total_components); + } else { + /* This is a loop back-edge so we haven't vectorized the sources yet. + * Combine them in a vec which, if they are vectorized later, will be + * cleaned up by copy propagation. + */ + b.cursor = nir_after_block_before_jump(pred_block); + new_src = nir_vec_scalars(&b, new_srcs, total_components); + } + + nir_phi_src *new_phi_src = + nir_phi_instr_add_src(new_phi, src1->pred, new_src); + list_addtail(&new_phi_src->src.use_link, &new_src->uses); + } + + b.cursor = nir_after_phis(block); + + nir_alu_src replace_phi1 = { nir_src_for_ssa(&new_phi->def) }; + nir_alu_src replace_phi2 = { nir_src_for_ssa(&new_phi->def) }; + + for (unsigned i = 0; i < total_components; i++) { + unsigned comp = dest_swizzle[i]; + if (comp < phi1_components) + replace_phi1.swizzle[comp] = i; + else + replace_phi2.swizzle[comp - phi1_components] = i; + } + + nir_def_rewrite_uses_with_alu_src(&b, &phi1->def, replace_phi1); + nir_def_rewrite_uses_with_alu_src(&b, &phi2->def, replace_phi2); + + nir_instr_remove(&phi1->instr); + nir_instr_remove(&phi2->instr); + + return &new_phi->instr; +} + +static nir_instr * +instr_try_combine_alu(struct set *instr_set, nir_alu_instr *alu1, nir_alu_instr *alu2) +{ + assert(alu1->def.bit_size == alu2->def.bit_size); + unsigned alu1_components = alu1->def.num_components; + unsigned alu2_components = alu2->def.num_components; + unsigned total_components = alu1_components + alu2_components; + + assert(alu1->instr.pass_flags == alu2->instr.pass_flags); + if (total_components > alu1->instr.pass_flags) + return NULL; + + nir_builder b = nir_builder_at(nir_after_instr(&alu1->instr)); + + nir_alu_instr *new_alu = nir_alu_instr_create(b.shader, alu1->op); + nir_def_init(&new_alu->instr, &new_alu->def, total_components, + alu1->def.bit_size); + new_alu->instr.pass_flags = alu1->instr.pass_flags; + + /* fp_math_ctrl is a set of restrictions, take the union of both. + */ + new_alu->fp_math_ctrl = alu1->fp_math_ctrl | alu2->fp_math_ctrl; + + /* If all channels don't wrap, we can say that the whole vector doesn't + * wrap. + */ + new_alu->no_signed_wrap = alu1->no_signed_wrap && alu2->no_signed_wrap; + new_alu->no_unsigned_wrap = alu1->no_unsigned_wrap && alu2->no_unsigned_wrap; + + bool srcs_swapped = alu_srcs_match_swapped(alu1, alu2, alu1->instr.pass_flags); + + for (unsigned i = 0; i < nir_op_infos[alu1->op].num_inputs; i++) { + unsigned alu2_src_idx = srcs_swapped && i < 2 ? 1 - i : i; + const nir_alu_src *alu2_src = &alu2->src[alu2_src_idx]; + + /* handle constant merging case */ + if (alu1->src[i].src.ssa != alu2_src->src.ssa) { + nir_const_value *c1 = nir_src_as_const_value(alu1->src[i].src); + nir_const_value *c2 = nir_src_as_const_value(alu2_src->src); + assert(c1 && c2); + nir_const_value value[NIR_MAX_VEC_COMPONENTS]; + unsigned bit_size = alu1->src[i].src.ssa->bit_size; + + for (unsigned j = 0; j < total_components; j++) { + value[j].u64 = j < alu1_components ? c1[alu1->src[i].swizzle[j]].u64 : c2[alu2_src->swizzle[j - alu1_components]].u64; + } + nir_def *def = nir_build_imm(&b, total_components, bit_size, value); + + new_alu->src[i].src = nir_src_for_ssa(def); + for (unsigned j = 0; j < total_components; j++) + new_alu->src[i].swizzle[j] = j; + continue; + } + + new_alu->src[i].src = alu1->src[i].src; + + for (unsigned j = 0; j < alu1_components; j++) + new_alu->src[i].swizzle[j] = alu1->src[i].swizzle[j]; + + for (unsigned j = 0; j < alu2_components; j++) { + new_alu->src[i].swizzle[j + alu1_components] = + alu2_src->swizzle[j]; + } + } + + nir_builder_instr_insert(&b, &new_alu->instr); + rewrite_uses(&b, instr_set, &alu1->def, &alu2->def, &new_alu->def); + + return &new_alu->instr; +} + +/* + * Tries to combine two instructions whose sources are different components of + * the same instructions into one vectorized instruction. Note that instr1 + * should dominate instr2. + */ +static nir_instr * +instr_try_combine(struct set *instr_set, nir_instr *instr1, nir_instr *instr2) +{ + switch (instr1->type) { + case nir_instr_type_alu: + assert(instr2->type == nir_instr_type_alu); + return instr_try_combine_alu(instr_set, nir_instr_as_alu(instr1), + nir_instr_as_alu(instr2)); + + case nir_instr_type_phi: + assert(instr2->type == nir_instr_type_phi); + return instr_try_combine_phi(instr_set, nir_instr_as_phi(instr1), + nir_instr_as_phi(instr2)); + + default: + UNREACHABLE("Unsupported instruction type"); + } +} + +static void +vec_instr_set_init(struct set *instr_set) +{ + _mesa_set_init(instr_set, NULL, hash_instr, instrs_equal); +} + +static void +vec_instr_set_fini(struct set *instr_set) +{ + _mesa_set_fini(instr_set, NULL); +} + +static bool +vec_instr_set_add_or_rewrite(struct set *instr_set, nir_instr *instr, + nir_vectorize_cb filter, void *data) +{ + /* set max vector to instr pass flags: this is used to hash swizzles */ + instr->pass_flags = filter ? filter(instr, data) : 4; + assert(util_is_power_of_two_or_zero(instr->pass_flags)); + + if (!instr_can_rewrite(instr)) + return false; + + struct set_entry *entry = _mesa_set_search(instr_set, instr); + if (entry) { + nir_instr *old_instr = (nir_instr *)entry->key; + + /* We cannot combine the instructions if the old one doesn't dominate + * the new one. Since we will never encounter a block again that is + * dominated by the old instruction, overwrite it with the new one in + * the instruction set. + */ + if (!nir_block_dominates(old_instr->block, instr->block)) { + entry->key = instr; + return false; + } + + _mesa_set_remove(instr_set, entry); + nir_instr *new_instr = instr_try_combine(instr_set, old_instr, instr); + if (new_instr) { + if (instr_can_rewrite(new_instr)) + _mesa_set_add(instr_set, new_instr); + return true; + } + } + + _mesa_set_add(instr_set, instr); + return false; +} + +static bool +nir_opt_vectorize_impl(nir_function_impl *impl, + nir_vectorize_cb filter, void *data) +{ + struct set instr_set; + vec_instr_set_init(&instr_set); + + nir_metadata_require(impl, + nir_metadata_block_index | nir_metadata_dominance); + + bool progress = false; + + nir_foreach_block(block, impl) { + nir_foreach_instr_safe(instr, block) { + progress |= vec_instr_set_add_or_rewrite(&instr_set, instr, filter, data); + } + } + + nir_progress(progress, impl, nir_metadata_control_flow); + + vec_instr_set_fini(&instr_set); + return progress; +} + +bool +nir_opt_vectorize(nir_shader *shader, nir_vectorize_cb filter, + void *data) +{ + bool progress = false; + + nir_foreach_function_impl(impl, shader) { + progress |= nir_opt_vectorize_impl(impl, filter, data); + } + + return progress; +} diff --git a/references/nir_opt_vectorize_io.c b/references/nir_opt_vectorize_io.c new file mode 100644 index 00000000..0669b6e9 --- /dev/null +++ b/references/nir_opt_vectorize_io.c @@ -0,0 +1,671 @@ +/* + * Copyright 2024 Advanced Micro Devices, Inc. + * + * SPDX-License-Identifier: MIT + */ + +/** + * This pass: + * - vectorizes lowered input/output loads and stores + * - vectorizes low and high 16-bit loads and stores by merging them into + * a single 32-bit load or store (except load_interpolated_input and XFB, + * which have to keep bit_size=16) + * - performs DCE of output stores that overwrite the previous value by writing + * into the same slot and component. + * + * Vectorization is only local within basic blocks. No vectorization occurs + * across basic block boundaries, barriers (only TCS outputs), emits (only + * GS outputs), and output load <-> output store dependencies. + * + * All loads and stores must be scalar. 64-bit loads and stores are forbidden. + * + * For each basic block, the time complexity is O(n*log(n)) where n is + * the number of IO instructions within that block. + */ + +#include "util/u_dynarray.h" +#include "nir.h" +#include "nir_builder.h" + +/* Return 0 if loads/stores are vectorizable. Return 1 or -1 to define + * an ordering between non-vectorizable instructions. This is used by qsort, + * to sort all gathered instructions into groups of vectorizable instructions. + */ +static int +compare_is_not_vectorizable(nir_intrinsic_instr *a, nir_intrinsic_instr *b) +{ + if (a->intrinsic != b->intrinsic) + return a->intrinsic > b->intrinsic ? 1 : -1; + + nir_src *offset0 = nir_get_io_offset_src(a); + nir_src *offset1 = nir_get_io_offset_src(b); + if (offset0 && offset0->ssa != offset1->ssa) + return offset0->ssa->index > offset1->ssa->index ? 1 : -1; + + nir_src *array_idx0 = nir_get_io_arrayed_index_src(a); + nir_src *array_idx1 = nir_get_io_arrayed_index_src(b); + if (array_idx0 && array_idx0->ssa != array_idx1->ssa) + return array_idx0->ssa->index > array_idx1->ssa->index ? 1 : -1; + + /* Compare barycentrics or vertex index. */ + if ((a->intrinsic == nir_intrinsic_load_interpolated_input || + a->intrinsic == nir_intrinsic_load_input_vertex) && + a->src[0].ssa != b->src[0].ssa) + return a->src[0].ssa->index > b->src[0].ssa->index ? 1 : -1; + + nir_io_semantics sem0 = nir_intrinsic_io_semantics(a); + nir_io_semantics sem1 = nir_intrinsic_io_semantics(b); + if (sem0.location != sem1.location) + return sem0.location > sem1.location ? 1 : -1; + + /* The mediump flag isn't mergable. */ + if (sem0.medium_precision != sem1.medium_precision) + return sem0.medium_precision > sem1.medium_precision ? 1 : -1; + + /* Don't merge per-view attributes with non-per-view attributes. */ + if (sem0.per_view != sem1.per_view) + return sem0.per_view > sem1.per_view ? 1 : -1; + + if (sem0.interp_explicit_strict != sem1.interp_explicit_strict) + return sem0.interp_explicit_strict > sem1.interp_explicit_strict ? 1 : -1; + + /* Only load_interpolated_input can't merge low and high halves of 16-bit + * loads/stores. + */ + if (a->intrinsic == nir_intrinsic_load_interpolated_input && + sem0.high_16bits != sem1.high_16bits) + return sem0.high_16bits > sem1.high_16bits ? 1 : -1; + + /* TODO: vectorize (f32, f32, f16vec2, f16vec2) -> vec4 + * For now, different bit sizes are not vectorized together. + */ + if (nir_intrinsic_has_src_type(a)) { + /* Stores. */ + if (a->src[0].ssa->bit_size != b->src[0].ssa->bit_size) + return a->src[0].ssa->bit_size > b->src[0].ssa->bit_size ? 1 : -1; + } else { + /* Loads. */ + if (a->def.bit_size != b->def.bit_size) + return a->def.bit_size > b->def.bit_size ? 1 : -1; + } + + nir_shader *shader = a->instr.block->impl->function->shader; + + /* Compare the types. */ + if (!(shader->options->io_options & nir_io_vectorizer_ignores_types)) { + unsigned type_a, type_b; + + if (nir_intrinsic_has_src_type(a)) { + type_a = nir_intrinsic_src_type(a); + type_b = nir_intrinsic_src_type(b); + } else { + type_a = nir_intrinsic_dest_type(a); + type_b = nir_intrinsic_dest_type(b); + } + + if (type_a != type_b) + return type_a > type_b ? 1 : -1; + } + + return 0; +} + +static int +compare_intr(const void *xa, const void *xb) +{ + nir_intrinsic_instr *a = *(nir_intrinsic_instr **)xa; + nir_intrinsic_instr *b = *(nir_intrinsic_instr **)xb; + + int comp = compare_is_not_vectorizable(a, b); + if (comp) + return comp; + + /* qsort isn't stable. This ensures that later stores aren't moved before earlier stores. */ + return a->instr.index > b->instr.index ? 1 : -1; +} + +typedef enum { + merge_low_high_16_to_32, + vectorize_high_16_separately, + vectorize_the_rest, +} nir_vectorize_op_step; + +static bool +apply_radv_workaround(nir_builder *b) +{ + return b->shader->options->io_options & + nir_io_radv_intrinsic_component_workaround; +} + +static void +vectorize_load(nir_intrinsic_instr *chan[8], unsigned start, unsigned count, + nir_vectorize_op_step step) +{ + nir_intrinsic_instr *first = NULL; + + /* Find the first instruction where the vectorized load will be + * inserted. + */ + for (unsigned i = start; i < start + count; i++) { + if (!chan[i]) + continue; + + first = !first || chan[i]->instr.index < first->instr.index ? chan[i] : first; + if (step == merge_low_high_16_to_32) { + first = !first || chan[4 + i]->instr.index < first->instr.index ? chan[4 + i] : first; + } + } + + /* Insert the vectorized load. */ + nir_builder b = nir_builder_at(nir_before_instr(&first->instr)); + nir_intrinsic_instr *new_intr = + nir_intrinsic_instr_create(b.shader, first->intrinsic); + + new_intr->num_components = count; + nir_def_init(&new_intr->instr, &new_intr->def, count, + step == merge_low_high_16_to_32 ? 32 : first->def.bit_size); + memcpy(new_intr->src, first->src, + nir_intrinsic_infos[first->intrinsic].num_srcs * sizeof(nir_src)); + nir_intrinsic_copy_const_indices(new_intr, first); + if (apply_radv_workaround(&b)) + nir_intrinsic_set_component(new_intr, start); + else + nir_intrinsic_set_component(new_intr, start & 0x3); /* Bits 4..7 should map to 0..3 */ + assert(start % 4 + count <= 4); + + nir_io_semantics sem = nir_intrinsic_io_semantics(new_intr); + + for (unsigned i = start; i < start + count; i++) { + if (chan[i]) { + if (!nir_intrinsic_io_semantics(chan[i]).no_signed_zero) + sem.no_signed_zero = 0; + } + + if (step == merge_low_high_16_to_32 && chan[4 + i]) { + if (!nir_intrinsic_io_semantics(chan[4 + i]).no_signed_zero) + sem.no_signed_zero = 0; + } + } + + if (step == vectorize_high_16_separately) { + assert(start >= 4); + sem.high_16bits = 1; + } else { + assert(start <= 3); + } + + if (step == merge_low_high_16_to_32) { + sem.high_16bits = 0; + nir_intrinsic_set_dest_type(new_intr, + (nir_intrinsic_dest_type(new_intr) & ~16) | 32); + } + + nir_intrinsic_set_io_semantics(new_intr, sem); + + nir_builder_instr_insert(&b, &new_intr->instr); + nir_def *def = &new_intr->def; + + /* Replace the scalar loads. */ + if (step == merge_low_high_16_to_32) { + for (unsigned i = start; i < start + count; i++) { + nir_def *comp = nir_channel(&b, def, i - start); + + nir_def_rewrite_uses(&chan[i]->def, + nir_unpack_32_2x16_split_x(&b, comp)); + nir_def_rewrite_uses(&chan[4 + i]->def, + nir_unpack_32_2x16_split_y(&b, comp)); + nir_instr_remove(&chan[i]->instr); + nir_instr_remove(&chan[4 + i]->instr); + } + } else { + for (unsigned i = start; i < start + count; i++) { + if (chan[i]) + nir_def_replace(&chan[i]->def, nir_channel(&b, def, i - start)); + } + } +} + +static void +vectorize_store(nir_intrinsic_instr *chan[8], unsigned start, unsigned count, + nir_vectorize_op_step step) +{ + nir_intrinsic_instr *last = NULL; + + /* Find the last instruction where the vectorized store will be + * inserted. + */ + for (unsigned i = start; i < start + count; i++) { + last = !last || chan[i]->instr.index > last->instr.index ? chan[i] : last; + if (step == merge_low_high_16_to_32) { + last = !last || chan[4 + i]->instr.index > last->instr.index ? chan[4 + i] : last; + } + } + + /* Change the last instruction to a vectorized store. Update xfb first + * because we need to read some info from "last" before overwriting it. + */ + if (nir_intrinsic_has_io_xfb(last)) { + /* 0 = low/full channels + * 1 = high channels + */ + nir_io_xfb xfb[2] = { { { { 0 } } } }; + + for (unsigned i = start; i < start + count; i++) { + xfb[i / 4].out[i % 4] = nir_intrinsic_io_xfb(chan[i]).out[i % 4]; + + /* Merging low and high 16 bits to 32 bits is not possible + * with xfb in some cases. + */ + assert(!xfb[i / 4].out[i % 4].num_components || + step != merge_low_high_16_to_32); + } + + /* Now vectorize xfb info by merging the individual elements. */ + for (unsigned i = start; i < start + count; i++) { + /* mediump means that xfb upconverts to 32 bits when writing to + * memory. + */ + unsigned xfb_comp_size = + nir_intrinsic_io_semantics(chan[i]).medium_precision ? 32 : chan[i]->src[0].ssa->bit_size; + + for (unsigned j = i + 1; j < start + count; j++) { + if (xfb[i / 4].out[i % 4].buffer != xfb[j / 4].out[j % 4].buffer || + xfb[i / 4].out[i % 4].offset != xfb[j / 4].out[j % 4].offset + + xfb_comp_size * (j - i)) + break; + + xfb[i / 4].out[i % 4].num_components++; + memset(&xfb[j / 4].out[j % 4], 0, sizeof(xfb[j / 4].out[j % 4])); + } + } + + if (start >= 4) { + nir_intrinsic_set_io_xfb(last, xfb[1]); + } else { + assert(start + count <= 4); + nir_intrinsic_set_io_xfb(last, xfb[0]); + } + } + + /* Update gs_streams. */ + unsigned gs_streams = 0; + for (unsigned i = start; i < start + count; i++) { + gs_streams |= (nir_intrinsic_io_semantics(chan[i]).gs_streams & 0x3) << ((i - start) * 2); + } + + nir_io_semantics sem = nir_intrinsic_io_semantics(last); + sem.gs_streams = gs_streams; + + if (step == vectorize_high_16_separately) { + assert(start >= 4); + sem.high_16bits = 1; + } else { + assert(start <= 3); + } + + /* Update other flags. */ + for (unsigned i = start; i < start + count; i++) { + if (!nir_intrinsic_io_semantics(chan[i]).no_sysval_output) + sem.no_sysval_output = 0; + if (!nir_intrinsic_io_semantics(chan[i]).no_varying) + sem.no_varying = 0; + if (!nir_intrinsic_io_semantics(chan[i]).no_signed_zero) + sem.no_signed_zero = 0; + } + + if (step == merge_low_high_16_to_32) { + /* Update "no" flags for high bits. */ + for (unsigned i = start; i < start + count; i++) { + if (!nir_intrinsic_io_semantics(chan[4 + i]).no_sysval_output) + sem.no_sysval_output = 0; + if (!nir_intrinsic_io_semantics(chan[4 + i]).no_varying) + sem.no_varying = 0; + if (!nir_intrinsic_io_semantics(chan[4 + i]).no_signed_zero) + sem.no_signed_zero = 0; + } + + /* Update the type. */ + sem.high_16bits = 0; + nir_intrinsic_set_src_type(last, + (nir_intrinsic_src_type(last) & ~16) | 32); + } + + /* TODO: Merge names? */ + + nir_builder b = nir_builder_at(nir_before_instr(&last->instr)); + + /* Update the rest. */ + nir_intrinsic_set_io_semantics(last, sem); + if (apply_radv_workaround(&b)) + nir_intrinsic_set_component(last, start); + else + nir_intrinsic_set_component(last, start & 0x3); /* Bits 4..7 should map to 0..3 */ + assert(start % 4 + count <= 4); + nir_intrinsic_set_write_mask(last, BITFIELD_MASK(count)); + last->num_components = count; + + /* Replace the stored scalar with the vector. */ + if (step == merge_low_high_16_to_32) { + nir_def *value[4]; + for (unsigned i = start; i < start + count; i++) { + value[i] = nir_pack_32_2x16_split(&b, chan[i]->src[0].ssa, + chan[4 + i]->src[0].ssa); + } + + nir_src_rewrite(&last->src[0], nir_vec(&b, &value[start], count)); + } else { + nir_def *value[8]; + for (unsigned i = start; i < start + count; i++) + value[i] = chan[i]->src[0].ssa; + + nir_src_rewrite(&last->src[0], nir_vec(&b, &value[start], count)); + } + + /* Remove the scalar stores. */ + for (unsigned i = start; i < start + count; i++) { + if (chan[i] != last) + nir_instr_remove(&chan[i]->instr); + if (step == merge_low_high_16_to_32 && chan[4 + i] != last) + nir_instr_remove(&chan[4 + i]->instr); + } +} + +/* Vectorize a vector of scalar instructions. chan[8] are the channels. + * (the last 4 are the high 16-bit channels) + */ +static bool +vectorize_slot(nir_intrinsic_instr *chan[8], unsigned mask, bool allow_holes) +{ + bool progress = false; + assert(mask); + bool is_load = nir_intrinsic_infos[chan[ffs(mask) - 1]->intrinsic].has_dest; + + /* First, merge low and high 16-bit halves into 32 bits separately when + * possible. Then vectorize what's left. + */ + for (nir_vectorize_op_step step = merge_low_high_16_to_32; + step <= vectorize_the_rest; step++) { + unsigned scan_mask; + + if (step == merge_low_high_16_to_32) { + /* Get the subset of the mask where both low and high bits are set. */ + scan_mask = 0; + for (unsigned i = 0; i < 4; i++) { + unsigned low_high_bits = BITFIELD_BIT(i) | BITFIELD_BIT(i + 4); + + if ((mask & low_high_bits) == low_high_bits) { + /* Merging low and high 16 bits to 32 bits is not possible + * with xfb in some cases. + */ + if (nir_intrinsic_has_io_xfb(chan[i])) { + unsigned hi = i + 4; + + if (nir_intrinsic_io_xfb(chan[i]).out[i].num_components || + nir_intrinsic_io_xfb(chan[hi]).out[i].num_components) + continue; + } + + /* The GS stream must be the same for both halves. */ + if ((nir_intrinsic_io_semantics(chan[i]).gs_streams & 0x3) != + (nir_intrinsic_io_semantics(chan[4 + i]).gs_streams & 0x3)) + continue; + + scan_mask |= BITFIELD_BIT(i); + mask &= ~low_high_bits; + } + } + } else if (step == vectorize_high_16_separately) { + scan_mask = mask & BITFIELD_RANGE(4, 4); + mask &= ~scan_mask; + + if (is_load && allow_holes && scan_mask) { + unsigned num = util_last_bit(scan_mask); + unsigned start = ffs(scan_mask) - 1; + scan_mask = BITFIELD_RANGE(start, num - start); + } + } else { + scan_mask = mask; + + if (is_load && allow_holes && scan_mask) { + unsigned num = util_last_bit(scan_mask); + unsigned start = ffs(scan_mask) - 1; + scan_mask = BITFIELD_RANGE(start, num - start); + } + } + + while (scan_mask) { + int start, count; + + u_bit_scan_consecutive_range(&scan_mask, &start, &count); + + if (count == 1 && step != merge_low_high_16_to_32) + continue; /* There is nothing to vectorize. */ + + if (is_load) + vectorize_load(chan, start, count, step); + else + vectorize_store(chan, start, count, step); + + progress = true; + } + } + + return progress; +} + +static bool +vectorize_batch(struct util_dynarray *io_instructions, bool allow_holes) +{ + unsigned num_instr = util_dynarray_num_elements(io_instructions, void *); + + /* We need to at least 2 instructions to have something to do. */ + if (num_instr <= 1) { + /* Clear the array. The next block will reuse it. */ + util_dynarray_clear(io_instructions); + return false; + } + + /* The instructions are sorted such that groups of vectorizable + * instructions are next to each other. Multiple incompatible + * groups of vectorizable instructions can occur in this array. + * The reason why 2 groups would be incompatible is that they + * could have a different intrinsic, indirect index, array index, + * vertex index, barycentrics, or location. Each group is vectorized + * separately. + * + * This reorders instructions in the array, but not in the shader. + */ + qsort(io_instructions->data, num_instr, sizeof(void *), compare_intr); + + nir_intrinsic_instr *chan[8] = { 0 }, *prev = NULL; + unsigned chan_mask = 0; + bool progress = false; + + /* Vectorize all groups. + * + * The channels for each group are gathered. If 2 stores overwrite + * the same channel, the earlier store is DCE'd here. + */ + util_dynarray_foreach(io_instructions, nir_intrinsic_instr *, intr) { + /* If the next instruction is not vectorizable, vectorize what + * we have gathered so far. + */ + if (prev && compare_is_not_vectorizable(prev, *intr)) { + /* We need at least 2 instructions to have something to do. */ + if (util_bitcount(chan_mask) > 1) + progress |= vectorize_slot(chan, chan_mask, allow_holes); + + prev = NULL; + memset(chan, 0, sizeof(chan)); + chan_mask = 0; + } + + /* This performs DCE of output stores because the previous value + * is being overwritten. + */ + unsigned index = nir_intrinsic_io_semantics(*intr).high_16bits * 4 + + nir_intrinsic_component(*intr); + bool is_store = !nir_intrinsic_infos[(*intr)->intrinsic].has_dest; + if (is_store && chan[index]) + nir_instr_remove(&chan[index]->instr); + + /* Gather the channel. */ + chan[index] = *intr; + prev = *intr; + chan_mask |= BITFIELD_BIT(index); + } + + /* Vectorize the last group. */ + if (prev && util_bitcount(chan_mask) > 1) + progress |= vectorize_slot(chan, chan_mask, allow_holes); + + /* Clear the array. The next block will reuse it. */ + util_dynarray_clear(io_instructions); + return progress; +} + +/* Vectorize lowered IO (load_input/store_output/...). + * + * modes specifies whether to vectorize inputs and/or outputs. + * + * allow_holes enables vectorization of loads with holes, e.g.: + * load X; load W; ==> load XYZW; + * + * This is useful for VS input loads where it might not be possible to skip + * loading unused components, e.g. with AMD where loading W also loads XYZ, + * so if we also load X separately again, it's wasteful. It's better to get + * X from the vector that loads (XYZ)W. + */ +bool +nir_opt_vectorize_io(nir_shader *shader, nir_variable_mode modes, + bool allow_holes) +{ + assert(!(modes & ~(nir_var_shader_in | nir_var_shader_out))); + + if (shader->info.stage == MESA_SHADER_FRAGMENT && + shader->options->io_options & nir_io_prefer_scalar_fs_inputs) + modes &= ~nir_var_shader_in; + + if ((shader->info.stage == MESA_SHADER_TESS_CTRL || + shader->info.stage == MESA_SHADER_GEOMETRY) && + util_bitcount(modes) == 2) { + /* When vectorizing TCS and GS IO, inputs can ignore barriers and emits, + * but that is only done when outputs are ignored, so vectorize them + * separately. + */ + bool progress_in = nir_opt_vectorize_io(shader, nir_var_shader_in, + allow_holes); + bool progress_out = nir_opt_vectorize_io(shader, nir_var_shader_out, + allow_holes); + return progress_in || progress_out; + } + + /* Initialize dynamic arrays. */ + struct util_dynarray io_instructions = UTIL_DYNARRAY_INIT; + bool global_progress = false; + + nir_foreach_function_impl(impl, shader) { + bool progress = false; + nir_metadata_require(impl, nir_metadata_instr_index); + + nir_foreach_block(block, impl) { + BITSET_DECLARE(has_output_loads, NUM_TOTAL_VARYING_SLOTS * 8); + BITSET_DECLARE(has_output_stores, NUM_TOTAL_VARYING_SLOTS * 8); + BITSET_ZERO(has_output_loads); + BITSET_ZERO(has_output_stores); + + /* Gather load/store intrinsics within the block. */ + nir_foreach_instr(instr, block) { + if (instr->type != nir_instr_type_intrinsic) + continue; + + nir_intrinsic_instr *intr = nir_instr_as_intrinsic(instr); + bool is_load = nir_intrinsic_infos[intr->intrinsic].has_dest; + bool is_output = false; + nir_io_semantics sem = { 0 }; + unsigned index = 0; + + if (nir_intrinsic_has_io_semantics(intr)) { + sem = nir_intrinsic_io_semantics(intr); + assert(sem.location < NUM_TOTAL_VARYING_SLOTS); + index = sem.location * 8 + sem.high_16bits * 4; + + if (nir_intrinsic_has_component(intr)) + index += nir_intrinsic_component(intr); + } + + switch (intr->intrinsic) { + case nir_intrinsic_load_input: + case nir_intrinsic_load_per_primitive_input: + case nir_intrinsic_load_input_vertex: + case nir_intrinsic_load_interpolated_input: + case nir_intrinsic_load_per_vertex_input: + if (!(modes & nir_var_shader_in)) + continue; + break; + + case nir_intrinsic_load_output: + case nir_intrinsic_load_per_vertex_output: + case nir_intrinsic_load_per_view_output: + case nir_intrinsic_load_per_primitive_output: + case nir_intrinsic_store_output: + case nir_intrinsic_store_per_vertex_output: + case nir_intrinsic_store_per_view_output: + case nir_intrinsic_store_per_primitive_output: + if (!(modes & nir_var_shader_out)) + continue; + + /* Break the batch if an output load is followed by an output + * store to the same channel and vice versa. + */ + if (BITSET_TEST(is_load ? has_output_stores : has_output_loads, + index)) { + progress |= vectorize_batch(&io_instructions, allow_holes); + BITSET_ZERO(has_output_loads); + BITSET_ZERO(has_output_stores); + } + is_output = true; + break; + + case nir_intrinsic_barrier: + /* Don't vectorize across TCS barriers. */ + if (modes & nir_var_shader_out && + nir_intrinsic_memory_modes(intr) & nir_var_shader_out) { + progress |= vectorize_batch(&io_instructions, allow_holes); + BITSET_ZERO(has_output_loads); + BITSET_ZERO(has_output_stores); + } + continue; + + case nir_intrinsic_emit_vertex: + /* Don't vectorize across GS emits. */ + progress |= vectorize_batch(&io_instructions, allow_holes); + BITSET_ZERO(has_output_loads); + BITSET_ZERO(has_output_stores); + continue; + + default: + continue; + } + + /* Only scalar 16 and 32-bit instructions are allowed. */ + ASSERTED nir_def *value = is_load ? &intr->def : intr->src[0].ssa; + assert(value->num_components == 1); + assert(value->bit_size == 16 || value->bit_size == 32); + + util_dynarray_append(&io_instructions, intr); + if (is_output) + BITSET_SET(is_load ? has_output_loads : has_output_stores, index); + } + + progress |= vectorize_batch(&io_instructions, allow_holes); + } + + nir_progress(progress, impl, + nir_metadata_block_index | nir_metadata_dominance); + global_progress |= progress; + } + util_dynarray_fini(&io_instructions); + + return global_progress; +} diff --git a/references/nir_opt_vectorize_io_vars.c b/references/nir_opt_vectorize_io_vars.c new file mode 100644 index 00000000..dd8c8505 --- /dev/null +++ b/references/nir_opt_vectorize_io_vars.c @@ -0,0 +1,636 @@ +/* + * Copyright © 2019 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "util/u_dynarray.h" +#include "nir.h" +#include "nir_builder.h" +#include "nir_deref.h" + +/** @file nir_opt_vectorize_io_vars.c + * + * Merges compatible input/output variables residing in different components + * of the same location. It's expected that further passes such as + * nir_lower_io_vars_to_temporaries will combine loads and stores of the merged + * variables, producing vector nir_load_input/nir_store_output instructions + * when all is said and done. + */ + +/* FRAG_RESULT_MAX+1 instead of just FRAG_RESULT_MAX because of how this pass + * handles dual source blending */ +#define MAX_SLOTS MAX2(VARYING_SLOT_TESS_MAX, FRAG_RESULT_MAX + 1) + +static unsigned +get_slot(const nir_variable *var) +{ + /* This handling of dual-source blending might not be correct when more than + * one render target is supported, but it seems no driver supports more than + * one. */ + return var->data.location + var->data.index; +} + +static const struct glsl_type * +get_per_vertex_type(const nir_shader *shader, const nir_variable *var, + unsigned *num_vertices) +{ + if (nir_is_arrayed_io(var, shader->info.stage)) { + assert(glsl_type_is_array(var->type)); + if (num_vertices) + *num_vertices = glsl_get_length(var->type); + return glsl_get_array_element(var->type); + } else { + if (num_vertices) + *num_vertices = 0; + return var->type; + } +} + +static const struct glsl_type * +resize_array_vec_type(const struct glsl_type *type, unsigned num_components) +{ + if (glsl_type_is_array(type)) { + const struct glsl_type *arr_elem = + resize_array_vec_type(glsl_get_array_element(type), num_components); + return glsl_array_type(arr_elem, glsl_get_length(type), 0); + } else { + assert(glsl_type_is_vector_or_scalar(type)); + return glsl_vector_type(glsl_get_base_type(type), num_components); + } +} + +static bool +variables_can_merge(const nir_shader *shader, + const nir_variable *a, const nir_variable *b, + bool same_array_structure) +{ + if (a->data.compact || b->data.compact) + return false; + + if (a->data.per_view || b->data.per_view) + return false; + + const struct glsl_type *a_type_tail = a->type; + const struct glsl_type *b_type_tail = b->type; + + if (nir_is_arrayed_io(a, shader->info.stage) != + nir_is_arrayed_io(b, shader->info.stage)) + return false; + + /* They must have the same array structure */ + if (same_array_structure) { + while (glsl_type_is_array(a_type_tail)) { + if (!glsl_type_is_array(b_type_tail)) + return false; + + if (glsl_get_length(a_type_tail) != glsl_get_length(b_type_tail)) + return false; + + a_type_tail = glsl_get_array_element(a_type_tail); + b_type_tail = glsl_get_array_element(b_type_tail); + } + if (glsl_type_is_array(b_type_tail)) + return false; + } else { + a_type_tail = glsl_without_array(a_type_tail); + b_type_tail = glsl_without_array(b_type_tail); + } + + if (!glsl_type_is_vector_or_scalar(a_type_tail) || + !glsl_type_is_vector_or_scalar(b_type_tail)) + return false; + + if (glsl_get_base_type(a_type_tail) != glsl_get_base_type(b_type_tail)) + return false; + + /* TODO: add 64/16bit support ? */ + if (glsl_get_bit_size(a_type_tail) != 32) + return false; + + assert(a->data.mode == b->data.mode); + if (shader->info.stage == MESA_SHADER_FRAGMENT && + a->data.mode == nir_var_shader_in && + (a->data.interpolation != b->data.interpolation || + a->data.centroid != b->data.centroid || + a->data.sample != b->data.sample)) + return false; + + if (shader->info.stage == MESA_SHADER_FRAGMENT && + a->data.mode == nir_var_shader_out && + a->data.index != b->data.index) + return false; + + /* It's tricky to merge XFB-outputs correctly, because we need there + * to not be any overlaps when we get to + * nir_gather_xfb_info_with_varyings later on. We'll end up + * triggering an assert there if we merge here. + */ + if ((shader->info.stage == MESA_SHADER_VERTEX || + shader->info.stage == MESA_SHADER_TESS_EVAL || + shader->info.stage == MESA_SHADER_GEOMETRY) && + a->data.mode == nir_var_shader_out && + (a->data.explicit_xfb_buffer || b->data.explicit_xfb_buffer)) + return false; + + return true; +} + +static const struct glsl_type * +get_flat_type(const nir_shader *shader, nir_variable *old_vars[MAX_SLOTS][4], + unsigned *loc, nir_variable **first_var, unsigned *num_vertices) +{ + unsigned todo = 1; + unsigned slots = 0; + unsigned num_vars = 0; + enum glsl_base_type base = GLSL_TYPE_ERROR; + *num_vertices = 0; + *first_var = NULL; + + while (todo) { + assert(*loc < MAX_SLOTS); + for (unsigned frac = 0; frac < 4; frac++) { + nir_variable *var = old_vars[*loc][frac]; + if (!var) + continue; + if ((*first_var && + !variables_can_merge(shader, var, *first_var, false)) || + var->data.compact) { + (*loc)++; + return NULL; + } + + if (!*first_var) { + if (!glsl_type_is_vector_or_scalar(glsl_without_array(var->type))) { + (*loc)++; + return NULL; + } + *first_var = var; + base = glsl_get_base_type( + glsl_without_array(get_per_vertex_type(shader, var, NULL))); + } + + bool vs_in = shader->info.stage == MESA_SHADER_VERTEX && + var->data.mode == nir_var_shader_in; + unsigned var_slots = glsl_count_attribute_slots( + get_per_vertex_type(shader, var, num_vertices), vs_in); + todo = MAX2(todo, var_slots); + num_vars++; + } + todo--; + slots++; + (*loc)++; + } + + if (num_vars <= 1) + return NULL; + + if (slots == 1) + return glsl_vector_type(base, 4); + else + return glsl_array_type(glsl_vector_type(base, 4), slots, 0); +} + +static bool +create_new_io_vars(nir_shader *shader, nir_variable_mode mode, + nir_variable *new_vars[MAX_SLOTS][4], + bool flat_vars[MAX_SLOTS], + struct util_dynarray *demote_vars) +{ + nir_variable *old_vars[MAX_SLOTS][4] = { { 0 } }; + + bool has_io_var = false; + nir_foreach_variable_with_modes(var, shader, mode) { + unsigned frac = var->data.location_frac; + old_vars[get_slot(var)][frac] = var; + has_io_var = true; + } + + if (!has_io_var) + return false; + + bool merged_any_vars = false; + + for (unsigned loc = 0; loc < MAX_SLOTS; loc++) { + unsigned frac = 0; + while (frac < 4) { + nir_variable *first_var = old_vars[loc][frac]; + if (!first_var) { + frac++; + continue; + } + + int first = frac; + bool found_merge = false; + + while (frac < 4) { + nir_variable *var = old_vars[loc][frac]; + if (!var) + break; + + if (var != first_var) { + if (!variables_can_merge(shader, first_var, var, true)) + break; + + found_merge = true; + } + + const unsigned num_components = + glsl_get_vector_elements(glsl_without_array(var->type)); + if (!num_components) { + assert(frac == 0); + frac++; + break; /* The type was a struct. */ + } + + /* We had better not have any overlapping vars */ + for (unsigned i = 1; i < num_components; i++) + assert(old_vars[loc][frac + i] == NULL); + + frac += num_components; + } + + if (!found_merge) + continue; + + merged_any_vars = true; + + nir_variable *var = nir_variable_clone(old_vars[loc][first], shader); + var->data.location_frac = first; + var->type = resize_array_vec_type(var->type, frac - first); + + nir_shader_add_variable(shader, var); + for (unsigned i = first; i < frac; i++) { + new_vars[loc][i] = var; + if (old_vars[loc][i]) { + util_dynarray_append(demote_vars, old_vars[loc][i]); + old_vars[loc][i] = NULL; + } + } + + old_vars[loc][first] = var; + } + } + + /* "flat" mode: tries to ensure there is at most one variable per slot by + * merging variables into vec4s + */ + for (unsigned loc = 0; loc < MAX_SLOTS;) { + nir_variable *first_var; + unsigned num_vertices; + unsigned new_loc = loc; + const struct glsl_type *flat_type = + get_flat_type(shader, old_vars, &new_loc, &first_var, &num_vertices); + if (flat_type) { + merged_any_vars = true; + + nir_variable *var = nir_variable_clone(first_var, shader); + var->data.location_frac = 0; + if (num_vertices) + var->type = glsl_array_type(flat_type, num_vertices, 0); + else + var->type = flat_type; + + nir_shader_add_variable(shader, var); + unsigned num_slots = + glsl_type_is_array(flat_type) ? glsl_get_length(flat_type) : 1; + for (unsigned i = 0; i < num_slots; i++) { + for (unsigned j = 0; j < 4; j++) + new_vars[loc + i][j] = var; + flat_vars[loc + i] = true; + } + } + loc = new_loc; + } + + return merged_any_vars; +} + +static nir_deref_instr * +build_array_deref_of_new_var(nir_builder *b, nir_variable *new_var, + nir_deref_instr *leader) +{ + if (leader->deref_type == nir_deref_type_var) + return nir_build_deref_var(b, new_var); + + nir_deref_instr *parent = + build_array_deref_of_new_var(b, new_var, nir_deref_instr_parent(leader)); + + return nir_build_deref_follower(b, parent, leader); +} + +static nir_def * +build_array_index(nir_builder *b, nir_deref_instr *deref, nir_def *base, + bool vs_in, bool per_vertex) +{ + switch (deref->deref_type) { + case nir_deref_type_var: + return base; + case nir_deref_type_array: { + nir_def *index = nir_i2iN(b, deref->arr.index.ssa, + deref->def.bit_size); + + if (nir_deref_instr_parent(deref)->deref_type == nir_deref_type_var && + per_vertex) + return base; + + return nir_iadd( + b, build_array_index(b, nir_deref_instr_parent(deref), base, vs_in, per_vertex), + nir_amul_imm(b, index, glsl_count_attribute_slots(deref->type, vs_in))); + } + default: + UNREACHABLE("Invalid deref instruction type"); + } +} + +static nir_deref_instr * +build_array_deref_of_new_var_flat(nir_shader *shader, + nir_builder *b, nir_variable *new_var, + nir_deref_instr *leader, unsigned base) +{ + nir_deref_instr *deref = nir_build_deref_var(b, new_var); + + bool per_vertex = nir_is_arrayed_io(new_var, shader->info.stage); + if (per_vertex) { + nir_deref_path path; + nir_deref_path_init(&path, leader, NULL); + + assert(path.path[0]->deref_type == nir_deref_type_var); + nir_deref_instr *p = path.path[1]; + nir_deref_path_finish(&path); + + nir_def *index = p->arr.index.ssa; + deref = nir_build_deref_array(b, deref, index); + } + + if (!glsl_type_is_array(deref->type)) + return deref; + + bool vs_in = shader->info.stage == MESA_SHADER_VERTEX && + new_var->data.mode == nir_var_shader_in; + return nir_build_deref_array(b, deref, + build_array_index(b, leader, nir_imm_int(b, base), vs_in, per_vertex)); +} + +static bool +nir_opt_vectorize_io_vars_impl(nir_function_impl *impl, nir_variable_mode modes) +{ + assert(!(modes & ~(nir_var_shader_in | nir_var_shader_out))); + + nir_builder b = nir_builder_create(impl); + + nir_metadata_require(impl, nir_metadata_dominance); + + struct util_dynarray demote_vars = UTIL_DYNARRAY_INIT; + + nir_shader *shader = impl->function->shader; + nir_variable *new_inputs[MAX_SLOTS][4] = { { 0 } }; + nir_variable *new_outputs[MAX_SLOTS][4] = { { 0 } }; + bool flat_inputs[MAX_SLOTS] = { 0 }; + bool flat_outputs[MAX_SLOTS] = { 0 }; + + if (modes & nir_var_shader_in) { + /* Vertex shaders support overlapping inputs. We don't do those */ + assert(b.shader->info.stage != MESA_SHADER_VERTEX); + + /* If we don't actually merge any variables, remove that bit from modes + * so we don't bother doing extra non-work. + */ + if (!create_new_io_vars(shader, nir_var_shader_in, + new_inputs, flat_inputs, &demote_vars)) + modes &= ~nir_var_shader_in; + } + + if (modes & nir_var_shader_out) { + /* If we don't actually merge any variables, remove that bit from modes + * so we don't bother doing extra non-work. + */ + if (!create_new_io_vars(shader, nir_var_shader_out, + new_outputs, flat_outputs, &demote_vars)) + modes &= ~nir_var_shader_out; + } + + if (!modes) + return false; + + bool progress = false; + + /* Actually lower all the IO load/store intrinsics. Load instructions are + * lowered to a vector load and an ALU instruction to grab the channels we + * want. Outputs are lowered to a write-masked store of the vector output. + * For non-TCS outputs, we then run nir_lower_io_vars_to_temporaries at the end + * to clean up the partial writes. + */ + nir_foreach_block(block, impl) { + nir_foreach_instr_safe(instr, block) { + if (instr->type != nir_instr_type_intrinsic) + continue; + + nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr); + + switch (intrin->intrinsic) { + case nir_intrinsic_load_deref: + case nir_intrinsic_interp_deref_at_centroid: + case nir_intrinsic_interp_deref_at_sample: + case nir_intrinsic_interp_deref_at_offset: + case nir_intrinsic_interp_deref_at_vertex: { + nir_deref_instr *old_deref = nir_src_as_deref(intrin->src[0]); + if (!nir_deref_mode_is_one_of(old_deref, modes)) + break; + + nir_variable *old_var = nir_deref_instr_get_variable(old_deref); + + const unsigned loc = get_slot(old_var); + const unsigned old_frac = old_var->data.location_frac; + nir_variable *new_var = old_var->data.mode == nir_var_shader_in ? new_inputs[loc][old_frac] : new_outputs[loc][old_frac]; + bool flat = old_var->data.mode == nir_var_shader_in ? flat_inputs[loc] : flat_outputs[loc]; + if (!new_var) + break; + + const unsigned new_frac = new_var->data.location_frac; + + nir_component_mask_t vec4_comp_mask = + ((1 << intrin->num_components) - 1) << old_frac; + + b.cursor = nir_before_instr(&intrin->instr); + + /* Rewrite the load to use the new variable and only select a + * portion of the result. + */ + nir_deref_instr *new_deref; + if (flat) { + new_deref = build_array_deref_of_new_var_flat( + shader, &b, new_var, old_deref, loc - get_slot(new_var)); + } else { + assert(get_slot(new_var) == loc); + new_deref = build_array_deref_of_new_var(&b, new_var, old_deref); + assert(glsl_type_is_vector(new_deref->type)); + } + nir_src_rewrite(&intrin->src[0], &new_deref->def); + + intrin->num_components = + glsl_get_components(new_deref->type); + intrin->def.num_components = intrin->num_components; + + b.cursor = nir_after_instr(&intrin->instr); + + nir_def *new_vec = nir_channels(&b, &intrin->def, + vec4_comp_mask >> new_frac); + nir_def_rewrite_uses_after(&intrin->def, new_vec); + + progress = true; + break; + } + + case nir_intrinsic_store_deref: { + nir_deref_instr *old_deref = nir_src_as_deref(intrin->src[0]); + if (!nir_deref_mode_is(old_deref, nir_var_shader_out)) + break; + + nir_variable *old_var = nir_deref_instr_get_variable(old_deref); + + const unsigned loc = get_slot(old_var); + const unsigned old_frac = old_var->data.location_frac; + nir_variable *new_var = new_outputs[loc][old_frac]; + bool flat = flat_outputs[loc]; + if (!new_var) + break; + + const unsigned new_frac = new_var->data.location_frac; + + b.cursor = nir_before_instr(&intrin->instr); + + /* Rewrite the store to be a masked store to the new variable */ + nir_deref_instr *new_deref; + if (flat) { + new_deref = build_array_deref_of_new_var_flat( + shader, &b, new_var, old_deref, loc - get_slot(new_var)); + } else { + assert(get_slot(new_var) == loc); + new_deref = build_array_deref_of_new_var(&b, new_var, old_deref); + assert(glsl_type_is_vector(new_deref->type)); + } + nir_src_rewrite(&intrin->src[0], &new_deref->def); + + intrin->num_components = + glsl_get_components(new_deref->type); + + nir_component_mask_t old_wrmask = nir_intrinsic_write_mask(intrin); + + nir_def *old_value = intrin->src[1].ssa; + nir_scalar comps[4]; + for (unsigned c = 0; c < intrin->num_components; c++) { + if (new_frac + c >= old_frac && + (old_wrmask & 1 << (new_frac + c - old_frac))) { + comps[c] = nir_get_scalar(old_value, + new_frac + c - old_frac); + } else { + comps[c] = nir_get_scalar(nir_undef(&b, old_value->num_components, + old_value->bit_size), + 0); + } + } + nir_def *new_value = nir_vec_scalars(&b, comps, intrin->num_components); + nir_src_rewrite(&intrin->src[1], new_value); + + nir_intrinsic_set_write_mask(intrin, + old_wrmask << (old_frac - new_frac)); + + progress = true; + break; + } + + default: + break; + } + } + } + + /* Demote the old var to a global, so that things like + * nir_lower_io_vars_to_temporaries() don't trigger on it. + */ + util_dynarray_foreach(&demote_vars, nir_variable *, varp) { + (*varp)->data.mode = nir_var_shader_temp; + } + nir_fixup_deref_modes(b.shader); + util_dynarray_fini(&demote_vars); + + return nir_progress(progress, impl, nir_metadata_control_flow); +} + +bool +nir_opt_vectorize_io_vars(nir_shader *shader, nir_variable_mode modes) +{ + bool progress = false; + + nir_foreach_function_impl(impl, shader) { + progress |= nir_opt_vectorize_io_vars_impl(impl, modes); + } + + return progress; +} + +static bool +is_tess_level_variable(nir_variable *var) +{ + return var->data.location == VARYING_SLOT_TESS_LEVEL_OUTER || + var->data.location == VARYING_SLOT_TESS_LEVEL_INNER; +} + +/* Make the tess factor variables vectors instead of compact arrays, so accesses + * can be combined by nir_opt_cse()/nir_opt_combine_stores(). + */ +bool +nir_lower_tess_level_array_vars_to_vec(nir_shader *shader) +{ + nir_variable_mode mode; + if (shader->info.stage == MESA_SHADER_TESS_CTRL) + mode = nir_var_shader_out; + else if (shader->info.stage == MESA_SHADER_TESS_EVAL) + mode = nir_var_shader_in; + else + return false; + + bool progress = false; + nir_foreach_variable_with_modes(var, shader, mode) { + if (is_tess_level_variable(var)) { + var->type = glsl_vector_type(GLSL_TYPE_FLOAT, glsl_get_length(var->type)); + var->data.compact = false; + progress = true; + } + } + + if (progress) { + nir_fixup_deref_types(shader); + + nir_lower_array_deref_of_vec(shader, mode, is_tess_level_variable, + nir_lower_direct_array_deref_of_vec_load | + nir_lower_indirect_array_deref_of_vec_load | + nir_lower_direct_array_deref_of_vec_store | + nir_lower_indirect_array_deref_of_vec_store); + + /* Remove dead array deref instructions to avoid nir_validate() complain + * array_deref on vector variable. + */ + nir_opt_dce(shader); + } + + return progress; +} diff --git a/references/writer/BUILD.bazel b/references/writer/BUILD.bazel new file mode 100644 index 00000000..8ecd6b79 --- /dev/null +++ b/references/writer/BUILD.bazel @@ -0,0 +1,140 @@ +# Copyright 2023 The Dawn & Tint Authors +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +################################################################################ +# File generated by 'tools/src/cmd/gen' using the template: +# tools/src/cmd/gen/build/BUILD.bazel.tmpl +# +# To regenerate run: './tools/run gen' +# +# Do not modify this file directly +################################################################################ + +load("//src/tint:flags.bzl", "COPTS") +load("@bazel_skylib//lib:selects.bzl", "selects") +cc_library( + name = "writer", + srcs = [ + "output.cc", + "writer.cc", + ], + hdrs = [ + "output.h", + "writer.h", + ], + deps = [ + "//src/tint/api/common", + "//src/tint/api/options", + "//src/tint/lang/core", + "//src/tint/lang/core/constant", + "//src/tint/lang/core/type", + "//src/tint/lang/glsl/writer/raise", + "//src/tint/lang/wgsl", + "//src/tint/lang/wgsl/ast", + "//src/tint/lang/wgsl/ast/transform", + "//src/tint/lang/wgsl/features", + "//src/tint/lang/wgsl/program", + "//src/tint/lang/wgsl/sem", + "//src/tint/utils/containers", + "//src/tint/utils/diagnostic", + "//src/tint/utils/generator", + "//src/tint/utils/ice", + "//src/tint/utils/id", + "//src/tint/utils/macros", + "//src/tint/utils/math", + "//src/tint/utils/memory", + "//src/tint/utils/reflection", + "//src/tint/utils/result", + "//src/tint/utils/rtti", + "//src/tint/utils/symbol", + "//src/tint/utils/text", + "//src/tint/utils/traits", + ] + select({ + ":tint_build_glsl_writer": [ + "//src/tint/lang/glsl/writer/ast_printer", + "//src/tint/lang/glsl/writer/common", + "//src/tint/lang/glsl/writer/printer", + ], + "//conditions:default": [], + }), + copts = COPTS, + visibility = ["//visibility:public"], +) +cc_library( + name = "bench", + alwayslink = True, + srcs = [ + "writer_bench.cc", + ], + deps = [ + "//src/tint/api/common", + "//src/tint/api/options", + "//src/tint/cmd/bench:bench", + "//src/tint/lang/core", + "//src/tint/lang/core/constant", + "//src/tint/lang/core/type", + "//src/tint/lang/wgsl", + "//src/tint/lang/wgsl/ast", + "//src/tint/lang/wgsl/ast/transform", + "//src/tint/lang/wgsl/features", + "//src/tint/lang/wgsl/program", + "//src/tint/lang/wgsl/sem", + "//src/tint/utils/containers", + "//src/tint/utils/diagnostic", + "//src/tint/utils/ice", + "//src/tint/utils/id", + "//src/tint/utils/macros", + "//src/tint/utils/math", + "//src/tint/utils/memory", + "//src/tint/utils/reflection", + "//src/tint/utils/result", + "//src/tint/utils/rtti", + "//src/tint/utils/symbol", + "//src/tint/utils/text", + "//src/tint/utils/traits", + "@benchmark", + ] + select({ + ":tint_build_glsl_writer": [ + "//src/tint/lang/glsl/writer", + "//src/tint/lang/glsl/writer/common", + ], + "//conditions:default": [], + }), + copts = COPTS, + visibility = ["//visibility:public"], +) + +alias( + name = "tint_build_glsl_writer", + actual = "//src/tint:tint_build_glsl_writer_true", +) + +alias( + name = "tint_build_wgsl_reader", + actual = "//src/tint:tint_build_wgsl_reader_true", +) + diff --git a/references/writer/BUILD.cfg b/references/writer/BUILD.cfg new file mode 100644 index 00000000..7459430e --- /dev/null +++ b/references/writer/BUILD.cfg @@ -0,0 +1,3 @@ +{ + "condition": "tint_build_glsl_writer" +} diff --git a/references/writer/BUILD.cmake b/references/writer/BUILD.cmake new file mode 100644 index 00000000..6939a358 --- /dev/null +++ b/references/writer/BUILD.cmake @@ -0,0 +1,198 @@ +# Copyright 2023 The Dawn & Tint Authors +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +################################################################################ +# File generated by 'tools/src/cmd/gen' using the template: +# tools/src/cmd/gen/build/BUILD.cmake.tmpl +# +# To regenerate run: './tools/run gen' +# +# Do not modify this file directly +################################################################################ + +include(lang/glsl/writer/ast_printer/BUILD.cmake) +include(lang/glsl/writer/ast_raise/BUILD.cmake) +include(lang/glsl/writer/common/BUILD.cmake) +include(lang/glsl/writer/printer/BUILD.cmake) +include(lang/glsl/writer/raise/BUILD.cmake) + +if(TINT_BUILD_GLSL_WRITER) +################################################################################ +# Target: tint_lang_glsl_writer +# Kind: lib +# Condition: TINT_BUILD_GLSL_WRITER +################################################################################ +tint_add_target(tint_lang_glsl_writer lib + lang/glsl/writer/output.cc + lang/glsl/writer/output.h + lang/glsl/writer/writer.cc + lang/glsl/writer/writer.h +) + +tint_target_add_dependencies(tint_lang_glsl_writer lib + tint_api_common + tint_api_options + tint_lang_core + tint_lang_core_constant + tint_lang_core_type + tint_lang_glsl_writer_raise + tint_lang_wgsl + tint_lang_wgsl_ast + tint_lang_wgsl_ast_transform + tint_lang_wgsl_features + tint_lang_wgsl_program + tint_lang_wgsl_sem + tint_utils_containers + tint_utils_diagnostic + tint_utils_generator + tint_utils_ice + tint_utils_id + tint_utils_macros + tint_utils_math + tint_utils_memory + tint_utils_reflection + tint_utils_result + tint_utils_rtti + tint_utils_symbol + tint_utils_text + tint_utils_traits +) + +if(TINT_BUILD_GLSL_WRITER) + tint_target_add_dependencies(tint_lang_glsl_writer lib + tint_lang_glsl_writer_ast_printer + tint_lang_glsl_writer_common + tint_lang_glsl_writer_printer + ) +endif(TINT_BUILD_GLSL_WRITER) + +endif(TINT_BUILD_GLSL_WRITER) +if(TINT_BUILD_GLSL_WRITER) +################################################################################ +# Target: tint_lang_glsl_writer_bench +# Kind: bench +# Condition: TINT_BUILD_GLSL_WRITER +################################################################################ +tint_add_target(tint_lang_glsl_writer_bench bench + lang/glsl/writer/writer_bench.cc +) + +tint_target_add_dependencies(tint_lang_glsl_writer_bench bench + tint_api_common + tint_api_options + tint_cmd_bench_bench + tint_lang_core + tint_lang_core_constant + tint_lang_core_type + tint_lang_wgsl + tint_lang_wgsl_ast + tint_lang_wgsl_ast_transform + tint_lang_wgsl_features + tint_lang_wgsl_program + tint_lang_wgsl_sem + tint_utils_containers + tint_utils_diagnostic + tint_utils_ice + tint_utils_id + tint_utils_macros + tint_utils_math + tint_utils_memory + tint_utils_reflection + tint_utils_result + tint_utils_rtti + tint_utils_symbol + tint_utils_text + tint_utils_traits +) + +tint_target_add_external_dependencies(tint_lang_glsl_writer_bench bench + "google-benchmark" +) + +if(TINT_BUILD_GLSL_WRITER) + tint_target_add_dependencies(tint_lang_glsl_writer_bench bench + tint_lang_glsl_writer + tint_lang_glsl_writer_common + ) +endif(TINT_BUILD_GLSL_WRITER) + +endif(TINT_BUILD_GLSL_WRITER) +if(TINT_BUILD_GLSL_WRITER) +################################################################################ +# Target: tint_lang_glsl_writer_fuzz +# Kind: fuzz +# Condition: TINT_BUILD_GLSL_WRITER +################################################################################ +tint_add_target(tint_lang_glsl_writer_fuzz fuzz +) + +tint_target_add_dependencies(tint_lang_glsl_writer_fuzz fuzz + tint_api_common + tint_api_options + tint_lang_core + tint_lang_core_constant + tint_lang_core_type + tint_lang_wgsl + tint_lang_wgsl_ast + tint_lang_wgsl_ast_transform + tint_lang_wgsl_features + tint_lang_wgsl_inspector + tint_lang_wgsl_program + tint_lang_wgsl_sem + tint_utils_bytes + tint_utils_containers + tint_utils_diagnostic + tint_utils_ice + tint_utils_id + tint_utils_macros + tint_utils_math + tint_utils_memory + tint_utils_reflection + tint_utils_result + tint_utils_rtti + tint_utils_symbol + tint_utils_text + tint_utils_traits +) + +if(TINT_BUILD_GLSL_WRITER) + tint_target_add_dependencies(tint_lang_glsl_writer_fuzz fuzz + tint_lang_glsl_writer + tint_lang_glsl_writer_common + ) +endif(TINT_BUILD_GLSL_WRITER) + +if(TINT_BUILD_WGSL_READER) + tint_target_add_sources(tint_lang_glsl_writer_fuzz fuzz + "lang/glsl/writer/writer_ast_fuzz.cc" + ) + tint_target_add_dependencies(tint_lang_glsl_writer_fuzz fuzz + tint_cmd_fuzz_wgsl_fuzz + ) +endif(TINT_BUILD_WGSL_READER) + +endif(TINT_BUILD_GLSL_WRITER) \ No newline at end of file diff --git a/references/writer/BUILD.gn b/references/writer/BUILD.gn new file mode 100644 index 00000000..4b6b5145 --- /dev/null +++ b/references/writer/BUILD.gn @@ -0,0 +1,176 @@ +# Copyright 2023 The Dawn & Tint Authors +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +################################################################################ +# File generated by 'tools/src/cmd/gen' using the template: +# tools/src/cmd/gen/build/BUILD.gn.tmpl +# +# To regenerate run: './tools/run gen' +# +# Do not modify this file directly +################################################################################ + +import("../../../../../scripts/tint_overrides_with_defaults.gni") + +import("${tint_src_dir}/tint.gni") + +if (tint_build_unittests || tint_build_benchmarks) { + import("//testing/test.gni") +} +if (tint_build_glsl_writer) { + libtint_source_set("writer") { + sources = [ + "output.cc", + "output.h", + "writer.cc", + "writer.h", + ] + deps = [ + "${tint_src_dir}/api/common", + "${tint_src_dir}/api/options", + "${tint_src_dir}/lang/core", + "${tint_src_dir}/lang/core/constant", + "${tint_src_dir}/lang/core/type", + "${tint_src_dir}/lang/glsl/writer/raise", + "${tint_src_dir}/lang/wgsl", + "${tint_src_dir}/lang/wgsl/ast", + "${tint_src_dir}/lang/wgsl/ast/transform", + "${tint_src_dir}/lang/wgsl/features", + "${tint_src_dir}/lang/wgsl/program", + "${tint_src_dir}/lang/wgsl/sem", + "${tint_src_dir}/utils/containers", + "${tint_src_dir}/utils/diagnostic", + "${tint_src_dir}/utils/generator", + "${tint_src_dir}/utils/ice", + "${tint_src_dir}/utils/id", + "${tint_src_dir}/utils/macros", + "${tint_src_dir}/utils/math", + "${tint_src_dir}/utils/memory", + "${tint_src_dir}/utils/reflection", + "${tint_src_dir}/utils/result", + "${tint_src_dir}/utils/rtti", + "${tint_src_dir}/utils/symbol", + "${tint_src_dir}/utils/text", + "${tint_src_dir}/utils/traits", + ] + + if (tint_build_glsl_writer) { + deps += [ + "${tint_src_dir}/lang/glsl/writer/ast_printer", + "${tint_src_dir}/lang/glsl/writer/common", + "${tint_src_dir}/lang/glsl/writer/printer", + ] + } + } +} +if (tint_build_benchmarks) { + if (tint_build_glsl_writer) { + tint_unittests_source_set("bench") { + sources = [ "writer_bench.cc" ] + deps = [ + "${tint_src_dir}:google_benchmark", + "${tint_src_dir}/api/common", + "${tint_src_dir}/api/options", + "${tint_src_dir}/cmd/bench:bench", + "${tint_src_dir}/lang/core", + "${tint_src_dir}/lang/core/constant", + "${tint_src_dir}/lang/core/type", + "${tint_src_dir}/lang/wgsl", + "${tint_src_dir}/lang/wgsl/ast", + "${tint_src_dir}/lang/wgsl/ast/transform", + "${tint_src_dir}/lang/wgsl/features", + "${tint_src_dir}/lang/wgsl/program", + "${tint_src_dir}/lang/wgsl/sem", + "${tint_src_dir}/utils/containers", + "${tint_src_dir}/utils/diagnostic", + "${tint_src_dir}/utils/ice", + "${tint_src_dir}/utils/id", + "${tint_src_dir}/utils/macros", + "${tint_src_dir}/utils/math", + "${tint_src_dir}/utils/memory", + "${tint_src_dir}/utils/reflection", + "${tint_src_dir}/utils/result", + "${tint_src_dir}/utils/rtti", + "${tint_src_dir}/utils/symbol", + "${tint_src_dir}/utils/text", + "${tint_src_dir}/utils/traits", + ] + + if (tint_build_glsl_writer) { + deps += [ + "${tint_src_dir}/lang/glsl/writer", + "${tint_src_dir}/lang/glsl/writer/common", + ] + } + } + } +} +if (tint_build_glsl_writer) { + tint_fuzz_source_set("fuzz") { + sources = [] + deps = [ + "${tint_src_dir}/api/common", + "${tint_src_dir}/api/options", + "${tint_src_dir}/lang/core", + "${tint_src_dir}/lang/core/constant", + "${tint_src_dir}/lang/core/type", + "${tint_src_dir}/lang/wgsl", + "${tint_src_dir}/lang/wgsl/ast", + "${tint_src_dir}/lang/wgsl/ast/transform", + "${tint_src_dir}/lang/wgsl/features", + "${tint_src_dir}/lang/wgsl/inspector", + "${tint_src_dir}/lang/wgsl/program", + "${tint_src_dir}/lang/wgsl/sem", + "${tint_src_dir}/utils/bytes", + "${tint_src_dir}/utils/containers", + "${tint_src_dir}/utils/diagnostic", + "${tint_src_dir}/utils/ice", + "${tint_src_dir}/utils/id", + "${tint_src_dir}/utils/macros", + "${tint_src_dir}/utils/math", + "${tint_src_dir}/utils/memory", + "${tint_src_dir}/utils/reflection", + "${tint_src_dir}/utils/result", + "${tint_src_dir}/utils/rtti", + "${tint_src_dir}/utils/symbol", + "${tint_src_dir}/utils/text", + "${tint_src_dir}/utils/traits", + ] + + if (tint_build_glsl_writer) { + deps += [ + "${tint_src_dir}/lang/glsl/writer", + "${tint_src_dir}/lang/glsl/writer/common", + ] + } + + if (tint_build_wgsl_reader) { + sources += [ "writer_ast_fuzz.cc" ] + deps += [ "${tint_src_dir}/cmd/fuzz/wgsl:fuzz" ] + } + } +} diff --git a/references/writer/ast_printer/BUILD.bazel b/references/writer/ast_printer/BUILD.bazel new file mode 100644 index 00000000..28417196 --- /dev/null +++ b/references/writer/ast_printer/BUILD.bazel @@ -0,0 +1,167 @@ +# Copyright 2023 The Dawn & Tint Authors +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +################################################################################ +# File generated by 'tools/src/cmd/gen' using the template: +# tools/src/cmd/gen/build/BUILD.bazel.tmpl +# +# To regenerate run: './tools/run gen' +# +# Do not modify this file directly +################################################################################ + +load("//src/tint:flags.bzl", "COPTS") +load("@bazel_skylib//lib:selects.bzl", "selects") +cc_library( + name = "ast_printer", + srcs = [ + "ast_printer.cc", + ], + hdrs = [ + "ast_printer.h", + ], + deps = [ + "//src/tint/api/common", + "//src/tint/api/options", + "//src/tint/lang/core", + "//src/tint/lang/core/constant", + "//src/tint/lang/core/type", + "//src/tint/lang/wgsl", + "//src/tint/lang/wgsl/ast", + "//src/tint/lang/wgsl/ast/transform", + "//src/tint/lang/wgsl/features", + "//src/tint/lang/wgsl/helpers", + "//src/tint/lang/wgsl/program", + "//src/tint/lang/wgsl/sem", + "//src/tint/utils/containers", + "//src/tint/utils/diagnostic", + "//src/tint/utils/generator", + "//src/tint/utils/ice", + "//src/tint/utils/id", + "//src/tint/utils/macros", + "//src/tint/utils/math", + "//src/tint/utils/memory", + "//src/tint/utils/reflection", + "//src/tint/utils/result", + "//src/tint/utils/rtti", + "//src/tint/utils/symbol", + "//src/tint/utils/text", + "//src/tint/utils/traits", + ] + select({ + ":tint_build_glsl_writer": [ + "//src/tint/lang/glsl/writer/ast_raise", + "//src/tint/lang/glsl/writer/common", + ], + "//conditions:default": [], + }), + copts = COPTS, + visibility = ["//visibility:public"], +) +cc_library( + name = "test", + alwayslink = True, + srcs = [ + "array_accessor_test.cc", + "assign_test.cc", + "ast_printer_test.cc", + "binary_test.cc", + "bitcast_test.cc", + "block_test.cc", + "break_test.cc", + "builtin_test.cc", + "builtin_texture_test.cc", + "call_test.cc", + "case_test.cc", + "cast_test.cc", + "constructor_test.cc", + "continue_test.cc", + "discard_test.cc", + "function_test.cc", + "helper_test.h", + "identifier_test.cc", + "if_test.cc", + "import_test.cc", + "loop_test.cc", + "member_accessor_test.cc", + "module_constant_test.cc", + "return_test.cc", + "sanitizer_test.cc", + "storage_buffer_test.cc", + "switch_test.cc", + "type_test.cc", + "unary_op_test.cc", + "uniform_buffer_test.cc", + "variable_decl_statement_test.cc", + "workgroup_var_test.cc", + ], + deps = [ + "//src/tint/api/common", + "//src/tint/api/options", + "//src/tint/lang/core", + "//src/tint/lang/core/constant", + "//src/tint/lang/core/type", + "//src/tint/lang/wgsl", + "//src/tint/lang/wgsl/ast", + "//src/tint/lang/wgsl/ast/transform", + "//src/tint/lang/wgsl/ast:test", + "//src/tint/lang/wgsl/common", + "//src/tint/lang/wgsl/features", + "//src/tint/lang/wgsl/program", + "//src/tint/lang/wgsl/resolver", + "//src/tint/lang/wgsl/sem", + "//src/tint/utils/containers", + "//src/tint/utils/diagnostic", + "//src/tint/utils/generator", + "//src/tint/utils/ice", + "//src/tint/utils/id", + "//src/tint/utils/macros", + "//src/tint/utils/math", + "//src/tint/utils/memory", + "//src/tint/utils/reflection", + "//src/tint/utils/result", + "//src/tint/utils/rtti", + "//src/tint/utils/symbol", + "//src/tint/utils/text", + "//src/tint/utils/traits", + "@gtest", + ] + select({ + ":tint_build_glsl_writer": [ + "//src/tint/lang/glsl/writer", + "//src/tint/lang/glsl/writer/ast_printer", + "//src/tint/lang/glsl/writer/common", + ], + "//conditions:default": [], + }), + copts = COPTS, + visibility = ["//visibility:public"], +) + +alias( + name = "tint_build_glsl_writer", + actual = "//src/tint:tint_build_glsl_writer_true", +) + diff --git a/references/writer/ast_printer/BUILD.cfg b/references/writer/ast_printer/BUILD.cfg new file mode 100644 index 00000000..7459430e --- /dev/null +++ b/references/writer/ast_printer/BUILD.cfg @@ -0,0 +1,3 @@ +{ + "condition": "tint_build_glsl_writer" +} diff --git a/references/writer/ast_printer/BUILD.cmake b/references/writer/ast_printer/BUILD.cmake new file mode 100644 index 00000000..a8e36ccd --- /dev/null +++ b/references/writer/ast_printer/BUILD.cmake @@ -0,0 +1,169 @@ +# Copyright 2023 The Dawn & Tint Authors +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +################################################################################ +# File generated by 'tools/src/cmd/gen' using the template: +# tools/src/cmd/gen/build/BUILD.cmake.tmpl +# +# To regenerate run: './tools/run gen' +# +# Do not modify this file directly +################################################################################ + +if(TINT_BUILD_GLSL_WRITER) +################################################################################ +# Target: tint_lang_glsl_writer_ast_printer +# Kind: lib +# Condition: TINT_BUILD_GLSL_WRITER +################################################################################ +tint_add_target(tint_lang_glsl_writer_ast_printer lib + lang/glsl/writer/ast_printer/ast_printer.cc + lang/glsl/writer/ast_printer/ast_printer.h +) + +tint_target_add_dependencies(tint_lang_glsl_writer_ast_printer lib + tint_api_common + tint_api_options + tint_lang_core + tint_lang_core_constant + tint_lang_core_type + tint_lang_wgsl + tint_lang_wgsl_ast + tint_lang_wgsl_ast_transform + tint_lang_wgsl_features + tint_lang_wgsl_helpers + tint_lang_wgsl_program + tint_lang_wgsl_sem + tint_utils_containers + tint_utils_diagnostic + tint_utils_generator + tint_utils_ice + tint_utils_id + tint_utils_macros + tint_utils_math + tint_utils_memory + tint_utils_reflection + tint_utils_result + tint_utils_rtti + tint_utils_symbol + tint_utils_text + tint_utils_traits +) + +if(TINT_BUILD_GLSL_WRITER) + tint_target_add_dependencies(tint_lang_glsl_writer_ast_printer lib + tint_lang_glsl_writer_ast_raise + tint_lang_glsl_writer_common + ) +endif(TINT_BUILD_GLSL_WRITER) + +endif(TINT_BUILD_GLSL_WRITER) +if(TINT_BUILD_GLSL_WRITER) +################################################################################ +# Target: tint_lang_glsl_writer_ast_printer_test +# Kind: test +# Condition: TINT_BUILD_GLSL_WRITER +################################################################################ +tint_add_target(tint_lang_glsl_writer_ast_printer_test test + lang/glsl/writer/ast_printer/array_accessor_test.cc + lang/glsl/writer/ast_printer/assign_test.cc + lang/glsl/writer/ast_printer/ast_printer_test.cc + lang/glsl/writer/ast_printer/binary_test.cc + lang/glsl/writer/ast_printer/bitcast_test.cc + lang/glsl/writer/ast_printer/block_test.cc + lang/glsl/writer/ast_printer/break_test.cc + lang/glsl/writer/ast_printer/builtin_test.cc + lang/glsl/writer/ast_printer/builtin_texture_test.cc + lang/glsl/writer/ast_printer/call_test.cc + lang/glsl/writer/ast_printer/case_test.cc + lang/glsl/writer/ast_printer/cast_test.cc + lang/glsl/writer/ast_printer/constructor_test.cc + lang/glsl/writer/ast_printer/continue_test.cc + lang/glsl/writer/ast_printer/discard_test.cc + lang/glsl/writer/ast_printer/function_test.cc + lang/glsl/writer/ast_printer/helper_test.h + lang/glsl/writer/ast_printer/identifier_test.cc + lang/glsl/writer/ast_printer/if_test.cc + lang/glsl/writer/ast_printer/import_test.cc + lang/glsl/writer/ast_printer/loop_test.cc + lang/glsl/writer/ast_printer/member_accessor_test.cc + lang/glsl/writer/ast_printer/module_constant_test.cc + lang/glsl/writer/ast_printer/return_test.cc + lang/glsl/writer/ast_printer/sanitizer_test.cc + lang/glsl/writer/ast_printer/storage_buffer_test.cc + lang/glsl/writer/ast_printer/switch_test.cc + lang/glsl/writer/ast_printer/type_test.cc + lang/glsl/writer/ast_printer/unary_op_test.cc + lang/glsl/writer/ast_printer/uniform_buffer_test.cc + lang/glsl/writer/ast_printer/variable_decl_statement_test.cc + lang/glsl/writer/ast_printer/workgroup_var_test.cc +) + +tint_target_add_dependencies(tint_lang_glsl_writer_ast_printer_test test + tint_api_common + tint_api_options + tint_lang_core + tint_lang_core_constant + tint_lang_core_type + tint_lang_wgsl + tint_lang_wgsl_ast + tint_lang_wgsl_ast_transform + tint_lang_wgsl_ast_test + tint_lang_wgsl_common + tint_lang_wgsl_features + tint_lang_wgsl_program + tint_lang_wgsl_resolver + tint_lang_wgsl_sem + tint_utils_containers + tint_utils_diagnostic + tint_utils_generator + tint_utils_ice + tint_utils_id + tint_utils_macros + tint_utils_math + tint_utils_memory + tint_utils_reflection + tint_utils_result + tint_utils_rtti + tint_utils_symbol + tint_utils_text + tint_utils_traits +) + +tint_target_add_external_dependencies(tint_lang_glsl_writer_ast_printer_test test + "gtest" +) + +if(TINT_BUILD_GLSL_WRITER) + tint_target_add_dependencies(tint_lang_glsl_writer_ast_printer_test test + tint_lang_glsl_writer + tint_lang_glsl_writer_ast_printer + tint_lang_glsl_writer_common + ) +endif(TINT_BUILD_GLSL_WRITER) + +endif(TINT_BUILD_GLSL_WRITER) \ No newline at end of file diff --git a/references/writer/ast_printer/BUILD.gn b/references/writer/ast_printer/BUILD.gn new file mode 100644 index 00000000..692ca9b8 --- /dev/null +++ b/references/writer/ast_printer/BUILD.gn @@ -0,0 +1,165 @@ +# Copyright 2023 The Dawn & Tint Authors +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +################################################################################ +# File generated by 'tools/src/cmd/gen' using the template: +# tools/src/cmd/gen/build/BUILD.gn.tmpl +# +# To regenerate run: './tools/run gen' +# +# Do not modify this file directly +################################################################################ + +import("../../../../../../scripts/tint_overrides_with_defaults.gni") + +import("${tint_src_dir}/tint.gni") + +if (tint_build_unittests || tint_build_benchmarks) { + import("//testing/test.gni") +} +if (tint_build_glsl_writer) { + libtint_source_set("ast_printer") { + sources = [ + "ast_printer.cc", + "ast_printer.h", + ] + deps = [ + "${tint_src_dir}/api/common", + "${tint_src_dir}/api/options", + "${tint_src_dir}/lang/core", + "${tint_src_dir}/lang/core/constant", + "${tint_src_dir}/lang/core/type", + "${tint_src_dir}/lang/wgsl", + "${tint_src_dir}/lang/wgsl/ast", + "${tint_src_dir}/lang/wgsl/ast/transform", + "${tint_src_dir}/lang/wgsl/features", + "${tint_src_dir}/lang/wgsl/helpers", + "${tint_src_dir}/lang/wgsl/program", + "${tint_src_dir}/lang/wgsl/sem", + "${tint_src_dir}/utils/containers", + "${tint_src_dir}/utils/diagnostic", + "${tint_src_dir}/utils/generator", + "${tint_src_dir}/utils/ice", + "${tint_src_dir}/utils/id", + "${tint_src_dir}/utils/macros", + "${tint_src_dir}/utils/math", + "${tint_src_dir}/utils/memory", + "${tint_src_dir}/utils/reflection", + "${tint_src_dir}/utils/result", + "${tint_src_dir}/utils/rtti", + "${tint_src_dir}/utils/symbol", + "${tint_src_dir}/utils/text", + "${tint_src_dir}/utils/traits", + ] + + if (tint_build_glsl_writer) { + deps += [ + "${tint_src_dir}/lang/glsl/writer/ast_raise", + "${tint_src_dir}/lang/glsl/writer/common", + ] + } + } +} +if (tint_build_unittests) { + if (tint_build_glsl_writer) { + tint_unittests_source_set("unittests") { + sources = [ + "array_accessor_test.cc", + "assign_test.cc", + "ast_printer_test.cc", + "binary_test.cc", + "bitcast_test.cc", + "block_test.cc", + "break_test.cc", + "builtin_test.cc", + "builtin_texture_test.cc", + "call_test.cc", + "case_test.cc", + "cast_test.cc", + "constructor_test.cc", + "continue_test.cc", + "discard_test.cc", + "function_test.cc", + "helper_test.h", + "identifier_test.cc", + "if_test.cc", + "import_test.cc", + "loop_test.cc", + "member_accessor_test.cc", + "module_constant_test.cc", + "return_test.cc", + "sanitizer_test.cc", + "storage_buffer_test.cc", + "switch_test.cc", + "type_test.cc", + "unary_op_test.cc", + "uniform_buffer_test.cc", + "variable_decl_statement_test.cc", + "workgroup_var_test.cc", + ] + deps = [ + "${tint_src_dir}:gmock_and_gtest", + "${tint_src_dir}/api/common", + "${tint_src_dir}/api/options", + "${tint_src_dir}/lang/core", + "${tint_src_dir}/lang/core/constant", + "${tint_src_dir}/lang/core/type", + "${tint_src_dir}/lang/wgsl", + "${tint_src_dir}/lang/wgsl/ast", + "${tint_src_dir}/lang/wgsl/ast:unittests", + "${tint_src_dir}/lang/wgsl/ast/transform", + "${tint_src_dir}/lang/wgsl/common", + "${tint_src_dir}/lang/wgsl/features", + "${tint_src_dir}/lang/wgsl/program", + "${tint_src_dir}/lang/wgsl/resolver", + "${tint_src_dir}/lang/wgsl/sem", + "${tint_src_dir}/utils/containers", + "${tint_src_dir}/utils/diagnostic", + "${tint_src_dir}/utils/generator", + "${tint_src_dir}/utils/ice", + "${tint_src_dir}/utils/id", + "${tint_src_dir}/utils/macros", + "${tint_src_dir}/utils/math", + "${tint_src_dir}/utils/memory", + "${tint_src_dir}/utils/reflection", + "${tint_src_dir}/utils/result", + "${tint_src_dir}/utils/rtti", + "${tint_src_dir}/utils/symbol", + "${tint_src_dir}/utils/text", + "${tint_src_dir}/utils/traits", + ] + + if (tint_build_glsl_writer) { + deps += [ + "${tint_src_dir}/lang/glsl/writer", + "${tint_src_dir}/lang/glsl/writer/ast_printer", + "${tint_src_dir}/lang/glsl/writer/common", + ] + } + } + } +} diff --git a/references/writer/ast_printer/array_accessor_test.cc b/references/writer/ast_printer/array_accessor_test.cc new file mode 100644 index 00000000..5f3daf2f --- /dev/null +++ b/references/writer/ast_printer/array_accessor_test.cc @@ -0,0 +1,57 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" + +#include "src/tint/lang/core/fluent_types.h" +#include "src/tint/utils/text/string_stream.h" + +#include "gmock/gmock.h" + +using namespace tint::core::number_suffixes; // NOLINT +using namespace tint::core::fluent_types; // NOLINT + +namespace tint::glsl::writer { +namespace { + +using GlslASTPrinterTest_Expression = TestHelper; + +TEST_F(GlslASTPrinterTest_Expression, IndexAccessor) { + GlobalVar("ary", ty.array(), core::AddressSpace::kPrivate); + auto* expr = IndexAccessor("ary", 5_i); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "ary[5]"); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/assign_test.cc b/references/writer/ast_printer/assign_test.cc new file mode 100644 index 00000000..9977e129 --- /dev/null +++ b/references/writer/ast_printer/assign_test.cc @@ -0,0 +1,53 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" + +#include "gmock/gmock.h" + +namespace tint::glsl::writer { +namespace { + +using GlslASTPrinterTest_Assign = TestHelper; + +TEST_F(GlslASTPrinterTest_Assign, Emit_Assign) { + GlobalVar("lhs", ty.i32(), core::AddressSpace::kPrivate); + GlobalVar("rhs", ty.i32(), core::AddressSpace::kPrivate); + auto* assign = Assign("lhs", "rhs"); + WrapInFunction(assign); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + + gen.EmitStatement(assign); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), " lhs = rhs;\n"); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/ast_printer.cc b/references/writer/ast_printer/ast_printer.cc new file mode 100644 index 00000000..29abc934 --- /dev/null +++ b/references/writer/ast_printer/ast_printer.cc @@ -0,0 +1,3028 @@ +/// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_printer/ast_printer.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "src/tint/lang/core/constant/splat.h" +#include "src/tint/lang/core/constant/value.h" +#include "src/tint/lang/core/fluent_types.h" +#include "src/tint/lang/core/type/array.h" +#include "src/tint/lang/core/type/atomic.h" +#include "src/tint/lang/core/type/depth_multisampled_texture.h" +#include "src/tint/lang/core/type/depth_texture.h" +#include "src/tint/lang/core/type/multisampled_texture.h" +#include "src/tint/lang/core/type/sampled_texture.h" +#include "src/tint/lang/core/type/storage_texture.h" +#include "src/tint/lang/core/type/texture_dimension.h" +#include "src/tint/lang/glsl/writer/ast_raise/combine_samplers.h" +#include "src/tint/lang/glsl/writer/ast_raise/pad_structs.h" +#include "src/tint/lang/glsl/writer/ast_raise/texture_1d_to_2d.h" +#include "src/tint/lang/glsl/writer/ast_raise/texture_builtins_from_uniform.h" +#include "src/tint/lang/glsl/writer/common/options.h" +#include "src/tint/lang/glsl/writer/common/printer_support.h" +#include "src/tint/lang/wgsl/ast/call_statement.h" +#include "src/tint/lang/wgsl/ast/id_attribute.h" +#include "src/tint/lang/wgsl/ast/internal_attribute.h" +#include "src/tint/lang/wgsl/ast/interpolate_attribute.h" +#include "src/tint/lang/wgsl/ast/transform/add_block_attribute.h" +#include "src/tint/lang/wgsl/ast/transform/add_empty_entry_point.h" +#include "src/tint/lang/wgsl/ast/transform/binding_remapper.h" +#include "src/tint/lang/wgsl/ast/transform/builtin_polyfill.h" +#include "src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.h" +#include "src/tint/lang/wgsl/ast/transform/clamp_frag_depth.h" +#include "src/tint/lang/wgsl/ast/transform/demote_to_helper.h" +#include "src/tint/lang/wgsl/ast/transform/direct_variable_access.h" +#include "src/tint/lang/wgsl/ast/transform/disable_uniformity_analysis.h" +#include "src/tint/lang/wgsl/ast/transform/expand_compound_assignment.h" +#include "src/tint/lang/wgsl/ast/transform/fold_constants.h" +#include "src/tint/lang/wgsl/ast/transform/manager.h" +#include "src/tint/lang/wgsl/ast/transform/multiplanar_external_texture.h" +#include "src/tint/lang/wgsl/ast/transform/offset_first_index.h" +#include "src/tint/lang/wgsl/ast/transform/preserve_padding.h" +#include "src/tint/lang/wgsl/ast/transform/promote_initializers_to_let.h" +#include "src/tint/lang/wgsl/ast/transform/promote_side_effects_to_decl.h" +#include "src/tint/lang/wgsl/ast/transform/remove_continue_in_switch.h" +#include "src/tint/lang/wgsl/ast/transform/remove_phonies.h" +#include "src/tint/lang/wgsl/ast/transform/renamer.h" +#include "src/tint/lang/wgsl/ast/transform/robustness.h" +#include "src/tint/lang/wgsl/ast/transform/simplify_pointers.h" +#include "src/tint/lang/wgsl/ast/transform/single_entry_point.h" +#include "src/tint/lang/wgsl/ast/transform/std140.h" +#include "src/tint/lang/wgsl/ast/transform/unshadow.h" +#include "src/tint/lang/wgsl/ast/transform/zero_init_workgroup_memory.h" +#include "src/tint/lang/wgsl/ast/variable_decl_statement.h" +#include "src/tint/lang/wgsl/helpers/append_vector.h" +#include "src/tint/lang/wgsl/helpers/check_supported_extensions.h" +#include "src/tint/lang/wgsl/sem/block_statement.h" +#include "src/tint/lang/wgsl/sem/builtin_enum_expression.h" +#include "src/tint/lang/wgsl/sem/call.h" +#include "src/tint/lang/wgsl/sem/function.h" +#include "src/tint/lang/wgsl/sem/member_accessor_expression.h" +#include "src/tint/lang/wgsl/sem/module.h" +#include "src/tint/lang/wgsl/sem/statement.h" +#include "src/tint/lang/wgsl/sem/struct.h" +#include "src/tint/lang/wgsl/sem/switch_statement.h" +#include "src/tint/lang/wgsl/sem/value_constructor.h" +#include "src/tint/lang/wgsl/sem/value_conversion.h" +#include "src/tint/lang/wgsl/sem/variable.h" +#include "src/tint/utils/containers/map.h" +#include "src/tint/utils/ice/ice.h" +#include "src/tint/utils/macros/defer.h" +#include "src/tint/utils/macros/scoped_assignment.h" +#include "src/tint/utils/rtti/switch.h" +#include "src/tint/utils/text/string.h" +#include "src/tint/utils/text/string_stream.h" + +using namespace tint::core::number_suffixes; // NOLINT +using namespace tint::core::fluent_types; // NOLINT + +namespace tint::glsl::writer { +namespace { + +const char kTempNamePrefix[] = "tint_tmp"; + +bool last_is_break(const ast::BlockStatement* stmts) { + return tint::IsAnyOf(stmts->Last()); +} + +bool IsRelational(tint::core::BinaryOp op) { + return op == tint::core::BinaryOp::kEqual || op == tint::core::BinaryOp::kNotEqual || + op == tint::core::BinaryOp::kLessThan || op == tint::core::BinaryOp::kGreaterThan || + op == tint::core::BinaryOp::kLessThanEqual || + op == tint::core::BinaryOp::kGreaterThanEqual; +} + +bool RequiresOESSampleVariables(tint::core::BuiltinValue builtin) { + switch (builtin) { + case tint::core::BuiltinValue::kSampleIndex: + case tint::core::BuiltinValue::kSampleMask: + return true; + default: + return false; + } +} + +} // namespace + +SanitizedResult::SanitizedResult() = default; +SanitizedResult::~SanitizedResult() = default; +SanitizedResult::SanitizedResult(SanitizedResult&&) = default; + +SanitizedResult Sanitize(const Program& in, + const Options& options, + const std::string& entry_point) { + ast::transform::Manager manager; + ast::transform::DataMap data; + + manager.Add(); + + manager.Add(); + + // ExpandCompoundAssignment must come before BuiltinPolyfill + manager.Add(); + + if (!entry_point.empty()) { + manager.Add(); + data.Add(entry_point); + } + + manager.Add(); // Must come before DirectVariableAccess + + manager.Add(); // Must come before DirectVariableAccess + + manager.Add(); + + if (!options.disable_robustness) { + // Robustness must come after PromoteSideEffectsToDecl + // Robustness must come before BuiltinPolyfill and CanonicalizeEntryPointIO + manager.Add(); + } + + if (!options.disable_workgroup_init) { + // ZeroInitWorkgroupMemory must come before CanonicalizeEntryPointIO as + // ZeroInitWorkgroupMemory may inject new builtin parameters. + manager.Add(); + } + + manager.Add(); + + // TextureBuiltinsFromUniform must come before CombineSamplers to preserve texture binding point + // info, instead of combined sampler binding point. As a result, TextureBuiltinsFromUniform also + // comes before BindingRemapper so the binding point info it reflects is before remapping. + manager.Add(); + data.Add( + options.texture_builtins_from_uniform.ubo_binding, + options.texture_builtins_from_uniform.ubo_bindingpoint_ordering); + + // Note: it is more efficient for MultiplanarExternalTexture to come after Robustness + // Must come before builtin polyfills + data.Add( + options.external_texture_options.bindings_map); + manager.Add(); + + // Must be after multiplanar and must be before OffsetFirstindex + manager.Add(); + + // This must come before ClampFragDepth as the AddBlockAttribute will change around the struct + // that gets created for the push constants and we end up with the `inner` structure sitting at + // the same offset we want to place the first_instance value. + manager.Add(); + data.Add(options.first_vertex_offset, + options.first_instance_offset); + + // ClampFragDepth must come before CanonicalizeEntryPointIO, or the assignments to FragDepth are + // lost + manager.Add(); + data.Add(options.depth_range_offsets); + + // CanonicalizeEntryPointIO must come after Robustness + manager.Add(); + data.Add( + ast::transform::CanonicalizeEntryPointIO::ShaderStyle::kGlsl); + + // DemoteToHelper must come after PromoteSideEffectsToDecl and ExpandCompoundAssignment. + manager.Add(); + + { // Builtin polyfills + ast::transform::BuiltinPolyfill::Builtins polyfills; + polyfills.acosh = ast::transform::BuiltinPolyfill::Level::kRangeCheck; + polyfills.atanh = ast::transform::BuiltinPolyfill::Level::kRangeCheck; + polyfills.bgra8unorm = true; + polyfills.bitshift_modulo = true; + polyfills.conv_f32_to_iu32 = true; + polyfills.count_leading_zeros = true; + polyfills.count_trailing_zeros = true; + polyfills.extract_bits = ast::transform::BuiltinPolyfill::Level::kClampParameters; + polyfills.first_leading_bit = true; + polyfills.first_trailing_bit = true; + polyfills.insert_bits = ast::transform::BuiltinPolyfill::Level::kClampParameters; + polyfills.int_div_mod = !options.disable_polyfill_integer_div_mod; + polyfills.saturate = true; + polyfills.texture_sample_base_clamp_to_edge_2d_f32 = true; + polyfills.workgroup_uniform_load = true; + polyfills.dot_4x8_packed = true; + polyfills.pack_unpack_4x8 = true; + polyfills.pack_4xu8_clamp = true; + data.Add(polyfills); + manager.Add(); // Must come before DirectVariableAccess + } + + manager.Add(); + + // Must come after builtin polyfills (specifically texture_sample_base_clamp_to_edge_2d_f32) + data.Add(options.combined_samplers_info); + manager.Add(); + + // Must come after CombineSamplers + data.Add( + options.binding_remapper_options.binding_points, + std::unordered_map{}, + /* allow_collisions */ true); + manager.Add(); + + // PadStructs must come after CanonicalizeEntryPointIO and CombineSamplers + manager.Add(); + + // Promote initializers must come after binding polyfill + manager.Add(); + manager.Add(); + manager.Add(); + + // Std140 must come after PromoteSideEffectsToDecl and before SimplifyPointers. + manager.Add(); + + manager.Add(); + + manager.Add(); + + SanitizedResult result; + ast::transform::DataMap outputs; + result.program = manager.Run(in, data, outputs); + return result; +} + +ASTPrinter::ASTPrinter(const Program& program, const Version& version) + : builder_(ProgramBuilder::Wrap(program)), version_(version) {} + +ASTPrinter::~ASTPrinter() = default; + +bool ASTPrinter::Generate() { + if (!tint::wgsl::CheckSupportedExtensions( + "GLSL", builder_.AST(), diagnostics_, + Vector{ + wgsl::Extension::kChromiumDisableUniformityAnalysis, + wgsl::Extension::kChromiumExperimentalPushConstant, + wgsl::Extension::kChromiumInternalDualSourceBlending, + wgsl::Extension::kChromiumInternalGraphite, + wgsl::Extension::kF16, + wgsl::Extension::kDualSourceBlending, + })) { + return false; + } + + { + auto out = Line(); + out << "#version " << version_.major_version << version_.minor_version << "0"; + if (version_.IsES()) { + out << " es"; + } + } + + auto helpers_insertion_point = current_buffer_->lines.size(); + + Line(); + + auto* mod = builder_.Sem().Module(); + for (auto* decl : mod->DependencyOrderedDeclarations()) { + if (decl->IsAnyOf()) { + continue; // These are not emitted. + } + + Switch( + decl, // + [&](const ast::Variable* global) { return EmitGlobalVariable(global); }, + [&](const ast::Struct* str) { + auto* sem = builder_.Sem().Get(str); + bool has_rt_arr = false; + if (auto* arr = sem->Members().Back()->Type()->As()) { + has_rt_arr = arr->Count()->Is(); + } + bool is_block = + ast::HasAttribute( + str->attributes) && + !sem->UsedAs(core::AddressSpace::kPushConstant); + if (!has_rt_arr && !is_block) { + EmitStructType(current_buffer_, sem); + } + }, + [&](const ast::Function* func) { + if (func->IsEntryPoint()) { + EmitEntryPointFunction(func); + } else { + EmitFunction(func); + } + }, + [&](const ast::Enable* enable) { + // Record the required extension for generating extension directive later + RecordExtension(enable); + }, // + [&](const ast::Requires*) { + // Do nothing for requiring language features in GLSL. + }, // + TINT_ICE_ON_NO_MATCH); + } + + TextBuffer extensions; + + if (version_.IsES() && requires_oes_sample_variables_) { + extensions.Append("#extension GL_OES_sample_variables : require"); + } + + if (requires_f16_extension_) { + extensions.Append("#extension GL_AMD_gpu_shader_half_float : require"); + } + + if (requires_dual_source_blending_extension_) { + extensions.Append("#extension GL_EXT_blend_func_extended : require"); + } + + auto indent = current_buffer_->current_indent; + + if (!extensions.lines.empty()) { + current_buffer_->Insert(extensions, helpers_insertion_point, indent); + helpers_insertion_point += extensions.lines.size(); + } + + if (version_.IsES() && requires_default_precision_qualifier_) { + current_buffer_->Insert("precision highp float;", helpers_insertion_point++, indent); + current_buffer_->Insert("precision highp int;", helpers_insertion_point++, indent); + } + + if (!helpers_.lines.empty()) { + current_buffer_->Insert("", helpers_insertion_point++, indent); + current_buffer_->Insert(helpers_, helpers_insertion_point, indent); + helpers_insertion_point += helpers_.lines.size(); + } + + return !diagnostics_.ContainsErrors(); +} + +void ASTPrinter::RecordExtension(const ast::Enable* enable) { + // Deal with extension node here, recording it within the generator for later emition. + + if (enable->HasExtension(wgsl::Extension::kF16)) { + requires_f16_extension_ = true; + } + + if (enable->HasExtension(wgsl::Extension::kChromiumInternalDualSourceBlending) || + enable->HasExtension(wgsl::Extension::kDualSourceBlending)) { + requires_dual_source_blending_extension_ = true; + } +} + +void ASTPrinter::EmitIndexAccessor(StringStream& out, const ast::IndexAccessorExpression* expr) { + EmitExpression(out, expr->object); + out << "["; + EmitExpression(out, expr->index); + out << "]"; +} + +void ASTPrinter::EmitBitcastCall(StringStream& out, const ast::CallExpression* call) { + auto* arg = call->args[0]; + auto* src_type = TypeOf(arg)->UnwrapRef(); + auto* dst_type = TypeOf(call); + + if (!dst_type->is_integer_scalar_or_vector() && !dst_type->is_float_scalar_or_vector()) { + diagnostics_.AddError(Source{}) + << "Unable to do bitcast to type " << dst_type->FriendlyName(); + return; + } + + // Handle identity bitcast. + if (src_type == dst_type) { + return EmitExpression(out, arg); + } + + // Use packFloat2x16 and unpackFloat2x16 for f16 types. + if (src_type->DeepestElement()->Is()) { + // Source type must be vec2 or vec4, since type f16 and vec3 can only have + // identity bitcast. + auto* src_vec = src_type->As(); + TINT_ASSERT(src_vec); + TINT_ASSERT(((src_vec->Width() == 2u) || (src_vec->Width() == 4u))); + std::string fn = + GetOrAdd(bitcast_funcs_, BinaryOperandType{{src_type, dst_type}}, [&]() -> std::string { + TextBuffer b; + TINT_DEFER(helpers_.Append(b)); + + auto fn_name = UniqueIdentifier("tint_bitcast_from_f16"); + { + auto decl = Line(&b); + EmitTypeAndName(decl, dst_type, core::AddressSpace::kUndefined, + core::Access::kUndefined, fn_name); + { + ScopedParen sp(decl); + EmitTypeAndName(decl, src_type, core::AddressSpace::kUndefined, + core::Access::kUndefined, "src"); + } + decl << " {"; + } + { + ScopedIndent si(&b); + switch (src_vec->Width()) { + case 2: { + Line(&b) << "uint r = packFloat2x16(src);"; + break; + } + case 4: { + Line(&b) + << "uvec2 r = uvec2(packFloat2x16(src.xy), packFloat2x16(src.zw));"; + break; + } + } + auto s = Line(&b); + s << "return "; + if (dst_type->is_float_scalar_or_vector()) { + s << "uintBitsToFloat"; + } else { + EmitType(s, dst_type, core::AddressSpace::kUndefined, + core::Access::kReadWrite, ""); + } + s << "(r);"; + } + Line(&b) << "}"; + return fn_name; + }); + // Call the helper + out << fn; + { + ScopedParen sp(out); + EmitExpression(out, arg); + } + } else if (dst_type->DeepestElement()->Is()) { + // Destination type must be vec2 or vec4. + auto* dst_vec = dst_type->As(); + TINT_ASSERT(dst_vec); + TINT_ASSERT(((dst_vec->Width() == 2u) || (dst_vec->Width() == 4u))); + std::string fn = + GetOrAdd(bitcast_funcs_, BinaryOperandType{{src_type, dst_type}}, [&]() -> std::string { + TextBuffer b; + TINT_DEFER(helpers_.Append(b)); + + auto fn_name = UniqueIdentifier("tint_bitcast_to_f16"); + { + auto decl = Line(&b); + EmitTypeAndName(decl, dst_type, core::AddressSpace::kUndefined, + core::Access::kUndefined, fn_name); + { + ScopedParen sp(decl); + EmitTypeAndName(decl, src_type, core::AddressSpace::kUndefined, + core::Access::kUndefined, "src"); + } + decl << " {"; + } + { + ScopedIndent si(&b); + if (auto src_vec = src_type->As()) { + // Source vector type must be vec2, destination type vec4. + TINT_ASSERT( + (src_vec->DeepestElement() + ->IsAnyOf())); + TINT_ASSERT((src_vec->Width() == 2u)); + { + auto s = Line(&b); + s << "uvec2 r = "; + if (src_type->is_float_scalar_or_vector()) { + s << "floatBitsToUint"; + } else { + s << "uvec2"; + } + s << "(src);"; + } + Line(&b) << "f16vec2 v_xy = unpackFloat2x16(r.x);"; + Line(&b) << "f16vec2 v_zw = unpackFloat2x16(r.y);"; + Line(&b) << "return f16vec4(v_xy.x, v_xy.y, v_zw.x, v_zw.y);"; + } else { + // Source scalar type must be f32/i32/u32, destination type vec2. + TINT_ASSERT( + (src_type + ->IsAnyOf())); + { + auto s = Line(&b); + s << "uint r = "; + if (src_type->is_float_scalar_or_vector()) { + s << "floatBitsToUint"; + } else { + s << "uint"; + } + s << "(src);"; + } + Line(&b) << "return unpackFloat2x16(r);"; + } + } + Line(&b) << "}"; + return fn_name; + }); + // Call the helper + out << fn; + { + ScopedParen sp(out); + EmitExpression(out, arg); + } + } else { + if (src_type->is_float_scalar_or_vector() && + dst_type->is_signed_integer_scalar_or_vector()) { + out << "floatBitsToInt"; + } else if (src_type->is_float_scalar_or_vector() && + dst_type->is_unsigned_integer_scalar_or_vector()) { + out << "floatBitsToUint"; + } else if (src_type->is_signed_integer_scalar_or_vector() && + dst_type->is_float_scalar_or_vector()) { + out << "intBitsToFloat"; + } else if (src_type->is_unsigned_integer_scalar_or_vector() && + dst_type->is_float_scalar_or_vector()) { + out << "uintBitsToFloat"; + } else { + EmitType(out, dst_type, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); + } + ScopedParen sp(out); + EmitExpression(out, arg); + } +} + +void ASTPrinter::EmitAssign(const ast::AssignmentStatement* stmt) { + auto out = Line(); + EmitExpression(out, stmt->lhs); + out << " = "; + EmitExpression(out, stmt->rhs); + out << ";"; +} + +void ASTPrinter::EmitVectorRelational(StringStream& out, const ast::BinaryExpression* expr) { + switch (expr->op) { + case core::BinaryOp::kEqual: + out << "equal"; + break; + case core::BinaryOp::kNotEqual: + out << "notEqual"; + break; + case core::BinaryOp::kLessThan: + out << "lessThan"; + break; + case core::BinaryOp::kGreaterThan: + out << "greaterThan"; + break; + case core::BinaryOp::kLessThanEqual: + out << "lessThanEqual"; + break; + case core::BinaryOp::kGreaterThanEqual: + out << "greaterThanEqual"; + break; + default: + break; + } + ScopedParen sp(out); + EmitExpression(out, expr->lhs); + out << ", "; + EmitExpression(out, expr->rhs); +} + +void ASTPrinter::EmitBitwiseBoolOp(StringStream& out, const ast::BinaryExpression* expr) { + auto* bool_type = TypeOf(expr->lhs)->UnwrapRef(); + auto* uint_type = BoolTypeToUint(bool_type); + + // Cast result to bool scalar or vector type. + EmitType(out, bool_type, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); + ScopedParen outerCastParen(out); + // Cast LHS to uint scalar or vector type. + EmitType(out, uint_type, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); + { + ScopedParen innerCastParen(out); + // Emit LHS. + EmitExpression(out, expr->lhs); + } + // Emit operator. + if (expr->op == core::BinaryOp::kAnd) { + out << " & "; + } else if (TINT_LIKELY(expr->op == core::BinaryOp::kOr)) { + out << " | "; + } else { + TINT_ICE() << "unexpected binary op: " << expr->op; + } + + // Cast RHS to uint scalar or vector type. + EmitType(out, uint_type, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); + { + ScopedParen innerCastParen(out); + // Emit RHS. + EmitExpression(out, expr->rhs); + } +} + +void ASTPrinter::EmitFloatModulo(StringStream& out, const ast::BinaryExpression* expr) { + std::string fn; + auto* ret_ty = TypeOf(expr)->UnwrapRef(); + auto* lhs_ty = TypeOf(expr->lhs)->UnwrapRef(); + auto* rhs_ty = TypeOf(expr->rhs)->UnwrapRef(); + fn = tint::GetOrAdd(float_modulo_funcs_, BinaryOperandType{{lhs_ty, rhs_ty}}, + [&]() -> std::string { + TextBuffer b; + TINT_DEFER(helpers_.Append(b)); + + auto fn_name = UniqueIdentifier("tint_float_modulo"); + std::vector parameter_names; + { + auto decl = Line(&b); + EmitTypeAndName(decl, ret_ty, core::AddressSpace::kUndefined, + core::Access::kUndefined, fn_name); + { + ScopedParen sp(decl); + const auto* ty = TypeOf(expr->lhs)->UnwrapRef(); + EmitTypeAndName(decl, ty, core::AddressSpace::kUndefined, + core::Access::kUndefined, "lhs"); + decl << ", "; + ty = TypeOf(expr->rhs)->UnwrapRef(); + EmitTypeAndName(decl, ty, core::AddressSpace::kUndefined, + core::Access::kUndefined, "rhs"); + } + decl << " {"; + } + { + ScopedIndent si(&b); + Line(&b) << "return (lhs - rhs * trunc(lhs / rhs));"; + } + Line(&b) << "}"; + Line(&b); + return fn_name; + }); + + // Call the helper + out << fn; + { + ScopedParen sp(out); + EmitExpression(out, expr->lhs); + out << ", "; + EmitExpression(out, expr->rhs); + } +} + +void ASTPrinter::EmitBinary(StringStream& out, const ast::BinaryExpression* expr) { + if (IsRelational(expr->op) && !TypeOf(expr->lhs)->UnwrapRef()->Is()) { + EmitVectorRelational(out, expr); + return; + } + + if (expr->op == core::BinaryOp::kLogicalAnd || expr->op == core::BinaryOp::kLogicalOr) { + auto name = UniqueIdentifier(kTempNamePrefix); + + { + auto pre = Line(); + pre << "bool " << name << " = "; + EmitExpression(pre, expr->lhs); + pre << ";"; + } + + if (expr->op == core::BinaryOp::kLogicalOr) { + Line() << "if (!" << name << ") {"; + } else { + Line() << "if (" << name << ") {"; + } + + { + ScopedIndent si(this); + auto pre = Line(); + pre << name << " = "; + EmitExpression(pre, expr->rhs); + pre << ";"; + } + + Line() << "}"; + + out << "(" << name << ")"; + return; + } + + if ((expr->op == core::BinaryOp::kAnd || expr->op == core::BinaryOp::kOr) && + TypeOf(expr->lhs)->UnwrapRef()->is_bool_scalar_or_vector()) { + EmitBitwiseBoolOp(out, expr); + return; + } + + if (expr->op == core::BinaryOp::kModulo && + (TypeOf(expr->lhs)->UnwrapRef()->is_float_scalar_or_vector() || + TypeOf(expr->rhs)->UnwrapRef()->is_float_scalar_or_vector())) { + EmitFloatModulo(out, expr); + return; + } + + ScopedParen sp(out); + EmitExpression(out, expr->lhs); + out << " "; + + switch (expr->op) { + case core::BinaryOp::kAnd: + out << "&"; + break; + case core::BinaryOp::kOr: + out << "|"; + break; + case core::BinaryOp::kXor: + out << "^"; + break; + case core::BinaryOp::kLogicalAnd: + case core::BinaryOp::kLogicalOr: { + // These are both handled above. + TINT_UNREACHABLE(); + } + case core::BinaryOp::kEqual: + out << "=="; + break; + case core::BinaryOp::kNotEqual: + out << "!="; + break; + case core::BinaryOp::kLessThan: + out << "<"; + break; + case core::BinaryOp::kGreaterThan: + out << ">"; + break; + case core::BinaryOp::kLessThanEqual: + out << "<="; + break; + case core::BinaryOp::kGreaterThanEqual: + out << ">="; + break; + case core::BinaryOp::kShiftLeft: + out << "<<"; + break; + case core::BinaryOp::kShiftRight: + out << R"(>>)"; + break; + + case core::BinaryOp::kAdd: + out << "+"; + break; + case core::BinaryOp::kSubtract: + out << "-"; + break; + case core::BinaryOp::kMultiply: + out << "*"; + break; + case core::BinaryOp::kDivide: + out << "/"; + break; + case core::BinaryOp::kModulo: + out << "%"; + break; + } + out << " "; + EmitExpression(out, expr->rhs); +} + +void ASTPrinter::EmitStatements(VectorRef stmts) { + for (auto* s : stmts) { + EmitStatement(s); + } +} + +void ASTPrinter::EmitStatementsWithIndent(VectorRef stmts) { + ScopedIndent si(this); + EmitStatements(stmts); +} + +void ASTPrinter::EmitBlock(const ast::BlockStatement* stmt) { + Line() << "{"; + EmitStatementsWithIndent(stmt->statements); + Line() << "}"; +} + +void ASTPrinter::EmitBreak(const ast::BreakStatement*) { + Line() << "break;"; +} + +void ASTPrinter::EmitBreakIf(const ast::BreakIfStatement* b) { + auto out = Line(); + out << "if ("; + EmitExpression(out, b->condition); + out << ") { break; }"; +} + +void ASTPrinter::EmitCall(StringStream& out, const ast::CallExpression* expr) { + auto* call = builder_.Sem().Get(expr); + Switch( + call->Target(), // + [&](const sem::Function* fn) { EmitFunctionCall(out, call, fn); }, + [&](const sem::BuiltinFn* builtin) { EmitBuiltinCall(out, call, builtin); }, + [&](const sem::ValueConversion* conv) { EmitValueConversion(out, call, conv); }, + [&](const sem::ValueConstructor* ctor) { EmitValueConstructor(out, call, ctor); }, + TINT_ICE_ON_NO_MATCH); +} + +void ASTPrinter::EmitFunctionCall(StringStream& out, + const sem::Call* call, + const sem::Function* fn) { + const auto& args = call->Arguments(); + auto* ident = fn->Declaration()->name; + + out << ident->symbol.Name(); + ScopedParen sp(out); + + bool first = true; + for (auto* arg : args) { + if (!first) { + out << ", "; + } + first = false; + + EmitExpression(out, arg->Declaration()); + } +} + +void ASTPrinter::EmitBuiltinCall(StringStream& out, + const sem::Call* call, + const sem::BuiltinFn* builtin) { + auto* expr = call->Declaration(); + if (builtin->IsTexture()) { + EmitTextureCall(out, call, builtin); + } else if (builtin->Fn() == wgsl::BuiltinFn::kCountOneBits) { + EmitCountOneBitsCall(out, expr); + } else if (builtin->Fn() == wgsl::BuiltinFn::kSelect) { + EmitSelectCall(out, expr, builtin); + } else if (builtin->Fn() == wgsl::BuiltinFn::kBitcast) { + EmitBitcastCall(out, expr); + } else if (builtin->Fn() == wgsl::BuiltinFn::kDot) { + EmitDotCall(out, expr, builtin); + } else if (builtin->Fn() == wgsl::BuiltinFn::kModf) { + EmitModfCall(out, expr, builtin); + } else if (builtin->Fn() == wgsl::BuiltinFn::kFrexp) { + EmitFrexpCall(out, expr, builtin); + } else if (builtin->Fn() == wgsl::BuiltinFn::kDegrees) { + EmitDegreesCall(out, expr, builtin); + } else if (builtin->Fn() == wgsl::BuiltinFn::kRadians) { + EmitRadiansCall(out, expr, builtin); + } else if (builtin->Fn() == wgsl::BuiltinFn::kQuantizeToF16) { + EmitQuantizeToF16Call(out, expr, builtin); + } else if (builtin->Fn() == wgsl::BuiltinFn::kArrayLength) { + EmitArrayLength(out, expr); + } else if (builtin->Fn() == wgsl::BuiltinFn::kExtractBits) { + EmitExtractBits(out, expr); + } else if (builtin->Fn() == wgsl::BuiltinFn::kInsertBits) { + EmitInsertBits(out, expr); + } else if (builtin->Fn() == wgsl::BuiltinFn::kFma && version_.IsES()) { + EmitEmulatedFMA(out, expr); + } else if (builtin->Fn() == wgsl::BuiltinFn::kAbs && + TypeOf(expr->args[0])->UnwrapRef()->is_unsigned_integer_scalar_or_vector()) { + // GLSL does not support abs() on unsigned arguments. However, it's a no-op. + EmitExpression(out, expr->args[0]); + } else if ((builtin->Fn() == wgsl::BuiltinFn::kAny || builtin->Fn() == wgsl::BuiltinFn::kAll) && + TypeOf(expr->args[0])->UnwrapRef()->Is()) { + // GLSL does not support any() or all() on scalar arguments. It's a no-op. + EmitExpression(out, expr->args[0]); + } else if (builtin->IsBarrier()) { + EmitBarrierCall(out, builtin); + } else if (builtin->IsAtomic()) { + EmitWorkgroupAtomicCall(out, expr, builtin); + } else { + auto name = generate_builtin_name(builtin); + if (name.empty()) { + return; + } + + out << name; + ScopedParen sp(out); + + bool first = true; + for (auto* arg : call->Arguments()) { + if (!first) { + out << ", "; + } + first = false; + + EmitExpression(out, arg->Declaration()); + } + } +} + +void ASTPrinter::EmitValueConversion(StringStream& out, + const sem::Call* call, + const sem::ValueConversion* conv) { + EmitType(out, conv->Target(), core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); + ScopedParen sp(out); + EmitExpression(out, call->Arguments()[0]->Declaration()); +} + +void ASTPrinter::EmitValueConstructor(StringStream& out, + const sem::Call* call, + const sem::ValueConstructor* ctor) { + auto* type = ctor->ReturnType(); + + // If the value constructor is empty then we need to construct with the zero value for all + // components. + if (call->Arguments().IsEmpty()) { + EmitZeroValue(out, type); + return; + } + + EmitType(out, type, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); + ScopedParen sp(out); + + bool first = true; + for (auto* arg : call->Arguments()) { + if (!first) { + out << ", "; + } + first = false; + + EmitExpression(out, arg->Declaration()); + } +} + +void ASTPrinter::EmitWorkgroupAtomicCall(StringStream& out, + const ast::CallExpression* expr, + const sem::BuiltinFn* builtin) { + auto call = [&](const char* name) { + out << name; + { + ScopedParen sp(out); + for (size_t i = 0; i < expr->args.Length(); i++) { + auto* arg = expr->args[i]; + if (i > 0) { + out << ", "; + } + EmitExpression(out, arg); + } + } + return; + }; + + switch (builtin->Fn()) { + case wgsl::BuiltinFn::kAtomicLoad: { + // GLSL does not have an atomicLoad, so we emulate it with + // atomicOr using 0 as the OR value + out << "atomicOr"; + { + ScopedParen sp(out); + EmitExpression(out, expr->args[0]); + out << ", 0"; + if (builtin->ReturnType()->Is()) { + out << "u"; + } + } + return; + } + case wgsl::BuiltinFn::kAtomicCompareExchangeWeak: { + EmitStructType(&helpers_, builtin->ReturnType()->As()); + + auto* dest = expr->args[0]; + auto* compare_value = expr->args[1]; + auto* value = expr->args[2]; + + std::string result = UniqueIdentifier("atomic_compare_result"); + + { + auto pre = Line(); + EmitTypeAndName(pre, builtin->ReturnType(), core::AddressSpace::kUndefined, + core::Access::kUndefined, result); + pre << ";"; + } + { + auto pre = Line(); + pre << result << ".old_value = atomicCompSwap"; + { + ScopedParen sp(pre); + EmitExpression(pre, dest); + pre << ", "; + EmitExpression(pre, compare_value); + pre << ", "; + EmitExpression(pre, value); + } + pre << ";"; + } + { + auto pre = Line(); + pre << result << ".exchanged = " << result << ".old_value == "; + EmitExpression(pre, compare_value); + pre << ";"; + } + + out << result; + return; + } + + case wgsl::BuiltinFn::kAtomicAdd: + call("atomicAdd"); + return; + + case wgsl::BuiltinFn::kAtomicSub: + out << "atomicAdd"; + { + ScopedParen sp(out); + TINT_ASSERT(expr->args.Length() == 2); + EmitExpression(out, expr->args[0]); + out << ", -"; + { + ScopedParen argSP(out); + EmitExpression(out, expr->args[1]); + } + } + return; + + case wgsl::BuiltinFn::kAtomicMax: + call("atomicMax"); + return; + + case wgsl::BuiltinFn::kAtomicMin: + call("atomicMin"); + return; + + case wgsl::BuiltinFn::kAtomicAnd: + call("atomicAnd"); + return; + + case wgsl::BuiltinFn::kAtomicOr: + call("atomicOr"); + return; + + case wgsl::BuiltinFn::kAtomicXor: + call("atomicXor"); + return; + + case wgsl::BuiltinFn::kAtomicExchange: + case wgsl::BuiltinFn::kAtomicStore: + // GLSL does not have an atomicStore, so we emulate it with + // atomicExchange. + call("atomicExchange"); + return; + + default: + break; + } + + TINT_UNREACHABLE() << "unsupported atomic builtin: " << builtin->Fn(); +} + +void ASTPrinter::EmitArrayLength(StringStream& out, const ast::CallExpression* expr) { + out << "uint("; + EmitExpression(out, expr->args[0]); + out << ".length())"; +} + +void ASTPrinter::EmitExtractBits(StringStream& out, const ast::CallExpression* expr) { + out << "bitfieldExtract("; + EmitExpression(out, expr->args[0]); + out << ", int("; + EmitExpression(out, expr->args[1]); + out << "), int("; + EmitExpression(out, expr->args[2]); + out << "))"; +} + +void ASTPrinter::EmitInsertBits(StringStream& out, const ast::CallExpression* expr) { + out << "bitfieldInsert("; + EmitExpression(out, expr->args[0]); + out << ", "; + EmitExpression(out, expr->args[1]); + out << ", int("; + EmitExpression(out, expr->args[2]); + out << "), int("; + EmitExpression(out, expr->args[3]); + out << "))"; +} + +void ASTPrinter::EmitEmulatedFMA(StringStream& out, const ast::CallExpression* expr) { + out << "(("; + EmitExpression(out, expr->args[0]); + out << ") * ("; + EmitExpression(out, expr->args[1]); + out << ") + ("; + EmitExpression(out, expr->args[2]); + out << "))"; +} + +void ASTPrinter::EmitCountOneBitsCall(StringStream& out, const ast::CallExpression* expr) { + // GLSL's bitCount returns an integer type, so cast it to the appropriate + // unsigned type. + EmitType(out, TypeOf(expr)->UnwrapRef(), core::AddressSpace::kUndefined, + core::Access::kReadWrite, ""); + out << "(bitCount("; + EmitExpression(out, expr->args[0]); + out << "))"; +} + +void ASTPrinter::EmitSelectCall(StringStream& out, + const ast::CallExpression* expr, + const sem::BuiltinFn* builtin) { + // GLSL does not support ternary expressions with a bool vector conditional, + // so polyfill with a helper. + if (auto* vec = builtin->Parameters()[2]->Type()->As()) { + CallBuiltinHelper(out, expr, builtin, + [&](TextBuffer* b, const std::vector& params) { + auto l = Line(b); + l << " return "; + EmitType(l, builtin->ReturnType(), core::AddressSpace::kUndefined, + core::Access::kUndefined, ""); + { + ScopedParen sp(l); + for (uint32_t i = 0; i < vec->Width(); i++) { + if (i > 0) { + l << ", "; + } + l << params[2] << "[" << i << "] ? " << params[1] << "[" << i + << "] : " << params[0] << "[" << i << "]"; + } + } + l << ";"; + }); + return; + } + + auto* expr_false = expr->args[0]; + auto* expr_true = expr->args[1]; + auto* expr_cond = expr->args[2]; + + ScopedParen paren(out); + EmitExpression(out, expr_cond); + + out << " ? "; + EmitExpression(out, expr_true); + out << " : "; + EmitExpression(out, expr_false); +} + +void ASTPrinter::EmitDotCall(StringStream& out, + const ast::CallExpression* expr, + const sem::BuiltinFn* builtin) { + auto* vec_ty = builtin->Parameters()[0]->Type()->As(); + std::string fn = "dot"; + if (vec_ty->type()->is_integer_scalar()) { + // GLSL does not have a builtin for dot() with integer vector types. + // Generate the helper function if it hasn't been created already + fn = tint::GetOrAdd(int_dot_funcs_, vec_ty, [&]() -> std::string { + TextBuffer b; + TINT_DEFER(helpers_.Append(b)); + + auto fn_name = UniqueIdentifier("tint_int_dot"); + + std::string v; + { + StringStream s; + EmitType(s, vec_ty->type(), core::AddressSpace::kUndefined, core::Access::kRead, + ""); + v = s.str(); + } + { // (u)int tint_int_dot([i|u]vecN a, [i|u]vecN b) { + auto l = Line(&b); + EmitType(l, vec_ty->type(), core::AddressSpace::kUndefined, core::Access::kRead, + ""); + l << " " << fn_name << "("; + EmitType(l, vec_ty, core::AddressSpace::kUndefined, core::Access::kRead, ""); + l << " a, "; + EmitType(l, vec_ty, core::AddressSpace::kUndefined, core::Access::kRead, ""); + l << " b) {"; + } + { + auto l = Line(&b); + l << " return "; + for (uint32_t i = 0; i < vec_ty->Width(); i++) { + if (i > 0) { + l << " + "; + } + l << "a[" << i << "]*b[" << i << "]"; + } + l << ";"; + } + Line(&b) << "}"; + return fn_name; + }); + } + + out << fn; + ScopedParen sp(out); + + EmitExpression(out, expr->args[0]); + out << ", "; + EmitExpression(out, expr->args[1]); +} + +void ASTPrinter::EmitModfCall(StringStream& out, + const ast::CallExpression* expr, + const sem::BuiltinFn* builtin) { + TINT_ASSERT(expr->args.Length() == 1); + CallBuiltinHelper( + out, expr, builtin, [&](TextBuffer* b, const std::vector& params) { + // Emit the builtin return type unique to this overload. This does not + // exist in the AST, so it will not be generated in Generate(). + EmitStructType(&helpers_, builtin->ReturnType()->As()); + + { + auto l = Line(b); + EmitType(l, builtin->ReturnType(), core::AddressSpace::kUndefined, + core::Access::kUndefined, ""); + l << " result;"; + } + Line(b) << "result.fract = modf(" << params[0] << ", result.whole);"; + Line(b) << "return result;"; + }); +} + +void ASTPrinter::EmitFrexpCall(StringStream& out, + const ast::CallExpression* expr, + const sem::BuiltinFn* builtin) { + TINT_ASSERT(expr->args.Length() == 1); + CallBuiltinHelper( + out, expr, builtin, [&](TextBuffer* b, const std::vector& params) { + // Emit the builtin return type unique to this overload. This does not + // exist in the AST, so it will not be generated in Generate(). + EmitStructType(&helpers_, builtin->ReturnType()->As()); + + { + auto l = Line(b); + EmitType(l, builtin->ReturnType(), core::AddressSpace::kUndefined, + core::Access::kUndefined, ""); + l << " result;"; + } + Line(b) << "result.fract = frexp(" << params[0] << ", result.exp);"; + Line(b) << "return result;"; + }); +} + +void ASTPrinter::EmitDegreesCall(StringStream& out, + const ast::CallExpression* expr, + const sem::BuiltinFn* builtin) { + auto* return_elem_type = builtin->ReturnType()->DeepestElement(); + const std::string suffix = Is(return_elem_type) ? "hf" : "f"; + CallBuiltinHelper(out, expr, builtin, + [&](TextBuffer* b, const std::vector& params) { + Line(b) << "return " << params[0] << " * " << std::setprecision(20) + << sem::kRadToDeg << suffix << ";"; + }); +} + +void ASTPrinter::EmitRadiansCall(StringStream& out, + const ast::CallExpression* expr, + const sem::BuiltinFn* builtin) { + auto* return_elem_type = builtin->ReturnType()->DeepestElement(); + const std::string suffix = Is(return_elem_type) ? "hf" : "f"; + CallBuiltinHelper(out, expr, builtin, + [&](TextBuffer* b, const std::vector& params) { + Line(b) << "return " << params[0] << " * " << std::setprecision(20) + << sem::kDegToRad << suffix << ";"; + }); +} + +void ASTPrinter::EmitQuantizeToF16Call(StringStream& out, + const ast::CallExpression* expr, + const sem::BuiltinFn* builtin) { + // Emulate by casting to f16 and back again. + CallBuiltinHelper( + out, expr, builtin, [&](TextBuffer* b, const std::vector& params) { + const auto v = params[0]; + if (auto* vec = builtin->ReturnType()->As()) { + switch (vec->Width()) { + case 2: { + Line(b) << "return unpackHalf2x16(packHalf2x16(" << v << "));"; + return; + } + case 3: { + Line(b) << "return vec3("; + Line(b) << " unpackHalf2x16(packHalf2x16(" << v << ".xy)),"; + Line(b) << " unpackHalf2x16(packHalf2x16(" << v << ".zz)).x);"; + return; + } + default: { + Line(b) << "return vec4("; + Line(b) << " unpackHalf2x16(packHalf2x16(" << v << ".xy)),"; + Line(b) << " unpackHalf2x16(packHalf2x16(" << v << ".zw)));"; + return; + } + } + } + Line(b) << "return unpackHalf2x16(packHalf2x16(vec2(" << v << "))).x;"; + }); +} + +void ASTPrinter::EmitBarrierCall(StringStream& out, const sem::BuiltinFn* builtin) { + // TODO(crbug.com/tint/661): Combine sequential barriers to a single + // instruction. + if (builtin->Fn() == wgsl::BuiltinFn::kWorkgroupBarrier) { + out << "barrier()"; + } else if (builtin->Fn() == wgsl::BuiltinFn::kStorageBarrier) { + out << "{ barrier(); memoryBarrierBuffer(); }"; + } else if (builtin->Fn() == wgsl::BuiltinFn::kTextureBarrier) { + out << "{ barrier(); memoryBarrierImage(); }"; + } else { + TINT_UNREACHABLE() << "unexpected barrier builtin type " << builtin->Fn(); + } +} + +const ast::Expression* ASTPrinter::CreateF32Zero(const sem::Statement* stmt) { + auto* zero = builder_.Expr(0_f); + auto* f32 = builder_.create(); + auto* sem_zero = builder_.create( + zero, f32, core::EvaluationStage::kRuntime, stmt, /* constant_value */ nullptr, + /* has_side_effects */ false); + builder_.Sem().Add(zero, sem_zero); + return zero; +} + +void ASTPrinter::EmitTextureCall(StringStream& out, + const sem::Call* call, + const sem::BuiltinFn* builtin) { + using Usage = core::ParameterUsage; + + auto& signature = builtin->Signature(); + auto* expr = call->Declaration(); + auto arguments = expr->args; + + // Returns the argument with the given usage + auto arg = [&](Usage usage) { + auto idx = signature.IndexOf(usage); + return (idx >= 0) ? arguments[static_cast(idx)] : nullptr; + }; + + auto* texture = arg(Usage::kTexture); + if (TINT_UNLIKELY(!texture)) { + TINT_ICE() << "missing texture argument"; + } + + auto* texture_type = TypeOf(texture)->UnwrapRef()->As(); + + auto emit_signed_int_type = [&](const core::type::Type* ty) { + uint32_t width = ty->Elements().count; + if (width > 1) { + out << "ivec" << width; + } else { + out << "int"; + } + }; + + auto emit_unsigned_int_type = [&](const core::type::Type* ty) { + uint32_t width = ty->Elements().count; + if (width > 1) { + out << "uvec" << width; + } else { + out << "uint"; + } + }; + + auto emit_expr_as_signed = [&](const ast::Expression* e) { + auto* ty = TypeOf(e)->UnwrapRef(); + if (!ty->is_unsigned_integer_scalar_or_vector()) { + EmitExpression(out, e); + return; + } + emit_signed_int_type(ty); + ScopedParen sp(out); + EmitExpression(out, e); + return; + }; + + switch (builtin->Fn()) { + case wgsl::BuiltinFn::kTextureDimensions: { + // textureDimensions() returns an unsigned scalar / vector in WGSL. + // textureSize() / imageSize() returns a signed scalar / vector in GLSL. + // Cast. + emit_unsigned_int_type(call->Type()); + ScopedParen sp(out); + + if (texture_type->Is()) { + out << "imageSize("; + } else { + out << "textureSize("; + } + EmitExpression(out, texture); + + // The LOD parameter is mandatory on textureSize() for non-multisampled + // textures. + if (!texture_type->Is() && + !texture_type->Is() && + !texture_type->Is()) { + out << ", "; + if (auto* level_arg = arg(Usage::kLevel)) { + emit_expr_as_signed(level_arg); + } else { + out << "0"; + } + } + out << ")"; + // textureSize() on array samplers returns the array size in the + // final component, so strip it out. + if (texture_type->dim() == core::type::TextureDimension::k2dArray || + texture_type->dim() == core::type::TextureDimension::kCubeArray) { + out << ".xy"; + } + return; + } + case wgsl::BuiltinFn::kTextureNumLayers: { + // textureNumLayers() returns an unsigned scalar in WGSL. + // textureSize() / imageSize() returns a signed scalar / vector in GLSL. + // Cast. + out << "uint"; + ScopedParen sp(out); + + if (texture_type->Is()) { + out << "imageSize("; + } else { + out << "textureSize("; + } + // textureSize() on sampler2dArray returns the array size in the + // final component, so return it + EmitExpression(out, texture); + + // The LOD parameter is mandatory on textureSize() for non-multisampled + // textures. + if (!texture_type->Is() && + !texture_type->Is() && + !texture_type->Is()) { + out << ", "; + if (auto* level_arg = arg(Usage::kLevel)) { + emit_expr_as_signed(level_arg); + } else { + out << "0"; + } + } + out << ").z"; + return; + } + case wgsl::BuiltinFn::kTextureNumLevels: { + // textureNumLevels() returns an unsigned scalar in WGSL. + // textureQueryLevels() returns a signed scalar in GLSL. + // Cast. + out << "uint"; + ScopedParen sp(out); + + out << "textureQueryLevels("; + EmitExpression(out, texture); + out << ")"; + return; + } + case wgsl::BuiltinFn::kTextureNumSamples: { + // textureNumSamples() returns an unsigned scalar in WGSL. + // textureSamples() returns a signed scalar in GLSL. + // Cast. + out << "uint"; + ScopedParen sp(out); + + out << "textureSamples("; + EmitExpression(out, texture); + out << ")"; + return; + } + default: + break; + } + + uint32_t glsl_ret_width = 4u; + bool append_depth_ref_to_coords = true; + bool is_depth = texture_type->Is(); + + switch (builtin->Fn()) { + case wgsl::BuiltinFn::kTextureSample: + case wgsl::BuiltinFn::kTextureSampleBias: + out << "texture"; + if (is_depth) { + glsl_ret_width = 1u; + } + break; + case wgsl::BuiltinFn::kTextureSampleLevel: + out << "textureLod"; + if (is_depth) { + glsl_ret_width = 1u; + } + break; + case wgsl::BuiltinFn::kTextureGather: + case wgsl::BuiltinFn::kTextureGatherCompare: + out << "textureGather"; + append_depth_ref_to_coords = false; + break; + case wgsl::BuiltinFn::kTextureSampleGrad: + out << "textureGrad"; + break; + case wgsl::BuiltinFn::kTextureSampleCompare: + case wgsl::BuiltinFn::kTextureSampleCompareLevel: + out << "texture"; + glsl_ret_width = 1; + break; + case wgsl::BuiltinFn::kTextureLoad: + if (texture_type->Is()) { + out << "imageLoad"; + } else { + out << "texelFetch"; + } + break; + case wgsl::BuiltinFn::kTextureStore: + out << "imageStore"; + break; + default: + TINT_ICE() << "Unhandled texture builtin '" << std::string(builtin->str()) << "'"; + } + + if (builtin->Signature().IndexOf(core::ParameterUsage::kOffset) >= 0) { + out << "Offset"; + } + + out << "("; + EmitExpression(out, texture); + out << ", "; + + auto* param_coords = arg(Usage::kCoords); + if (TINT_UNLIKELY(!param_coords)) { + TINT_ICE() << "missing coords argument"; + } + + if (auto* array_index = arg(Usage::kArrayIndex)) { + // Array index needs to be appended to the coordinates. + param_coords = + tint::wgsl::AppendVector(&builder_, param_coords, array_index)->Declaration(); + } + + // GLSL requires Dref to be appended to the coordinates, *unless* it's + // samplerCubeArrayShadow, in which case it will be handled as a separate + // parameter. + if (texture_type->dim() == core::type::TextureDimension::kCubeArray) { + append_depth_ref_to_coords = false; + } + + if (is_depth && append_depth_ref_to_coords) { + auto* depth_ref = arg(Usage::kDepthRef); + if (!depth_ref) { + // Sampling a depth texture in GLSL always requires a depth reference, so + // append zero here. + depth_ref = CreateF32Zero(builder_.Sem().Get(param_coords)->Stmt()); + } + param_coords = tint::wgsl::AppendVector(&builder_, param_coords, depth_ref)->Declaration(); + } + + emit_expr_as_signed(param_coords); + + for (auto usage : {Usage::kLevel, Usage::kDdx, Usage::kDdy, Usage::kSampleIndex}) { + if (auto* e = arg(usage)) { + out << ", "; + if (usage == Usage::kLevel && is_depth) { + // WGSL's textureSampleLevel() "level" param is i32 for depth textures, + // whereas GLSL's textureLod() "lod" param is always float, so cast it. + out << "float("; + EmitExpression(out, e); + out << ")"; + } else { + emit_expr_as_signed(e); + } + } + } + + if (auto* e = arg(Usage::kValue)) { + out << ", "; + EmitExpression(out, e); + } + + // GLSL's textureGather always requires a refZ parameter. + if (is_depth && builtin->Fn() == wgsl::BuiltinFn::kTextureGather) { + out << ", 0.0"; + } + + // [1] samplerCubeArrayShadow requires a separate depthRef parameter + if (is_depth && !append_depth_ref_to_coords) { + if (auto* e = arg(Usage::kDepthRef)) { + out << ", "; + EmitExpression(out, e); + } else if (builtin->Fn() == wgsl::BuiltinFn::kTextureSample) { + out << ", 0.0f"; + } + } + + for (auto usage : {Usage::kOffset, Usage::kComponent, Usage::kBias}) { + if (auto* e = arg(usage)) { + out << ", "; + emit_expr_as_signed(e); + } + } + + out << ")"; + + if (builtin->ReturnType()->Is()) { + return; + } + // If the builtin return type does not match the number of elements of the + // GLSL builtin, we need to swizzle the expression to generate the correct + // number of components. + uint32_t wgsl_ret_width = 1; + if (auto* vec = builtin->ReturnType()->As()) { + wgsl_ret_width = vec->Width(); + } + if (wgsl_ret_width < glsl_ret_width) { + out << "."; + for (uint32_t i = 0; i < wgsl_ret_width; i++) { + out << "xyz"[i]; + } + } + if (TINT_UNLIKELY(wgsl_ret_width > glsl_ret_width)) { + TINT_ICE() << "WGSL return width (" << wgsl_ret_width + << ") is wider than GLSL return width (" << glsl_ret_width << ") for " + << builtin->Fn(); + } +} + +std::string ASTPrinter::generate_builtin_name(const sem::BuiltinFn* builtin) { + switch (builtin->Fn()) { + case wgsl::BuiltinFn::kAbs: + case wgsl::BuiltinFn::kAcos: + case wgsl::BuiltinFn::kAcosh: + case wgsl::BuiltinFn::kAll: + case wgsl::BuiltinFn::kAny: + case wgsl::BuiltinFn::kAsin: + case wgsl::BuiltinFn::kAsinh: + case wgsl::BuiltinFn::kAtan: + case wgsl::BuiltinFn::kAtanh: + case wgsl::BuiltinFn::kCeil: + case wgsl::BuiltinFn::kClamp: + case wgsl::BuiltinFn::kCos: + case wgsl::BuiltinFn::kCosh: + case wgsl::BuiltinFn::kCross: + case wgsl::BuiltinFn::kDeterminant: + case wgsl::BuiltinFn::kDistance: + case wgsl::BuiltinFn::kDot: + case wgsl::BuiltinFn::kExp: + case wgsl::BuiltinFn::kExp2: + case wgsl::BuiltinFn::kFloor: + case wgsl::BuiltinFn::kFrexp: + case wgsl::BuiltinFn::kLdexp: + case wgsl::BuiltinFn::kLength: + case wgsl::BuiltinFn::kLog: + case wgsl::BuiltinFn::kLog2: + case wgsl::BuiltinFn::kMax: + case wgsl::BuiltinFn::kMin: + case wgsl::BuiltinFn::kModf: + case wgsl::BuiltinFn::kNormalize: + case wgsl::BuiltinFn::kPow: + case wgsl::BuiltinFn::kReflect: + case wgsl::BuiltinFn::kRefract: + case wgsl::BuiltinFn::kRound: + case wgsl::BuiltinFn::kSign: + case wgsl::BuiltinFn::kSin: + case wgsl::BuiltinFn::kSinh: + case wgsl::BuiltinFn::kSqrt: + case wgsl::BuiltinFn::kStep: + case wgsl::BuiltinFn::kTan: + case wgsl::BuiltinFn::kTanh: + case wgsl::BuiltinFn::kTranspose: + case wgsl::BuiltinFn::kTrunc: + return builtin->str(); + case wgsl::BuiltinFn::kAtan2: + return "atan"; + case wgsl::BuiltinFn::kCountOneBits: + return "bitCount"; + case wgsl::BuiltinFn::kDpdx: + return "dFdx"; + case wgsl::BuiltinFn::kDpdxCoarse: + if (version_.IsES()) { + return "dFdx"; + } + return "dFdxCoarse"; + case wgsl::BuiltinFn::kDpdxFine: + if (version_.IsES()) { + return "dFdx"; + } + return "dFdxFine"; + case wgsl::BuiltinFn::kDpdy: + return "dFdy"; + case wgsl::BuiltinFn::kDpdyCoarse: + if (version_.IsES()) { + return "dFdy"; + } + return "dFdyCoarse"; + case wgsl::BuiltinFn::kDpdyFine: + if (version_.IsES()) { + return "dFdy"; + } + return "dFdyFine"; + case wgsl::BuiltinFn::kFaceForward: + return "faceforward"; + case wgsl::BuiltinFn::kFract: + return "fract"; + case wgsl::BuiltinFn::kFma: + return "fma"; + case wgsl::BuiltinFn::kFwidth: + case wgsl::BuiltinFn::kFwidthCoarse: + case wgsl::BuiltinFn::kFwidthFine: + return "fwidth"; + case wgsl::BuiltinFn::kInverseSqrt: + return "inversesqrt"; + case wgsl::BuiltinFn::kMix: + return "mix"; + case wgsl::BuiltinFn::kPack2X16Float: + return "packHalf2x16"; + case wgsl::BuiltinFn::kPack2X16Snorm: + return "packSnorm2x16"; + case wgsl::BuiltinFn::kPack2X16Unorm: + return "packUnorm2x16"; + case wgsl::BuiltinFn::kPack4X8Snorm: + return "packSnorm4x8"; + case wgsl::BuiltinFn::kPack4X8Unorm: + return "packUnorm4x8"; + case wgsl::BuiltinFn::kReverseBits: + return "bitfieldReverse"; + case wgsl::BuiltinFn::kSmoothstep: + return "smoothstep"; + case wgsl::BuiltinFn::kUnpack2X16Float: + return "unpackHalf2x16"; + case wgsl::BuiltinFn::kUnpack2X16Snorm: + return "unpackSnorm2x16"; + case wgsl::BuiltinFn::kUnpack2X16Unorm: + return "unpackUnorm2x16"; + case wgsl::BuiltinFn::kUnpack4X8Snorm: + return "unpackSnorm4x8"; + case wgsl::BuiltinFn::kUnpack4X8Unorm: + return "unpackUnorm4x8"; + default: + diagnostics_.AddError(Source{}) << "Unknown builtin method: " << builtin; + } + + return ""; +} + +void ASTPrinter::EmitCase(const ast::CaseStatement* stmt) { + auto* sem = builder_.Sem().Get(stmt); + for (auto* selector : sem->Selectors()) { + auto out = Line(); + + if (selector->IsDefault()) { + out << "default"; + } else { + out << "case "; + EmitConstant(out, selector->Value()); + } + out << ":"; + if (selector == sem->Selectors().back()) { + out << " {"; + } + } + + { + ScopedIndent si(this); + EmitStatements(stmt->body->statements); + if (!last_is_break(stmt->body)) { + Line() << "break;"; + } + } + + Line() << "}"; +} + +void ASTPrinter::EmitContinue(const ast::ContinueStatement*) { + if (emit_continuing_) { + emit_continuing_(); + } + Line() << "continue;"; +} + +void ASTPrinter::EmitDiscard(const ast::DiscardStatement*) { + // TODO(dsinclair): Verify this is correct when the discard semantics are + // defined for WGSL (https://github.com/gpuweb/gpuweb/issues/361) + Line() << "discard;"; +} + +void ASTPrinter::EmitExpression(StringStream& out, const ast::Expression* expr) { + if (auto* sem = builder_.Sem().GetVal(expr)) { + if (auto* constant = sem->ConstantValue()) { + EmitConstant(out, constant); + return; + } + } + Switch( + expr, // + [&](const ast::IndexAccessorExpression* a) { EmitIndexAccessor(out, a); }, + [&](const ast::BinaryExpression* b) { EmitBinary(out, b); }, + [&](const ast::CallExpression* c) { EmitCall(out, c); }, + [&](const ast::IdentifierExpression* i) { EmitIdentifier(out, i); }, + [&](const ast::LiteralExpression* l) { EmitLiteral(out, l); }, + [&](const ast::MemberAccessorExpression* m) { EmitMemberAccessor(out, m); }, + [&](const ast::UnaryOpExpression* u) { EmitUnaryOp(out, u); }, // + TINT_ICE_ON_NO_MATCH); +} + +void ASTPrinter::EmitIdentifier(StringStream& out, const ast::IdentifierExpression* expr) { + out << expr->identifier->symbol.Name(); +} + +void ASTPrinter::EmitIf(const ast::IfStatement* stmt) { + { + auto out = Line(); + out << "if ("; + EmitExpression(out, stmt->condition); + out << ") {"; + } + EmitStatementsWithIndent(stmt->body->statements); + + if (stmt->else_statement) { + Line() << "} else {"; + if (auto* block = stmt->else_statement->As()) { + EmitStatementsWithIndent(block->statements); + } else { + EmitStatementsWithIndent(Vector{stmt->else_statement}); + } + } + Line() << "}"; +} + +void ASTPrinter::EmitFunction(const ast::Function* func) { + auto* sem = builder_.Sem().Get(func); + + if (ast::HasAttribute(func->attributes)) { + // An internal function. Do not emit. + return; + } + + { + auto out = Line(); + auto name = func->name->symbol.Name(); + EmitType(out, sem->ReturnType(), core::AddressSpace::kUndefined, core::Access::kReadWrite, + ""); + out << " " << name << "("; + + bool first = true; + for (auto* v : sem->Parameters()) { + if (!first) { + out << ", "; + } + first = false; + + auto const* type = v->Type(); + + if (auto* ptr = type->As()) { + // 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 AddressSpace::kUndefined on parameters, however + // the sanitizer transforms generates load / store functions for storage + // or uniform buffers. These functions have a buffer parameter with + // AddressSpace::kStorage or AddressSpace::kUniform. This is required to + // correctly translate the parameter to a [RW]ByteAddressBuffer for + // storage buffers and a uint4[N] for uniform buffers. + EmitTypeAndName(out, type, v->AddressSpace(), v->Access(), + v->Declaration()->name->symbol.Name()); + } + out << ") {"; + } + + EmitStatementsWithIndent(func->body->statements); + + Line() << "}"; + Line(); +} + +void ASTPrinter::EmitGlobalVariable(const ast::Variable* global) { + Switch( + global, // + [&](const ast::Var* var) { + auto* sem = builder_.Sem().Get(global); + switch (sem->AddressSpace()) { + case core::AddressSpace::kUniform: + EmitUniformVariable(var, sem); + return; + case core::AddressSpace::kStorage: + EmitStorageVariable(var, sem); + return; + case core::AddressSpace::kHandle: + EmitHandleVariable(var, sem); + return; + case core::AddressSpace::kPrivate: + EmitPrivateVariable(sem); + return; + case core::AddressSpace::kWorkgroup: + EmitWorkgroupVariable(sem); + return; + case core::AddressSpace::kIn: + case core::AddressSpace::kOut: + EmitIOVariable(sem); + return; + case core::AddressSpace::kPushConstant: + EmitPushConstant(sem); + return; + default: { + TINT_ICE() << "unhandled address space " << sem->AddressSpace(); + } + } + }, + [&](const ast::Let* let) { EmitProgramConstVariable(let); }, + [&](const ast::Override*) { + // Override is removed with SubstituteOverride + diagnostics_.AddError(Source{}) + << "override-expressions should have been removed with the " + "SubstituteOverride transform"; + }, + [&](const ast::Const*) { + // Constants are embedded at their use + }, // + TINT_ICE_ON_NO_MATCH); +} + +void ASTPrinter::EmitUniformVariable(const ast::Var* var, const sem::Variable* sem) { + auto* type = sem->Type()->UnwrapRef(); + auto* str = type->As(); + if (TINT_UNLIKELY(!str)) { + TINT_ICE() << "storage variable must be of struct type"; + } + auto bp = *sem->As()->Attributes().binding_point; + { + auto out = Line(); + out << "layout(binding = " << bp.binding << ", std140"; + out << ") uniform " << UniqueIdentifier(StructName(str) + "_ubo") << " {"; + } + EmitStructMembers(current_buffer_, str); + auto name = var->name->symbol.Name(); + Line() << "} " << name << ";"; + Line(); +} + +void ASTPrinter::EmitStorageVariable(const ast::Var* var, const sem::Variable* sem) { + auto* type = sem->Type()->UnwrapRef(); + auto* str = type->As(); + if (TINT_UNLIKELY(!str)) { + TINT_ICE() << "storage variable must be of struct type"; + } + auto bp = *sem->As()->Attributes().binding_point; + Line() << "layout(binding = " << bp.binding << ", std430) buffer " + << UniqueIdentifier(StructName(str) + "_ssbo") << " {"; + EmitStructMembers(current_buffer_, str); + auto name = var->name->symbol.Name(); + Line() << "} " << name << ";"; + Line(); +} + +void ASTPrinter::EmitHandleVariable(const ast::Var* var, const sem::Variable* sem) { + auto out = Line(); + + auto name = var->name->symbol.Name(); + auto* type = sem->Type()->UnwrapRef(); + if (type->Is()) { + // GLSL ignores Sampler variables. + return; + } + + if (auto* storage = type->As()) { + out << "layout("; + switch (storage->texel_format()) { + case core::TexelFormat::kBgra8Unorm: + TINT_ICE() << "bgra8unorm should have been polyfilled to rgba8unorm"; + case core::TexelFormat::kR32Uint: + out << "r32ui"; + break; + case core::TexelFormat::kR32Sint: + out << "r32i"; + break; + case core::TexelFormat::kR32Float: + out << "r32f"; + break; + case core::TexelFormat::kRgba8Unorm: + out << "rgba8"; + break; + case core::TexelFormat::kRgba8Snorm: + out << "rgba8_snorm"; + break; + case core::TexelFormat::kRgba8Uint: + out << "rgba8ui"; + break; + case core::TexelFormat::kRgba8Sint: + out << "rgba8i"; + break; + case core::TexelFormat::kRg32Uint: + out << "rg32ui"; + break; + case core::TexelFormat::kRg32Sint: + out << "rg32i"; + break; + case core::TexelFormat::kRg32Float: + out << "rg32f"; + break; + case core::TexelFormat::kRgba16Uint: + out << "rgba16ui"; + break; + case core::TexelFormat::kRgba16Sint: + out << "rgba16i"; + break; + case core::TexelFormat::kRgba16Float: + out << "rgba16f"; + break; + case core::TexelFormat::kRgba32Uint: + out << "rgba32ui"; + break; + case core::TexelFormat::kRgba32Sint: + out << "rgba32i"; + break; + case core::TexelFormat::kRgba32Float: + out << "rgba32f"; + break; + case core::TexelFormat::kR8Unorm: + out << "r8"; + break; + case core::TexelFormat::kUndefined: + TINT_ICE() << "invalid texel format"; + } + out << ") "; + } + EmitTypeAndName(out, type, sem->AddressSpace(), sem->Access(), name); + out << ";"; +} + +void ASTPrinter::EmitPrivateVariable(const sem::Variable* var) { + auto* decl = var->Declaration(); + auto out = Line(); + + auto name = decl->name->symbol.Name(); + auto* type = var->Type()->UnwrapRef(); + EmitTypeAndName(out, type, var->AddressSpace(), var->Access(), name); + + out << " = "; + if (auto* initializer = decl->initializer) { + EmitExpression(out, initializer); + } else { + EmitZeroValue(out, var->Type()->UnwrapRef()); + } + out << ";"; +} + +void ASTPrinter::EmitWorkgroupVariable(const sem::Variable* var) { + auto* decl = var->Declaration(); + auto out = Line(); + + out << "shared "; + + auto name = decl->name->symbol.Name(); + auto* type = var->Type()->UnwrapRef(); + EmitTypeAndName(out, type, var->AddressSpace(), var->Access(), name); + + if (auto* initializer = decl->initializer) { + out << " = "; + EmitExpression(out, initializer); + } + + out << ";"; +} + +void ASTPrinter::EmitIOVariable(const sem::GlobalVariable* var) { + auto* decl = var->Declaration(); + + if (auto* attr = ast::GetAttribute(decl->attributes)) { + auto builtin = builder_.Sem().Get(attr)->Value(); + // Use of gl_SampleID requires the GL_OES_sample_variables extension + if (RequiresOESSampleVariables(builtin)) { + requires_oes_sample_variables_ = true; + } + // Do not emit builtin (gl_) variables. + return; + } + + auto out = Line(); + EmitAttributes(out, var); + EmitInterpolationQualifiers(out, decl->attributes); + + auto name = decl->name->symbol.Name(); + auto* type = var->Type()->UnwrapRef(); + EmitTypeAndName(out, type, var->AddressSpace(), var->Access(), name); + + if (auto* initializer = decl->initializer) { + out << " = "; + EmitExpression(out, initializer); + } + out << ";"; +} + +void ASTPrinter::EmitPushConstant(const sem::GlobalVariable* var) { + auto* decl = var->Declaration(); + + auto out = Line(); + + auto name = decl->name->symbol.Name(); + auto* type = var->Type()->UnwrapRef(); + out << "layout(location=0) "; + EmitTypeAndName(out, type, var->AddressSpace(), var->Access(), name); + out << ";"; +} + +void ASTPrinter::EmitInterpolationQualifiers(StringStream& out, + VectorRef attributes) { + for (auto* attr : attributes) { + if (auto* interpolate = attr->As()) { + auto& sem = builder_.Sem(); + auto i_type = + sem.Get>(interpolate->type) + ->Value(); + switch (i_type) { + case core::InterpolationType::kPerspective: + case core::InterpolationType::kLinear: + case core::InterpolationType::kUndefined: + break; + case core::InterpolationType::kFlat: + out << "flat "; + break; + } + + if (interpolate->sampling) { + auto i_smpl = sem.Get>( + interpolate->sampling) + ->Value(); + switch (i_smpl) { + case core::InterpolationSampling::kCentroid: + out << "centroid "; + break; + case core::InterpolationSampling::kSample: + case core::InterpolationSampling::kCenter: + case core::InterpolationSampling::kUndefined: + break; + } + } + } + } +} + +void ASTPrinter::EmitAttributes(StringStream& out, const sem::GlobalVariable* var) { + auto& attrs = var->Attributes(); + + bool first = true; + if (attrs.location.has_value()) { + out << (first ? "layout(" : ", "); + out << "location = " << std::to_string(attrs.location.value()); + first = false; + } + if (attrs.index.has_value()) { + out << ", index = " << std::to_string(attrs.index.value()); + } + if (!first) { + out << ") "; + } +} + +void ASTPrinter::EmitEntryPointFunction(const ast::Function* func) { + auto* func_sem = builder_.Sem().Get(func); + + if (func->PipelineStage() == ast::PipelineStage::kFragment) { + requires_default_precision_qualifier_ = true; + } + + if (func->PipelineStage() == ast::PipelineStage::kCompute) { + auto out = Line(); + // Emit the layout(local_size) attributes. + auto wgsize = func_sem->WorkgroupSize(); + out << "layout("; + for (size_t i = 0; i < 3; i++) { + if (i > 0) { + out << ", "; + } + out << "local_size_" << (i == 0 ? "x" : i == 1 ? "y" : "z") << " = "; + + if (!wgsize[i].has_value()) { + diagnostics_.AddError(Source{}) + << "override-expressions should have been removed with the SubstituteOverride " + "transform"; + return; + } + out << std::to_string(wgsize[i].value()); + } + out << ") in;"; + } + + // Emit original entry point signature + { + auto out = Line(); + EmitTypeAndName(out, func_sem->ReturnType(), core::AddressSpace::kUndefined, + core::Access::kUndefined, func->name->symbol.Name()); + out << "("; + + bool first = true; + + // Emit entry point parameters. + for (auto* var : func->params) { + auto* sem = builder_.Sem().Get(var); + auto* type = sem->Type(); + if (TINT_UNLIKELY(!type->Is())) { + // ICE likely indicates that the CanonicalizeEntryPointIO transform was + // not run, or a builtin parameter was added after it was run. + TINT_ICE() << "Unsupported non-struct entry point parameter"; + } + + if (!first) { + out << ", "; + } + first = false; + + EmitTypeAndName(out, type, sem->AddressSpace(), sem->Access(), + var->name->symbol.Name()); + } + + out << ") {"; + } + + // Emit original entry point function body + { + ScopedIndent si(this); + if (func->PipelineStage() == ast::PipelineStage::kVertex) { + Line() << "gl_PointSize = 1.0;"; + } + + EmitStatements(func->body->statements); + + if (!Is(func->body->Last())) { + ast::ReturnStatement ret(GenerationID{}, ast::NodeID{}, Source{}); + EmitStatement(&ret); + } + } + + Line() << "}"; +} + +void ASTPrinter::EmitConstant(StringStream& out, const core::constant::Value* constant) { + Switch( + constant->Type(), // + [&](const core::type::Bool*) { out << (constant->ValueAs() ? "true" : "false"); }, + [&](const core::type::F32*) { PrintF32(out, constant->ValueAs()); }, + [&](const core::type::F16*) { PrintF16(out, constant->ValueAs()); }, + [&](const core::type::I32*) { PrintI32(out, constant->ValueAs()); }, + [&](const core::type::U32*) { out << constant->ValueAs() << "u"; }, + [&](const core::type::Vector* v) { + EmitType(out, v, core::AddressSpace::kUndefined, core::Access::kUndefined, ""); + + ScopedParen sp(out); + + if (auto* splat = constant->As()) { + EmitConstant(out, splat->el); + return; + } + + for (size_t i = 0; i < v->Width(); i++) { + if (i > 0) { + out << ", "; + } + EmitConstant(out, constant->Index(i)); + } + }, + [&](const core::type::Matrix* m) { + EmitType(out, m, core::AddressSpace::kUndefined, core::Access::kUndefined, ""); + + ScopedParen sp(out); + + for (size_t column_idx = 0; column_idx < m->columns(); column_idx++) { + if (column_idx > 0) { + out << ", "; + } + EmitConstant(out, constant->Index(column_idx)); + } + }, + [&](const core::type::Array* a) { + EmitType(out, a, core::AddressSpace::kUndefined, core::Access::kUndefined, ""); + + ScopedParen sp(out); + + auto count = a->ConstantCount(); + if (!count) { + diagnostics_.AddError(Source{}) << core::type::Array::kErrExpectedConstantCount; + return; + } + + for (size_t i = 0; i < count; i++) { + if (i > 0) { + out << ", "; + } + EmitConstant(out, constant->Index(i)); + } + }, + [&](const core::type::Struct* s) { + EmitStructType(&helpers_, s); + + out << StructName(s); + + ScopedParen sp(out); + + for (size_t i = 0; i < s->Members().Length(); i++) { + if (i > 0) { + out << ", "; + } + EmitConstant(out, constant->Index(i)); + } + }, // + TINT_ICE_ON_NO_MATCH); +} + +void ASTPrinter::EmitLiteral(StringStream& out, const ast::LiteralExpression* lit) { + Switch( + lit, // + [&](const ast::BoolLiteralExpression* l) { out << (l->value ? "true" : "false"); }, + [&](const ast::FloatLiteralExpression* l) { + if (l->suffix == ast::FloatLiteralExpression::Suffix::kH) { + PrintF16(out, static_cast(l->value)); + } else { + PrintF32(out, static_cast(l->value)); + } + }, + [&](const ast::IntLiteralExpression* i) { + switch (i->suffix) { + case ast::IntLiteralExpression::Suffix::kNone: + case ast::IntLiteralExpression::Suffix::kI: { + PrintI32(out, static_cast(i->value)); + return; + } + case ast::IntLiteralExpression::Suffix::kU: { + out << i->value << "u"; + return; + } + } + diagnostics_.AddError(Source{}) << "unknown integer literal suffix type"; + }, // + TINT_ICE_ON_NO_MATCH); +} + +void ASTPrinter::EmitZeroValue(StringStream& out, const core::type::Type* type) { + if (type->Is()) { + out << "false"; + } else if (type->Is()) { + out << "0.0f"; + } else if (type->Is()) { + out << "0.0hf"; + } else if (type->Is()) { + out << "0"; + } else if (type->Is()) { + out << "0u"; + } else if (auto* vec = type->As()) { + EmitType(out, type, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); + ScopedParen sp(out); + for (uint32_t i = 0; i < vec->Width(); i++) { + if (i != 0) { + out << ", "; + } + EmitZeroValue(out, vec->type()); + } + } else if (auto* mat = type->As()) { + EmitType(out, type, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); + ScopedParen sp(out); + for (uint32_t i = 0; i < (mat->rows() * mat->columns()); i++) { + if (i != 0) { + out << ", "; + } + EmitZeroValue(out, mat->type()); + } + } else if (auto* str = type->As()) { + EmitType(out, type, core::AddressSpace::kUndefined, core::Access::kUndefined, ""); + bool first = true; + ScopedParen sp(out); + for (auto* member : str->Members()) { + if (!first) { + out << ", "; + } else { + first = false; + } + EmitZeroValue(out, member->Type()); + } + } else if (auto* arr = type->As()) { + EmitType(out, type, core::AddressSpace::kUndefined, core::Access::kUndefined, ""); + ScopedParen sp(out); + + auto count = arr->ConstantCount(); + if (!count) { + diagnostics_.AddError(Source{}) << core::type::Array::kErrExpectedConstantCount; + return; + } + + for (uint32_t i = 0; i < count; i++) { + if (i != 0) { + out << ", "; + } + EmitZeroValue(out, arr->ElemType()); + } + } else { + diagnostics_.AddError(Source{}) + << "Invalid type for zero emission: " << type->FriendlyName(); + } +} + +void ASTPrinter::EmitLoop(const ast::LoopStatement* stmt) { + auto emit_continuing = [this, stmt] { + if (stmt->continuing && !stmt->continuing->Empty()) { + EmitBlock(stmt->continuing); + } + }; + + TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing); + Line() << "while (true) {"; + { + ScopedIndent si(this); + EmitStatements(stmt->body->statements); + emit_continuing_(); + } + Line() << "}"; +} + +void ASTPrinter::EmitForLoop(const ast::ForLoopStatement* stmt) { + // Nest a for loop with a new block. In HLSL the initializer scope is not + // nested by the for-loop, so we may get variable redefinitions. + Line() << "{"; + IncrementIndent(); + TINT_DEFER({ + DecrementIndent(); + Line() << "}"; + }); + + TextBuffer init_buf; + if (auto* init = stmt->initializer) { + TINT_SCOPED_ASSIGNMENT(current_buffer_, &init_buf); + EmitStatement(init); + } + + TextBuffer cond_pre; + StringStream cond_buf; + if (auto* cond = stmt->condition) { + TINT_SCOPED_ASSIGNMENT(current_buffer_, &cond_pre); + EmitExpression(cond_buf, cond); + } + + TextBuffer cont_buf; + if (auto* cont = stmt->continuing) { + TINT_SCOPED_ASSIGNMENT(current_buffer_, &cont_buf); + EmitStatement(cont); + } + + // If the for-loop has a multi-statement conditional and / or continuing, then + // we cannot emit this as a regular for-loop in HLSL. Instead we need to + // generate a `while(true)` loop. + bool emit_as_loop = cond_pre.lines.size() > 0 || cont_buf.lines.size() > 1; + + // If the for-loop has multi-statement initializer, or is going to be emitted + // as a `while(true)` loop, then declare the initializer statement(s) before + // the loop. + if (init_buf.lines.size() > 1 || (stmt->initializer && emit_as_loop)) { + current_buffer_->Append(init_buf); + init_buf.lines.clear(); // Don't emit the initializer again in the 'for' + } + + if (emit_as_loop) { + auto emit_continuing = [&] { current_buffer_->Append(cont_buf); }; + + TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing); + Line() << "while (true) {"; + IncrementIndent(); + TINT_DEFER({ + DecrementIndent(); + Line() << "}"; + }); + + if (stmt->condition) { + current_buffer_->Append(cond_pre); + Line() << "if (!(" << cond_buf.str() << ")) { break; }"; + } + + EmitStatements(stmt->body->statements); + emit_continuing_(); + } else { + // For-loop can be generated. + { + auto out = Line(); + out << "for"; + { + ScopedParen sp(out); + + if (!init_buf.lines.empty()) { + out << init_buf.lines[0].content << " "; + } else { + out << "; "; + } + + out << cond_buf.str() << "; "; + + if (!cont_buf.lines.empty()) { + out << tint::TrimSuffix(cont_buf.lines[0].content, ";"); + } + } + out << " {"; + } + { + auto emit_continuing = [] { return true; }; + TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing); + EmitStatementsWithIndent(stmt->body->statements); + } + Line() << "}"; + } +} + +void ASTPrinter::EmitWhile(const ast::WhileStatement* stmt) { + TextBuffer cond_pre; + StringStream cond_buf; + { + auto* cond = stmt->condition; + TINT_SCOPED_ASSIGNMENT(current_buffer_, &cond_pre); + EmitExpression(cond_buf, cond); + } + + auto emit_continuing = [&] {}; + TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing); + + // If the whilehas a multi-statement conditional, then we cannot emit this + // as a regular while in GLSL. Instead we need to generate a `while(true)` loop. + bool emit_as_loop = cond_pre.lines.size() > 0; + if (emit_as_loop) { + Line() << "while (true) {"; + IncrementIndent(); + TINT_DEFER({ + DecrementIndent(); + Line() << "}"; + }); + + current_buffer_->Append(cond_pre); + Line() << "if (!(" << cond_buf.str() << ")) { break; }"; + + EmitStatements(stmt->body->statements); + } else { + // While can be generated. + { + auto out = Line(); + out << "while"; + { + ScopedParen sp(out); + out << cond_buf.str(); + } + out << " {"; + } + EmitStatementsWithIndent(stmt->body->statements); + Line() << "}"; + } +} + +void ASTPrinter::EmitMemberAccessor(StringStream& out, const ast::MemberAccessorExpression* expr) { + EmitExpression(out, expr->object); + out << "."; + + auto* sem = builder_.Sem().Get(expr)->UnwrapLoad(); + + Switch( + sem, + [&](const sem::Swizzle*) { + // Swizzles output the name directly + out << expr->member->symbol.Name(); + }, + [&](const sem::StructMemberAccess* member_access) { + out << member_access->Member()->Name().Name(); + }, // + TINT_ICE_ON_NO_MATCH); +} + +void ASTPrinter::EmitReturn(const ast::ReturnStatement* stmt) { + if (stmt->value) { + auto out = Line(); + out << "return "; + EmitExpression(out, stmt->value); + out << ";"; + } else { + Line() << "return;"; + } +} + +void ASTPrinter::EmitStatement(const ast::Statement* stmt) { + Switch( + stmt, // + [&](const ast::AssignmentStatement* a) { EmitAssign(a); }, + [&](const ast::BlockStatement* b) { EmitBlock(b); }, + [&](const ast::BreakStatement* b) { EmitBreak(b); }, + [&](const ast::BreakIfStatement* b) { EmitBreakIf(b); }, + [&](const ast::CallStatement* c) { + auto out = Line(); + EmitCall(out, c->expr); + out << ";"; + }, + [&](const ast::ContinueStatement* c) { EmitContinue(c); }, + [&](const ast::DiscardStatement* d) { EmitDiscard(d); }, + [&](const ast::IfStatement* i) { EmitIf(i); }, + [&](const ast::LoopStatement* l) { EmitLoop(l); }, + [&](const ast::ForLoopStatement* l) { EmitForLoop(l); }, + [&](const ast::WhileStatement* l) { EmitWhile(l); }, + [&](const ast::ReturnStatement* r) { EmitReturn(r); }, + [&](const ast::SwitchStatement* s) { EmitSwitch(s); }, + [&](const ast::VariableDeclStatement* v) { + Switch( + v->variable, // + [&](const ast::Var* var) { EmitVar(var); }, + [&](const ast::Let* let) { EmitLet(let); }, + [&](const ast::Const*) { + // Constants are embedded at their use + }, // + TINT_ICE_ON_NO_MATCH); + }, + [&](const ast::ConstAssert*) { + // Not emitted + }, // + TINT_ICE_ON_NO_MATCH); +} + +void ASTPrinter::EmitSwitch(const ast::SwitchStatement* stmt) { + { // switch(expr) { + auto out = Line(); + out << "switch("; + EmitExpression(out, stmt->condition); + out << ") {"; + } + + { + ScopedIndent si(this); + for (auto* s : stmt->body) { + EmitCase(s); + } + } + + Line() << "}"; +} + +void ASTPrinter::EmitType(StringStream& out, + const core::type::Type* type, + core::AddressSpace address_space, + core::Access access, + const std::string& name, + bool* name_printed /* = nullptr */) { + if (name_printed) { + *name_printed = false; + } + switch (address_space) { + case core::AddressSpace::kIn: { + out << "in "; + break; + } + case core::AddressSpace::kOut: { + out << "out "; + break; + } + case core::AddressSpace::kUniform: + case core::AddressSpace::kPushConstant: + case core::AddressSpace::kHandle: { + out << "uniform "; + break; + } + default: + break; + } + + if (auto* ary = type->As()) { + const core::type::Type* base_type = ary; + std::vector sizes; + while (auto* arr = base_type->As()) { + if (arr->Count()->Is()) { + sizes.push_back(0); + } else { + auto count = arr->ConstantCount(); + if (!count) { + diagnostics_.AddError(Source{}) << core::type::Array::kErrExpectedConstantCount; + return; + } + sizes.push_back(count.value()); + } + + base_type = arr->ElemType(); + } + EmitType(out, base_type, address_space, access, ""); + if (!name.empty()) { + out << " " << name; + if (name_printed) { + *name_printed = true; + } + } + for (uint32_t size : sizes) { + if (size > 0) { + out << "[" << size << "]"; + } else { + out << "[]"; + } + } + } else if (type->Is()) { + out << "bool"; + } else if (type->Is()) { + out << "float"; + } else if (type->Is()) { + out << "float16_t"; + } else if (type->Is()) { + out << "int"; + } else if (auto* mat = type->As()) { + TINT_ASSERT((mat->type()->IsAnyOf())); + if (mat->type()->Is()) { + out << "f16"; + } + out << "mat" << mat->columns(); + if (mat->rows() != mat->columns()) { + out << "x" << mat->rows(); + } + } else if (TINT_UNLIKELY(type->Is())) { + TINT_ICE() << "Attempting to emit pointer type. These should have been removed with the " + "SimplifyPointers transform"; + } else if (type->Is()) { + } else if (auto* str = type->As()) { + out << StructName(str); + } else if (auto* tex = type->As()) { + if (TINT_UNLIKELY(tex->Is())) { + TINT_ICE() << "Multiplanar external texture transform was not run."; + } + + auto* storage = tex->As(); + auto* ms = tex->As(); + auto* depth_ms = tex->As(); + auto* sampled = tex->As(); + + out << "highp "; + + if (storage) { + switch (storage->access()) { + case core::Access::kRead: + out << "readonly "; + break; + case core::Access::kWrite: + out << "writeonly "; + break; + case core::Access::kReadWrite: { + // ESSL 3.1 SPEC (chapter 4.9, Memory Access Qualifiers): + // Except for image variables qualified with the format qualifiers r32f, r32i, + // and r32ui, image variables must specify either memory qualifier readonly or + // the memory qualifier writeonly. + switch (storage->texel_format()) { + case core::TexelFormat::kR32Float: + case core::TexelFormat::kR32Sint: + case core::TexelFormat::kR32Uint: + break; + default: { + // TODO(dawn:1972): Fix the tests that contain read-write storage + // textures with illegal formats. + out << "writeonly "; + break; + } + } + } break; + default: + TINT_UNREACHABLE() << "unexpected storage texture access " << storage->access(); + } + } + auto* subtype = sampled ? sampled->type() + : storage ? storage->type() + : ms ? ms->type() + : nullptr; + if (!subtype || subtype->Is()) { + } else if (subtype->Is()) { + out << "i"; + } else if (TINT_LIKELY(subtype->Is())) { + out << "u"; + } else { + TINT_ICE() << "Unsupported texture type"; + } + + out << (storage ? "image" : "sampler"); + + switch (tex->dim()) { + case core::type::TextureDimension::k1d: + out << "1D"; + break; + case core::type::TextureDimension::k2d: + out << ((ms || depth_ms) ? "2DMS" : "2D"); + break; + case core::type::TextureDimension::k2dArray: + out << ((ms || depth_ms) ? "2DMSArray" : "2DArray"); + break; + case core::type::TextureDimension::k3d: + out << "3D"; + break; + case core::type::TextureDimension::kCube: + out << "Cube"; + break; + case core::type::TextureDimension::kCubeArray: + out << "CubeArray"; + break; + default: + TINT_UNREACHABLE() << "unexpected TextureDimension " << tex->dim(); + } + if (tex->Is()) { + out << "Shadow"; + } + } else if (type->Is()) { + out << "uint"; + } else if (auto* vec = type->As()) { + auto width = vec->Width(); + if (vec->type()->Is() && width >= 1 && width <= 4) { + out << "vec" << width; + } else if (vec->type()->Is() && width >= 1 && width <= 4) { + out << "f16vec" << width; + } else if (vec->type()->Is() && width >= 1 && width <= 4) { + out << "ivec" << width; + } else if (vec->type()->Is() && width >= 1 && width <= 4) { + out << "uvec" << width; + } else if (vec->type()->Is() && width >= 1 && width <= 4) { + out << "bvec" << width; + } else { + out << "vector<"; + EmitType(out, vec->type(), address_space, access, ""); + out << ", " << width << ">"; + } + } else if (auto* atomic = type->As()) { + EmitType(out, atomic->Type(), address_space, access, name); + } else if (type->Is()) { + out << "void"; + } else { + diagnostics_.AddError(Source{}) << "unknown type in EmitType"; + } +} + +void ASTPrinter::EmitTypeAndName(StringStream& out, + const core::type::Type* type, + core::AddressSpace address_space, + core::Access access, + const std::string& name) { + bool printed_name = false; + EmitType(out, type, address_space, access, name, &printed_name); + if (!name.empty() && !printed_name) { + out << " " << name; + } +} + +void ASTPrinter::EmitStructType(TextBuffer* b, const core::type::Struct* str) { + auto it = emitted_structs_.emplace(str); + if (!it.second) { + return; + } + + auto address_space_uses = str->AddressSpaceUsage(); + Line(b) << "struct " << StructName(str) << " {"; + EmitStructMembers(b, str); + Line(b) << "};"; + Line(b); +} + +void ASTPrinter::EmitStructMembers(TextBuffer* b, const core::type::Struct* str) { + ScopedIndent si(b); + for (auto* mem : str->Members()) { + auto name = mem->Name().Name(); + auto* ty = mem->Type(); + + auto out = Line(b); + EmitTypeAndName(out, ty, core::AddressSpace::kUndefined, core::Access::kReadWrite, name); + out << ";"; + } +} + +void ASTPrinter::EmitUnaryOp(StringStream& out, const ast::UnaryOpExpression* expr) { + switch (expr->op) { + case core::UnaryOp::kIndirection: + case core::UnaryOp::kAddressOf: + EmitExpression(out, expr->expr); + return; + case core::UnaryOp::kComplement: + out << "~"; + break; + case core::UnaryOp::kNot: + if (TypeOf(expr)->UnwrapRef()->Is()) { + out << "!"; + } else { + out << "not"; + } + break; + case core::UnaryOp::kNegation: + out << "-"; + break; + } + + ScopedParen sp(out); + EmitExpression(out, expr->expr); +} + +void ASTPrinter::EmitVar(const ast::Var* var) { + auto* sem = builder_.Sem().Get(var); + auto* type = sem->Type()->UnwrapRef(); + + auto out = Line(); + EmitTypeAndName(out, type, sem->AddressSpace(), sem->Access(), var->name->symbol.Name()); + + out << " = "; + + if (var->initializer) { + EmitExpression(out, var->initializer); + } else { + EmitZeroValue(out, type); + } + out << ";"; +} + +void ASTPrinter::EmitLet(const ast::Let* let) { + auto* sem = builder_.Sem().Get(let); + auto* type = sem->Type()->UnwrapRef(); + + auto out = Line(); + // TODO(senorblanco): handle const + EmitTypeAndName(out, type, core::AddressSpace::kUndefined, core::Access::kUndefined, + let->name->symbol.Name()); + + out << " = "; + EmitExpression(out, let->initializer); + out << ";"; +} + +void ASTPrinter::EmitProgramConstVariable(const ast::Variable* var) { + auto* sem = builder_.Sem().Get(var); + auto* type = sem->Type(); + + auto out = Line(); + out << "const "; + EmitTypeAndName(out, type, core::AddressSpace::kUndefined, core::Access::kUndefined, + var->name->symbol.Name()); + out << " = "; + EmitExpression(out, var->initializer); + out << ";"; +} + +template +void ASTPrinter::CallBuiltinHelper(StringStream& out, + const ast::CallExpression* call, + const sem::BuiltinFn* builtin, + F&& build) { + // Generate the helper function if it hasn't been created already + auto fn = tint::GetOrAdd(builtins_, builtin, [&]() -> std::string { + TextBuffer b; + TINT_DEFER(helpers_.Append(b)); + + auto fn_name = UniqueIdentifier(std::string("tint_") + wgsl::str(builtin->Fn())); + std::vector parameter_names; + { + auto decl = Line(&b); + EmitTypeAndName(decl, builtin->ReturnType(), core::AddressSpace::kUndefined, + core::Access::kUndefined, fn_name); + { + ScopedParen sp(decl); + for (auto* param : builtin->Parameters()) { + if (!parameter_names.empty()) { + decl << ", "; + } + auto param_name = "param_" + std::to_string(parameter_names.size()); + const auto* ty = param->Type(); + if (auto* ptr = ty->As()) { + decl << "inout "; + ty = ptr->StoreType(); + } + EmitTypeAndName(decl, ty, core::AddressSpace::kUndefined, + core::Access::kUndefined, param_name); + parameter_names.emplace_back(std::move(param_name)); + } + } + decl << " {"; + } + { + ScopedIndent si(&b); + build(&b, parameter_names); + } + Line(&b) << "}"; + Line(&b); + return fn_name; + }); + + // Call the helper + out << fn; + { + ScopedParen sp(out); + bool first = true; + for (auto* arg : call->args) { + if (!first) { + out << ", "; + } + first = false; + EmitExpression(out, arg); + } + } +} + +core::type::Type* ASTPrinter::BoolTypeToUint(const core::type::Type* type) { + auto* u32 = builder_.create(); + if (type->Is()) { + return u32; + } else if (auto* vec = type->As()) { + return builder_.create(u32, vec->Width()); + } else { + return nullptr; + } +} + +std::string ASTPrinter::StructName(const core::type::Struct* s) { + auto name = s->Name().Name(); + if (HasPrefix(name, "__")) { + name = tint::GetOrAdd(builtin_struct_names_, s, + [&] { return UniqueIdentifier(name.substr(2)); }); + } + return name; +} + +std::string ASTPrinter::UniqueIdentifier(const std::string& prefix /* = "" */) { + return builder_.Symbols().New(prefix).Name(); +} + +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/ast_printer.h b/references/writer/ast_printer/ast_printer.h new file mode 100644 index 00000000..a1c41aea --- /dev/null +++ b/references/writer/ast_printer/ast_printer.h @@ -0,0 +1,500 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef SRC_TINT_LANG_GLSL_WRITER_AST_PRINTER_AST_PRINTER_H_ +#define SRC_TINT_LANG_GLSL_WRITER_AST_PRINTER_AST_PRINTER_H_ + +#include +#include +#include +#include +#include + +#include "src/tint/api/options/texture_builtins_from_uniform.h" +#include "src/tint/lang/core/builtin_value.h" +#include "src/tint/lang/glsl/writer/common/version.h" +#include "src/tint/lang/wgsl/program/program_builder.h" +#include "src/tint/utils/containers/scope_stack.h" +#include "src/tint/utils/generator/text_generator.h" +#include "src/tint/utils/math/hash.h" +#include "src/tint/utils/text/string_stream.h" + +// Forward declarations +namespace tint::sem { +class BuiltinFn; +class Call; +class ValueConstructor; +class ValueConversion; +} // namespace tint::sem +namespace tint::glsl::writer { +struct Options; +} + +namespace tint::glsl::writer { + +/// The result of sanitizing a program for generation. +struct SanitizedResult { + /// Constructor + SanitizedResult(); + /// Destructor + ~SanitizedResult(); + /// Move constructor + SanitizedResult(SanitizedResult&&); + + /// The sanitized program. + Program program; +}; + +/// Sanitize a program in preparation for generating GLSL. +/// @param program The program to sanitize +/// @param options The HLSL generator options. +/// @param entry_point the entry point to generate GLSL for +/// @returns the sanitized program and any supplementary information +SanitizedResult Sanitize(const Program& program, + const Options& options, + const std::string& entry_point); + +/// Implementation class for GLSL generator +class ASTPrinter : public tint::TextGenerator { + public: + /// Constructor + /// @param program the program to generate + /// @param version the GLSL version to use + ASTPrinter(const Program& program, const Version& version); + ~ASTPrinter() override; + + /// Generates the GLSL shader + /// @returns true on successful generation, false otherwise + bool Generate(); + + /// Record an extension directive within the generator + /// @param ext the extension to record + void RecordExtension(const ast::Enable* ext); + /// Handles an index accessor expression + /// @param out the output of the expression stream + /// @param expr the expression to emit + void EmitIndexAccessor(StringStream& out, const ast::IndexAccessorExpression* expr); + /// Handles an assignment statement + /// @param stmt the statement to emit + void EmitAssign(const ast::AssignmentStatement* stmt); + /// Handles emission of bitwise operators (&|) on bool scalars and vectors + /// @param out the output of the expression stream + /// @param expr the binary expression + void EmitBitwiseBoolOp(StringStream& out, const ast::BinaryExpression* expr); + /// Handles generating a binary expression + /// @param out the output of the expression stream + /// @param expr the binary expression + void EmitFloatModulo(StringStream& out, const ast::BinaryExpression* expr); + /// Handles generating the modulo operator on float vector operands + /// @param out the output of the expression stream + /// @param expr the binary expression + void EmitBinary(StringStream& out, const ast::BinaryExpression* expr); + /// Handles generating a bitcast expression + /// @param out the output of the expression stream + /// @param expr the expression + void EmitVectorRelational(StringStream& out, const ast::BinaryExpression* expr); + /// Handles a bitcast call expression + /// @param out the output of the expression stream + /// @param expr the expression + void EmitBitcastCall(StringStream& out, const ast::CallExpression* expr); + /// Emits a list of statements + /// @param stmts the statement list + void EmitStatements(VectorRef stmts); + /// Emits a list of statements with an indentation + /// @param stmts the statement list + void EmitStatementsWithIndent(VectorRef stmts); + /// Handles a block statement + /// @param stmt the statement to emit + void EmitBlock(const ast::BlockStatement* stmt); + /// Handles a break statement + /// @param stmt the statement to emit + void EmitBreak(const ast::BreakStatement* stmt); + /// Handles a break-if statement + /// @param stmt the statement to emit + void EmitBreakIf(const ast::BreakIfStatement* stmt); + /// Handles generating a call expression + /// @param out the output of the expression stream + /// @param expr the call expression + void EmitCall(StringStream& out, const ast::CallExpression* expr); + /// Handles generating a function call expression + /// @param out the output of the expression stream + /// @param call the call expression + /// @param fn the function being called + void EmitFunctionCall(StringStream& out, const sem::Call* call, const sem::Function* fn); + /// Handles generating a builtin call expression + /// @param out the output of the expression stream + /// @param call the call expression + /// @param builtin the builtin being called + void EmitBuiltinCall(StringStream& out, const sem::Call* call, const sem::BuiltinFn* builtin); + /// Handles generating a value conversion expression + /// @param out the output of the expression stream + /// @param call the call expression + /// @param conv the value conversion + void EmitValueConversion(StringStream& out, + const sem::Call* call, + const sem::ValueConversion* conv); + /// Handles generating a value constructor expression + /// @param out the output of the expression stream + /// @param call the call expression + /// @param ctor the value constructor + void EmitValueConstructor(StringStream& out, + const sem::Call* call, + const sem::ValueConstructor* ctor); + /// Handles generating a barrier builtin call + /// @param out the output of the expression stream + /// @param builtin the semantic information for the barrier builtin + void EmitBarrierCall(StringStream& out, const sem::BuiltinFn* builtin); + /// Handles generating an atomic builtin call for a workgroup variable + /// @param out the output of the expression stream + /// @param expr the call expression + /// @param builtin the semantic information for the atomic builtin + void EmitWorkgroupAtomicCall(StringStream& out, + const ast::CallExpression* expr, + const sem::BuiltinFn* builtin); + /// Handles generating an array.length() call + /// @param out the output of the expression stream + /// @param expr the call expression + void EmitArrayLength(StringStream& out, const ast::CallExpression* expr); + /// Handles generating a call to `bitfieldExtract` + /// @param out the output of the expression stream + /// @param expr the call expression + void EmitExtractBits(StringStream& out, const ast::CallExpression* expr); + /// Handles generating a call to `bitfieldInsert` + /// @param out the output of the expression stream + /// @param expr the call expression + void EmitInsertBits(StringStream& out, const ast::CallExpression* expr); + /// Emulates 'fma' on GLSL ES, where it is unsupported. + /// @param out the output of the expression stream + /// @param expr the fma() expression + void EmitEmulatedFMA(StringStream& out, const ast::CallExpression* expr); + /// Create a float literal zero AST node, and associated semantic nodes. + /// @param stmt the statement which will own the semantic expression node + /// @returns an AST expression representing 0.0f + const ast::Expression* CreateF32Zero(const sem::Statement* stmt); + + /// Handles generating a call to a texture function (`textureSample`, + /// `textureSampleGrad`, etc) + /// @param out the output of the expression stream + /// @param call the call expression + /// @param builtin the semantic information for the texture builtin + void EmitTextureCall(StringStream& out, const sem::Call* call, const sem::BuiltinFn* builtin); + /// Handles generating a call to the `select()` builtin + /// @param out the output of the expression stream + /// @param expr the call expression + void EmitCountOneBitsCall(StringStream& out, const ast::CallExpression* expr); + /// Handles generating a call to the `countOneBits()` builtin + /// @param out the output of the expression stream + /// @param expr the call expression + /// @param builtin the semantic information for the builtin + void EmitSelectCall(StringStream& out, + const ast::CallExpression* expr, + const sem::BuiltinFn* builtin); + /// Handles generating a call to the `dot()` builtin + /// @param out the output of the expression stream + /// @param expr the call expression + /// @param builtin the semantic information for the builtin + void EmitDotCall(StringStream& out, + const ast::CallExpression* expr, + const sem::BuiltinFn* builtin); + /// Handles generating a call to the `modf()` builtin + /// @param out the output of the expression stream + /// @param expr the call expression + /// @param builtin the semantic information for the builtin + void EmitModfCall(StringStream& out, + const ast::CallExpression* expr, + const sem::BuiltinFn* builtin); + /// Handles generating a call to the `frexp()` builtin + /// @param out the output of the expression stream + /// @param expr the call expression + /// @param builtin the semantic information for the builtin + void EmitFrexpCall(StringStream& out, + const ast::CallExpression* expr, + const sem::BuiltinFn* builtin); + /// Handles generating a call to the `degrees()` builtin + /// @param out the output of the expression stream + /// @param expr the call expression + /// @param builtin the semantic information for the builtin + void EmitDegreesCall(StringStream& out, + const ast::CallExpression* expr, + const sem::BuiltinFn* builtin); + /// Handles generating a call to the `radians()` builtin + /// @param out the output of the expression stream + /// @param expr the call expression + /// @param builtin the semantic information for the builtin + void EmitRadiansCall(StringStream& out, + const ast::CallExpression* expr, + const sem::BuiltinFn* builtin); + /// Handles generating a call to the `quantizeToF16()` intrinsic + /// @param out the output of the expression stream + /// @param expr the call expression + /// @param builtin the semantic information for the builtin + void EmitQuantizeToF16Call(StringStream& out, + const ast::CallExpression* expr, + const sem::BuiltinFn* builtin); + /// Handles a case statement + /// @param stmt the statement + void EmitCase(const ast::CaseStatement* stmt); + /// Handles generating a discard statement + /// @param stmt the discard statement + void EmitDiscard(const ast::DiscardStatement* stmt); + /// Handles a continue statement + /// @param stmt the statement to emit + void EmitContinue(const ast::ContinueStatement* stmt); + /// Handles generate an Expression + /// @param out the output of the expression stream + /// @param expr the expression + void EmitExpression(StringStream& out, const ast::Expression* expr); + /// Handles generating a function + /// @param func the function to generate + void EmitFunction(const ast::Function* func); + + /// Handles emitting a global variable + /// @param global the global variable + void EmitGlobalVariable(const ast::Variable* global); + + /// Handles emitting a global variable with the uniform address space + /// @param var the AST node for the 'var' + /// @param sem the semantic node for the 'var' + void EmitUniformVariable(const ast::Var* var, const sem::Variable* sem); + + /// Handles emitting a global variable with the storage address space + /// @param var the AST node for the 'var' + /// @param sem the semantic node for the 'var' + void EmitStorageVariable(const ast::Var* var, const sem::Variable* sem); + + /// Handles emitting a global variable with the handle address space + /// @param var the AST node for the 'var' + /// @param sem the semantic node for the 'var' + void EmitHandleVariable(const ast::Var* var, const sem::Variable* sem); + + /// Handles emitting a global variable with the private address space + /// @param var the global variable + void EmitPrivateVariable(const sem::Variable* var); + + /// Handles emitting a global variable with the workgroup address space + /// @param var the global variable + void EmitWorkgroupVariable(const sem::Variable* var); + + /// Handles emitting a global variable with the input or output address space + /// @param var the global variable + void EmitIOVariable(const sem::GlobalVariable* var); + + /// Handles emitting a global variable with the push_constant address space + /// @param var the global variable + void EmitPushConstant(const sem::GlobalVariable* var); + + /// Handles emitting interpolation qualifiers + /// @param out the output of the expression stream + /// @param attrs the attributes + void EmitInterpolationQualifiers(StringStream& out, VectorRef attrs); + /// Handles emitting attributes + /// @param out the output of the expression stream + /// @param var the global variable semantics + void EmitAttributes(StringStream& out, const sem::GlobalVariable* var); + /// Handles emitting the entry point function + /// @param func the entry point + void EmitEntryPointFunction(const ast::Function* func); + /// Handles an if statement + /// @param stmt the statement to emit + void EmitIf(const ast::IfStatement* stmt); + /// Handles a constant value + /// @param out the output stream + /// @param constant the constant value to emit + void EmitConstant(StringStream& out, const core::constant::Value* constant); + /// Handles a literal + /// @param out the output stream + /// @param lit the literal to emit + void EmitLiteral(StringStream& out, const ast::LiteralExpression* lit); + /// Handles a loop statement + /// @param stmt the statement to emit + void EmitLoop(const ast::LoopStatement* stmt); + /// Handles a for loop statement + /// @param stmt the statement to emit + void EmitForLoop(const ast::ForLoopStatement* stmt); + /// Handles a while statement + /// @param stmt the statement to emit + void EmitWhile(const ast::WhileStatement* stmt); + /// Handles generating an identifier expression + /// @param out the output of the expression stream + /// @param expr the identifier expression + void EmitIdentifier(StringStream& out, const ast::IdentifierExpression* expr); + /// Handles a member accessor expression + /// @param out the output of the expression stream + /// @param expr the member accessor expression + void EmitMemberAccessor(StringStream& out, const ast::MemberAccessorExpression* expr); + /// Handles return statements + /// @param stmt the statement to emit + void EmitReturn(const ast::ReturnStatement* stmt); + /// Handles statement + /// @param stmt the statement to emit + void EmitStatement(const ast::Statement* stmt); + /// Handles generating a switch statement + /// @param stmt the statement to emit + void EmitSwitch(const ast::SwitchStatement* stmt); + /// Handles generating type + /// @param out the output stream + /// @param type the type to generate + /// @param address_space the address space of the variable + /// @param access the access control type of the variable + /// @param name the name of the variable, used for array emission. + /// @param name_printed (optional) if not nullptr and an array was printed + /// then the boolean is set to true. + void EmitType(StringStream& out, + const core::type::Type* type, + core::AddressSpace address_space, + core::Access access, + const std::string& name, + bool* name_printed = nullptr); + /// Handles generating type and name + /// @param out the output stream + /// @param type the type to generate + /// @param address_space the address space of the variable + /// @param access the access control type of the variable + /// @param name the name to emit + void EmitTypeAndName(StringStream& out, + const core::type::Type* type, + core::AddressSpace address_space, + core::Access access, + const std::string& name); + /// Handles generating a structure declaration. If the structure has already been emitted, then + /// this function will simply return `true` without emitting anything. + /// @param buffer the text buffer that the type declaration will be written to + /// @param ty the struct to generate + void EmitStructType(TextBuffer* buffer, const core::type::Struct* ty); + /// Handles generating the members of a structure + /// @param buffer the text buffer that the struct members will be written to + /// @param ty the struct to generate + void EmitStructMembers(TextBuffer* buffer, const core::type::Struct* ty); + /// Handles a unary op expression + /// @param out the output of the expression stream + /// @param expr the expression to emit + void EmitUnaryOp(StringStream& out, const ast::UnaryOpExpression* expr); + /// Emits the zero value for the given type + /// @param out the output stream + /// @param type the type to emit the value for + void EmitZeroValue(StringStream& out, const core::type::Type* type); + /// Handles generating a 'var' declaration + /// @param var the variable to generate + void EmitVar(const ast::Var* var); + /// Handles generating a 'let' declaration + /// @param let the variable to generate + void EmitLet(const ast::Let* let); + /// Handles generating a module-scope 'let' declaration + /// @param let the 'let' to emit + void EmitProgramConstVariable(const ast::Variable* let); + /// Handles generating a builtin method name + /// @param builtin the semantic info for the builtin + /// @returns the name or "" if not valid + std::string generate_builtin_name(const sem::BuiltinFn* builtin); + /// Converts a builtin to a gl_ string + /// @param builtin the builtin to convert + /// @param stage pipeline stage in which this builtin is used + /// @returns the string name of the builtin or blank on error + const char* builtin_to_string(core::BuiltinValue builtin, ast::PipelineStage stage); + /// Converts a builtin to a core::type::Type appropriate for GLSL. + /// @param builtin the builtin to convert + /// @returns the appropriate semantic type or null on error. + core::type::Type* builtin_type(core::BuiltinValue builtin); + + private: + enum class VarType { kIn, kOut }; + + struct EntryPointData { + std::string struct_name; + std::string var_name; + }; + + /// The map key for two semantic types. + using BinaryOperandType = + tint::UnorderedKeyWrapper>; + + /// CallBuiltinHelper will call the builtin helper function, creating it + /// if it hasn't been built already. If the builtin needs to be built then + /// CallBuiltinHelper will generate the function signature and will call + /// `build` to emit the body of the function. + /// @param out the output of the expression stream + /// @param call the call expression + /// @param builtin the semantic information for the builtin + /// @param build a function with the signature: + /// `bool(TextBuffer* buffer, const std::vector& params)` + /// Where: + /// `buffer` is the body of the generated function + /// `params` is the name of all the generated function parameters + template + void CallBuiltinHelper(StringStream& out, + const ast::CallExpression* call, + const sem::BuiltinFn* builtin, + F&& build); + + /// Create a uint type corresponding to the given bool or bool vector type. + /// @param type the bool or bool vector type to convert + /// @returns the corresponding uint type + core::type::Type* BoolTypeToUint(const core::type::Type* type); + + /// @param s the structure + /// @returns the name of the structure, taking special care of builtin structures that start + /// with double underscores. If the structure is a builtin, then the returned name will be a + /// unique name without the leading underscores. + std::string StructName(const core::type::Struct* s); + + /// @return a new, unique identifier with the given prefix. + /// @param prefix optional prefix to apply to the generated identifier. If empty "tint_symbol" + /// will be used. + std::string UniqueIdentifier(const std::string& prefix = ""); + + /// Alias for builder_.TypeOf(ptr) + template + auto TypeOf(T* ptr) { + return builder_.TypeOf(ptr); + } + + ProgramBuilder builder_; + + /// Helper functions emitted at the top of the output + TextBuffer helpers_; + + /// Map of builtin structure to unique generated name + std::unordered_map builtin_struct_names_; + std::function emit_continuing_; + std::unordered_map builtins_; + std::unordered_map dynamic_vector_write_; + std::unordered_map int_dot_funcs_; + std::unordered_map float_modulo_funcs_; + // Polyfill functions for bitcast expression, BinaryOperandType indicates the source type and + // the destination type + std::unordered_map bitcast_funcs_; + std::unordered_set emitted_structs_; + bool requires_oes_sample_variables_ = false; + bool requires_default_precision_qualifier_ = false; + bool requires_f16_extension_ = false; + bool requires_dual_source_blending_extension_ = false; + Version version_; +}; + +} // namespace tint::glsl::writer + +#endif // SRC_TINT_LANG_GLSL_WRITER_AST_PRINTER_AST_PRINTER_H_ diff --git a/references/writer/ast_printer/ast_printer_test.cc b/references/writer/ast_printer/ast_printer_test.cc new file mode 100644 index 00000000..125afe8b --- /dev/null +++ b/references/writer/ast_printer/ast_printer_test.cc @@ -0,0 +1,147 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" + +#include "gmock/gmock.h" + +namespace tint::glsl::writer { +namespace { + +using GlslASTPrinterTest = TestHelper; + +TEST_F(GlslASTPrinterTest, InvalidProgram) { + Diagnostics().AddError(Source{}) << "make the program invalid"; + ASSERT_FALSE(IsValid()); + auto program = resolver::Resolve(*this); + ASSERT_FALSE(program.IsValid()); + auto result = Generate(program, Options{}, ""); + EXPECT_NE(result, Success); + EXPECT_EQ(result.Failure().reason.Str(), "error: make the program invalid"); +} + +TEST_F(GlslASTPrinterTest, Generate) { + Func("my_func", tint::Empty, ty.void_(), tint::Empty); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void my_func() { +} + +)"); +} + +TEST_F(GlslASTPrinterTest, GenerateDesktop) { + Func("my_func", tint::Empty, ty.void_(), tint::Empty); + + ASTPrinter& gen = Build(Version(Version::Standard::kDesktop, 4, 4)); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 440 + +void my_func() { +} + +)"); +} + +TEST_F(GlslASTPrinterTest, GenerateSampleIndexES) { + GlobalVar("gl_SampleID", ty.i32(), + Vector{ + Builtin(core::BuiltinValue::kSampleIndex), + Disable(ast::DisabledValidation::kIgnoreAddressSpace), + }, + core::AddressSpace::kIn); + Func("my_func", tint::Empty, ty.i32(), + Vector{ + Return(Expr("gl_SampleID")), + }); + + ASTPrinter& gen = Build(Version(Version::Standard::kES, 3, 1)); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +#extension GL_OES_sample_variables : require + +int my_func() { + return gl_SampleID; +} + +)"); +} + +TEST_F(GlslASTPrinterTest, GenerateSampleIndexDesktop) { + GlobalVar("gl_SampleID", ty.i32(), + Vector{ + Builtin(core::BuiltinValue::kSampleIndex), + Disable(ast::DisabledValidation::kIgnoreAddressSpace), + }, + core::AddressSpace::kIn); + Func("my_func", tint::Empty, ty.i32(), + Vector{ + Return(Expr("gl_SampleID")), + }); + + ASTPrinter& gen = Build(Version(Version::Standard::kDesktop, 4, 4)); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 440 + +int my_func() { + return gl_SampleID; +} + +)"); +} + +TEST_F(GlslASTPrinterTest, UnsupportedExtension) { + Enable(Source{{12, 34}}, wgsl::Extension::kChromiumInternalRelaxedUniformLayout); + + ASTPrinter& gen = Build(); + + ASSERT_FALSE(gen.Generate()); + EXPECT_EQ( + gen.Diagnostics().Str(), + R"(12:34 error: GLSL backend does not support extension 'chromium_internal_relaxed_uniform_layout')"); +} + +TEST_F(GlslASTPrinterTest, RequiresDirective) { + Require(wgsl::LanguageFeature::kReadonlyAndReadwriteStorageTextures); + + ASTPrinter& gen = Build(); + + ASSERT_TRUE(gen.Generate()) << gen.Diagnostics(); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +)"); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/binary_test.cc b/references/writer/ast_printer/binary_test.cc new file mode 100644 index 00000000..6895b48d --- /dev/null +++ b/references/writer/ast_printer/binary_test.cc @@ -0,0 +1,901 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" +#include "src/tint/lang/wgsl/ast/call_statement.h" +#include "src/tint/lang/wgsl/ast/variable_decl_statement.h" +#include "src/tint/utils/text/string_stream.h" + +#include "gmock/gmock.h" + +namespace tint::glsl::writer { +namespace { + +using namespace tint::core::fluent_types; // NOLINT +using namespace tint::core::number_suffixes; // NOLINT + +using GlslASTPrinterTest_Binary = TestHelper; + +struct BinaryData { + const char* result; + core::BinaryOp op; +}; +inline std::ostream& operator<<(std::ostream& out, BinaryData data) { + StringStream str; + str << data.op; + out << str.str(); + return out; +} + +using GlslBinaryTest = TestParamHelper; +TEST_P(GlslBinaryTest, Emit_f32) { + auto params = GetParam(); + + // Skip ops that are illegal for this type + if (params.op == core::BinaryOp::kAnd || params.op == core::BinaryOp::kOr || + params.op == core::BinaryOp::kXor || params.op == core::BinaryOp::kShiftLeft || + params.op == core::BinaryOp::kShiftRight || params.op == core::BinaryOp::kModulo) { + return; + } + + GlobalVar("left", ty.f32(), core::AddressSpace::kPrivate); + GlobalVar("right", ty.f32(), core::AddressSpace::kPrivate); + + auto* left = Expr("left"); + auto* right = Expr("right"); + + auto* expr = create(params.op, left, right); + + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), params.result); +} +TEST_P(GlslBinaryTest, Emit_f16) { + auto params = GetParam(); + + // Skip ops that are illegal for this type + if (params.op == core::BinaryOp::kAnd || params.op == core::BinaryOp::kOr || + params.op == core::BinaryOp::kXor || params.op == core::BinaryOp::kShiftLeft || + params.op == core::BinaryOp::kShiftRight || params.op == core::BinaryOp::kModulo) { + return; + } + + Enable(wgsl::Extension::kF16); + + GlobalVar("left", ty.f16(), core::AddressSpace::kPrivate); + GlobalVar("right", ty.f16(), core::AddressSpace::kPrivate); + + auto* left = Expr("left"); + auto* right = Expr("right"); + + auto* expr = create(params.op, left, right); + + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), params.result); +} +TEST_P(GlslBinaryTest, Emit_u32) { + auto params = GetParam(); + + GlobalVar("left", ty.u32(), core::AddressSpace::kPrivate); + GlobalVar("right", ty.u32(), core::AddressSpace::kPrivate); + + auto* left = Expr("left"); + auto* right = Expr("right"); + + auto* expr = create(params.op, left, right); + + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), params.result); +} +TEST_P(GlslBinaryTest, Emit_i32) { + auto params = GetParam(); + + // Skip ops that are illegal for this type + if (params.op == core::BinaryOp::kShiftLeft || params.op == core::BinaryOp::kShiftRight) { + return; + } + + GlobalVar("left", ty.i32(), core::AddressSpace::kPrivate); + GlobalVar("right", ty.i32(), core::AddressSpace::kPrivate); + + auto* left = Expr("left"); + auto* right = Expr("right"); + + auto* expr = create(params.op, left, right); + + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), params.result); +} +INSTANTIATE_TEST_SUITE_P( + GlslASTPrinterTest, + GlslBinaryTest, + testing::Values(BinaryData{"(left & right)", core::BinaryOp::kAnd}, + BinaryData{"(left | right)", core::BinaryOp::kOr}, + BinaryData{"(left ^ right)", core::BinaryOp::kXor}, + BinaryData{"(left == right)", core::BinaryOp::kEqual}, + BinaryData{"(left != right)", core::BinaryOp::kNotEqual}, + BinaryData{"(left < right)", core::BinaryOp::kLessThan}, + BinaryData{"(left > right)", core::BinaryOp::kGreaterThan}, + BinaryData{"(left <= right)", core::BinaryOp::kLessThanEqual}, + BinaryData{"(left >= right)", core::BinaryOp::kGreaterThanEqual}, + BinaryData{"(left << right)", core::BinaryOp::kShiftLeft}, + BinaryData{"(left >> right)", core::BinaryOp::kShiftRight}, + BinaryData{"(left + right)", core::BinaryOp::kAdd}, + BinaryData{"(left - right)", core::BinaryOp::kSubtract}, + BinaryData{"(left * right)", core::BinaryOp::kMultiply}, + BinaryData{"(left / right)", core::BinaryOp::kDivide}, + BinaryData{"(left % right)", core::BinaryOp::kModulo})); + +TEST_F(GlslASTPrinterTest_Binary, Multiply_VectorScalar_f32) { + GlobalVar("a", Call>(1_f, 1_f, 1_f), core::AddressSpace::kPrivate); + auto* lhs = Expr("a"); + auto* rhs = Expr(1_f); + + auto* expr = create(core::BinaryOp::kMultiply, lhs, rhs); + + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "(a * 1.0f)"); +} + +TEST_F(GlslASTPrinterTest_Binary, Multiply_VectorScalar_f16) { + Enable(wgsl::Extension::kF16); + + GlobalVar("a", Call>(1_h, 1_h, 1_h), core::AddressSpace::kPrivate); + auto* lhs = Expr("a"); + auto* rhs = Expr(1_h); + + auto* expr = create(core::BinaryOp::kMultiply, lhs, rhs); + + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "(a * 1.0hf)"); +} + +TEST_F(GlslASTPrinterTest_Binary, Multiply_ScalarVector_f32) { + GlobalVar("a", Call>(1_f, 1_f, 1_f), core::AddressSpace::kPrivate); + auto* lhs = Expr(1_f); + auto* rhs = Expr("a"); + + auto* expr = create(core::BinaryOp::kMultiply, lhs, rhs); + + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "(1.0f * a)"); +} + +TEST_F(GlslASTPrinterTest_Binary, Multiply_ScalarVector_f16) { + Enable(wgsl::Extension::kF16); + + GlobalVar("a", Call>(1_h, 1_h, 1_h), core::AddressSpace::kPrivate); + auto* lhs = Expr(1_h); + auto* rhs = Expr("a"); + + auto* expr = create(core::BinaryOp::kMultiply, lhs, rhs); + + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "(1.0hf * a)"); +} + +TEST_F(GlslASTPrinterTest_Binary, Multiply_MatrixScalar_f32) { + GlobalVar("mat", ty.mat3x3(), core::AddressSpace::kPrivate); + auto* lhs = Expr("mat"); + auto* rhs = Expr(1_f); + + auto* expr = create(core::BinaryOp::kMultiply, lhs, rhs); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "(mat * 1.0f)"); +} + +TEST_F(GlslASTPrinterTest_Binary, Multiply_MatrixScalar_f16) { + Enable(wgsl::Extension::kF16); + + GlobalVar("mat", ty.mat3x3(), core::AddressSpace::kPrivate); + auto* lhs = Expr("mat"); + auto* rhs = Expr(1_h); + + auto* expr = create(core::BinaryOp::kMultiply, lhs, rhs); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "(mat * 1.0hf)"); +} + +TEST_F(GlslASTPrinterTest_Binary, Multiply_ScalarMatrix_f32) { + GlobalVar("mat", ty.mat3x3(), core::AddressSpace::kPrivate); + auto* lhs = Expr(1_f); + auto* rhs = Expr("mat"); + + auto* expr = create(core::BinaryOp::kMultiply, lhs, rhs); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "(1.0f * mat)"); +} + +TEST_F(GlslASTPrinterTest_Binary, Multiply_ScalarMatrix_f16) { + Enable(wgsl::Extension::kF16); + + GlobalVar("mat", ty.mat3x3(), core::AddressSpace::kPrivate); + auto* lhs = Expr(1_h); + auto* rhs = Expr("mat"); + + auto* expr = create(core::BinaryOp::kMultiply, lhs, rhs); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "(1.0hf * mat)"); +} + +TEST_F(GlslASTPrinterTest_Binary, Multiply_MatrixVector_f32) { + GlobalVar("mat", ty.mat3x3(), core::AddressSpace::kPrivate); + auto* lhs = Expr("mat"); + auto* rhs = Call>(1_f, 1_f, 1_f); + + auto* expr = create(core::BinaryOp::kMultiply, lhs, rhs); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "(mat * vec3(1.0f))"); +} + +TEST_F(GlslASTPrinterTest_Binary, Multiply_MatrixVector_f16) { + Enable(wgsl::Extension::kF16); + + GlobalVar("mat", ty.mat3x3(), core::AddressSpace::kPrivate); + auto* lhs = Expr("mat"); + auto* rhs = Call>(1_h, 1_h, 1_h); + + auto* expr = create(core::BinaryOp::kMultiply, lhs, rhs); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "(mat * f16vec3(1.0hf))"); +} + +TEST_F(GlslASTPrinterTest_Binary, Multiply_VectorMatrix_f32) { + GlobalVar("mat", ty.mat3x3(), core::AddressSpace::kPrivate); + auto* lhs = Call>(1_f, 1_f, 1_f); + auto* rhs = Expr("mat"); + + auto* expr = create(core::BinaryOp::kMultiply, lhs, rhs); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "(vec3(1.0f) * mat)"); +} + +TEST_F(GlslASTPrinterTest_Binary, Multiply_VectorMatrix_f16) { + Enable(wgsl::Extension::kF16); + + GlobalVar("mat", ty.mat3x3(), core::AddressSpace::kPrivate); + auto* lhs = Call>(1_h, 1_h, 1_h); + auto* rhs = Expr("mat"); + + auto* expr = create(core::BinaryOp::kMultiply, lhs, rhs); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "(f16vec3(1.0hf) * mat)"); +} + +TEST_F(GlslASTPrinterTest_Binary, Multiply_MatrixMatrix_f32) { + GlobalVar("lhs", ty.mat3x3(), core::AddressSpace::kPrivate); + GlobalVar("rhs", ty.mat3x3(), core::AddressSpace::kPrivate); + + auto* expr = create(core::BinaryOp::kMultiply, Expr("lhs"), Expr("rhs")); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "(lhs * rhs)"); +} + +TEST_F(GlslASTPrinterTest_Binary, Multiply_MatrixMatrix_f16) { + Enable(wgsl::Extension::kF16); + + GlobalVar("lhs", ty.mat3x3(), core::AddressSpace::kPrivate); + GlobalVar("rhs", ty.mat3x3(), core::AddressSpace::kPrivate); + + auto* expr = create(core::BinaryOp::kMultiply, Expr("lhs"), Expr("rhs")); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "(lhs * rhs)"); +} + +TEST_F(GlslASTPrinterTest_Binary, ModF32) { + GlobalVar("a", ty.f32(), core::AddressSpace::kPrivate); + GlobalVar("b", ty.f32(), core::AddressSpace::kPrivate); + + auto* expr = create(core::BinaryOp::kModulo, Expr("a"), Expr("b")); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "tint_float_modulo(a, b)"); +} + +TEST_F(GlslASTPrinterTest_Binary, ModF16) { + Enable(wgsl::Extension::kF16); + + GlobalVar("a", ty.f16(), core::AddressSpace::kPrivate); + GlobalVar("b", ty.f16(), core::AddressSpace::kPrivate); + + auto* expr = create(core::BinaryOp::kModulo, Expr("a"), Expr("b")); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "tint_float_modulo(a, b)"); +} + +TEST_F(GlslASTPrinterTest_Binary, ModVec3F32) { + GlobalVar("a", ty.vec3(), core::AddressSpace::kPrivate); + GlobalVar("b", ty.vec3(), core::AddressSpace::kPrivate); + + auto* expr = create(core::BinaryOp::kModulo, Expr("a"), Expr("b")); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "tint_float_modulo(a, b)"); +} + +TEST_F(GlslASTPrinterTest_Binary, ModVec3F16) { + Enable(wgsl::Extension::kF16); + + GlobalVar("a", ty.vec3(), core::AddressSpace::kPrivate); + GlobalVar("b", ty.vec3(), core::AddressSpace::kPrivate); + + auto* expr = create(core::BinaryOp::kModulo, Expr("a"), Expr("b")); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "tint_float_modulo(a, b)"); +} + +TEST_F(GlslASTPrinterTest_Binary, ModVec3F32ScalarF32) { + GlobalVar("a", ty.vec3(), core::AddressSpace::kPrivate); + GlobalVar("b", ty.f32(), core::AddressSpace::kPrivate); + + auto* expr = create(core::BinaryOp::kModulo, Expr("a"), Expr("b")); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "tint_float_modulo(a, b)"); +} + +TEST_F(GlslASTPrinterTest_Binary, ModVec3F16ScalarF16) { + Enable(wgsl::Extension::kF16); + + GlobalVar("a", ty.vec3(), core::AddressSpace::kPrivate); + GlobalVar("b", ty.f16(), core::AddressSpace::kPrivate); + + auto* expr = create(core::BinaryOp::kModulo, Expr("a"), Expr("b")); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "tint_float_modulo(a, b)"); +} + +TEST_F(GlslASTPrinterTest_Binary, ModScalarF32Vec3F32) { + GlobalVar("a", ty.f32(), core::AddressSpace::kPrivate); + GlobalVar("b", ty.vec3(), core::AddressSpace::kPrivate); + + auto* expr = create(core::BinaryOp::kModulo, Expr("a"), Expr("b")); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "tint_float_modulo(a, b)"); +} + +TEST_F(GlslASTPrinterTest_Binary, ModScalarF16Vec3F16) { + Enable(wgsl::Extension::kF16); + + GlobalVar("a", ty.f16(), core::AddressSpace::kPrivate); + GlobalVar("b", ty.vec3(), core::AddressSpace::kPrivate); + + auto* expr = create(core::BinaryOp::kModulo, Expr("a"), Expr("b")); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "tint_float_modulo(a, b)"); +} + +TEST_F(GlslASTPrinterTest_Binary, ModMixedVec3ScalarF32) { + GlobalVar("a", ty.vec3(), core::AddressSpace::kPrivate); + GlobalVar("b", ty.f32(), core::AddressSpace::kPrivate); + + auto* expr_vec_mod_vec = + create(core::BinaryOp::kModulo, Expr("a"), Expr("a")); + auto* expr_vec_mod_scalar = + create(core::BinaryOp::kModulo, Expr("a"), Expr("b")); + auto* expr_scalar_mod_vec = + create(core::BinaryOp::kModulo, Expr("b"), Expr("a")); + WrapInFunction(expr_vec_mod_vec, expr_vec_mod_scalar, expr_scalar_mod_vec); + + ASTPrinter& gen = Build(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +vec3 tint_float_modulo(vec3 lhs, vec3 rhs) { + return (lhs - rhs * trunc(lhs / rhs)); +} + +vec3 tint_float_modulo_1(vec3 lhs, float rhs) { + return (lhs - rhs * trunc(lhs / rhs)); +} + +vec3 tint_float_modulo_2(float lhs, vec3 rhs) { + return (lhs - rhs * trunc(lhs / rhs)); +} + + +vec3 a = vec3(0.0f, 0.0f, 0.0f); +float b = 0.0f; +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void test_function() { + vec3 tint_symbol = tint_float_modulo(a, a); + vec3 tint_symbol_1 = tint_float_modulo_1(a, b); + vec3 tint_symbol_2 = tint_float_modulo_2(b, a); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Binary, ModMixedVec3ScalarF16) { + Enable(wgsl::Extension::kF16); + + GlobalVar("a", ty.vec3(), core::AddressSpace::kPrivate); + GlobalVar("b", ty.f16(), core::AddressSpace::kPrivate); + + auto* expr_vec_mod_vec = + create(core::BinaryOp::kModulo, Expr("a"), Expr("a")); + auto* expr_vec_mod_scalar = + create(core::BinaryOp::kModulo, Expr("a"), Expr("b")); + auto* expr_scalar_mod_vec = + create(core::BinaryOp::kModulo, Expr("b"), Expr("a")); + WrapInFunction(expr_vec_mod_vec, expr_vec_mod_scalar, expr_scalar_mod_vec); + + ASTPrinter& gen = Build(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +#extension GL_AMD_gpu_shader_half_float : require + +f16vec3 tint_float_modulo(f16vec3 lhs, f16vec3 rhs) { + return (lhs - rhs * trunc(lhs / rhs)); +} + +f16vec3 tint_float_modulo_1(f16vec3 lhs, float16_t rhs) { + return (lhs - rhs * trunc(lhs / rhs)); +} + +f16vec3 tint_float_modulo_2(float16_t lhs, f16vec3 rhs) { + return (lhs - rhs * trunc(lhs / rhs)); +} + + +f16vec3 a = f16vec3(0.0hf, 0.0hf, 0.0hf); +float16_t b = 0.0hf; +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void test_function() { + f16vec3 tint_symbol = tint_float_modulo(a, a); + f16vec3 tint_symbol_1 = tint_float_modulo_1(a, b); + f16vec3 tint_symbol_2 = tint_float_modulo_2(b, a); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Binary, Logical_And) { + GlobalVar("a", ty.bool_(), core::AddressSpace::kPrivate); + GlobalVar("b", ty.bool_(), core::AddressSpace::kPrivate); + + auto* expr = create(core::BinaryOp::kLogicalAnd, Expr("a"), Expr("b")); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "(tint_tmp)"); + EXPECT_EQ(gen.Result(), R"(bool tint_tmp = a; +if (tint_tmp) { + tint_tmp = b; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Binary, Logical_Multi) { + // (a && b) || (c || d) + GlobalVar("a", ty.bool_(), core::AddressSpace::kPrivate); + GlobalVar("b", ty.bool_(), core::AddressSpace::kPrivate); + GlobalVar("c", ty.bool_(), core::AddressSpace::kPrivate); + GlobalVar("d", ty.bool_(), core::AddressSpace::kPrivate); + + auto* expr = create( + core::BinaryOp::kLogicalOr, + create(core::BinaryOp::kLogicalAnd, Expr("a"), Expr("b")), + create(core::BinaryOp::kLogicalOr, Expr("c"), Expr("d"))); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "(tint_tmp)"); + EXPECT_EQ(gen.Result(), R"(bool tint_tmp_1 = a; +if (tint_tmp_1) { + tint_tmp_1 = b; +} +bool tint_tmp = (tint_tmp_1); +if (!tint_tmp) { + bool tint_tmp_2 = c; + if (!tint_tmp_2) { + tint_tmp_2 = d; + } + tint_tmp = (tint_tmp_2); +} +)"); +} + +TEST_F(GlslASTPrinterTest_Binary, Logical_Or) { + GlobalVar("a", ty.bool_(), core::AddressSpace::kPrivate); + GlobalVar("b", ty.bool_(), core::AddressSpace::kPrivate); + + auto* expr = create(core::BinaryOp::kLogicalOr, Expr("a"), Expr("b")); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "(tint_tmp)"); + EXPECT_EQ(gen.Result(), R"(bool tint_tmp = a; +if (!tint_tmp) { + tint_tmp = b; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Binary, If_WithLogical) { + // if (a && b) { + // return 1i; + // } else if (b || c) { + // return 2i; + // } else { + // return 3i; + // } + + GlobalVar("a", ty.bool_(), core::AddressSpace::kPrivate); + GlobalVar("b", ty.bool_(), core::AddressSpace::kPrivate); + GlobalVar("c", ty.bool_(), core::AddressSpace::kPrivate); + + auto* expr = + If(create(core::BinaryOp::kLogicalAnd, Expr("a"), Expr("b")), + Block(Return(1_i)), + Else(If(create(core::BinaryOp::kLogicalOr, Expr("b"), Expr("c")), + Block(Return(2_i)), Else(Block(Return(3_i)))))); + Func("func", tint::Empty, ty.i32(), Vector{WrapInStatement(expr)}); + + ASTPrinter& gen = Build(); + + gen.EmitStatement(expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(bool tint_tmp = a; +if (tint_tmp) { + tint_tmp = b; +} +if ((tint_tmp)) { + return 1; +} else { + bool tint_tmp_1 = b; + if (!tint_tmp_1) { + tint_tmp_1 = c; + } + if ((tint_tmp_1)) { + return 2; + } else { + return 3; + } +} +)"); +} + +TEST_F(GlslASTPrinterTest_Binary, Return_WithLogical) { + // return (a && b) || c; + + GlobalVar("a", ty.bool_(), core::AddressSpace::kPrivate); + GlobalVar("b", ty.bool_(), core::AddressSpace::kPrivate); + GlobalVar("c", ty.bool_(), core::AddressSpace::kPrivate); + + auto* expr = Return(create( + core::BinaryOp::kLogicalOr, + create(core::BinaryOp::kLogicalAnd, Expr("a"), Expr("b")), + Expr("c"))); + Func("func", tint::Empty, ty.bool_(), Vector{WrapInStatement(expr)}); + + ASTPrinter& gen = Build(); + + gen.EmitStatement(expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(bool tint_tmp_1 = a; +if (tint_tmp_1) { + tint_tmp_1 = b; +} +bool tint_tmp = (tint_tmp_1); +if (!tint_tmp) { + tint_tmp = c; +} +return (tint_tmp); +)"); +} + +TEST_F(GlslASTPrinterTest_Binary, Assign_WithLogical) { + // a = (b || c) && d; + + GlobalVar("a", ty.bool_(), core::AddressSpace::kPrivate); + GlobalVar("b", ty.bool_(), core::AddressSpace::kPrivate); + GlobalVar("c", ty.bool_(), core::AddressSpace::kPrivate); + GlobalVar("d", ty.bool_(), core::AddressSpace::kPrivate); + + auto* expr = + Assign(Expr("a"), + create( + core::BinaryOp::kLogicalAnd, + create(core::BinaryOp::kLogicalOr, Expr("b"), Expr("c")), + Expr("d"))); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + gen.EmitStatement(expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(bool tint_tmp_1 = b; +if (!tint_tmp_1) { + tint_tmp_1 = c; +} +bool tint_tmp = (tint_tmp_1); +if (tint_tmp) { + tint_tmp = d; +} +a = (tint_tmp); +)"); +} + +TEST_F(GlslASTPrinterTest_Binary, Decl_WithLogical) { + // var a : bool = (b && c) || d; + + GlobalVar("b", ty.bool_(), core::AddressSpace::kPrivate); + GlobalVar("c", ty.bool_(), core::AddressSpace::kPrivate); + GlobalVar("d", ty.bool_(), core::AddressSpace::kPrivate); + + auto* var = + Var("a", ty.bool_(), + create( + core::BinaryOp::kLogicalOr, + create(core::BinaryOp::kLogicalAnd, Expr("b"), Expr("c")), + Expr("d"))); + + auto* decl = Decl(var); + WrapInFunction(decl); + + ASTPrinter& gen = Build(); + + gen.EmitStatement(decl); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(bool tint_tmp_1 = b; +if (tint_tmp_1) { + tint_tmp_1 = c; +} +bool tint_tmp = (tint_tmp_1); +if (!tint_tmp) { + tint_tmp = d; +} +bool a = (tint_tmp); +)"); +} + +TEST_F(GlslASTPrinterTest_Binary, Call_WithLogical) { + // foo(a && b, c || d, (a || c) && (b || d)) + + Func("foo", + Vector{ + Param(Sym(), ty.bool_()), + Param(Sym(), ty.bool_()), + Param(Sym(), ty.bool_()), + }, + ty.void_(), tint::Empty, tint::Empty); + GlobalVar("a", ty.bool_(), core::AddressSpace::kPrivate); + GlobalVar("b", ty.bool_(), core::AddressSpace::kPrivate); + GlobalVar("c", ty.bool_(), core::AddressSpace::kPrivate); + GlobalVar("d", ty.bool_(), core::AddressSpace::kPrivate); + + Vector params{ + create(core::BinaryOp::kLogicalAnd, Expr("a"), Expr("b")), + create(core::BinaryOp::kLogicalOr, Expr("c"), Expr("d")), + create( + core::BinaryOp::kLogicalAnd, + create(core::BinaryOp::kLogicalOr, Expr("a"), Expr("c")), + create(core::BinaryOp::kLogicalOr, Expr("b"), Expr("d"))), + }; + + auto* expr = CallStmt(Call("foo", params)); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + gen.EmitStatement(expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(bool tint_tmp = a; +if (tint_tmp) { + tint_tmp = b; +} +bool tint_tmp_1 = c; +if (!tint_tmp_1) { + tint_tmp_1 = d; +} +bool tint_tmp_3 = a; +if (!tint_tmp_3) { + tint_tmp_3 = c; +} +bool tint_tmp_2 = (tint_tmp_3); +if (tint_tmp_2) { + bool tint_tmp_4 = b; + if (!tint_tmp_4) { + tint_tmp_4 = d; + } + tint_tmp_2 = (tint_tmp_4); +} +foo((tint_tmp), (tint_tmp_1), (tint_tmp_2)); +)"); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/bitcast_test.cc b/references/writer/ast_printer/bitcast_test.cc new file mode 100644 index 00000000..76570451 --- /dev/null +++ b/references/writer/ast_printer/bitcast_test.cc @@ -0,0 +1,204 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" + +#include "src/tint/utils/text/string_stream.h" + +#include "gmock/gmock.h" + +using namespace tint::core::fluent_types; // NOLINT +using namespace tint::core::number_suffixes; // NOLINT + +namespace tint::glsl::writer { +namespace { + +using GlslASTPrinterTest_Bitcast = TestHelper; + +TEST_F(GlslASTPrinterTest_Bitcast, EmitExpression_Bitcast_Float) { + auto* a = Let("a", Expr(1_i)); + auto* bitcast = Bitcast(Expr("a")); + WrapInFunction(a, bitcast); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, bitcast); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "intBitsToFloat(a)"); +} + +TEST_F(GlslASTPrinterTest_Bitcast, EmitExpression_Bitcast_Int) { + auto* a = Let("a", Expr(1_u)); + auto* bitcast = Bitcast(Expr("a")); + WrapInFunction(a, bitcast); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, bitcast); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "int(a)"); +} + +TEST_F(GlslASTPrinterTest_Bitcast, EmitExpression_Bitcast_Uint) { + auto* a = Let("a", Expr(1_i)); + auto* bitcast = Bitcast(Expr("a")); + WrapInFunction(a, bitcast); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, bitcast); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "uint(a)"); +} + +TEST_F(GlslASTPrinterTest_Bitcast, EmitExpression_Bitcast_F16_Vec2) { + Enable(wgsl::Extension::kF16); + + auto* a = Let("a", Call>(1_h, 2_h)); + auto* b = Let("b", Bitcast(Expr("a"))); + auto* c = Let("c", Bitcast>(Expr("b"))); + auto* d = Let("d", Bitcast(Expr("c"))); + auto* e = Let("e", Bitcast>(Expr("d"))); + auto* f = Let("f", Bitcast(Expr("e"))); + auto* g = Let("g", Bitcast>(Expr("f"))); + WrapInFunction(a, b, c, d, e, f, g); + + ASTPrinter& gen = Build(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +#extension GL_AMD_gpu_shader_half_float : require + +int tint_bitcast_from_f16(f16vec2 src) { + uint r = packFloat2x16(src); + return int(r); +} +f16vec2 tint_bitcast_to_f16(int src) { + uint r = uint(src); + return unpackFloat2x16(r); +} +float tint_bitcast_from_f16_1(f16vec2 src) { + uint r = packFloat2x16(src); + return uintBitsToFloat(r); +} +f16vec2 tint_bitcast_to_f16_1(float src) { + uint r = floatBitsToUint(src); + return unpackFloat2x16(r); +} +uint tint_bitcast_from_f16_2(f16vec2 src) { + uint r = packFloat2x16(src); + return uint(r); +} +f16vec2 tint_bitcast_to_f16_2(uint src) { + uint r = uint(src); + return unpackFloat2x16(r); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void test_function() { + f16vec2 a = f16vec2(1.0hf, 2.0hf); + int b = tint_bitcast_from_f16(a); + f16vec2 c = tint_bitcast_to_f16(b); + float d = tint_bitcast_from_f16_1(c); + f16vec2 e = tint_bitcast_to_f16_1(d); + uint f = tint_bitcast_from_f16_2(e); + f16vec2 g = tint_bitcast_to_f16_2(f); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Bitcast, EmitExpression_Bitcast_F16_Vec4) { + Enable(wgsl::Extension::kF16); + + auto* a = Let("a", Call>(1_h, 2_h, 3_h, 4_h)); + auto* b = Let("b", Bitcast>(Expr("a"))); + auto* c = Let("c", Bitcast>(Expr("b"))); + auto* d = Let("d", Bitcast>(Expr("c"))); + auto* e = Let("e", Bitcast>(Expr("d"))); + auto* f = Let("f", Bitcast>(Expr("e"))); + auto* g = Let("g", Bitcast>(Expr("f"))); + WrapInFunction(a, b, c, d, e, f, g); + + ASTPrinter& gen = Build(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +#extension GL_AMD_gpu_shader_half_float : require + +ivec2 tint_bitcast_from_f16(f16vec4 src) { + uvec2 r = uvec2(packFloat2x16(src.xy), packFloat2x16(src.zw)); + return ivec2(r); +} +f16vec4 tint_bitcast_to_f16(ivec2 src) { + uvec2 r = uvec2(src); + f16vec2 v_xy = unpackFloat2x16(r.x); + f16vec2 v_zw = unpackFloat2x16(r.y); + return f16vec4(v_xy.x, v_xy.y, v_zw.x, v_zw.y); +} +vec2 tint_bitcast_from_f16_1(f16vec4 src) { + uvec2 r = uvec2(packFloat2x16(src.xy), packFloat2x16(src.zw)); + return uintBitsToFloat(r); +} +f16vec4 tint_bitcast_to_f16_1(vec2 src) { + uvec2 r = floatBitsToUint(src); + f16vec2 v_xy = unpackFloat2x16(r.x); + f16vec2 v_zw = unpackFloat2x16(r.y); + return f16vec4(v_xy.x, v_xy.y, v_zw.x, v_zw.y); +} +uvec2 tint_bitcast_from_f16_2(f16vec4 src) { + uvec2 r = uvec2(packFloat2x16(src.xy), packFloat2x16(src.zw)); + return uvec2(r); +} +f16vec4 tint_bitcast_to_f16_2(uvec2 src) { + uvec2 r = uvec2(src); + f16vec2 v_xy = unpackFloat2x16(r.x); + f16vec2 v_zw = unpackFloat2x16(r.y); + return f16vec4(v_xy.x, v_xy.y, v_zw.x, v_zw.y); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void test_function() { + f16vec4 a = f16vec4(1.0hf, 2.0hf, 3.0hf, 4.0hf); + ivec2 b = tint_bitcast_from_f16(a); + f16vec4 c = tint_bitcast_to_f16(b); + vec2 d = tint_bitcast_from_f16_1(c); + f16vec4 e = tint_bitcast_to_f16_1(d); + uvec2 f = tint_bitcast_from_f16_2(e); + f16vec4 g = tint_bitcast_to_f16_2(f); + return; +} +)"); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/block_test.cc b/references/writer/ast_printer/block_test.cc new file mode 100644 index 00000000..7257ffc8 --- /dev/null +++ b/references/writer/ast_printer/block_test.cc @@ -0,0 +1,53 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" + +#include "gmock/gmock.h" + +namespace tint::glsl::writer { +namespace { + +using GlslASTPrinterTest_Block = TestHelper; + +TEST_F(GlslASTPrinterTest_Block, Emit_Block) { + auto* b = Block(Return()); + WrapInFunction(b); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitStatement(b); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( { + return; + } +)"); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/break_test.cc b/references/writer/ast_printer/break_test.cc new file mode 100644 index 00000000..fd54c195 --- /dev/null +++ b/references/writer/ast_printer/break_test.cc @@ -0,0 +1,50 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" + +#include "gmock/gmock.h" + +namespace tint::glsl::writer { +namespace { + +using GlslASTPrinterTest_Break = TestHelper; + +TEST_F(GlslASTPrinterTest_Break, Emit_Break) { + auto* b = create(); + WrapInFunction(Loop(Block(b))); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitStatement(b); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), " break;\n"); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/builtin_test.cc b/references/writer/ast_printer/builtin_test.cc new file mode 100644 index 00000000..b923ec6a --- /dev/null +++ b/references/writer/ast_printer/builtin_test.cc @@ -0,0 +1,1680 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "gmock/gmock.h" +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" +#include "src/tint/lang/wgsl/ast/call_statement.h" +#include "src/tint/lang/wgsl/ast/stage_attribute.h" +#include "src/tint/lang/wgsl/sem/call.h" +#include "src/tint/utils/text/string_stream.h" + +namespace tint::glsl::writer { +namespace { + +using ::testing::HasSubstr; +using namespace tint::core::fluent_types; // NOLINT +using namespace tint::core::number_suffixes; // NOLINT + +using GlslASTPrinterTest_Builtin = TestHelper; + +enum class CallParamType { + kF32, + kU32, + kBool, + kF16, +}; + +struct BuiltinData { + wgsl::BuiltinFn builtin; + CallParamType type; + const char* glsl_name; +}; +inline std::ostream& operator<<(std::ostream& out, BuiltinData data) { + out << data.glsl_name << "<"; + switch (data.type) { + case CallParamType::kF32: + out << "f32"; + break; + case CallParamType::kU32: + out << "u32"; + break; + case CallParamType::kBool: + out << "bool"; + break; + case CallParamType::kF16: + out << "f16"; + break; + } + out << ">"; + return out; +} + +const ast::CallExpression* GenerateCall(wgsl::BuiltinFn builtin, + CallParamType type, + ProgramBuilder* builder) { + std::string name; + StringStream str; + str << name << builtin; + switch (builtin) { + case wgsl::BuiltinFn::kAcos: + case wgsl::BuiltinFn::kAsin: + case wgsl::BuiltinFn::kAtan: + case wgsl::BuiltinFn::kCeil: + case wgsl::BuiltinFn::kCos: + case wgsl::BuiltinFn::kCosh: + case wgsl::BuiltinFn::kDpdx: + case wgsl::BuiltinFn::kDpdxCoarse: + case wgsl::BuiltinFn::kDpdxFine: + case wgsl::BuiltinFn::kDpdy: + case wgsl::BuiltinFn::kDpdyCoarse: + case wgsl::BuiltinFn::kDpdyFine: + case wgsl::BuiltinFn::kExp: + case wgsl::BuiltinFn::kExp2: + case wgsl::BuiltinFn::kFloor: + case wgsl::BuiltinFn::kFract: + case wgsl::BuiltinFn::kFwidth: + case wgsl::BuiltinFn::kFwidthCoarse: + case wgsl::BuiltinFn::kFwidthFine: + case wgsl::BuiltinFn::kInverseSqrt: + case wgsl::BuiltinFn::kLength: + case wgsl::BuiltinFn::kLog: + case wgsl::BuiltinFn::kLog2: + case wgsl::BuiltinFn::kNormalize: + case wgsl::BuiltinFn::kRound: + case wgsl::BuiltinFn::kSin: + case wgsl::BuiltinFn::kSinh: + case wgsl::BuiltinFn::kSqrt: + case wgsl::BuiltinFn::kTan: + case wgsl::BuiltinFn::kTanh: + case wgsl::BuiltinFn::kTrunc: + case wgsl::BuiltinFn::kSign: + if (type == CallParamType::kF16) { + return builder->Call(str.str(), "h2"); + } else { + return builder->Call(str.str(), "f2"); + } + case wgsl::BuiltinFn::kLdexp: + if (type == CallParamType::kF16) { + return builder->Call(str.str(), "h2", "i2"); + } else { + return builder->Call(str.str(), "f2", "i2"); + } + case wgsl::BuiltinFn::kAtan2: + case wgsl::BuiltinFn::kDot: + case wgsl::BuiltinFn::kDistance: + case wgsl::BuiltinFn::kPow: + case wgsl::BuiltinFn::kReflect: + case wgsl::BuiltinFn::kStep: + if (type == CallParamType::kF16) { + return builder->Call(str.str(), "h2", "h2"); + } else { + return builder->Call(str.str(), "f2", "f2"); + } + case wgsl::BuiltinFn::kCross: + if (type == CallParamType::kF16) { + return builder->Call(str.str(), "h3", "h3"); + } else { + return builder->Call(str.str(), "f3", "f3"); + } + case wgsl::BuiltinFn::kFma: + case wgsl::BuiltinFn::kMix: + case wgsl::BuiltinFn::kFaceForward: + case wgsl::BuiltinFn::kSmoothstep: + if (type == CallParamType::kF16) { + return builder->Call(str.str(), "h2", "h2", "h2"); + } else { + return builder->Call(str.str(), "f2", "f2", "f2"); + } + case wgsl::BuiltinFn::kAll: + case wgsl::BuiltinFn::kAny: + return builder->Call(str.str(), "b2"); + case wgsl::BuiltinFn::kAbs: + if (type == CallParamType::kF32) { + return builder->Call(str.str(), "f2"); + } else if (type == CallParamType::kF16) { + return builder->Call(str.str(), "h2"); + } else { + return builder->Call(str.str(), "u2"); + } + case wgsl::BuiltinFn::kCountOneBits: + case wgsl::BuiltinFn::kReverseBits: + return builder->Call(str.str(), "u2"); + case wgsl::BuiltinFn::kMax: + case wgsl::BuiltinFn::kMin: + if (type == CallParamType::kF32) { + return builder->Call(str.str(), "f2", "f2"); + } else if (type == CallParamType::kF16) { + return builder->Call(str.str(), "h2", "h2"); + } else { + return builder->Call(str.str(), "u2", "u2"); + } + case wgsl::BuiltinFn::kClamp: + if (type == CallParamType::kF32) { + return builder->Call(str.str(), "f2", "f2", "f2"); + } else if (type == CallParamType::kF16) { + return builder->Call(str.str(), "h2", "h2", "h2"); + } else { + return builder->Call(str.str(), "u2", "u2", "u2"); + } + case wgsl::BuiltinFn::kSelect: + if (type == CallParamType::kF16) { + return builder->Call(str.str(), "h2", "h2", "b2"); + } else { + return builder->Call(str.str(), "f2", "f2", "b2"); + } + case wgsl::BuiltinFn::kDeterminant: + if (type == CallParamType::kF16) { + return builder->Call(str.str(), "hm2x2"); + } else { + return builder->Call(str.str(), "m2x2"); + } + case wgsl::BuiltinFn::kTranspose: + if (type == CallParamType::kF16) { + return builder->Call(str.str(), "hm3x2"); + } else { + return builder->Call(str.str(), "m3x2"); + } + default: + break; + } + return nullptr; +} +using GlslBuiltinTest = TestParamHelper; +TEST_P(GlslBuiltinTest, Emit) { + auto param = GetParam(); + + if (param.type == CallParamType::kF16) { + Enable(wgsl::Extension::kF16); + + GlobalVar("h2", ty.vec2(), core::AddressSpace::kPrivate); + GlobalVar("h3", ty.vec3(), core::AddressSpace::kPrivate); + GlobalVar("hm2x2", ty.mat2x2(), core::AddressSpace::kPrivate); + GlobalVar("hm3x2", ty.mat3x2(), core::AddressSpace::kPrivate); + } + + GlobalVar("f2", ty.vec2(), core::AddressSpace::kPrivate); + GlobalVar("f3", ty.vec3(), core::AddressSpace::kPrivate); + GlobalVar("u2", ty.vec2(), core::AddressSpace::kPrivate); + GlobalVar("i2", ty.vec2(), core::AddressSpace::kPrivate); + GlobalVar("b2", ty.vec2(), core::AddressSpace::kPrivate); + GlobalVar("m2x2", ty.mat2x2(), core::AddressSpace::kPrivate); + GlobalVar("m3x2", ty.mat3x2(), core::AddressSpace::kPrivate); + + auto* call = GenerateCall(param.builtin, param.type, this); + ASSERT_NE(nullptr, call) << "Unhandled builtin"; + Func("func", tint::Empty, ty.void_(), + Vector{ + Assign(Phony(), call), + }, + Vector{create(ast::PipelineStage::kFragment)}); + + ASTPrinter& gen = Build(); + + auto* sem = program->Sem().Get(call); + ASSERT_NE(sem, nullptr); + auto* target = sem->Target(); + ASSERT_NE(target, nullptr); + auto* builtin = target->As(); + ASSERT_NE(builtin, nullptr); + + EXPECT_EQ(gen.generate_builtin_name(builtin), param.glsl_name); +} +INSTANTIATE_TEST_SUITE_P( + GlslASTPrinterTest_Builtin, + GlslBuiltinTest, + testing::Values(/* Logical built-in */ + BuiltinData{wgsl::BuiltinFn::kAll, CallParamType::kBool, "all"}, + BuiltinData{wgsl::BuiltinFn::kAny, CallParamType::kBool, "any"}, + /* Float built-in */ + BuiltinData{wgsl::BuiltinFn::kAbs, CallParamType::kF32, "abs"}, + BuiltinData{wgsl::BuiltinFn::kAbs, CallParamType::kF16, "abs"}, + BuiltinData{wgsl::BuiltinFn::kAcos, CallParamType::kF32, "acos"}, + BuiltinData{wgsl::BuiltinFn::kAcos, CallParamType::kF16, "acos"}, + BuiltinData{wgsl::BuiltinFn::kAsin, CallParamType::kF32, "asin"}, + BuiltinData{wgsl::BuiltinFn::kAsin, CallParamType::kF16, "asin"}, + BuiltinData{wgsl::BuiltinFn::kAtan, CallParamType::kF32, "atan"}, + BuiltinData{wgsl::BuiltinFn::kAtan, CallParamType::kF16, "atan"}, + BuiltinData{wgsl::BuiltinFn::kAtan2, CallParamType::kF32, "atan"}, + BuiltinData{wgsl::BuiltinFn::kAtan2, CallParamType::kF16, "atan"}, + BuiltinData{wgsl::BuiltinFn::kCeil, CallParamType::kF32, "ceil"}, + BuiltinData{wgsl::BuiltinFn::kCeil, CallParamType::kF16, "ceil"}, + BuiltinData{wgsl::BuiltinFn::kClamp, CallParamType::kF32, "clamp"}, + BuiltinData{wgsl::BuiltinFn::kClamp, CallParamType::kF16, "clamp"}, + BuiltinData{wgsl::BuiltinFn::kCos, CallParamType::kF32, "cos"}, + BuiltinData{wgsl::BuiltinFn::kCos, CallParamType::kF16, "cos"}, + BuiltinData{wgsl::BuiltinFn::kCosh, CallParamType::kF32, "cosh"}, + BuiltinData{wgsl::BuiltinFn::kCosh, CallParamType::kF16, "cosh"}, + BuiltinData{wgsl::BuiltinFn::kCross, CallParamType::kF32, "cross"}, + BuiltinData{wgsl::BuiltinFn::kCross, CallParamType::kF16, "cross"}, + BuiltinData{wgsl::BuiltinFn::kDistance, CallParamType::kF32, "distance"}, + BuiltinData{wgsl::BuiltinFn::kDistance, CallParamType::kF16, "distance"}, + BuiltinData{wgsl::BuiltinFn::kExp, CallParamType::kF32, "exp"}, + BuiltinData{wgsl::BuiltinFn::kExp, CallParamType::kF16, "exp"}, + BuiltinData{wgsl::BuiltinFn::kExp2, CallParamType::kF32, "exp2"}, + BuiltinData{wgsl::BuiltinFn::kExp2, CallParamType::kF16, "exp2"}, + BuiltinData{wgsl::BuiltinFn::kFaceForward, CallParamType::kF32, "faceforward"}, + BuiltinData{wgsl::BuiltinFn::kFaceForward, CallParamType::kF16, "faceforward"}, + BuiltinData{wgsl::BuiltinFn::kFloor, CallParamType::kF32, "floor"}, + BuiltinData{wgsl::BuiltinFn::kFloor, CallParamType::kF16, "floor"}, + BuiltinData{wgsl::BuiltinFn::kFma, CallParamType::kF32, "fma"}, + BuiltinData{wgsl::BuiltinFn::kFma, CallParamType::kF16, "fma"}, + BuiltinData{wgsl::BuiltinFn::kFract, CallParamType::kF32, "fract"}, + BuiltinData{wgsl::BuiltinFn::kFract, CallParamType::kF16, "fract"}, + BuiltinData{wgsl::BuiltinFn::kInverseSqrt, CallParamType::kF32, "inversesqrt"}, + BuiltinData{wgsl::BuiltinFn::kInverseSqrt, CallParamType::kF16, "inversesqrt"}, + BuiltinData{wgsl::BuiltinFn::kLdexp, CallParamType::kF32, "ldexp"}, + BuiltinData{wgsl::BuiltinFn::kLdexp, CallParamType::kF16, "ldexp"}, + BuiltinData{wgsl::BuiltinFn::kLength, CallParamType::kF32, "length"}, + BuiltinData{wgsl::BuiltinFn::kLength, CallParamType::kF16, "length"}, + BuiltinData{wgsl::BuiltinFn::kLog, CallParamType::kF32, "log"}, + BuiltinData{wgsl::BuiltinFn::kLog, CallParamType::kF16, "log"}, + BuiltinData{wgsl::BuiltinFn::kLog2, CallParamType::kF32, "log2"}, + BuiltinData{wgsl::BuiltinFn::kLog2, CallParamType::kF16, "log2"}, + BuiltinData{wgsl::BuiltinFn::kMax, CallParamType::kF32, "max"}, + BuiltinData{wgsl::BuiltinFn::kMax, CallParamType::kF16, "max"}, + BuiltinData{wgsl::BuiltinFn::kMin, CallParamType::kF32, "min"}, + BuiltinData{wgsl::BuiltinFn::kMin, CallParamType::kF16, "min"}, + BuiltinData{wgsl::BuiltinFn::kMix, CallParamType::kF32, "mix"}, + BuiltinData{wgsl::BuiltinFn::kMix, CallParamType::kF16, "mix"}, + BuiltinData{wgsl::BuiltinFn::kNormalize, CallParamType::kF32, "normalize"}, + BuiltinData{wgsl::BuiltinFn::kNormalize, CallParamType::kF16, "normalize"}, + BuiltinData{wgsl::BuiltinFn::kPow, CallParamType::kF32, "pow"}, + BuiltinData{wgsl::BuiltinFn::kPow, CallParamType::kF16, "pow"}, + BuiltinData{wgsl::BuiltinFn::kReflect, CallParamType::kF32, "reflect"}, + BuiltinData{wgsl::BuiltinFn::kReflect, CallParamType::kF16, "reflect"}, + BuiltinData{wgsl::BuiltinFn::kSign, CallParamType::kF32, "sign"}, + BuiltinData{wgsl::BuiltinFn::kSign, CallParamType::kF16, "sign"}, + BuiltinData{wgsl::BuiltinFn::kSin, CallParamType::kF32, "sin"}, + BuiltinData{wgsl::BuiltinFn::kSin, CallParamType::kF16, "sin"}, + BuiltinData{wgsl::BuiltinFn::kSinh, CallParamType::kF32, "sinh"}, + BuiltinData{wgsl::BuiltinFn::kSinh, CallParamType::kF16, "sinh"}, + BuiltinData{wgsl::BuiltinFn::kSmoothstep, CallParamType::kF32, "smoothstep"}, + BuiltinData{wgsl::BuiltinFn::kSmoothstep, CallParamType::kF16, "smoothstep"}, + BuiltinData{wgsl::BuiltinFn::kSqrt, CallParamType::kF32, "sqrt"}, + BuiltinData{wgsl::BuiltinFn::kSqrt, CallParamType::kF16, "sqrt"}, + BuiltinData{wgsl::BuiltinFn::kStep, CallParamType::kF32, "step"}, + BuiltinData{wgsl::BuiltinFn::kStep, CallParamType::kF16, "step"}, + BuiltinData{wgsl::BuiltinFn::kTan, CallParamType::kF32, "tan"}, + BuiltinData{wgsl::BuiltinFn::kTan, CallParamType::kF16, "tan"}, + BuiltinData{wgsl::BuiltinFn::kTanh, CallParamType::kF32, "tanh"}, + BuiltinData{wgsl::BuiltinFn::kTanh, CallParamType::kF16, "tanh"}, + BuiltinData{wgsl::BuiltinFn::kTrunc, CallParamType::kF32, "trunc"}, + BuiltinData{wgsl::BuiltinFn::kTrunc, CallParamType::kF16, "trunc"}, + /* Integer built-in */ + BuiltinData{wgsl::BuiltinFn::kAbs, CallParamType::kU32, "abs"}, + BuiltinData{wgsl::BuiltinFn::kClamp, CallParamType::kU32, "clamp"}, + BuiltinData{wgsl::BuiltinFn::kCountOneBits, CallParamType::kU32, "bitCount"}, + BuiltinData{wgsl::BuiltinFn::kMax, CallParamType::kU32, "max"}, + BuiltinData{wgsl::BuiltinFn::kMin, CallParamType::kU32, "min"}, + BuiltinData{wgsl::BuiltinFn::kReverseBits, CallParamType::kU32, + "bitfieldReverse"}, + BuiltinData{wgsl::BuiltinFn::kRound, CallParamType::kU32, "round"}, + /* Matrix built-in */ + BuiltinData{wgsl::BuiltinFn::kDeterminant, CallParamType::kF32, "determinant"}, + BuiltinData{wgsl::BuiltinFn::kDeterminant, CallParamType::kF16, "determinant"}, + BuiltinData{wgsl::BuiltinFn::kTranspose, CallParamType::kF32, "transpose"}, + BuiltinData{wgsl::BuiltinFn::kTranspose, CallParamType::kF16, "transpose"}, + /* Vector built-in */ + BuiltinData{wgsl::BuiltinFn::kDot, CallParamType::kF32, "dot"}, + BuiltinData{wgsl::BuiltinFn::kDot, CallParamType::kF16, "dot"}, + /* Derivate built-in */ + BuiltinData{wgsl::BuiltinFn::kDpdx, CallParamType::kF32, "dFdx"}, + BuiltinData{wgsl::BuiltinFn::kDpdxCoarse, CallParamType::kF32, "dFdx"}, + BuiltinData{wgsl::BuiltinFn::kDpdxFine, CallParamType::kF32, "dFdx"}, + BuiltinData{wgsl::BuiltinFn::kDpdy, CallParamType::kF32, "dFdy"}, + BuiltinData{wgsl::BuiltinFn::kDpdyCoarse, CallParamType::kF32, "dFdy"}, + BuiltinData{wgsl::BuiltinFn::kDpdyFine, CallParamType::kF32, "dFdy"}, + BuiltinData{wgsl::BuiltinFn::kFwidth, CallParamType::kF32, "fwidth"}, + BuiltinData{wgsl::BuiltinFn::kFwidthCoarse, CallParamType::kF32, "fwidth"}, + BuiltinData{wgsl::BuiltinFn::kFwidthFine, CallParamType::kF32, "fwidth"})); + +TEST_F(GlslASTPrinterTest_Builtin, Builtin_Call) { + auto* call = Call("dot", "param1", "param2"); + + GlobalVar("param1", ty.vec3(), core::AddressSpace::kPrivate); + GlobalVar("param2", ty.vec3(), core::AddressSpace::kPrivate); + + WrapInFunction(Decl(Var("r", call))); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + StringStream out; + gen.EmitExpression(out, call); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "dot(param1, param2)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Select_Scalar) { + GlobalVar("a", Expr(1_f), core::AddressSpace::kPrivate); + GlobalVar("b", Expr(2_f), core::AddressSpace::kPrivate); + auto* call = Call("select", "a", "b", true); + WrapInFunction(Decl(Var("r", call))); + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + StringStream out; + gen.EmitExpression(out, call); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "(true ? b : a)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Select_Vector) { + GlobalVar("a", Call>(1_i, 2_i), core::AddressSpace::kPrivate); + GlobalVar("b", Call>(3_i, 4_i), core::AddressSpace::kPrivate); + auto* call = Call("select", "a", "b", Call>(true, false)); + WrapInFunction(Decl(Var("r", call))); + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + StringStream out; + gen.EmitExpression(out, call); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "tint_select(a, b, bvec2(true, false))"); +} + +TEST_F(GlslASTPrinterTest_Builtin, FMA_f32) { + auto* call = Call("fma", "a", "b", "c"); + + GlobalVar("a", ty.vec3(), core::AddressSpace::kPrivate); + GlobalVar("b", ty.vec3(), core::AddressSpace::kPrivate); + GlobalVar("c", ty.vec3(), core::AddressSpace::kPrivate); + + WrapInFunction(Decl(Var("r", call))); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + StringStream out; + gen.EmitExpression(out, call); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "((a) * (b) + (c))"); +} + +TEST_F(GlslASTPrinterTest_Builtin, FMA_f16) { + Enable(wgsl::Extension::kF16); + + GlobalVar("a", ty.vec3(), core::AddressSpace::kPrivate); + GlobalVar("b", ty.vec3(), core::AddressSpace::kPrivate); + GlobalVar("c", ty.vec3(), core::AddressSpace::kPrivate); + + auto* call = Call("fma", "a", "b", "c"); + WrapInFunction(Decl(Var("r", call))); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + StringStream out; + gen.EmitExpression(out, call); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "((a) * (b) + (c))"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Runtime_Modf_Scalar_f32) { + WrapInFunction(Decl(Let("f", Expr(1.5_f))), // + Decl(Let("v", Call("modf", "f")))); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +struct modf_result_f32 { + float fract; + float whole; +}; + +modf_result_f32 tint_modf(float param_0) { + modf_result_f32 result; + result.fract = modf(param_0, result.whole); + return result; +} + + +void test_function() { + float f = 1.5f; + modf_result_f32 v = tint_modf(f); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Runtime_Modf_Scalar_f16) { + Enable(wgsl::Extension::kF16); + + WrapInFunction(Decl(Let("f", Expr(1.5_h))), // + Decl(Let("v", Call("modf", "f")))); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +#extension GL_AMD_gpu_shader_half_float : require + +struct modf_result_f16 { + float16_t fract; + float16_t whole; +}; + +modf_result_f16 tint_modf(float16_t param_0) { + modf_result_f16 result; + result.fract = modf(param_0, result.whole); + return result; +} + + +void test_function() { + float16_t f = 1.5hf; + modf_result_f16 v = tint_modf(f); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Runtime_Modf_Vector_f32) { + WrapInFunction(Decl(Let("f", Call>(1.5_f, 2.5_f, 3.5_f))), // + Decl(Let("v", Call("modf", "f")))); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +struct modf_result_vec3_f32 { + vec3 fract; + vec3 whole; +}; + +modf_result_vec3_f32 tint_modf(vec3 param_0) { + modf_result_vec3_f32 result; + result.fract = modf(param_0, result.whole); + return result; +} + + +void test_function() { + vec3 f = vec3(1.5f, 2.5f, 3.5f); + modf_result_vec3_f32 v = tint_modf(f); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Runtime_Modf_Vector_f16) { + Enable(wgsl::Extension::kF16); + + WrapInFunction(Decl(Let("f", Call>(1.5_h, 2.5_h, 3.5_h))), // + Decl(Let("v", Call("modf", "f")))); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +#extension GL_AMD_gpu_shader_half_float : require + +struct modf_result_vec3_f16 { + f16vec3 fract; + f16vec3 whole; +}; + +modf_result_vec3_f16 tint_modf(f16vec3 param_0) { + modf_result_vec3_f16 result; + result.fract = modf(param_0, result.whole); + return result; +} + + +void test_function() { + f16vec3 f = f16vec3(1.5hf, 2.5hf, 3.5hf); + modf_result_vec3_f16 v = tint_modf(f); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Const_Modf_Scalar_f32) { + WrapInFunction(Decl(Let("v", Call("modf", 1.5_f)))); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +struct modf_result_f32 { + float fract; + float whole; +}; + + +void test_function() { + modf_result_f32 v = modf_result_f32(0.5f, 1.0f); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Const_Modf_Scalar_f16) { + Enable(wgsl::Extension::kF16); + + WrapInFunction(Decl(Let("v", Call("modf", 1.5_h)))); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +#extension GL_AMD_gpu_shader_half_float : require + +struct modf_result_f16 { + float16_t fract; + float16_t whole; +}; + + +void test_function() { + modf_result_f16 v = modf_result_f16(0.5hf, 1.0hf); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Const_Modf_Vector_f32) { + WrapInFunction(Decl(Let("v", Call("modf", Call>(1.5_f, 2.5_f, 3.5_f))))); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +struct modf_result_vec3_f32 { + vec3 fract; + vec3 whole; +}; + + +void test_function() { + modf_result_vec3_f32 v = modf_result_vec3_f32(vec3(0.5f), vec3(1.0f, 2.0f, 3.0f)); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Const_Modf_Vector_f16) { + Enable(wgsl::Extension::kF16); + + WrapInFunction(Decl(Let("v", Call("modf", Call>(1.5_h, 2.5_h, 3.5_h))))); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +#extension GL_AMD_gpu_shader_half_float : require + +struct modf_result_vec3_f16 { + f16vec3 fract; + f16vec3 whole; +}; + + +void test_function() { + modf_result_vec3_f16 v = modf_result_vec3_f16(f16vec3(0.5hf), f16vec3(1.0hf, 2.0hf, 3.0hf)); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Runtime_Frexp_Scalar_f32) { + WrapInFunction(Var("f", Expr(1_f)), // + Var("v", Call("frexp", "f"))); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +struct frexp_result_f32 { + float fract; + int exp; +}; + +frexp_result_f32 tint_frexp(float param_0) { + frexp_result_f32 result; + result.fract = frexp(param_0, result.exp); + return result; +} + + +void test_function() { + float f = 1.0f; + frexp_result_f32 v = tint_frexp(f); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Runtime_Frexp_Scalar_f16) { + Enable(wgsl::Extension::kF16); + + WrapInFunction(Var("f", Expr(1_h)), // + Var("v", Call("frexp", "f"))); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +#extension GL_AMD_gpu_shader_half_float : require + +struct frexp_result_f16 { + float16_t fract; + int exp; +}; + +frexp_result_f16 tint_frexp(float16_t param_0) { + frexp_result_f16 result; + result.fract = frexp(param_0, result.exp); + return result; +} + + +void test_function() { + float16_t f = 1.0hf; + frexp_result_f16 v = tint_frexp(f); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Runtime_Frexp_Vector_f32) { + WrapInFunction(Var("f", Call>()), // + Var("v", Call("frexp", "f"))); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +struct frexp_result_vec3_f32 { + vec3 fract; + ivec3 exp; +}; + +frexp_result_vec3_f32 tint_frexp(vec3 param_0) { + frexp_result_vec3_f32 result; + result.fract = frexp(param_0, result.exp); + return result; +} + + +void test_function() { + vec3 f = vec3(0.0f); + frexp_result_vec3_f32 v = tint_frexp(f); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Runtime_Frexp_Vector_f16) { + Enable(wgsl::Extension::kF16); + + WrapInFunction(Var("f", Call>()), // + Var("v", Call("frexp", "f"))); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +#extension GL_AMD_gpu_shader_half_float : require + +struct frexp_result_vec3_f16 { + f16vec3 fract; + ivec3 exp; +}; + +frexp_result_vec3_f16 tint_frexp(f16vec3 param_0) { + frexp_result_vec3_f16 result; + result.fract = frexp(param_0, result.exp); + return result; +} + + +void test_function() { + f16vec3 f = f16vec3(0.0hf); + frexp_result_vec3_f16 v = tint_frexp(f); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Const_Frexp_Scalar_f32) { + WrapInFunction(Decl(Let("v", Call("frexp", 1_f)))); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +struct frexp_result_f32 { + float fract; + int exp; +}; + + +void test_function() { + frexp_result_f32 v = frexp_result_f32(0.5f, 1); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Const_Frexp_Scalar_f16) { + Enable(wgsl::Extension::kF16); + + WrapInFunction(Decl(Let("v", Call("frexp", 1_h)))); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +#extension GL_AMD_gpu_shader_half_float : require + +struct frexp_result_f16 { + float16_t fract; + int exp; +}; + + +void test_function() { + frexp_result_f16 v = frexp_result_f16(0.5hf, 1); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Const_Frexp_Vector_f32) { + WrapInFunction(Decl(Let("v", Call("frexp", Call>())))); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +struct frexp_result_vec3_f32 { + vec3 fract; + ivec3 exp; +}; + + +void test_function() { + frexp_result_vec3_f32 v = frexp_result_vec3_f32(vec3(0.0f), ivec3(0)); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Const_Frexp_Vector_f16) { + Enable(wgsl::Extension::kF16); + + WrapInFunction(Decl(Let("v", Call("frexp", Call>())))); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +#extension GL_AMD_gpu_shader_half_float : require + +struct frexp_result_vec3_f16 { + f16vec3 fract; + ivec3 exp; +}; + + +void test_function() { + frexp_result_vec3_f16 v = frexp_result_vec3_f16(f16vec3(0.0hf), ivec3(0)); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Degrees_Scalar_f32) { + auto* val = Var("val", ty.f32()); + auto* call = Call("degrees", val); + WrapInFunction(val, call); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +float tint_degrees(float param_0) { + return param_0 * 57.29577951308232286465f; +} + + +void test_function() { + float val = 0.0f; + float tint_symbol = tint_degrees(val); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Degrees_Vector_f32) { + auto* val = Var("val", ty.vec3()); + auto* call = Call("degrees", val); + WrapInFunction(val, call); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +vec3 tint_degrees(vec3 param_0) { + return param_0 * 57.29577951308232286465f; +} + + +void test_function() { + vec3 val = vec3(0.0f, 0.0f, 0.0f); + vec3 tint_symbol = tint_degrees(val); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Degrees_Scalar_f16) { + Enable(wgsl::Extension::kF16); + + auto* val = Var("val", ty.f16()); + auto* call = Call("degrees", val); + WrapInFunction(val, call); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +#extension GL_AMD_gpu_shader_half_float : require + +float16_t tint_degrees(float16_t param_0) { + return param_0 * 57.29577951308232286465hf; +} + + +void test_function() { + float16_t val = 0.0hf; + float16_t tint_symbol = tint_degrees(val); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Degrees_Vector_f16) { + Enable(wgsl::Extension::kF16); + + auto* val = Var("val", ty.vec3()); + auto* call = Call("degrees", val); + WrapInFunction(val, call); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +#extension GL_AMD_gpu_shader_half_float : require + +f16vec3 tint_degrees(f16vec3 param_0) { + return param_0 * 57.29577951308232286465hf; +} + + +void test_function() { + f16vec3 val = f16vec3(0.0hf, 0.0hf, 0.0hf); + f16vec3 tint_symbol = tint_degrees(val); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Radians_Scalar_f32) { + auto* val = Var("val", ty.f32()); + auto* call = Call("radians", val); + WrapInFunction(val, call); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +float tint_radians(float param_0) { + return param_0 * 0.01745329251994329547f; +} + + +void test_function() { + float val = 0.0f; + float tint_symbol = tint_radians(val); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Radians_Vector_f32) { + auto* val = Var("val", ty.vec3()); + auto* call = Call("radians", val); + WrapInFunction(val, call); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +vec3 tint_radians(vec3 param_0) { + return param_0 * 0.01745329251994329547f; +} + + +void test_function() { + vec3 val = vec3(0.0f, 0.0f, 0.0f); + vec3 tint_symbol = tint_radians(val); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Radians_Scalar_f16) { + Enable(wgsl::Extension::kF16); + + auto* val = Var("val", ty.f16()); + auto* call = Call("radians", val); + WrapInFunction(val, call); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +#extension GL_AMD_gpu_shader_half_float : require + +float16_t tint_radians(float16_t param_0) { + return param_0 * 0.01745329251994329547hf; +} + + +void test_function() { + float16_t val = 0.0hf; + float16_t tint_symbol = tint_radians(val); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Radians_Vector_f16) { + Enable(wgsl::Extension::kF16); + + auto* val = Var("val", ty.vec3()); + auto* call = Call("radians", val); + WrapInFunction(val, call); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +#extension GL_AMD_gpu_shader_half_float : require + +f16vec3 tint_radians(f16vec3 param_0) { + return param_0 * 0.01745329251994329547hf; +} + + +void test_function() { + f16vec3 val = f16vec3(0.0hf, 0.0hf, 0.0hf); + f16vec3 tint_symbol = tint_radians(val); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, ExtractBits) { + auto* v = Var("v", ty.vec3()); + auto* offset = Var("offset", ty.u32()); + auto* count = Var("count", ty.u32()); + auto* call = Call("extractBits", v, offset, count); + WrapInFunction(v, offset, count, call); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +uvec3 tint_extract_bits(uvec3 v, uint offset, uint count) { + uint s = min(offset, 32u); + uint e = min(32u, (s + count)); + return bitfieldExtract(v, int(s), int((e - s))); +} + +void test_function() { + uvec3 v = uvec3(0u, 0u, 0u); + uint offset = 0u; + uint count = 0u; + uvec3 tint_symbol = tint_extract_bits(v, offset, count); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, InsertBits) { + auto* v = Var("v", ty.vec3()); + auto* n = Var("n", ty.vec3()); + auto* offset = Var("offset", ty.u32()); + auto* count = Var("count", ty.u32()); + auto* call = Call("insertBits", v, n, offset, count); + WrapInFunction(v, n, offset, count, call); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +uvec3 tint_insert_bits(uvec3 v, uvec3 n, uint offset, uint count) { + uint s = min(offset, 32u); + uint e = min(32u, (s + count)); + return bitfieldInsert(v, n, int(s), int((e - s))); +} + +void test_function() { + uvec3 v = uvec3(0u, 0u, 0u); + uvec3 n = uvec3(0u, 0u, 0u); + uint offset = 0u; + uint count = 0u; + uvec3 tint_symbol = tint_insert_bits(v, n, offset, count); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Pack4x8Snorm) { + auto* call = Call("pack4x8snorm", "p1"); + GlobalVar("p1", ty.vec4(), core::AddressSpace::kPrivate); + WrapInFunction(Decl(Var("r", call))); + ASTPrinter& gen = Build(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +vec4 p1 = vec4(0.0f, 0.0f, 0.0f, 0.0f); +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void test_function() { + uint r = packSnorm4x8(p1); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Pack4x8Unorm) { + auto* call = Call("pack4x8unorm", "p1"); + GlobalVar("p1", ty.vec4(), core::AddressSpace::kPrivate); + WrapInFunction(Decl(Var("r", call))); + ASTPrinter& gen = Build(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +vec4 p1 = vec4(0.0f, 0.0f, 0.0f, 0.0f); +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void test_function() { + uint r = packUnorm4x8(p1); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Pack2x16Snorm) { + auto* call = Call("pack2x16snorm", "p1"); + GlobalVar("p1", ty.vec2(), core::AddressSpace::kPrivate); + WrapInFunction(Decl(Var("r", call))); + ASTPrinter& gen = Build(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +vec2 p1 = vec2(0.0f, 0.0f); +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void test_function() { + uint r = packSnorm2x16(p1); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Pack2x16Unorm) { + auto* call = Call("pack2x16unorm", "p1"); + GlobalVar("p1", ty.vec2(), core::AddressSpace::kPrivate); + WrapInFunction(Decl(Var("r", call))); + ASTPrinter& gen = Build(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +vec2 p1 = vec2(0.0f, 0.0f); +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void test_function() { + uint r = packUnorm2x16(p1); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Pack2x16Float) { + auto* call = Call("pack2x16float", "p1"); + GlobalVar("p1", ty.vec2(), core::AddressSpace::kPrivate); + WrapInFunction(Decl(Var("r", call))); + ASTPrinter& gen = Build(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +vec2 p1 = vec2(0.0f, 0.0f); +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void test_function() { + uint r = packHalf2x16(p1); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Unpack4x8Snorm) { + auto* call = Call("unpack4x8snorm", "p1"); + GlobalVar("p1", ty.u32(), core::AddressSpace::kPrivate); + WrapInFunction(Decl(Var("r", call))); + ASTPrinter& gen = Build(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +uint p1 = 0u; +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void test_function() { + vec4 r = unpackSnorm4x8(p1); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Unpack4x8Unorm) { + auto* call = Call("unpack4x8unorm", "p1"); + GlobalVar("p1", ty.u32(), core::AddressSpace::kPrivate); + WrapInFunction(Decl(Var("r", call))); + ASTPrinter& gen = Build(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +uint p1 = 0u; +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void test_function() { + vec4 r = unpackUnorm4x8(p1); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Unpack2x16Snorm) { + auto* call = Call("unpack2x16snorm", "p1"); + GlobalVar("p1", ty.u32(), core::AddressSpace::kPrivate); + WrapInFunction(Decl(Var("r", call))); + ASTPrinter& gen = Build(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +uint p1 = 0u; +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void test_function() { + vec2 r = unpackSnorm2x16(p1); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Unpack2x16Unorm) { + auto* call = Call("unpack2x16unorm", "p1"); + GlobalVar("p1", ty.u32(), core::AddressSpace::kPrivate); + WrapInFunction(Decl(Var("r", call))); + ASTPrinter& gen = Build(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +uint p1 = 0u; +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void test_function() { + vec2 r = unpackUnorm2x16(p1); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, Unpack2x16Float) { + auto* call = Call("unpack2x16float", "p1"); + GlobalVar("p1", ty.u32(), core::AddressSpace::kPrivate); + WrapInFunction(Decl(Var("r", call))); + ASTPrinter& gen = Build(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +uint p1 = 0u; +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void test_function() { + vec2 r = unpackHalf2x16(p1); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, StorageBarrier) { + Func("main", tint::Empty, ty.void_(), + Vector{ + CallStmt(Call("storageBarrier")), + }, + Vector{ + Stage(ast::PipelineStage::kCompute), + WorkgroupSize(1_i), + }); + + ASTPrinter& gen = Build(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + { barrier(); memoryBarrierBuffer(); }; + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, WorkgroupBarrier) { + Func("main", tint::Empty, ty.void_(), + Vector{ + CallStmt(Call("workgroupBarrier")), + }, + Vector{ + Stage(ast::PipelineStage::kCompute), + WorkgroupSize(1_i), + }); + + ASTPrinter& gen = Build(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + barrier(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, DotI32) { + GlobalVar("v", ty.vec3(), core::AddressSpace::kPrivate); + WrapInFunction(Decl(Var("r", Call("dot", "v", "v")))); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +int tint_int_dot(ivec3 a, ivec3 b) { + return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; +} + +ivec3 v = ivec3(0, 0, 0); +void test_function() { + int r = tint_int_dot(v, v); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, DotU32) { + GlobalVar("v", ty.vec3(), core::AddressSpace::kPrivate); + WrapInFunction(Decl(Var("r", Call("dot", "v", "v")))); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +uint tint_int_dot(uvec3 a, uvec3 b) { + return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; +} + +uvec3 v = uvec3(0u, 0u, 0u); +void test_function() { + uint r = tint_int_dot(v, v); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, QuantizeToF16_Scalar) { + GlobalVar("v", Expr(2_f), core::AddressSpace::kPrivate); + WrapInFunction(Call("quantizeToF16", "v")); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +float tint_quantizeToF16(float param_0) { + return unpackHalf2x16(packHalf2x16(vec2(param_0))).x; +} + + +float v = 2.0f; +void test_function() { + float tint_symbol = tint_quantizeToF16(v); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, QuantizeToF16_Vec2) { + GlobalVar("v", Call>(2_f), core::AddressSpace::kPrivate); + WrapInFunction(Call("quantizeToF16", "v")); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +vec2 tint_quantizeToF16(vec2 param_0) { + return unpackHalf2x16(packHalf2x16(param_0)); +} + + +vec2 v = vec2(2.0f); +void test_function() { + vec2 tint_symbol = tint_quantizeToF16(v); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, QuantizeToF16_Vec3) { + GlobalVar("v", Call>(2_f), core::AddressSpace::kPrivate); + WrapInFunction(Call("quantizeToF16", "v")); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +vec3 tint_quantizeToF16(vec3 param_0) { + return vec3( + unpackHalf2x16(packHalf2x16(param_0.xy)), + unpackHalf2x16(packHalf2x16(param_0.zz)).x); +} + + +vec3 v = vec3(2.0f); +void test_function() { + vec3 tint_symbol = tint_quantizeToF16(v); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Builtin, QuantizeToF16_Vec4) { + GlobalVar("v", Call>(2_f), core::AddressSpace::kPrivate); + WrapInFunction(Call("quantizeToF16", "v")); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +vec4 tint_quantizeToF16(vec4 param_0) { + return vec4( + unpackHalf2x16(packHalf2x16(param_0.xy)), + unpackHalf2x16(packHalf2x16(param_0.zw))); +} + + +vec4 v = vec4(2.0f); +void test_function() { + vec4 tint_symbol = tint_quantizeToF16(v); +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/builtin_texture_test.cc b/references/writer/ast_printer/builtin_texture_test.cc new file mode 100644 index 00000000..b384c75c --- /dev/null +++ b/references/writer/ast_printer/builtin_texture_test.cc @@ -0,0 +1,318 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "gmock/gmock.h" +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" +#include "src/tint/lang/wgsl/ast/builtin_texture_helper_test.h" +#include "src/tint/lang/wgsl/ast/call_statement.h" +#include "src/tint/lang/wgsl/ast/stage_attribute.h" + +namespace tint::glsl::writer { +namespace { + +using ::testing::HasSubstr; + +struct ExpectedResult { + ExpectedResult(const char* o) : out(o) {} // NOLINT + + std::string pre; + std::string out; +}; + +ExpectedResult expected_texture_overload(ast::test::ValidTextureOverload overload) { + using ValidTextureOverload = ast::test::ValidTextureOverload; + switch (overload) { + case ValidTextureOverload::kDimensions1d: + case ValidTextureOverload::kDimensions2d: + case ValidTextureOverload::kDimensionsDepth2d: + case ValidTextureOverload::kDimensionsDepthMultisampled2d: + case ValidTextureOverload::kDimensionsMultisampled2d: + case ValidTextureOverload::kDimensions2dArray: + case ValidTextureOverload::kDimensionsDepth2dArray: + case ValidTextureOverload::kDimensions3d: + case ValidTextureOverload::kDimensionsCube: + case ValidTextureOverload::kDimensionsDepthCube: + case ValidTextureOverload::kDimensionsCubeArray: + case ValidTextureOverload::kDimensionsDepthCubeArray: + case ValidTextureOverload::kDimensions2dLevel: + case ValidTextureOverload::kDimensionsDepth2dLevel: + case ValidTextureOverload::kDimensions2dArrayLevel: + case ValidTextureOverload::kDimensionsDepth2dArrayLevel: + case ValidTextureOverload::kDimensions3dLevel: + case ValidTextureOverload::kDimensionsCubeLevel: + case ValidTextureOverload::kDimensionsDepthCubeLevel: + case ValidTextureOverload::kDimensionsCubeArrayLevel: + case ValidTextureOverload::kDimensionsDepthCubeArrayLevel: + return {"textureSize"}; + case ValidTextureOverload::kDimensionsStorageWO1d: + case ValidTextureOverload::kDimensionsStorageWO2d: + case ValidTextureOverload::kDimensionsStorageWO2dArray: + case ValidTextureOverload::kDimensionsStorageWO3d: + return {"imageSize"}; + case ValidTextureOverload::kGather2dF32: + return R"(textureGather(Texture_Sampler, vec2(1.0f, 2.0f), 0))"; + case ValidTextureOverload::kGather2dOffsetF32: + return R"(textureGatherOffset(Texture_Sampler, vec2(1.0f, 2.0f), ivec2(3, 4), int(0u)))"; + case ValidTextureOverload::kGather2dArrayF32: + return R"(textureGather(Texture_Sampler, vec3(1.0f, 2.0f, float(3)), 0))"; + case ValidTextureOverload::kGather2dArrayOffsetF32: + return R"(textureGatherOffset(Texture_Sampler, vec3(1.0f, 2.0f, float(3u)), ivec2(4, 5), int(0u)))"; + case ValidTextureOverload::kGatherCubeF32: + return R"(textureGather(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), 0))"; + case ValidTextureOverload::kGatherCubeArrayF32: + return R"(textureGather(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, float(4u)), int(0u))"; + case ValidTextureOverload::kGatherDepth2dF32: + return R"(textureGather(Texture_Sampler, vec2(1.0f, 2.0f), 0.0))"; + case ValidTextureOverload::kGatherDepth2dOffsetF32: + return R"(textureGatherOffset(Texture_Sampler, vec2(1.0f, 2.0f), 0.0, ivec2(3, 4))"; + case ValidTextureOverload::kGatherDepth2dArrayF32: + return R"(textureGather(Texture_Sampler, vec3(1.0f, 2.0f, float(3u)), 0.0))"; + case ValidTextureOverload::kGatherDepth2dArrayOffsetF32: + return R"(textureGatherOffset(Texture_Sampler, vec3(1.0f, 2.0f, float(3)), 0.0, ivec2(4, 5)))"; + case ValidTextureOverload::kGatherDepthCubeF32: + return R"(textureGather(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), 0.0))"; + case ValidTextureOverload::kGatherDepthCubeArrayF32: + return R"(textureGather(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, float(4u)), 0.0))"; + case ValidTextureOverload::kGatherCompareDepth2dF32: + return R"(textureGather(Texture_Sampler, vec2(1.0f, 2.0f), 3.0f))"; + case ValidTextureOverload::kGatherCompareDepth2dOffsetF32: + return R"(textureGatherOffset(Texture_Sampler, vec2(1.0f, 2.0f), 3.0f, ivec2(4, 5)))"; + case ValidTextureOverload::kGatherCompareDepth2dArrayF32: + return R"(textureGather(Texture_Sampler, vec3(1.0f, 2.0f, float(3)), 4.0f))"; + case ValidTextureOverload::kGatherCompareDepth2dArrayOffsetF32: + return R"(textureGatherOffset(Texture_Sampler, vec3(1.0f, 2.0f, float(3)), 4.0f, ivec2(5, 6)))"; + case ValidTextureOverload::kGatherCompareDepthCubeF32: + return R"(textureGather(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f))"; + case ValidTextureOverload::kGatherCompareDepthCubeArrayF32: + return R"(textureGather(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, float(4u)), 5.0f))"; + case ValidTextureOverload::kNumLayers2dArray: + case ValidTextureOverload::kNumLayersDepth2dArray: + case ValidTextureOverload::kNumLayersCubeArray: + case ValidTextureOverload::kNumLayersDepthCubeArray: + return {"textureSize"}; + case ValidTextureOverload::kNumLayersStorageWO2dArray: + return {"imageSize"}; + case ValidTextureOverload::kNumLevels2d: + case ValidTextureOverload::kNumLevelsCube: + case ValidTextureOverload::kNumLevelsDepth2d: + case ValidTextureOverload::kNumLevelsDepthCube: + case ValidTextureOverload::kNumLevels2dArray: + case ValidTextureOverload::kNumLevels3d: + case ValidTextureOverload::kNumLevelsCubeArray: + case ValidTextureOverload::kNumLevelsDepth2dArray: + case ValidTextureOverload::kNumLevelsDepthCubeArray: + return {"textureQueryLevels"}; + case ValidTextureOverload::kNumSamplesDepthMultisampled2d: + case ValidTextureOverload::kNumSamplesMultisampled2d: + return {"textureSamples"}; + case ValidTextureOverload::kSample1dF32: + return R"(texture(Texture_Sampler, vec2(1.0f, 0.5f));)"; + case ValidTextureOverload::kSample2dF32: + return R"(texture(Texture_Sampler, vec2(1.0f, 2.0f));)"; + case ValidTextureOverload::kSample2dOffsetF32: + return R"(textureOffset(Texture_Sampler, vec2(1.0f, 2.0f), ivec2(3, 4));)"; + case ValidTextureOverload::kSample2dArrayF32: + return R"(texture(Texture_Sampler, vec3(1.0f, 2.0f, float(3)));)"; + case ValidTextureOverload::kSample2dArrayOffsetF32: + return R"(textureOffset(Texture_Sampler, vec3(1.0f, 2.0f, float(3u)), ivec2(4, 5));)"; + case ValidTextureOverload::kSample3dF32: + return R"(texture(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f));)"; + case ValidTextureOverload::kSample3dOffsetF32: + return R"(textureOffset(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), ivec3(4, 5, 6));)"; + case ValidTextureOverload::kSampleCubeF32: + return R"(texture(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f));)"; + case ValidTextureOverload::kSampleCubeArrayF32: + return R"(texture(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, float(4)));)"; + case ValidTextureOverload::kSampleDepth2dF32: + return R"(texture(Texture_Sampler, vec3(1.0f, 2.0f, 0.0f));)"; + case ValidTextureOverload::kSampleDepth2dOffsetF32: + return R"(textureOffset(Texture_Sampler, vec3(1.0f, 2.0f, 0.0f), ivec2(3, 4));)"; + case ValidTextureOverload::kSampleDepth2dArrayF32: + return R"(texture(Texture_Sampler, vec4(1.0f, 2.0f, float(3), 0.0f));)"; + case ValidTextureOverload::kSampleDepth2dArrayOffsetF32: + return R"(textureOffset(Texture_Sampler, vec4(1.0f, 2.0f, float(3), 0.0f), ivec2(4, 5));)"; + case ValidTextureOverload::kSampleDepthCubeF32: + return R"(texture(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, 0.0f));)"; + case ValidTextureOverload::kSampleDepthCubeArrayF32: + return R"(texture(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, float(4u)), 0.0f);)"; + case ValidTextureOverload::kSampleBias2dF32: + return R"(texture(Texture_Sampler, vec2(1.0f, 2.0f), 3.0f);)"; + case ValidTextureOverload::kSampleBias2dOffsetF32: + return R"(textureOffset(Texture_Sampler, vec2(1.0f, 2.0f), ivec2(4, 5), 3.0f);)"; + case ValidTextureOverload::kSampleBias2dArrayF32: + return R"(texture(Texture_Sampler, vec3(1.0f, 2.0f, float(4u)), 3.0f);)"; + case ValidTextureOverload::kSampleBias2dArrayOffsetF32: + return R"(textureOffset(Texture_Sampler, vec3(1.0f, 2.0f, float(3)), ivec2(5, 6), 4.0f);)"; + case ValidTextureOverload::kSampleBias3dF32: + return R"(texture(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f);)"; + case ValidTextureOverload::kSampleBias3dOffsetF32: + return R"(textureOffset(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), ivec3(5, 6, 7), 4.0f);)"; + case ValidTextureOverload::kSampleBiasCubeF32: + return R"(texture(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f);)"; + case ValidTextureOverload::kSampleBiasCubeArrayF32: + return R"(texture(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, float(3)), 4.0f);)"; + case ValidTextureOverload::kSampleLevel2dF32: + return R"(textureLod(Texture_Sampler, vec2(1.0f, 2.0f), 3.0f);)"; + case ValidTextureOverload::kSampleLevel2dOffsetF32: + return R"(textureLodOffset(Texture_Sampler, vec2(1.0f, 2.0f), 3.0f, ivec2(4, 5));)"; + case ValidTextureOverload::kSampleLevel2dArrayF32: + return R"(textureLod(Texture_Sampler, vec3(1.0f, 2.0f, float(3)), 4.0f);)"; + case ValidTextureOverload::kSampleLevel2dArrayOffsetF32: + return R"(textureLodOffset(Texture_Sampler, vec3(1.0f, 2.0f, float(3)), 4.0f, ivec2(5, 6));)"; + case ValidTextureOverload::kSampleLevel3dF32: + return R"(textureLod(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f);)"; + case ValidTextureOverload::kSampleLevel3dOffsetF32: + return R"(textureLodOffset(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f, ivec3(5, 6, 7));)"; + case ValidTextureOverload::kSampleLevelCubeF32: + return R"(textureLod(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f);)"; + case ValidTextureOverload::kSampleLevelCubeArrayF32: + return R"(textureLod(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), 5.0f);)"; + case ValidTextureOverload::kSampleLevelDepth2dF32: + return R"(textureLod(Texture_Sampler, vec3(1.0f, 2.0f, 0.0f), float(3u));)"; + case ValidTextureOverload::kSampleLevelDepth2dOffsetF32: + return R"(textureLodOffset(Texture_Sampler, vec3(1.0f, 2.0f, 0.0f), float(3), ivec2(4, 5));)"; + case ValidTextureOverload::kSampleLevelDepth2dArrayF32: + return R"(textureLod(Texture_Sampler, vec4(1.0f, 2.0f, float(3u), 0.0f), float(4u));)"; + case ValidTextureOverload::kSampleLevelDepth2dArrayOffsetF32: + return R"(textureLodOffset(Texture_Sampler, vec4(1.0f, 2.0f, float(3u), 0.0f), float(4u), ivec2(5, 6));)"; + case ValidTextureOverload::kSampleLevelDepthCubeF32: + return R"(textureLod(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, 0.0f), float(4)))"; + case ValidTextureOverload::kSampleLevelDepthCubeArrayF32: + return R"(textureLod(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), float(5));)"; + case ValidTextureOverload::kSampleGrad2dF32: + return R"(textureGrad(Texture_Sampler, vec2(1.0f, 2.0f), vec2(3.0f, 4.0f), vec2(5.0f, 6.0f));)"; + case ValidTextureOverload::kSampleGrad2dOffsetF32: + return R"(textureGradOffset(Texture_Sampler, vec2(1.0f, 2.0f), vec2(3.0f, 4.0f), vec2(5.0f, 6.0f), ivec2(7));)"; + case ValidTextureOverload::kSampleGrad2dArrayF32: + return R"(textureGrad(Texture_Sampler, vec3(1.0f, 2.0f, float(3)), vec2(4.0f, 5.0f), vec2(6.0f, 7.0f));)"; + case ValidTextureOverload::kSampleGrad2dArrayOffsetF32: + return R"(textureGradOffset(Texture_Sampler, vec3(1.0f, 2.0f, float(3u)), vec2(4.0f, 5.0f), vec2(6.0f, 7.0f), ivec2(6, 7));)"; + case ValidTextureOverload::kSampleGrad3dF32: + return R"(textureGrad(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f), vec3(7.0f, 8.0f, 9.0f));)"; + case ValidTextureOverload::kSampleGrad3dOffsetF32: + return R"(textureGradOffset(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f), vec3(7.0f, 8.0f, 9.0f), ivec3(0, 1, 2));)"; + case ValidTextureOverload::kSampleGradCubeF32: + return R"(textureGrad(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f), vec3(7.0f, 8.0f, 9.0f));)"; + case ValidTextureOverload::kSampleGradCubeArrayF32: + return R"(textureGrad(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, float(4u)), vec3(5.0f, 6.0f, 7.0f), vec3(8.0f, 9.0f, 10.0f));)"; + case ValidTextureOverload::kSampleCompareDepth2dF32: + return R"(texture(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f));)"; + case ValidTextureOverload::kSampleCompareDepth2dOffsetF32: + return R"(textureOffset(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), ivec2(4, 5));)"; + case ValidTextureOverload::kSampleCompareDepth2dArrayF32: + return R"(texture(Texture_Sampler, vec4(1.0f, 2.0f, float(4), 3.0f));)"; + case ValidTextureOverload::kSampleCompareDepth2dArrayOffsetF32: + return R"(textureOffset(Texture_Sampler, vec4(1.0f, 2.0f, float(4u), 3.0f), ivec2(5, 6));)"; + case ValidTextureOverload::kSampleCompareDepthCubeF32: + return R"(texture(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, 4.0f));)"; + case ValidTextureOverload::kSampleCompareDepthCubeArrayF32: + return R"(texture(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), 5.0f);)"; + case ValidTextureOverload::kSampleCompareLevelDepth2dF32: + return R"(texture(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f));)"; + case ValidTextureOverload::kSampleCompareLevelDepth2dOffsetF32: + return R"(textureOffset(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), ivec2(4, 5));)"; + case ValidTextureOverload::kSampleCompareLevelDepth2dArrayF32: + return R"(texture(Texture_Sampler, vec4(1.0f, 2.0f, float(3), 4.0f));)"; + case ValidTextureOverload::kSampleCompareLevelDepth2dArrayOffsetF32: + return R"(textureOffset(Texture_Sampler, vec4(1.0f, 2.0f, float(3), 4.0f), ivec2(5, 6));)"; + case ValidTextureOverload::kSampleCompareLevelDepthCubeF32: + return R"(texture(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, 4.0f));)"; + case ValidTextureOverload::kSampleCompareLevelDepthCubeArrayF32: + return R"(texture(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), 5.0f);)"; + case ValidTextureOverload::kLoad1dLevelF32: + return R"(texelFetch(Texture_1, ivec2(uvec2(1u, 0u)), int(3u));)"; + case ValidTextureOverload::kLoad1dLevelU32: + case ValidTextureOverload::kLoad1dLevelI32: + return R"(texelFetch(Texture_1, ivec2(1, 0), 3);)"; + case ValidTextureOverload::kLoad2dLevelU32: + return R"(texelFetch(Texture_1, ivec2(1, 2), 3);)"; + case ValidTextureOverload::kLoad2dLevelF32: + case ValidTextureOverload::kLoad2dLevelI32: + return R"(texelFetch(Texture_1, ivec2(uvec2(1u, 2u)), int(3u));)"; + case ValidTextureOverload::kLoad2dArrayLevelF32: + case ValidTextureOverload::kLoad2dArrayLevelU32: + case ValidTextureOverload::kLoad3dLevelF32: + case ValidTextureOverload::kLoad3dLevelU32: + return R"(texelFetch(Texture_1, ivec3(1, 2, 3), 4);)"; + case ValidTextureOverload::kLoad2dArrayLevelI32: + case ValidTextureOverload::kLoad3dLevelI32: + return R"(texelFetch(Texture_1, ivec3(uvec3(1u, 2u, 3u)), int(4u));)"; + case ValidTextureOverload::kLoadMultisampled2dF32: + case ValidTextureOverload::kLoadMultisampled2dU32: + return R"(texelFetch(Texture_1, ivec2(1, 2), 3);)"; + case ValidTextureOverload::kLoadMultisampled2dI32: + return R"(texelFetch(Texture_1, ivec2(uvec2(1u, 2u)), int(3u));)"; + case ValidTextureOverload::kLoadDepth2dLevelF32: + return R"(texelFetch(Texture_1, ivec2(1, 2), 3).x;)"; + case ValidTextureOverload::kLoadDepth2dArrayLevelF32: + return R"(texelFetch(Texture_1, ivec3(uvec3(1u, 2u, 3u)), int(4u)).x;)"; + case ValidTextureOverload::kLoadDepthMultisampled2dF32: + return R"(texelFetch(Texture_1, ivec2(uvec2(1u, 2u)), int(3u)).x;)"; + case ValidTextureOverload::kStoreWO1dRgba32float: + return R"(imageStore(Texture, ivec2(1, 0), vec4(2.0f, 3.0f, 4.0f, 5.0f));)"; + case ValidTextureOverload::kStoreWO2dRgba32float: + return R"(imageStore(Texture, ivec2(1, 2), vec4(3.0f, 4.0f, 5.0f, 6.0f));)"; + case ValidTextureOverload::kStoreWO2dArrayRgba32float: + return R"(imageStore(Texture, ivec3(uvec3(1u, 2u, 3u)), vec4(4.0f, 5.0f, 6.0f, 7.0f));)"; + case ValidTextureOverload::kStoreWO3dRgba32float: + return R"(imageStore(Texture, ivec3(uvec3(1u, 2u, 3u)), vec4(4.0f, 5.0f, 6.0f, 7.0f));)"; + } + return ""; +} // NOLINT - Ignore the length of this function + +class GlslGeneratorBuiltinTextureTest : public TestParamHelper {}; + +TEST_P(GlslGeneratorBuiltinTextureTest, Call) { + auto param = GetParam(); + + param.BuildTextureVariable(this); + param.BuildSamplerVariable(this); + + auto* call = Call(param.function, param.args(this)); + auto* stmt = param.returns_value ? static_cast(Decl(Var("v", call))) + : static_cast(CallStmt(call)); + + Func("main", tint::Empty, ty.void_(), Vector{stmt}, + Vector{Stage(ast::PipelineStage::kFragment)}); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + + auto expected = expected_texture_overload(param.overload); + + EXPECT_THAT(gen.Result(), HasSubstr(expected.pre)); + EXPECT_THAT(gen.Result(), HasSubstr(expected.out)); +} + +INSTANTIATE_TEST_SUITE_P(GlslGeneratorBuiltinTextureTest, + GlslGeneratorBuiltinTextureTest, + testing::ValuesIn(ast::test::TextureOverloadCase::ValidCases())); + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/call_test.cc b/references/writer/ast_printer/call_test.cc new file mode 100644 index 00000000..ee6addd8 --- /dev/null +++ b/references/writer/ast_printer/call_test.cc @@ -0,0 +1,98 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" +#include "src/tint/lang/wgsl/ast/call_statement.h" +#include "src/tint/utils/text/string_stream.h" + +#include "gmock/gmock.h" + +using namespace tint::core::number_suffixes; // NOLINT + +namespace tint::glsl::writer { +namespace { + +using GlslASTPrinterTest_Call = TestHelper; + +TEST_F(GlslASTPrinterTest_Call, EmitExpression_Call_WithoutParams) { + Func("my_func", tint::Empty, ty.f32(), Vector{Return(1.23_f)}); + + auto* call = Call("my_func"); + WrapInFunction(call); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, call); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "my_func()"); +} + +TEST_F(GlslASTPrinterTest_Call, EmitExpression_Call_WithParams) { + Func("my_func", + Vector{ + Param(Sym(), ty.f32()), + Param(Sym(), ty.f32()), + }, + ty.f32(), Vector{Return(1.23_f)}); + GlobalVar("param1", ty.f32(), core::AddressSpace::kPrivate); + GlobalVar("param2", ty.f32(), core::AddressSpace::kPrivate); + + auto* call = Call("my_func", "param1", "param2"); + WrapInFunction(call); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, call); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "my_func(param1, param2)"); +} + +TEST_F(GlslASTPrinterTest_Call, EmitStatement_Call) { + Func("my_func", + Vector{ + Param(Sym(), ty.f32()), + Param(Sym(), ty.f32()), + }, + ty.void_(), tint::Empty, tint::Empty); + GlobalVar("param1", ty.f32(), core::AddressSpace::kPrivate); + GlobalVar("param2", ty.f32(), core::AddressSpace::kPrivate); + + auto* call = CallStmt(Call("my_func", "param1", "param2")); + WrapInFunction(call); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitStatement(call); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), " my_func(param1, param2);\n"); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/case_test.cc b/references/writer/ast_printer/case_test.cc new file mode 100644 index 00000000..f6707d7d --- /dev/null +++ b/references/writer/ast_printer/case_test.cc @@ -0,0 +1,108 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" + +#include "gmock/gmock.h" + +using namespace tint::core::number_suffixes; // NOLINT + +namespace tint::glsl::writer { +namespace { + +using GlslASTPrinterTest_Case = TestHelper; + +TEST_F(GlslASTPrinterTest_Case, Emit_Case) { + auto* s = + Switch(1_i, Case(CaseSelector(5_i), Block(create())), DefaultCase()); + WrapInFunction(s); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitCase(s->body[0]); + EXPECT_EQ(gen.Result(), R"( case 5: { + break; + } +)"); +} + +TEST_F(GlslASTPrinterTest_Case, Emit_Case_BreaksByDefault) { + auto* s = Switch(1_i, Case(CaseSelector(5_i), Block()), DefaultCase()); + WrapInFunction(s); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitCase(s->body[0]); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( case 5: { + break; + } +)"); +} + +TEST_F(GlslASTPrinterTest_Case, Emit_Case_MultipleSelectors) { + auto* s = Switch(1_i, + Case( + Vector{ + CaseSelector(5_i), + CaseSelector(6_i), + }, + Block(create())), + DefaultCase()); + WrapInFunction(s); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitCase(s->body[0]); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( case 5: + case 6: { + break; + } +)"); +} + +TEST_F(GlslASTPrinterTest_Case, Emit_Case_Default) { + auto* s = Switch(1_i, DefaultCase(Block(create()))); + WrapInFunction(s); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitCase(s->body[0]); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( default: { + break; + } +)"); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/cast_test.cc b/references/writer/ast_printer/cast_test.cc new file mode 100644 index 00000000..c2926b66 --- /dev/null +++ b/references/writer/ast_printer/cast_test.cc @@ -0,0 +1,66 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" +#include "src/tint/utils/text/string_stream.h" + +#include "gmock/gmock.h" + +namespace tint::glsl::writer { +namespace { + +using namespace tint::core::fluent_types; // NOLINT +using namespace tint::core::number_suffixes; // NOLINT + +using GlslASTPrinterTest_Cast = TestHelper; + +TEST_F(GlslASTPrinterTest_Cast, EmitExpression_Cast_Scalar) { + auto* cast = Call(1_i); + WrapInFunction(cast); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, cast); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "1.0f"); +} + +TEST_F(GlslASTPrinterTest_Cast, EmitExpression_Cast_Vector) { + auto* cast = Call>(Call>(1_i, 2_i, 3_i)); + WrapInFunction(cast); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, cast); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "vec3(1.0f, 2.0f, 3.0f)"); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/constructor_test.cc b/references/writer/ast_printer/constructor_test.cc new file mode 100644 index 00000000..fd694823 --- /dev/null +++ b/references/writer/ast_printer/constructor_test.cc @@ -0,0 +1,437 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "gmock/gmock.h" +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" + +namespace tint::glsl::writer { +namespace { + +using ::testing::HasSubstr; +using namespace tint::core::fluent_types; // NOLINT +using namespace tint::core::number_suffixes; // NOLINT + +using GlslASTPrinterTest_Constructor = TestHelper; + +TEST_F(GlslASTPrinterTest_Constructor, Bool) { + WrapInFunction(Expr(false)); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("false")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Int) { + WrapInFunction(Expr(-12345_i)); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("-12345")); +} + +TEST_F(GlslASTPrinterTest_Constructor, UInt) { + WrapInFunction(Expr(56779_u)); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("56779u")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Float) { + // Use a number close to 1<<30 but whose decimal representation ends in 0. + WrapInFunction(Expr(f32((1 << 30) - 4))); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("1073741824.0f")); +} + +TEST_F(GlslASTPrinterTest_Constructor, F16) { + Enable(wgsl::Extension::kF16); + + // Use a number close to 1<<16 but whose decimal representation ends in 0. + WrapInFunction(Expr(f16((1 << 15) - 8))); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("32752.0hf")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_Float) { + WrapInFunction(Call(-1.2e-5_f)); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("-0.00001200000042445026f")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_F16) { + Enable(wgsl::Extension::kF16); + + WrapInFunction(Call(-1.2e-3_h)); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("-0.0011997222900390625hf")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_Bool) { + WrapInFunction(Call(true)); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("true")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_Int) { + WrapInFunction(Call(-12345_i)); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("-12345")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_Uint) { + WrapInFunction(Call(12345_u)); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("12345u")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_Vec_F32) { + WrapInFunction(Call>(1_f, 2_f, 3_f)); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("vec3(1.0f, 2.0f, 3.0f)")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_Vec_F16) { + Enable(wgsl::Extension::kF16); + + WrapInFunction(Call>(1_h, 2_h, 3_h)); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("f16vec3(1.0hf, 2.0hf, 3.0hf)")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_Vec_Empty_F32) { + WrapInFunction(Call>()); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("vec3(0.0f)")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_Vec_Empty_F16) { + Enable(wgsl::Extension::kF16); + + WrapInFunction(Call>()); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("f16vec3(0.0hf)")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_Vec_SingleScalar_F32_Literal) { + WrapInFunction(Call>(2_f)); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("vec3(2.0f)")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_Vec_SingleScalar_F16_Literal) { + Enable(wgsl::Extension::kF16); + + WrapInFunction(Call>(2_h)); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("f16vec3(2.0hf)")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_Vec_SingleScalar_F32_Var) { + auto* var = Var("v", Expr(2_f)); + auto* cast = Call>(var); + WrapInFunction(var, cast); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr(R"(float v = 2.0f; + vec3 tint_symbol = vec3(v);)")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_Vec_SingleScalar_F16_Var) { + Enable(wgsl::Extension::kF16); + + auto* var = Var("v", Expr(2_h)); + auto* cast = Call>(var); + WrapInFunction(var, cast); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr(R"(float16_t v = 2.0hf; + f16vec3 tint_symbol = f16vec3(v);)")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_Vec_SingleScalar_Bool) { + WrapInFunction(Call>(true)); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("bvec3(true)")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_Vec_SingleScalar_Int) { + WrapInFunction(Call>(2_i)); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("ivec3(2)")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_Vec_SingleScalar_UInt) { + WrapInFunction(Call>(2_u)); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("uvec3(2u)")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_Mat_F32) { + WrapInFunction( + Call>(Call>(1_f, 2_f, 3_f), Call>(3_f, 4_f, 5_f))); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("mat2x3(vec3(1.0f, 2.0f, 3.0f), vec3(3.0f, 4.0f, 5.0f))")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_Mat_F16) { + Enable(wgsl::Extension::kF16); + + WrapInFunction( + Call>(Call>(1_h, 2_h, 3_h), Call>(3_h, 4_h, 5_h))); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), + HasSubstr("f16mat2x3(f16vec3(1.0hf, 2.0hf, 3.0hf), f16vec3(3.0hf, 4.0hf, 5.0hf))")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_Mat_Complex_F32) { + // mat4x4( + // vec4(2.0f, 3.0f, 4.0f, 8.0f), + // vec4(), + // vec4(7.0f), + // vec4(vec4(42.0f, 21.0f, 6.0f, -5.0f)), + // ); + auto* vector_literal = + Call>(Expr(f32(2.0)), Expr(f32(3.0)), Expr(f32(4.0)), Expr(f32(8.0))); + auto* vector_zero_init = Call>(); + auto* vector_single_scalar_init = Call>(Expr(f32(7.0))); + auto* vector_identical_init = Call>( + Call>(Expr(f32(42.0)), Expr(f32(21.0)), Expr(f32(6.0)), Expr(f32(-5.0)))); + + auto* ctor = Call>(vector_literal, vector_zero_init, vector_single_scalar_init, + vector_identical_init); + + WrapInFunction(ctor); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("mat4(vec4(2.0f, 3.0f, 4.0f, 8.0f), vec4(0.0f), " + "vec4(7.0f), vec4(42.0f, 21.0f, 6.0f, -5.0f))")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_Mat_Complex_F16) { + // mat4x4( + // vec4(2.0h, 3.0h, 4.0h, 8.0h), + // vec4(), + // vec4(7.0h), + // vec4(vec4(42.0h, 21.0h, 6.0h, -5.0h)), + // ); + Enable(wgsl::Extension::kF16); + + auto* vector_literal = + Call>(Expr(f16(2.0)), Expr(f16(3.0)), Expr(f16(4.0)), Expr(f16(8.0))); + auto* vector_zero_init = Call>(); + auto* vector_single_scalar_init = Call>(Expr(f16(7.0))); + auto* vector_identical_init = Call>( + Call>(Expr(f16(42.0)), Expr(f16(21.0)), Expr(f16(6.0)), Expr(f16(-5.0)))); + + auto* ctor = Call>(vector_literal, vector_zero_init, vector_single_scalar_init, + vector_identical_init); + + WrapInFunction(ctor); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), + HasSubstr("f16mat4(f16vec4(2.0hf, 3.0hf, 4.0hf, 8.0hf), f16vec4(0.0hf), " + "f16vec4(7.0hf), f16vec4(42.0hf, 21.0hf, 6.0hf, -5.0hf))")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_Mat_Empty_F32) { + WrapInFunction(Call>()); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("mat2x3 tint_symbol = mat2x3(vec3(0.0f), vec3(0.0f))")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_Mat_Empty_F16) { + Enable(wgsl::Extension::kF16); + + WrapInFunction(Call>()); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), + HasSubstr("f16mat2x3 tint_symbol = f16mat2x3(f16vec3(0.0hf), f16vec3(0.0hf))")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_Mat_Identity_F32) { + // fn f() { + // var m_1: mat4x4 = mat4x4(); + // var m_2: mat4x4 = mat4x4(m_1); + // } + + auto* m_1 = Var("m_1", ty.mat4x4(ty.f32()), Call>()); + auto* m_2 = Var("m_2", ty.mat4x4(ty.f32()), Call>(m_1)); + + WrapInFunction(m_1, m_2); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("mat4 m_2 = mat4(m_1);")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_Mat_Identity_F16) { + // fn f() { + // var m_1: mat4x4 = mat4x4(); + // var m_2: mat4x4 = mat4x4(m_1); + // } + + Enable(wgsl::Extension::kF16); + + auto* m_1 = Var("m_1", ty.mat4x4(ty.f16()), Call>()); + auto* m_2 = Var("m_2", ty.mat4x4(ty.f16()), Call>(m_1)); + + WrapInFunction(m_1, m_2); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("f16mat4 m_2 = f16mat4(m_1);")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_Array) { + WrapInFunction(Call, 3>>(Call>(1_f, 2_f, 3_f), + Call>(4_f, 5_f, 6_f), + Call>(7_f, 8_f, 9_f))); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("vec3[3](vec3(1.0f, 2.0f, 3.0f), " + "vec3(4.0f, 5.0f, 6.0f), " + "vec3(7.0f, 8.0f, 9.0f))")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_Array_Empty) { + WrapInFunction(Call, 3>>()); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("vec3[3](vec3(0.0f), vec3(0.0f), vec3(0.0f))")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_Struct) { + auto* str = Structure("S", Vector{ + Member("a", ty.i32()), + Member("b", ty.f32()), + Member("c", ty.vec3()), + }); + + WrapInFunction(Call(ty.Of(str), 1_i, 2_f, Call>(3_i, 4_i, 5_i))); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("S(1, 2.0f, ivec3(3, 4, 5))")); +} + +TEST_F(GlslASTPrinterTest_Constructor, Type_Struct_Empty) { + auto* str = Structure("S", Vector{ + Member("a", ty.i32()), + Member("b", ty.f32()), + Member("c", ty.vec3()), + }); + + WrapInFunction(Call(ty.Of(str))); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("S(0")); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/continue_test.cc b/references/writer/ast_printer/continue_test.cc new file mode 100644 index 00000000..fd52fc31 --- /dev/null +++ b/references/writer/ast_printer/continue_test.cc @@ -0,0 +1,57 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" + +#include "gmock/gmock.h" + +namespace tint::glsl::writer { +namespace { + +using GlslASTPrinterTest_Continue = TestHelper; + +TEST_F(GlslASTPrinterTest_Continue, Emit_Continue) { + auto* loop = Loop(Block(If(false, Block(Break())), Continue())); + WrapInFunction(loop); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + + gen.EmitStatement(loop); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( while (true) { + if (false) { + break; + } + continue; + } +)"); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/discard_test.cc b/references/writer/ast_printer/discard_test.cc new file mode 100644 index 00000000..f41a07a6 --- /dev/null +++ b/references/writer/ast_printer/discard_test.cc @@ -0,0 +1,51 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" + +#include "gmock/gmock.h" + +namespace tint::glsl::writer { +namespace { + +using GlslASTPrinterTest_Discard = TestHelper; + +TEST_F(GlslASTPrinterTest_Discard, Emit_Discard) { + auto* stmt = Discard(); + + Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)}); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitStatement(stmt); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), " discard;\n"); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/function_test.cc b/references/writer/ast_printer/function_test.cc new file mode 100644 index 00000000..a04b329a --- /dev/null +++ b/references/writer/ast_printer/function_test.cc @@ -0,0 +1,977 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "gmock/gmock.h" +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" +#include "src/tint/lang/wgsl/ast/stage_attribute.h" +#include "src/tint/lang/wgsl/ast/variable_decl_statement.h" +#include "src/tint/lang/wgsl/ast/workgroup_attribute.h" + +using ::testing::HasSubstr; + +namespace tint::glsl::writer { +namespace { + +using namespace tint::core::fluent_types; // NOLINT +using namespace tint::core::number_suffixes; // NOLINT + +using GlslASTPrinterTest_Function = TestHelper; + +TEST_F(GlslASTPrinterTest_Function, Emit_Function) { + Func("my_func", tint::Empty, ty.void_(), + Vector{ + Return(), + }); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( #version 310 es + + void my_func() { + return; + } + +)"); +} + +TEST_F(GlslASTPrinterTest_Function, Emit_Function_Name_Collision) { + Func("centroid", tint::Empty, ty.void_(), + Vector{ + Return(), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.IncrementIndent(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr(R"( void tint_symbol() { + return; + })")); +} + +TEST_F(GlslASTPrinterTest_Function, Emit_Function_WithParams) { + Func("my_func", + Vector{ + Param("a", ty.f32()), + Param("b", ty.i32()), + }, + ty.void_(), + Vector{ + Return(), + }); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( #version 310 es + + void my_func(float a, int b) { + return; + } + +)"); +} + +TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_NoReturn_Void) { + Func("func", tint::Empty, ty.void_(), tint::Empty /* no explicit return */, + Vector{ + Stage(ast::PipelineStage::kFragment), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +precision highp float; +precision highp int; + +void func() { + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Function, PtrParameter) { + // fn f(foo : ptr) -> f32 { + // return *foo; + // } + Func("f", Vector{Param("foo", ty.ptr())}, ty.f32(), + Vector{Return(Deref("foo"))}); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr(R"(float f(inout float foo) { + return foo; +} +)")); +} + +TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_WithInOutVars) { + // fn frag_main(@location(0) foo : f32) -> @location(1) f32 { + // return foo; + // } + Func("frag_main", + Vector{ + Param("foo", ty.f32(), Vector{Location(0_a)}), + }, + ty.f32(), + Vector{ + Return("foo"), + }, + Vector{ + Stage(ast::PipelineStage::kFragment), + }, + Vector{ + Location(1_a), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +precision highp float; +precision highp int; + +layout(location = 0) in float foo_1; +layout(location = 1) out float value; +float frag_main(float foo) { + return foo; +} + +void main() { + float inner_result = frag_main(foo_1); + value = inner_result; + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_WithInOut_Builtins) { + // fn frag_main(@position(0) coord : vec4) -> @frag_depth f32 { + // return coord.x; + // } + auto* coord_in = Param("coord", ty.vec4(), Vector{Builtin(core::BuiltinValue::kPosition)}); + Func("frag_main", + Vector{ + coord_in, + }, + ty.f32(), + Vector{ + Return(MemberAccessor("coord", "x")), + }, + Vector{ + Stage(ast::PipelineStage::kFragment), + }, + Vector{ + Builtin(core::BuiltinValue::kFragDepth), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +precision highp float; +precision highp int; + +float frag_main(vec4 coord) { + return coord.x; +} + +void main() { + float inner_result = frag_main(gl_FragCoord); + gl_FragDepth = inner_result; + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_SharedStruct_DifferentStages) { + // struct Interface { + // @builtin(position) pos : vec4; + // @location(1) col1 : f32; + // @location(2) col2 : f32; + // }; + // fn vert_main() -> Interface { + // return Interface(vec4(), 0.5, 0.25); + // } + // fn frag_main(inputs : Interface) { + // const r = inputs.col1; + // const g = inputs.col2; + // const p = inputs.pos; + // } + auto* interface_struct = + Structure("Interface", + Vector{ + Member("pos", ty.vec4(), Vector{Builtin(core::BuiltinValue::kPosition)}), + Member("col1", ty.f32(), Vector{Location(1_a)}), + Member("col2", ty.f32(), Vector{Location(2_a)}), + }); + + Func("vert_main", tint::Empty, ty.Of(interface_struct), + Vector{Return(Call(ty.Of(interface_struct), Call>(), 0.5_f, 0.25_f))}, + Vector{Stage(ast::PipelineStage::kVertex)}); + + Func("frag_main", Vector{Param("inputs", ty.Of(interface_struct))}, ty.void_(), + Vector{ + Decl(Let("r", ty.f32(), MemberAccessor("inputs", "col1"))), + Decl(Let("g", ty.f32(), MemberAccessor("inputs", "col2"))), + Decl(Let("p", ty.vec4(), MemberAccessor("inputs", "pos"))), + }, + Vector{Stage(ast::PipelineStage::kFragment)}); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +precision highp float; +precision highp int; + +layout(location = 1) out float col1_1; +layout(location = 2) out float col2_1; +layout(location = 1) in float col1_2; +layout(location = 2) in float col2_2; +struct Interface { + vec4 pos; + float col1; + float col2; +}; + +Interface vert_main() { + Interface tint_symbol = Interface(vec4(0.0f), 0.5f, 0.25f); + return tint_symbol; +} + +void main() { + gl_PointSize = 1.0; + Interface inner_result = vert_main(); + gl_Position = inner_result.pos; + col1_1 = inner_result.col1; + col2_1 = inner_result.col2; + gl_Position.y = -(gl_Position.y); + gl_Position.z = ((2.0f * gl_Position.z) - gl_Position.w); + return; +} +void frag_main(Interface inputs) { + float r = inputs.col1; + float g = inputs.col2; + vec4 p = inputs.pos; +} + +void main_1() { + Interface tint_symbol_1 = Interface(gl_FragCoord, col1_2, col2_2); + frag_main(tint_symbol_1); + return; +} +)"); +} + +#if 0 +TEST_F(GlslASTPrinterTest_Function, + Emit_Attribute_EntryPoint_SharedStruct_HelperFunction) { + // struct VertexOutput { + // @builtin(position) pos : vec4; + // }; + // fn foo(x : f32) -> VertexOutput { + // return VertexOutput(vec4(x, x, x, 1.0)); + // } + // fn vert_main1() -> VertexOutput { + // return foo(0.5); + // } + // fn vert_main2() -> VertexOutput { + // return foo(0.25); + // } + auto* vertex_output_struct = Structure( + "VertexOutput", + {Member("pos", ty.vec4(), {Builtin(core::BuiltinValue::kPosition)})}); + + Func("foo", Vector{Param("x", ty.f32())}, ty.Of(vertex_output_struct), + {Return(Call(ty.Of(vertex_output_struct), + Call>( "x", "x", "x", 1_f)))}, + {}); + + Func("vert_main1", tint::Empty, ty.Of(vertex_output_struct), + {Return(Call(ty.Of(vertex_output_struct), + Expr(Call("foo", Expr(0.5_f)))))}, + {Stage(ast::PipelineStage::kVertex)}); + + Func("vert_main2", tint::Empty, ty.Of(vertex_output_struct), + {Return(Call(ty.Of(vertex_output_struct), + Expr(Call("foo", Expr(0.25_f)))))}, + {Stage(ast::PipelineStage::kVertex)}); + + ASTPrinter& gen = SanitizeAndBuild(); + + ASSERT_TRUE(gen.Generate()) << gen.Diagnostics(); + EXPECT_EQ(gen.Result(), R"(struct VertexOutput { + float4 pos; +}; + +VertexOutput foo(float x) { + const VertexOutput tint_symbol_4 = {float4(x, x, x, 1.0f)}; + return tint_symbol_4; +} + +struct tint_symbol { + float4 pos : SV_Position; +}; + +tint_symbol vert_main1() { + const VertexOutput tint_symbol_1 = {foo(0.5f)}; + const tint_symbol tint_symbol_5 = {tint_symbol_1.pos}; + return tint_symbol_5; +} + +struct tint_symbol_2 { + float4 pos : SV_Position; +}; + +tint_symbol_2 vert_main2() { + const VertexOutput tint_symbol_3 = {foo(0.25f)}; + const tint_symbol_2 tint_symbol_6 = {tint_symbol_3.pos}; + return tint_symbol_6; +} +)"); +} +#endif + +TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_With_Uniform) { + auto* ubo_ty = Structure("UBO", Vector{Member("coord", ty.vec4())}); + auto* ubo = + GlobalVar("ubo", ty.Of(ubo_ty), core::AddressSpace::kUniform, Binding(0_a), Group(1_a)); + + Func("sub_func", + Vector{ + Param("param", ty.f32()), + }, + ty.f32(), + Vector{ + Return(MemberAccessor(MemberAccessor(ubo, "coord"), "x")), + }); + + auto* var = Var("v", ty.f32(), Call("sub_func", 1_f)); + + Func("frag_main", tint::Empty, ty.void_(), + Vector{ + Decl(var), + Return(), + }, + Vector{ + Stage(ast::PipelineStage::kFragment), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +precision highp float; +precision highp int; + +struct UBO { + vec4 coord; +}; + +layout(binding = 0, std140) uniform UBO_ubo { + vec4 coord; +} ubo; + +float sub_func(float param) { + return ubo.coord.x; +} + +void frag_main() { + float v = sub_func(1.0f); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_With_UniformStruct) { + auto* s = Structure("Uniforms", Vector{Member("coord", ty.vec4())}); + + GlobalVar("uniforms", ty.Of(s), core::AddressSpace::kUniform, Binding(0_a), Group(1_a)); + + auto* var = Var("v", ty.f32(), MemberAccessor(MemberAccessor("uniforms", "coord"), "x")); + + Func("frag_main", tint::Empty, ty.void_(), + Vector{ + Decl(var), + Return(), + }, + Vector{ + Stage(ast::PipelineStage::kFragment), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +precision highp float; +precision highp int; + +struct Uniforms { + vec4 coord; +}; + +layout(binding = 0, std140) uniform Uniforms_ubo { + vec4 coord; +} uniforms; + +void frag_main() { + float v = uniforms.coord.x; + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_With_RW_StorageBuffer_Read) { + auto* s = Structure("Data", Vector{ + Member("a", ty.i32()), + Member("b", ty.f32()), + }); + + GlobalVar("coord", ty.Of(s), core::AddressSpace::kStorage, core::Access::kReadWrite, + Binding(0_a), Group(1_a)); + + auto* var = Var("v", ty.f32(), MemberAccessor("coord", "b")); + + Func("frag_main", tint::Empty, ty.void_(), + Vector{ + Decl(var), + Return(), + }, + Vector{ + Stage(ast::PipelineStage::kFragment), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +precision highp float; +precision highp int; + +struct Data { + int a; + float b; +}; + +layout(binding = 0, std430) buffer coord_block_ssbo { + Data inner; +} coord; + +void frag_main() { + float v = coord.inner.b; + return; +} + +void main() { + frag_main(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_With_RO_StorageBuffer_Read) { + auto* s = Structure("Data", Vector{ + Member("a", ty.i32()), + Member("b", ty.f32()), + }); + + GlobalVar("coord", ty.Of(s), core::AddressSpace::kStorage, core::Access::kRead, Binding(0_a), + Group(1_a)); + + auto* var = Var("v", ty.f32(), MemberAccessor("coord", "b")); + + Func("frag_main", tint::Empty, ty.void_(), + Vector{ + Decl(var), + Return(), + }, + Vector{ + Stage(ast::PipelineStage::kFragment), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), + R"(#version 310 es +precision highp float; +precision highp int; + +struct Data { + int a; + float b; +}; + +layout(binding = 0, std430) buffer coord_block_ssbo { + Data inner; +} coord; + +void frag_main() { + float v = coord.inner.b; + return; +} + +void main() { + frag_main(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_With_WO_StorageBuffer_Store) { + auto* s = Structure("Data", Vector{ + Member("a", ty.i32()), + Member("b", ty.f32()), + }); + + GlobalVar("coord", ty.Of(s), core::AddressSpace::kStorage, core::Access::kReadWrite, + Binding(0_a), Group(1_a)); + + Func("frag_main", tint::Empty, ty.void_(), + Vector{ + Assign(MemberAccessor("coord", "b"), Expr(2_f)), + Return(), + }, + Vector{ + Stage(ast::PipelineStage::kFragment), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +precision highp float; +precision highp int; + +struct Data { + int a; + float b; +}; + +layout(binding = 0, std430) buffer coord_block_ssbo { + Data inner; +} coord; + +void frag_main() { + coord.inner.b = 2.0f; + return; +} + +void main() { + frag_main(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_With_StorageBuffer_Store) { + auto* s = Structure("Data", Vector{ + Member("a", ty.i32()), + Member("b", ty.f32()), + }); + + GlobalVar("coord", ty.Of(s), core::AddressSpace::kStorage, core::Access::kReadWrite, + Binding(0_a), Group(1_a)); + + Func("frag_main", tint::Empty, ty.void_(), + Vector{ + Assign(MemberAccessor("coord", "b"), Expr(2_f)), + Return(), + }, + Vector{ + Stage(ast::PipelineStage::kFragment), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +precision highp float; +precision highp int; + +struct Data { + int a; + float b; +}; + +layout(binding = 0, std430) buffer coord_block_ssbo { + Data inner; +} coord; + +void frag_main() { + coord.inner.b = 2.0f; + return; +} + +void main() { + frag_main(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_Called_By_EntryPoint_With_Uniform) { + auto* s = Structure("S", Vector{Member("x", ty.f32())}); + GlobalVar("coord", ty.Of(s), core::AddressSpace::kUniform, Binding(0_a), Group(1_a)); + + Func("sub_func", Vector{Param("param", ty.f32())}, ty.f32(), + Vector{ + Return(MemberAccessor("coord", "x")), + }); + + auto* var = Var("v", ty.f32(), Call("sub_func", 1_f)); + + Func("frag_main", tint::Empty, ty.void_(), + Vector{ + Decl(var), + Return(), + }, + Vector{ + Stage(ast::PipelineStage::kFragment), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +precision highp float; +precision highp int; + +struct S { + float x; +}; + +layout(binding = 0, std140) uniform S_ubo { + float x; +} coord; + +float sub_func(float param) { + return coord.x; +} + +void frag_main() { + float v = sub_func(1.0f); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_Called_By_EntryPoint_With_StorageBuffer) { + auto* s = Structure("S", Vector{Member("x", ty.f32())}); + GlobalVar("coord", ty.Of(s), core::AddressSpace::kStorage, core::Access::kReadWrite, + Binding(0_a), Group(1_a)); + + Func("sub_func", Vector{Param("param", ty.f32())}, ty.f32(), + Vector{ + Return(MemberAccessor("coord", "x")), + }); + + auto* var = Var("v", ty.f32(), Call("sub_func", 1_f)); + + Func("frag_main", tint::Empty, ty.void_(), + Vector{ + Decl(var), + Return(), + }, + Vector{ + Stage(ast::PipelineStage::kFragment), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), + R"(#version 310 es +precision highp float; +precision highp int; + +struct S { + float x; +}; + +layout(binding = 0, std430) buffer coord_block_ssbo { + S inner; +} coord; + +float sub_func(float param) { + return coord.inner.x; +} + +void frag_main() { + float v = sub_func(1.0f); + return; +} + +void main() { + frag_main(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_WithNameCollision) { + Func("centroid", tint::Empty, ty.void_(), {}, + Vector{ + Stage(ast::PipelineStage::kFragment), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +precision highp float; +precision highp int; + +void tint_symbol() { +} + +void main() { + tint_symbol(); + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_Compute) { + Func("main", tint::Empty, ty.void_(), + Vector{ + Return(), + }, + Vector{ + Stage(ast::PipelineStage::kCompute), + WorkgroupSize(1_i), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_Compute_WithWorkgroup_Literal) { + Func("main", tint::Empty, ty.void_(), {}, + Vector{ + Stage(ast::PipelineStage::kCompute), + WorkgroupSize(2_i, 4_i, 6_i), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +layout(local_size_x = 2, local_size_y = 4, local_size_z = 6) in; +void main() { + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_Compute_WithWorkgroup_Const) { + GlobalConst("width", ty.i32(), Call(2_i)); + GlobalConst("height", ty.i32(), Call(3_i)); + GlobalConst("depth", ty.i32(), Call(4_i)); + Func("main", tint::Empty, ty.void_(), {}, + Vector{ + Stage(ast::PipelineStage::kCompute), + WorkgroupSize("width", "height", "depth"), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +layout(local_size_x = 2, local_size_y = 3, local_size_z = 4) in; +void main() { + return; +} +)"); +} + +TEST_F(GlslASTPrinterTest_Function, + Emit_Attribute_EntryPoint_Compute_WithWorkgroup_OverridableConst) { + Override("width", ty.i32(), Call(2_i), Id(7_u)); + Override("height", ty.i32(), Call(3_i), Id(8_u)); + Override("depth", ty.i32(), Call(4_i), Id(9_u)); + Func("main", tint::Empty, ty.void_(), {}, + Vector{ + Stage(ast::PipelineStage::kCompute), + WorkgroupSize("width", "height", "depth"), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_EQ( + gen.Diagnostics().Str(), + R"(error: override-expressions should have been removed with the SubstituteOverride transform +error: override-expressions should have been removed with the SubstituteOverride transform +error: override-expressions should have been removed with the SubstituteOverride transform +error: override-expressions should have been removed with the SubstituteOverride transform)"); +} + +TEST_F(GlslASTPrinterTest_Function, Emit_Function_WithArrayParams) { + Func("my_func", Vector{Param("a", ty.array())}, ty.void_(), + Vector{ + Return(), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void my_func(float a[5]) { + return; +} + +)"); +} + +TEST_F(GlslASTPrinterTest_Function, Emit_Function_WithArrayReturn) { + Func("my_func", tint::Empty, ty.array(), + Vector{ + Return(Call>()), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +float[5] my_func() { + return float[5](0.0f, 0.0f, 0.0f, 0.0f, 0.0f); +} + +)"); +} + +// https://crbug.com/tint/297 +TEST_F(GlslASTPrinterTest_Function, Emit_Multiple_EntryPoint_With_Same_ModuleVar) { + // struct Data { + // d : f32; + // }; + // @binding(0) @group(0) var data : Data; + // + // @compute @workgroup_size(1) + // fn a() { + // var v = data.d; + // return; + // } + // + // @compute @workgroup_size(1) + // fn b() { + // var v = data.d; + // return; + // } + + auto* s = Structure("Data", Vector{Member("d", ty.f32())}); + + GlobalVar("data", ty.Of(s), core::AddressSpace::kStorage, core::Access::kReadWrite, + Binding(0_a), Group(0_a)); + + { + auto* var = Var("v", ty.f32(), MemberAccessor("data", "d")); + + Func("a", tint::Empty, ty.void_(), + Vector{ + Decl(var), + Return(), + }, + Vector{ + Stage(ast::PipelineStage::kCompute), + WorkgroupSize(1_i), + }); + } + + { + auto* var = Var("v", ty.f32(), MemberAccessor("data", "d")); + + Func("b", tint::Empty, ty.void_(), + Vector{ + Decl(var), + Return(), + }, + Vector{ + Stage(ast::PipelineStage::kCompute), + WorkgroupSize(1_i), + }); + } + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +struct Data { + float d; +}; + +layout(binding = 0, std430) buffer data_block_ssbo { + Data inner; +} data; + +void a() { + float v = data.inner.d; + return; +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + a(); + return; +} +void b() { + float v = data.inner.d; + return; +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main_1() { + b(); + return; +} +)"); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/helper_test.h b/references/writer/ast_printer/helper_test.h new file mode 100644 index 00000000..dc1972db --- /dev/null +++ b/references/writer/ast_printer/helper_test.h @@ -0,0 +1,140 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef SRC_TINT_LANG_GLSL_WRITER_AST_PRINTER_HELPER_TEST_H_ +#define SRC_TINT_LANG_GLSL_WRITER_AST_PRINTER_HELPER_TEST_H_ + +#include +#include +#include + +#include "gtest/gtest.h" +#include "src/tint/lang/glsl/writer/ast_printer/ast_printer.h" +#include "src/tint/lang/glsl/writer/common/version.h" +#include "src/tint/lang/glsl/writer/writer.h" +#include "src/tint/lang/wgsl/ast/transform/manager.h" +#include "src/tint/lang/wgsl/ast/transform/renamer.h" +#include "src/tint/lang/wgsl/resolver/resolve.h" + +namespace tint::glsl::writer { + +/// Helper class for testing +template +class TestHelperBase : public BODY, public ProgramBuilder { + public: + TestHelperBase() = default; + ~TestHelperBase() override = default; + + /// @returns the default generator options for SanitizeAndBuild(), if no explicit options are + /// provided. + static Options DefaultOptions() { + Options opts; + opts.disable_robustness = true; + return opts; + } + + /// Builds the program and returns a ASTPrinter from the program. + /// @note The generator is only built once. Multiple calls to Build() will + /// return the same ASTPrinter without rebuilding. + /// @param version the GLSL version + /// @return the built generator + ASTPrinter& Build(Version version = Version()) { + if (gen_) { + return *gen_; + } + if (!IsValid()) { + ADD_FAILURE() << "ProgramBuilder is not valid: " << Diagnostics(); + } + program = std::make_unique(resolver::Resolve(*this)); + if (!program->IsValid()) { + ADD_FAILURE() << program->Diagnostics(); + } + gen_ = std::make_unique(*program, version); + return *gen_; + } + + /// Builds the program, runs the program through the transform::Glsl sanitizer + /// and returns a ASTPrinter from the sanitized program. + /// @note The generator is only built once. Multiple calls to Build() will + /// return the same ASTPrinter without rebuilding. + /// @param version the GLSL version + /// @param options the GLSL backend options + /// @return the built generator + ASTPrinter& SanitizeAndBuild(Version version = Version(), + const Options& options = DefaultOptions()) { + if (gen_) { + return *gen_; + } + if (!IsValid()) { + ADD_FAILURE() << "ProgramBuilder is not valid: " << Diagnostics(); + } + program = std::make_unique(resolver::Resolve(*this)); + if (!program->IsValid()) { + ADD_FAILURE() << program->Diagnostics(); + } + + // Run the glsl keyword renamer before all other transforms. + ast::transform::Manager transform_manager; + ast::transform::DataMap transform_data; + ast::transform::DataMap outputs; + transform_data.Add( + ast::transform::Renamer::Target::kGlslKeywords, + /* preserve_unicode */ false); + transform_manager.Add(); + auto result = transform_manager.Run(*program, transform_data, outputs); + if (!result.IsValid()) { + ADD_FAILURE() << result.Diagnostics(); + } + *program = std::move(result); + + auto sanitized_result = Sanitize(*program, options, /* entry_point */ ""); + if (!sanitized_result.program.IsValid()) { + ADD_FAILURE() << sanitized_result.program.Diagnostics(); + } + + *program = std::move(sanitized_result.program); + gen_ = std::make_unique(*program, version); + return *gen_; + } + + /// The program built with a call to Build() + std::unique_ptr program; + + private: + std::unique_ptr gen_; +}; + +/// Test class +using TestHelper = TestHelperBase; + +/// Test param class +template +using TestParamHelper = TestHelperBase>; + +} // namespace tint::glsl::writer + +#endif // SRC_TINT_LANG_GLSL_WRITER_AST_PRINTER_HELPER_TEST_H_ diff --git a/references/writer/ast_printer/identifier_test.cc b/references/writer/ast_printer/identifier_test.cc new file mode 100644 index 00000000..3e041f8b --- /dev/null +++ b/references/writer/ast_printer/identifier_test.cc @@ -0,0 +1,53 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" +#include "src/tint/utils/text/string_stream.h" + +#include "gmock/gmock.h" + +namespace tint::glsl::writer { +namespace { + +using GlslASTPrinterTest_Identifier = TestHelper; + +TEST_F(GlslASTPrinterTest_Identifier, EmitIdentifierExpression) { + GlobalVar("foo", ty.i32(), core::AddressSpace::kPrivate); + + auto* i = Expr("foo"); + WrapInFunction(i); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, i); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "foo"); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/if_test.cc b/references/writer/ast_printer/if_test.cc new file mode 100644 index 00000000..c0f4456d --- /dev/null +++ b/references/writer/ast_printer/if_test.cc @@ -0,0 +1,137 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" + +#include "gmock/gmock.h" + +namespace tint::glsl::writer { +namespace { + +using GlslASTPrinterTest_If = TestHelper; + +TEST_F(GlslASTPrinterTest_If, Emit_If) { + GlobalVar("cond", ty.bool_(), core::AddressSpace::kPrivate); + + auto* cond = Expr("cond"); + auto* body = Block(Return()); + auto* i = If(cond, body); + WrapInFunction(i); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitStatement(i); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( if (cond) { + return; + } +)"); +} + +TEST_F(GlslASTPrinterTest_If, Emit_IfWithElseIf) { + GlobalVar("cond", ty.bool_(), core::AddressSpace::kPrivate); + GlobalVar("else_cond", ty.bool_(), core::AddressSpace::kPrivate); + + auto* else_cond = Expr("else_cond"); + auto* else_body = Block(Return()); + + auto* cond = Expr("cond"); + auto* body = Block(Return()); + auto* i = If(cond, body, Else(If(else_cond, else_body))); + WrapInFunction(i); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitStatement(i); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( if (cond) { + return; + } else { + if (else_cond) { + return; + } + } +)"); +} + +TEST_F(GlslASTPrinterTest_If, Emit_IfWithElse) { + GlobalVar("cond", ty.bool_(), core::AddressSpace::kPrivate); + + auto* else_body = Block(Return()); + + auto* cond = Expr("cond"); + auto* body = Block(Return()); + auto* i = If(cond, body, Else(else_body)); + WrapInFunction(i); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitStatement(i); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( if (cond) { + return; + } else { + return; + } +)"); +} + +TEST_F(GlslASTPrinterTest_If, Emit_IfWithMultiple) { + GlobalVar("cond", ty.bool_(), core::AddressSpace::kPrivate); + GlobalVar("else_cond", ty.bool_(), core::AddressSpace::kPrivate); + + auto* else_cond = Expr("else_cond"); + auto* else_body = Block(Return()); + auto* else_body_2 = Block(Return()); + + auto* cond = Expr("cond"); + auto* body = Block(Return()); + auto* i = If(cond, body, Else(If(else_cond, else_body, Else(else_body_2)))); + WrapInFunction(i); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitStatement(i); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( if (cond) { + return; + } else { + if (else_cond) { + return; + } else { + return; + } + } +)"); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/import_test.cc b/references/writer/ast_printer/import_test.cc new file mode 100644 index 00000000..5c0db0af --- /dev/null +++ b/references/writer/ast_printer/import_test.cc @@ -0,0 +1,296 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" +#include "src/tint/utils/text/string_stream.h" + +#include "gmock/gmock.h" + +namespace tint::glsl::writer { +namespace { + +using namespace tint::core::fluent_types; // NOLINT +using namespace tint::core::number_suffixes; // NOLINT + +using GlslASTPrinterTest_Import = TestHelper; + +struct GlslImportData { + const char* name; + const char* glsl_name; +}; +inline std::ostream& operator<<(std::ostream& out, GlslImportData data) { + out << data.name; + return out; +} + +using GlslImportData_SingleParamTest = TestParamHelper; +TEST_P(GlslImportData_SingleParamTest, FloatScalar) { + auto param = GetParam(); + + auto* expr = Call(param.name, 1_f); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitCall(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1.0f)"); +} +INSTANTIATE_TEST_SUITE_P(GlslASTPrinterTest_Import, + GlslImportData_SingleParamTest, + testing::Values(GlslImportData{"abs", "abs"}, + GlslImportData{"acos", "acos"}, + GlslImportData{"asin", "asin"}, + GlslImportData{"atan", "atan"}, + GlslImportData{"cos", "cos"}, + GlslImportData{"cosh", "cosh"}, + GlslImportData{"ceil", "ceil"}, + GlslImportData{"exp", "exp"}, + GlslImportData{"exp2", "exp2"}, + GlslImportData{"floor", "floor"}, + GlslImportData{"fract", "fract"}, + GlslImportData{"inverseSqrt", "inversesqrt"}, + GlslImportData{"length", "length"}, + GlslImportData{"log", "log"}, + GlslImportData{"log2", "log2"}, + GlslImportData{"round", "round"}, + GlslImportData{"sign", "sign"}, + GlslImportData{"sin", "sin"}, + GlslImportData{"sinh", "sinh"}, + GlslImportData{"sqrt", "sqrt"}, + GlslImportData{"tan", "tan"}, + GlslImportData{"tanh", "tanh"}, + GlslImportData{"trunc", "trunc"})); + +using GlslImportData_SingleIntParamTest = TestParamHelper; +TEST_P(GlslImportData_SingleIntParamTest, IntScalar) { + auto param = GetParam(); + + auto* expr = Call(param.name, Expr(1_i)); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitCall(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1)"); +} +INSTANTIATE_TEST_SUITE_P(GlslASTPrinterTest_Import, + GlslImportData_SingleIntParamTest, + testing::Values(GlslImportData{"abs", "abs"})); + +using GlslImportData_SingleVectorParamTest = TestParamHelper; +TEST_P(GlslImportData_SingleVectorParamTest, FloatVector) { + auto param = GetParam(); + + auto* expr = Call(param.name, Call>(0.1_f, 0.2_f, 0.3_f)); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitCall(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ( + out.str(), + std::string(param.glsl_name) + + "(vec3(0.10000000149011611938f, 0.20000000298023223877f, 0.30000001192092895508f))"); +} +INSTANTIATE_TEST_SUITE_P(GlslASTPrinterTest_Import, + GlslImportData_SingleVectorParamTest, + testing::Values(GlslImportData{"abs", "abs"}, + GlslImportData{"acos", "acos"}, + GlslImportData{"asin", "asin"}, + GlslImportData{"atan", "atan"}, + GlslImportData{"cos", "cos"}, + GlslImportData{"cosh", "cosh"}, + GlslImportData{"ceil", "ceil"}, + GlslImportData{"exp", "exp"}, + GlslImportData{"exp2", "exp2"}, + GlslImportData{"floor", "floor"}, + GlslImportData{"fract", "fract"}, + GlslImportData{"inverseSqrt", "inversesqrt"}, + GlslImportData{"length", "length"}, + GlslImportData{"log", "log"}, + GlslImportData{"log2", "log2"}, + GlslImportData{"normalize", "normalize"}, + GlslImportData{"round", "round"}, + GlslImportData{"sign", "sign"}, + GlslImportData{"sin", "sin"}, + GlslImportData{"sinh", "sinh"}, + GlslImportData{"sqrt", "sqrt"}, + GlslImportData{"tan", "tan"}, + GlslImportData{"tanh", "tanh"}, + GlslImportData{"trunc", "trunc"})); + +using GlslImportData_DualParam_ScalarTest = TestParamHelper; +TEST_P(GlslImportData_DualParam_ScalarTest, Float) { + auto param = GetParam(); + + auto* expr = Call(param.name, 1_f, 2_f); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitCall(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1.0f, 2.0f)"); +} +INSTANTIATE_TEST_SUITE_P(GlslASTPrinterTest_Import, + GlslImportData_DualParam_ScalarTest, + testing::Values(GlslImportData{"atan2", "atan"}, + GlslImportData{"distance", "distance"}, + GlslImportData{"max", "max"}, + GlslImportData{"min", "min"}, + GlslImportData{"pow", "pow"}, + GlslImportData{"step", "step"})); + +using GlslImportData_DualParam_VectorTest = TestParamHelper; +TEST_P(GlslImportData_DualParam_VectorTest, Float) { + auto param = GetParam(); + + auto* expr = Call(param.name, Call>(1_f, 2_f, 3_f), Call>(4_f, 5_f, 6_f)); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitCall(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), + std::string(param.glsl_name) + "(vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f))"); +} +INSTANTIATE_TEST_SUITE_P(GlslASTPrinterTest_Import, + GlslImportData_DualParam_VectorTest, + testing::Values(GlslImportData{"atan2", "atan"}, + GlslImportData{"cross", "cross"}, + GlslImportData{"distance", "distance"}, + GlslImportData{"max", "max"}, + GlslImportData{"min", "min"}, + GlslImportData{"pow", "pow"}, + GlslImportData{"reflect", "reflect"}, + GlslImportData{"step", "step"})); + +using GlslImportData_DualParam_Int_Test = TestParamHelper; +TEST_P(GlslImportData_DualParam_Int_Test, IntScalar) { + auto param = GetParam(); + + auto* expr = Call(param.name, 1_i, 2_i); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitCall(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1, 2)"); +} +INSTANTIATE_TEST_SUITE_P(GlslASTPrinterTest_Import, + GlslImportData_DualParam_Int_Test, + testing::Values(GlslImportData{"max", "max"}, + GlslImportData{"min", "min"})); + +using GlslImportData_TripleParam_ScalarTest = TestParamHelper; +TEST_P(GlslImportData_TripleParam_ScalarTest, Float) { + auto param = GetParam(); + + auto* expr = Call(param.name, 1_f, 2_f, 3_f); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitCall(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1.0f, 2.0f, 3.0f)"); +} +INSTANTIATE_TEST_SUITE_P(GlslASTPrinterTest_Import, + GlslImportData_TripleParam_ScalarTest, + testing::Values(GlslImportData{"mix", "mix"}, + GlslImportData{"clamp", "clamp"}, + GlslImportData{"smoothstep", "smoothstep"})); + +using GlslImportData_TripleParam_VectorTest = TestParamHelper; +TEST_P(GlslImportData_TripleParam_VectorTest, Float) { + auto param = GetParam(); + + auto* expr = Call(param.name, Call>(1_f, 2_f, 3_f), Call>(4_f, 5_f, 6_f), + Call>(7_f, 8_f, 9_f)); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitCall(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), + std::string(param.glsl_name) + + R"((vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f), vec3(7.0f, 8.0f, 9.0f)))"); +} +INSTANTIATE_TEST_SUITE_P(GlslASTPrinterTest_Import, + GlslImportData_TripleParam_VectorTest, + testing::Values(GlslImportData{"faceForward", "faceforward"}, + GlslImportData{"clamp", "clamp"}, + GlslImportData{"smoothstep", "smoothstep"})); + +using GlslImportData_TripleParam_Int_Test = TestParamHelper; +TEST_P(GlslImportData_TripleParam_Int_Test, IntScalar) { + auto param = GetParam(); + + auto* expr = Call(param.name, 1_i, 2_i, 3_i); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitCall(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1, 2, 3)"); +} +INSTANTIATE_TEST_SUITE_P(GlslASTPrinterTest_Import, + GlslImportData_TripleParam_Int_Test, + testing::Values(GlslImportData{"clamp", "clamp"})); + +TEST_F(GlslASTPrinterTest_Import, GlslImportData_Determinant) { + GlobalVar("var", ty.mat3x3(), core::AddressSpace::kPrivate); + + auto* expr = Call("determinant", "var"); + WrapInFunction(expr); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitCall(out, expr); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), std::string("determinant(var)")); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/loop_test.cc b/references/writer/ast_printer/loop_test.cc new file mode 100644 index 00000000..617acb53 --- /dev/null +++ b/references/writer/ast_printer/loop_test.cc @@ -0,0 +1,493 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" +#include "src/tint/lang/wgsl/ast/variable_decl_statement.h" + +#include "gmock/gmock.h" + +using namespace tint::core::number_suffixes; // NOLINT + +namespace tint::glsl::writer { +namespace { + +using GlslASTPrinterTest_Loop = TestHelper; + +TEST_F(GlslASTPrinterTest_Loop, Emit_Loop) { + auto* body = Block(Break()); + auto* continuing = Block(); + auto* l = Loop(body, continuing); + + Func("F", tint::Empty, ty.void_(), Vector{l}, Vector{Stage(ast::PipelineStage::kFragment)}); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitStatement(l); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( while (true) { + break; + } +)"); +} + +TEST_F(GlslASTPrinterTest_Loop, Emit_LoopWithContinuing) { + Func("a_statement", {}, ty.void_(), {}); + + auto* body = Block(Break()); + auto* continuing = Block(CallStmt(Call("a_statement"))); + auto* l = Loop(body, continuing); + + Func("F", tint::Empty, ty.void_(), Vector{l}, Vector{Stage(ast::PipelineStage::kFragment)}); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitStatement(l); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( while (true) { + break; + { + a_statement(); + } + } +)"); +} + +TEST_F(GlslASTPrinterTest_Loop, Emit_LoopWithContinuing_BreakIf) { + Func("a_statement", {}, ty.void_(), {}); + + auto* body = Block(Break()); + auto* continuing = Block(CallStmt(Call("a_statement")), BreakIf(true)); + auto* l = Loop(body, continuing); + + Func("F", tint::Empty, ty.void_(), Vector{l}, Vector{Stage(ast::PipelineStage::kFragment)}); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitStatement(l); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( while (true) { + break; + { + a_statement(); + if (true) { break; } + } + } +)"); +} + +TEST_F(GlslASTPrinterTest_Loop, Emit_LoopNestedWithContinuing) { + Func("a_statement", {}, ty.void_(), {}); + + GlobalVar("lhs", ty.f32(), core::AddressSpace::kPrivate); + GlobalVar("rhs", ty.f32(), core::AddressSpace::kPrivate); + + auto* body = Block(Break()); + auto* continuing = Block(CallStmt(Call("a_statement"))); + auto* inner = Loop(body, continuing); + + body = Block(inner); + + auto* lhs = Expr("lhs"); + auto* rhs = Expr("rhs"); + + continuing = Block(Assign(lhs, rhs), BreakIf(true)); + + auto* outer = Loop(body, continuing); + + Func("F", tint::Empty, ty.void_(), Vector{outer}, Vector{Stage(ast::PipelineStage::kFragment)}); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitStatement(outer); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( while (true) { + while (true) { + break; + { + a_statement(); + } + } + { + lhs = rhs; + if (true) { break; } + } + } +)"); +} + +TEST_F(GlslASTPrinterTest_Loop, Emit_LoopWithVarUsedInContinuing) { + // loop { + // var lhs : f32 = 2.5; + // var other : f32; + // break; + // continuing { + // lhs = rhs + // } + // } + + GlobalVar("rhs", ty.f32(), core::AddressSpace::kPrivate); + + auto* body = Block(Decl(Var("lhs", ty.f32(), Expr(2.5_f))), // + Decl(Var("other", ty.f32())), // + Break()); + auto* continuing = Block(Assign("lhs", "rhs")); + auto* outer = Loop(body, continuing); + WrapInFunction(outer); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitStatement(outer); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( while (true) { + float lhs = 2.5f; + float other = 0.0f; + break; + { + lhs = rhs; + } + } +)"); +} + +TEST_F(GlslASTPrinterTest_Loop, Emit_ForLoop) { + // for(; ; ) { + // return; + // } + + auto* f = For(nullptr, nullptr, nullptr, // + Block(Return())); + WrapInFunction(f); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitStatement(f); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( { + for(; ; ) { + return; + } + } +)"); +} + +TEST_F(GlslASTPrinterTest_Loop, Emit_ForLoopWithSimpleInit) { + // for(var i : i32; ; ) { + // return; + // } + + auto* f = For(Decl(Var("i", ty.i32())), nullptr, nullptr, // + Block(Return())); + WrapInFunction(f); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitStatement(f); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( { + for(int i = 0; ; ) { + return; + } + } +)"); +} + +TEST_F(GlslASTPrinterTest_Loop, Emit_ForLoopWithMultiStmtInit) { + // let t = true; + // for(var b = t && false; ; ) { + // return; + // } + + auto* t = Let("t", Expr(true)); + auto* multi_stmt = LogicalAnd(t, false); + auto* f = For(Decl(Var("b", multi_stmt)), nullptr, nullptr, Block(Return())); + WrapInFunction(t, f); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitStatement(f); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( { + bool tint_tmp = t; + if (tint_tmp) { + tint_tmp = false; + } + bool b = (tint_tmp); + for(; ; ) { + return; + } + } +)"); +} + +TEST_F(GlslASTPrinterTest_Loop, Emit_ForLoopWithSimpleCond) { + // for(; true; ) { + // return; + // } + + Func("a_statement", {}, ty.void_(), {}); + + auto* f = For(nullptr, true, nullptr, Block(CallStmt(Call("a_statement")))); + WrapInFunction(f); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitStatement(f); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( { + for(; true; ) { + a_statement(); + } + } +)"); +} + +TEST_F(GlslASTPrinterTest_Loop, Emit_ForLoopWithMultiStmtCond) { + // let t = true; + // for(; t && false; ) { + // return; + // } + + Func("a_statement", {}, ty.void_(), {}); + auto* t = Let("t", Expr(true)); + auto* multi_stmt = LogicalAnd(t, false); + auto* f = For(nullptr, multi_stmt, nullptr, Block(CallStmt(Call("a_statement")))); + WrapInFunction(t, f); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitStatement(f); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( { + while (true) { + bool tint_tmp = t; + if (tint_tmp) { + tint_tmp = false; + } + if (!((tint_tmp))) { break; } + a_statement(); + } + } +)"); +} + +TEST_F(GlslASTPrinterTest_Loop, Emit_ForLoopWithSimpleCont) { + // for(; ; i = i + 1i) { + // return; + // } + + auto* v = Decl(Var("i", ty.i32())); + auto* f = For(nullptr, nullptr, Assign("i", Add("i", 1_i)), // + Block(Return())); + WrapInFunction(v, f); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitStatement(f); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( { + for(; ; i = (i + 1)) { + return; + } + } +)"); +} + +TEST_F(GlslASTPrinterTest_Loop, Emit_ForLoopWithMultiStmtCont) { + // let t = true; + // for(; ; i = t && false) { + // return; + // } + + auto* t = Let("t", Expr(true)); + auto* multi_stmt = LogicalAnd(t, false); + auto* v = Decl(Var("i", ty.bool_())); + auto* f = For(nullptr, nullptr, Assign("i", multi_stmt), // + Block(Return())); + WrapInFunction(t, v, f); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitStatement(f); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( { + while (true) { + return; + bool tint_tmp = t; + if (tint_tmp) { + tint_tmp = false; + } + i = (tint_tmp); + } + } +)"); +} + +TEST_F(GlslASTPrinterTest_Loop, Emit_ForLoopWithSimpleInitCondCont) { + // for(var i : i32; true; i = ii + 1) { + // return; + // } + + auto* f = For(Decl(Var("i", ty.i32())), true, Assign("i", Add("i", 1_i)), Block(Return())); + WrapInFunction(f); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitStatement(f); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( { + for(int i = 0; true; i = (i + 1)) { + return; + } + } +)"); +} + +TEST_F(GlslASTPrinterTest_Loop, Emit_ForLoopWithMultiStmtInitCondCont) { + // let t = true; + // for(var i = t && false; t && false; i = t && false) { + // return; + // } + + auto* t = Let("t", Expr(true)); + auto* multi_stmt_a = LogicalAnd(t, false); + auto* multi_stmt_b = LogicalAnd(t, false); + auto* multi_stmt_c = LogicalAnd(t, false); + + auto* f = For(Decl(Var("i", multi_stmt_a)), multi_stmt_b, Assign("i", multi_stmt_c), // + Block(Return())); + WrapInFunction(t, f); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitStatement(f); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( { + bool tint_tmp = t; + if (tint_tmp) { + tint_tmp = false; + } + bool i = (tint_tmp); + while (true) { + bool tint_tmp_1 = t; + if (tint_tmp_1) { + tint_tmp_1 = false; + } + if (!((tint_tmp_1))) { break; } + return; + bool tint_tmp_2 = t; + if (tint_tmp_2) { + tint_tmp_2 = false; + } + i = (tint_tmp_2); + } + } +)"); +} + +TEST_F(GlslASTPrinterTest_Loop, Emit_While) { + // while(true) { + // return; + // } + + auto* f = While(Expr(true), Block(Return())); + WrapInFunction(f); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitStatement(f); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( while(true) { + return; + } +)"); +} + +TEST_F(GlslASTPrinterTest_Loop, Emit_While_WithContinue) { + // while(true) { + // continue; + // } + + auto* f = While(Expr(true), Block(Continue())); + WrapInFunction(f); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitStatement(f); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( while(true) { + continue; + } +)"); +} + +TEST_F(GlslASTPrinterTest_Loop, Emit_WhileWithMultiStmtCond) { + // let t = true; + // while(t && false) { + // return; + // } + + Func("a_statement", {}, ty.void_(), {}); + + auto* t = Let("t", Expr(true)); + auto* multi_stmt = LogicalAnd(t, false); + auto* f = While(multi_stmt, Block(CallStmt(Call("a_statement")))); + WrapInFunction(t, f); + + ASTPrinter& gen = Build(); + + gen.IncrementIndent(); + gen.EmitStatement(f); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( while (true) { + bool tint_tmp = t; + if (tint_tmp) { + tint_tmp = false; + } + if (!((tint_tmp))) { break; } + a_statement(); + } +)"); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/member_accessor_test.cc b/references/writer/ast_printer/member_accessor_test.cc new file mode 100644 index 00000000..1e4ee0c9 --- /dev/null +++ b/references/writer/ast_printer/member_accessor_test.cc @@ -0,0 +1,917 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" +#include "src/tint/lang/wgsl/ast/stage_attribute.h" + +#include "gmock/gmock.h" + +namespace tint::glsl::writer { +namespace { + +using ::testing::HasSubstr; +using namespace tint::core::fluent_types; // NOLINT +using namespace tint::core::number_suffixes; // NOLINT + +using create_type_func_ptr = ast::Type (*)(const ProgramBuilder::TypesBuilder& ty); + +inline ast::Type ty_i32(const ProgramBuilder::TypesBuilder& ty) { + return ty.i32(); +} +inline ast::Type ty_u32(const ProgramBuilder::TypesBuilder& ty) { + return ty.u32(); +} +inline ast::Type ty_f32(const ProgramBuilder::TypesBuilder& ty) { + return ty.f32(); +} +template +inline ast::Type ty_vec2(const ProgramBuilder::TypesBuilder& ty) { + return ty.vec2(); +} +template +inline ast::Type ty_vec3(const ProgramBuilder::TypesBuilder& ty) { + return ty.vec3(); +} +template +inline ast::Type ty_vec4(const ProgramBuilder::TypesBuilder& ty) { + return ty.vec4(); +} +template +inline ast::Type ty_mat2x2(const ProgramBuilder::TypesBuilder& ty) { + return ty.mat2x2(); +} +template +inline ast::Type ty_mat2x3(const ProgramBuilder::TypesBuilder& ty) { + return ty.mat2x3(); +} +template +inline ast::Type ty_mat2x4(const ProgramBuilder::TypesBuilder& ty) { + return ty.mat2x4(); +} +template +inline ast::Type ty_mat3x2(const ProgramBuilder::TypesBuilder& ty) { + return ty.mat3x2(); +} +template +inline ast::Type ty_mat3x3(const ProgramBuilder::TypesBuilder& ty) { + return ty.mat3x3(); +} +template +inline ast::Type ty_mat3x4(const ProgramBuilder::TypesBuilder& ty) { + return ty.mat3x4(); +} +template +inline ast::Type ty_mat4x2(const ProgramBuilder::TypesBuilder& ty) { + return ty.mat4x2(); +} +template +inline ast::Type ty_mat4x3(const ProgramBuilder::TypesBuilder& ty) { + return ty.mat4x3(); +} +template +inline ast::Type ty_mat4x4(const ProgramBuilder::TypesBuilder& ty) { + return ty.mat4x4(); +} + +template +class GlslASTPrinterTest_MemberAccessorBase : public BASE { + public: + void SetupStorageBuffer(VectorRef members) { + ProgramBuilder& b = *this; + + auto* s = b.Structure("Data", members); + + b.GlobalVar("data", b.ty.Of(s), core::AddressSpace::kStorage, core::Access::kReadWrite, + b.Group(1_a), b.Binding(0_a)); + } + + void SetupFunction(VectorRef statements) { + ProgramBuilder& b = *this; + b.Func("main", tint::Empty, b.ty.void_(), statements, + Vector{ + b.Stage(ast::PipelineStage::kFragment), + }); + } +}; + +using GlslASTPrinterTest_MemberAccessor = GlslASTPrinterTest_MemberAccessorBase; + +template +using GlslASTPrinterTest_MemberAccessorWithParam = + GlslASTPrinterTest_MemberAccessorBase>; + +TEST_F(GlslASTPrinterTest_MemberAccessor, EmitExpression_MemberAccessor) { + auto* s = Structure("Data", Vector{Member("mem", ty.f32())}); + GlobalVar("str", ty.Of(s), core::AddressSpace::kPrivate); + + auto* expr = MemberAccessor("str", "mem"); + WrapInFunction(Var("expr", ty.f32(), expr)); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +struct Data { + float mem; +}; + +Data str = Data(0.0f); +void test_function() { + float expr = str.mem; +} + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +void main() { + test_function(); + return; +} +)"); +} + +struct TypeCase { + create_type_func_ptr member_type; + std::string expected; +}; +inline std::ostream& operator<<(std::ostream& out, TypeCase c) { + return out << c.expected; +} + +using GlslASTPrinterTest_MemberAccessor_StorageBufferLoad = + GlslASTPrinterTest_MemberAccessorWithParam; +TEST_P(GlslASTPrinterTest_MemberAccessor_StorageBufferLoad, Test) { + // struct Data { + // a : i32; + // b : ; + // }; + // var data : Data; + // data.b; + + auto p = GetParam(); + + SetupStorageBuffer(Vector{ + Member("a", ty.i32()), + Member("b", p.member_type(ty)), + }); + + SetupFunction(Vector{ + Decl(Var("x", MemberAccessor("data", "b"))), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr(p.expected)); +} + +INSTANTIATE_TEST_SUITE_P(GlslASTPrinterTest_MemberAccessor, + GlslASTPrinterTest_MemberAccessor_StorageBufferLoad, + testing::Values(TypeCase{ty_u32, "data.inner.b"}, + TypeCase{ty_f32, "data.inner.b"}, + TypeCase{ty_i32, "data.inner.b"}, + TypeCase{ty_vec2, "data.inner.b"}, + TypeCase{ty_vec2, "data.inner.b"}, + TypeCase{ty_vec2, "data.inner.b"}, + TypeCase{ty_vec3, "data.inner.b"}, + TypeCase{ty_vec3, "data.inner.b"}, + TypeCase{ty_vec3, "data.inner.b"}, + TypeCase{ty_vec4, "data.inner.b"}, + TypeCase{ty_vec4, "data.inner.b"}, + TypeCase{ty_vec4, "data.inner.b"}, + TypeCase{ty_mat2x2, "data.inner.b"}, + TypeCase{ty_mat2x3, "data.inner.b"}, + TypeCase{ty_mat2x4, "data.inner.b"}, + TypeCase{ty_mat3x2, "data.inner.b"}, + TypeCase{ty_mat3x3, "data.inner.b"}, + TypeCase{ty_mat3x4, "data.inner.b"}, + TypeCase{ty_mat4x2, "data.inner.b"}, + TypeCase{ty_mat4x3, "data.inner.b"}, + TypeCase{ty_mat4x4, "data.inner.b"})); + +using GlslASTPrinterTest_MemberAccessor_StorageBufferStore = + GlslASTPrinterTest_MemberAccessorWithParam; +TEST_P(GlslASTPrinterTest_MemberAccessor_StorageBufferStore, Test) { + // struct Data { + // a : i32; + // b : ; + // }; + // var data : Data; + // data.b = (); + + auto p = GetParam(); + + SetupStorageBuffer(Vector{ + Member("a", ty.i32()), + Member("b", p.member_type(ty)), + }); + + SetupFunction(Vector{ + Decl(Var("value", p.member_type(ty), Call(p.member_type(ty)))), + Assign(MemberAccessor("data", "b"), Expr("value")), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr(p.expected)); +} + +INSTANTIATE_TEST_SUITE_P(GlslASTPrinterTest_MemberAccessor, + GlslASTPrinterTest_MemberAccessor_StorageBufferStore, + testing::Values(TypeCase{ty_u32, "data.inner.b = value"}, + TypeCase{ty_f32, "data.inner.b = value"}, + TypeCase{ty_i32, "data.inner.b = value"}, + TypeCase{ty_vec2, "data.inner.b = value"}, + TypeCase{ty_vec2, "data.inner.b = value"}, + TypeCase{ty_vec2, "data.inner.b = value"}, + TypeCase{ty_vec3, "data.inner.b = value"}, + TypeCase{ty_vec3, "data.inner.b = value"}, + TypeCase{ty_vec3, "data.inner.b = value"}, + TypeCase{ty_vec4, "data.inner.b = value"}, + TypeCase{ty_vec4, "data.inner.b = value"}, + TypeCase{ty_vec4, "data.inner.b = value"}, + TypeCase{ty_mat2x2, "data.inner.b = value"}, + TypeCase{ty_mat2x3, R"( + data.inner.b[0] = value[0u]; + data.inner.b[1] = value[1u];)"}, + TypeCase{ty_mat2x4, "data.inner.b = value"}, + TypeCase{ty_mat3x2, "data.inner.b = value"}, + TypeCase{ty_mat3x3, R"( + data.inner.b[0] = value[0u]; + data.inner.b[1] = value[1u]; + data.inner.b[2] = value[2u];)"}, + TypeCase{ty_mat3x4, "data.inner.b = value"}, + TypeCase{ty_mat4x2, "data.inner.b = value"}, + TypeCase{ty_mat4x3, R"( + data.inner.b[0] = value[0u]; + data.inner.b[1] = value[1u]; + data.inner.b[2] = value[2u]; + data.inner.b[3] = value[3u];)"}, + TypeCase{ty_mat4x4, "data.inner.b = value"})); + +TEST_F(GlslASTPrinterTest_MemberAccessor, StorageBuffer_Store_Matrix_Empty) { + // struct Data { + // z : f32; + // a : mat2x3; + // }; + // var data : Data; + // data.a = mat2x3(); + + SetupStorageBuffer(Vector{ + Member("a", ty.i32()), + Member("b", ty.mat2x3()), + }); + + SetupFunction(Vector{ + Assign(MemberAccessor("data", "b"), Call>()), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + + auto* expected = + R"(#version 310 es +precision highp float; +precision highp int; + +struct Data { + int a; + uint pad; + uint pad_1; + uint pad_2; + mat2x3 b; +}; + +layout(binding = 0, std430) buffer data_block_ssbo { + Data inner; +} data; + +void assign_and_preserve_padding_data_inner_b(mat2x3 value) { + data.inner.b[0] = value[0u]; + data.inner.b[1] = value[1u]; +} + +void tint_symbol() { + assign_and_preserve_padding_data_inner_b(mat2x3(vec3(0.0f), vec3(0.0f))); +} + +void main() { + tint_symbol(); + return; +} +)"; + EXPECT_EQ(gen.Result(), expected); +} + +TEST_F(GlslASTPrinterTest_MemberAccessor, StorageBuffer_Load_Matrix_Single_Element) { + // struct Data { + // z : f32; + // a : mat4x3; + // }; + // var data : Data; + // data.a[2i][1i]; + + SetupStorageBuffer(Vector{ + Member("z", ty.f32()), + Member("a", ty.mat4x3()), + }); + + SetupFunction(Vector{ + Decl(Var("x", IndexAccessor(IndexAccessor(MemberAccessor("data", "a"), 2_i), 1_i))), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + auto* expected = + R"(#version 310 es +precision highp float; +precision highp int; + +struct Data { + float z; + uint pad; + uint pad_1; + uint pad_2; + mat4x3 a; +}; + +layout(binding = 0, std430) buffer data_block_ssbo { + Data inner; +} data; + +void tint_symbol() { + float x = data.inner.a[2][1]; +} + +void main() { + tint_symbol(); + return; +} +)"; + EXPECT_EQ(gen.Result(), expected); +} + +TEST_F(GlslASTPrinterTest_MemberAccessor, + EmitExpression_IndexAccessor_StorageBuffer_Load_Int_FromArray) { + // struct Data { + // a : array; + // }; + // var data : Data; + // data.a[2]; + + SetupStorageBuffer(Vector{ + Member("z", ty.f32()), + Member("a", ty.array()), + }); + + SetupFunction(Vector{ + Decl(Var("x", IndexAccessor(MemberAccessor("data", "a"), 2_i))), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + auto* expected = + R"(#version 310 es +precision highp float; +precision highp int; + +struct Data { + float z; + int a[5]; +}; + +layout(binding = 0, std430) buffer data_block_ssbo { + Data inner; +} data; + +void tint_symbol() { + int x = data.inner.a[2]; +} + +void main() { + tint_symbol(); + return; +} +)"; + EXPECT_EQ(gen.Result(), expected); +} + +TEST_F(GlslASTPrinterTest_MemberAccessor, + EmitExpression_IndexAccessor_StorageBuffer_Load_Int_FromArray_ExprIdx) { + // struct Data { + // a : array; + // }; + // var data : Data; + // data.a[(2i + 4i) - 3i]; + + SetupStorageBuffer(Vector{ + Member("z", ty.f32()), + Member("a", ty.array()), + }); + + SetupFunction(Vector{ + Decl(Var("a", Expr(2_i))), + Decl(Var("b", Expr(4_i))), + Decl(Var("c", Expr(3_i))), + Decl(Var("x", IndexAccessor(MemberAccessor("data", "a"), Sub(Add("a", "b"), "c")))), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + auto* expected = + R"(#version 310 es +precision highp float; +precision highp int; + +struct Data { + float z; + int a[5]; +}; + +layout(binding = 0, std430) buffer data_block_ssbo { + Data inner; +} data; + +void tint_symbol() { + int a = 2; + int b = 4; + int c = 3; + int x = data.inner.a[((a + b) - c)]; +} + +void main() { + tint_symbol(); + return; +} +)"; + EXPECT_EQ(gen.Result(), expected); +} + +TEST_F(GlslASTPrinterTest_MemberAccessor, StorageBuffer_Store_ToArray) { + // struct Data { + // a : array; + // }; + // var data : Data; + // data.a[2i] = 2i; + + SetupStorageBuffer(Vector{ + Member("z", ty.f32()), + Member("a", ty.array()), + }); + + SetupFunction(Vector{ + Assign(IndexAccessor(MemberAccessor("data", "a"), 2_i), 2_i), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + auto* expected = + R"(#version 310 es +precision highp float; +precision highp int; + +struct Data { + float z; + int a[5]; +}; + +layout(binding = 0, std430) buffer data_block_ssbo { + Data inner; +} data; + +void tint_symbol() { + data.inner.a[2] = 2; +} + +void main() { + tint_symbol(); + return; +} +)"; + EXPECT_EQ(gen.Result(), expected); +} + +TEST_F(GlslASTPrinterTest_MemberAccessor, StorageBuffer_Load_MultiLevel) { + // struct Inner { + // a : vec3; + // b : vec3; + // }; + // struct Data { + // var c : array; + // }; + // + // var data : Pre; + // data.c[2i].b + + auto* inner = Structure("Inner", Vector{ + Member("a", ty.vec3()), + Member("b", ty.vec3()), + }); + + SetupStorageBuffer(Vector{ + Member("c", ty.array(ty.Of(inner), 4_u)), + }); + + SetupFunction(Vector{ + Decl(Var("x", MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"))), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + auto* expected = + R"(#version 310 es +precision highp float; +precision highp int; + +struct Inner { + vec3 a; + uint pad; + vec3 b; + uint pad_1; +}; + +struct Data { + Inner c[4]; +}; + +layout(binding = 0, std430) buffer data_block_ssbo { + Data inner; +} data; + +void tint_symbol() { + vec3 x = data.inner.c[2].b; +} + +void main() { + tint_symbol(); + return; +} +)"; + EXPECT_EQ(gen.Result(), expected); +} + +TEST_F(GlslASTPrinterTest_MemberAccessor, StorageBuffer_Load_MultiLevel_Swizzle) { + // struct Inner { + // a : vec3; + // b : vec3; + // }; + // struct Data { + // var c : array; + // }; + // + // var data : Pre; + // data.c[2i].b.xy + + auto* inner = Structure("Inner", Vector{ + Member("a", ty.vec3()), + Member("b", ty.vec3()), + }); + + SetupStorageBuffer(Vector{ + Member("c", ty.array(ty.Of(inner), 4_u)), + }); + + SetupFunction(Vector{ + Decl(Var("x", + MemberAccessor( + MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"), "xy"))), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + auto* expected = + R"(#version 310 es +precision highp float; +precision highp int; + +struct Inner { + vec3 a; + uint pad; + vec3 b; + uint pad_1; +}; + +struct Data { + Inner c[4]; +}; + +layout(binding = 0, std430) buffer data_block_ssbo { + Data inner; +} data; + +void tint_symbol() { + vec2 x = data.inner.c[2].b.xy; +} + +void main() { + tint_symbol(); + return; +} +)"; + EXPECT_EQ(gen.Result(), expected); +} + +TEST_F(GlslASTPrinterTest_MemberAccessor, + StorageBuffer_Load_MultiLevel_Swizzle_SingleLetter) { // NOLINT + // struct Inner { + // a : vec3; + // b : vec3; + // }; + // struct Data { + // var c : array; + // }; + // + // var data : Pre; + // data.c[2i].b.g + + auto* inner = Structure("Inner", Vector{ + Member("a", ty.vec3()), + Member("b", ty.vec3()), + }); + + SetupStorageBuffer(Vector{ + Member("c", ty.array(ty.Of(inner), 4_u)), + }); + + SetupFunction(Vector{ + Decl(Var("x", + MemberAccessor( + MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"), "g"))), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + auto* expected = + R"(#version 310 es +precision highp float; +precision highp int; + +struct Inner { + vec3 a; + uint pad; + vec3 b; + uint pad_1; +}; + +struct Data { + Inner c[4]; +}; + +layout(binding = 0, std430) buffer data_block_ssbo { + Data inner; +} data; + +void tint_symbol() { + float x = data.inner.c[2].b.g; +} + +void main() { + tint_symbol(); + return; +} +)"; + EXPECT_EQ(gen.Result(), expected); +} + +TEST_F(GlslASTPrinterTest_MemberAccessor, StorageBuffer_Load_MultiLevel_Index) { + // struct Inner { + // a : vec3; + // b : vec3; + // }; + // struct Data { + // var c : array; + // }; + // + // var data : Pre; + // data.c[2i].b[1i] + + auto* inner = Structure("Inner", Vector{ + Member("a", ty.vec3()), + Member("b", ty.vec3()), + }); + + SetupStorageBuffer(Vector{ + Member("c", ty.array(ty.Of(inner), 4_u)), + }); + + SetupFunction(Vector{ + Decl(Var("x", + IndexAccessor(MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"), + 1_i))), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + auto* expected = + R"(#version 310 es +precision highp float; +precision highp int; + +struct Inner { + vec3 a; + uint pad; + vec3 b; + uint pad_1; +}; + +struct Data { + Inner c[4]; +}; + +layout(binding = 0, std430) buffer data_block_ssbo { + Data inner; +} data; + +void tint_symbol() { + float x = data.inner.c[2].b[1]; +} + +void main() { + tint_symbol(); + return; +} +)"; + EXPECT_EQ(gen.Result(), expected); +} + +TEST_F(GlslASTPrinterTest_MemberAccessor, StorageBuffer_Store_MultiLevel) { + // struct Inner { + // a : vec3; + // b : vec3; + // }; + // struct Data { + // var c : array; + // }; + // + // var data : Pre; + // data.c[2i].b = vec3(1.f, 2.f, 3.f); + + auto* inner = Structure("Inner", Vector{ + Member("a", ty.vec3()), + Member("b", ty.vec3()), + }); + + SetupStorageBuffer(Vector{ + Member("c", ty.array(ty.Of(inner), 4_u)), + }); + + SetupFunction(Vector{ + Assign(MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"), + Call>(1_f, 2_f, 3_f)), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + auto* expected = + R"(#version 310 es +precision highp float; +precision highp int; + +struct Inner { + vec3 a; + uint pad; + vec3 b; + uint pad_1; +}; + +struct Data { + Inner c[4]; +}; + +layout(binding = 0, std430) buffer data_block_ssbo { + Data inner; +} data; + +void tint_symbol() { + data.inner.c[2].b = vec3(1.0f, 2.0f, 3.0f); +} + +void main() { + tint_symbol(); + return; +} +)"; + EXPECT_EQ(gen.Result(), expected); +} + +TEST_F(GlslASTPrinterTest_MemberAccessor, StorageBuffer_Store_Swizzle_SingleLetter) { + // struct Inner { + // a : vec3; + // b : vec3; + // }; + // struct Data { + // var c : array; + // }; + // + // var data : Pre; + // data.c[2i].b.y = 1.f; + + auto* inner = Structure("Inner", Vector{ + Member("a", ty.vec3()), + Member("b", ty.vec3()), + }); + + SetupStorageBuffer(Vector{ + Member("c", ty.array(ty.Of(inner), 4_u)), + }); + + SetupFunction(Vector{ + Assign(MemberAccessor(MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"), + "y"), + Expr(1_f)), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + auto* expected = + R"(#version 310 es +precision highp float; +precision highp int; + +struct Inner { + ivec3 a; + uint pad; + vec3 b; + uint pad_1; +}; + +struct Data { + Inner c[4]; +}; + +layout(binding = 0, std430) buffer data_block_ssbo { + Data inner; +} data; + +void tint_symbol() { + data.inner.c[2].b.y = 1.0f; +} + +void main() { + tint_symbol(); + return; +} +)"; + EXPECT_EQ(gen.Result(), expected); +} + +TEST_F(GlslASTPrinterTest_MemberAccessor, Swizzle_xyz) { + auto* var = Var("my_vec", ty.vec4(), Call>(1_f, 2_f, 3_f, 4_f)); + auto* expr = MemberAccessor("my_vec", "xyz"); + WrapInFunction(var, expr); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("my_vec.xyz")); +} + +TEST_F(GlslASTPrinterTest_MemberAccessor, Swizzle_gbr) { + auto* var = Var("my_vec", ty.vec4(), Call>(1_f, 2_f, 3_f, 4_f)); + auto* expr = MemberAccessor("my_vec", "gbr"); + WrapInFunction(var, expr); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("my_vec.gbr")); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/module_constant_test.cc b/references/writer/ast_printer/module_constant_test.cc new file mode 100644 index 00000000..9dfb9c3b --- /dev/null +++ b/references/writer/ast_printer/module_constant_test.cc @@ -0,0 +1,349 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" +#include "src/tint/lang/wgsl/ast/id_attribute.h" + +#include "gmock/gmock.h" + +namespace tint::glsl::writer { +namespace { + +using namespace tint::core::fluent_types; // NOLINT +using namespace tint::core::number_suffixes; // NOLINT + +using GlslASTPrinterTest_ModuleConstant = TestHelper; + +TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalLet) { + auto* var = Let("pos", ty.array(), Call>(1_f, 2_f, 3_f)); + WrapInFunction(Decl(var)); + + ASTPrinter& gen = Build(); + gen.EmitProgramConstVariable(var); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), "const float pos[3] = float[3](1.0f, 2.0f, 3.0f);\n"); +} + +TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_AInt) { + auto* var = GlobalConst("G", Expr(1_a)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(Let("l", Expr(var))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void f() { + int l = 1; +} + +)"); +} + +TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_AFloat) { + auto* var = GlobalConst("G", Expr(1._a)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(Let("l", Expr(var))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void f() { + float l = 1.0f; +} + +)"); +} + +TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_i32) { + auto* var = GlobalConst("G", Expr(1_i)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(Let("l", Expr(var))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void f() { + int l = 1; +} + +)"); +} + +TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_u32) { + auto* var = GlobalConst("G", Expr(1_u)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(Let("l", Expr(var))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void f() { + uint l = 1u; +} + +)"); +} + +TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_f32) { + auto* var = GlobalConst("G", Expr(1_f)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(Let("l", Expr(var))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void f() { + float l = 1.0f; +} + +)"); +} + +TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_f16) { + Enable(wgsl::Extension::kF16); + + auto* var = GlobalConst("G", Expr(1_h)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(Let("l", Expr(var))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +#extension GL_AMD_gpu_shader_half_float : require + +void f() { + float16_t l = 1.0hf; +} + +)"); +} + +TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_vec3_AInt) { + auto* var = GlobalConst("G", Call>(1_a, 2_a, 3_a)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(Let("l", Expr(var))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void f() { + ivec3 l = ivec3(1, 2, 3); +} + +)"); +} + +TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_vec3_AFloat) { + auto* var = GlobalConst("G", Call>(1._a, 2._a, 3._a)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(Let("l", Expr(var))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void f() { + vec3 l = vec3(1.0f, 2.0f, 3.0f); +} + +)"); +} + +TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_vec3_f32) { + auto* var = GlobalConst("G", Call>(1_f, 2_f, 3_f)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(Let("l", Expr(var))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void f() { + vec3 l = vec3(1.0f, 2.0f, 3.0f); +} + +)"); +} + +TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_vec3_f16) { + Enable(wgsl::Extension::kF16); + + auto* var = GlobalConst("G", Call>(1_h, 2_h, 3_h)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(Let("l", Expr(var))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +#extension GL_AMD_gpu_shader_half_float : require + +void f() { + f16vec3 l = f16vec3(1.0hf, 2.0hf, 3.0hf); +} + +)"); +} + +TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_mat2x3_AFloat) { + auto* var = GlobalConst("G", Call>(1._a, 2._a, 3._a, 4._a, 5._a, 6._a)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(Let("l", Expr(var))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void f() { + mat2x3 l = mat2x3(vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f)); +} + +)"); +} + +TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_mat2x3_f32) { + auto* var = GlobalConst("G", Call>(1_f, 2_f, 3_f, 4_f, 5_f, 6_f)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(Let("l", Expr(var))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void f() { + mat2x3 l = mat2x3(vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f)); +} + +)"); +} + +TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_mat2x3_f16) { + Enable(wgsl::Extension::kF16); + + auto* var = GlobalConst("G", Call>(1_h, 2_h, 3_h, 4_h, 5_h, 6_h)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(Let("l", Expr(var))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +#extension GL_AMD_gpu_shader_half_float : require + +void f() { + f16mat2x3 l = f16mat2x3(f16vec3(1.0hf, 2.0hf, 3.0hf), f16vec3(4.0hf, 5.0hf, 6.0hf)); +} + +)"); +} + +TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_arr_f32) { + auto* var = GlobalConst("G", Call>(1_f, 2_f, 3_f)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(Let("l", Expr(var))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void f() { + float l[3] = float[3](1.0f, 2.0f, 3.0f); +} + +)"); +} + +TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_arr_vec2_bool) { + auto* var = GlobalConst("G", Call, 3>>( // + Call>(true, false), // + Call>(false, true), // + Call>(true, true))); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(Let("l", Expr(var))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void f() { + bvec2 l[3] = bvec2[3](bvec2(true, false), bvec2(false, true), bvec2(true)); +} + +)"); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/return_test.cc b/references/writer/ast_printer/return_test.cc new file mode 100644 index 00000000..45ae9bb3 --- /dev/null +++ b/references/writer/ast_printer/return_test.cc @@ -0,0 +1,62 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" + +#include "gmock/gmock.h" + +using namespace tint::core::number_suffixes; // NOLINT + +namespace tint::glsl::writer { +namespace { + +using GlslASTPrinterTest_Return = TestHelper; + +TEST_F(GlslASTPrinterTest_Return, Emit_Return) { + auto* r = Return(); + WrapInFunction(r); + + ASTPrinter& gen = Build(); + gen.IncrementIndent(); + gen.EmitStatement(r); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), " return;\n"); +} + +TEST_F(GlslASTPrinterTest_Return, Emit_ReturnWithValue) { + auto* r = Return(123_i); + Func("f", tint::Empty, ty.i32(), Vector{r}); + + ASTPrinter& gen = Build(); + gen.IncrementIndent(); + gen.EmitStatement(r); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), " return 123;\n"); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/sanitizer_test.cc b/references/writer/ast_printer/sanitizer_test.cc new file mode 100644 index 00000000..0cd03ab6 --- /dev/null +++ b/references/writer/ast_printer/sanitizer_test.cc @@ -0,0 +1,339 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" +#include "src/tint/lang/wgsl/ast/call_statement.h" +#include "src/tint/lang/wgsl/ast/stage_attribute.h" +#include "src/tint/lang/wgsl/ast/variable_decl_statement.h" + +#include "gmock/gmock.h" + +namespace tint::glsl::writer { +namespace { + +using namespace tint::core::fluent_types; // NOLINT +using namespace tint::core::number_suffixes; // NOLINT + +using GlslSanitizerTest = TestHelper; + +TEST_F(GlslSanitizerTest, Call_ArrayLength) { + auto* s = Structure("my_struct", Vector{Member(0, "a", ty.array())}); + GlobalVar("b", ty.Of(s), core::AddressSpace::kStorage, core::Access::kRead, Binding(1_a), + Group(2_a)); + + Func("a_func", tint::Empty, ty.void_(), + Vector{ + Decl(Var("len", ty.u32(), Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))), + }, + Vector{ + Stage(ast::PipelineStage::kFragment), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + + auto got = gen.Result(); + auto* expect = R"(#version 310 es +precision highp float; +precision highp int; + +layout(binding = 1, std430) buffer my_struct_ssbo { + float a[]; +} b; + +void a_func() { + uint len = uint(b.a.length()); +} + +void main() { + a_func(); + return; +} +)"; + EXPECT_EQ(expect, got); +} + +TEST_F(GlslSanitizerTest, Call_ArrayLength_OtherMembersInStruct) { + auto* s = Structure("my_struct", Vector{ + Member(0, "z", ty.f32()), + Member(4, "a", ty.array()), + }); + GlobalVar("b", ty.Of(s), core::AddressSpace::kStorage, core::Access::kRead, Binding(1_a), + Group(2_a)); + + Func("a_func", tint::Empty, ty.void_(), + Vector{ + Decl(Var("len", ty.u32(), Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))), + }, + Vector{ + Stage(ast::PipelineStage::kFragment), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + + auto got = gen.Result(); + auto* expect = R"(#version 310 es +precision highp float; +precision highp int; + +layout(binding = 1, std430) buffer my_struct_ssbo { + float z; + float a[]; +} b; + +void a_func() { + uint len = uint(b.a.length()); +} + +void main() { + a_func(); + return; +} +)"; + + EXPECT_EQ(expect, got); +} + +TEST_F(GlslSanitizerTest, Call_ArrayLength_ViaLets) { + auto* s = Structure("my_struct", Vector{Member(0, "a", ty.array())}); + GlobalVar("b", ty.Of(s), core::AddressSpace::kStorage, core::Access::kRead, Binding(1_a), + Group(2_a)); + + auto* p = Let("p", AddressOf("b")); + auto* p2 = Let("p2", AddressOf(MemberAccessor(Deref(p), "a"))); + + Func("a_func", tint::Empty, ty.void_(), + Vector{ + Decl(p), + Decl(p2), + Decl(Var("len", ty.u32(), Call("arrayLength", p2))), + }, + Vector{ + Stage(ast::PipelineStage::kFragment), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + + auto got = gen.Result(); + auto* expect = R"(#version 310 es +precision highp float; +precision highp int; + +layout(binding = 1, std430) buffer my_struct_ssbo { + float a[]; +} b; + +void a_func() { + uint len = uint(b.a.length()); +} + +void main() { + a_func(); + return; +} +)"; + + EXPECT_EQ(expect, got); +} + +TEST_F(GlslSanitizerTest, PromoteArrayInitializerToConstVar) { + auto* array_init = Call>(1_i, 2_i, 3_i, 4_i); + + Func("main", tint::Empty, ty.void_(), + Vector{ + Decl(Var("idx", Expr(3_i))), + Decl(Var("pos", ty.i32(), IndexAccessor(array_init, "idx"))), + }, + Vector{ + Stage(ast::PipelineStage::kFragment), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + + auto got = gen.Result(); + auto* expect = R"(#version 310 es +precision highp float; +precision highp int; + +void tint_symbol() { + int idx = 3; + int tint_symbol_1[4] = int[4](1, 2, 3, 4); + int pos = tint_symbol_1[idx]; +} + +void main() { + tint_symbol(); + return; +} +)"; + EXPECT_EQ(expect, got); +} + +TEST_F(GlslSanitizerTest, PromoteStructInitializerToConstVar) { + auto* str = Structure("S", Vector{ + Member("a", ty.i32()), + Member("b", ty.vec3()), + Member("c", ty.i32()), + }); + auto* runtime_value = Var("runtime_value", Expr(3_f)); + auto* struct_init = Call(ty.Of(str), 1_i, Call>(2_f, runtime_value, 4_f), 4_i); + auto* struct_access = MemberAccessor(struct_init, "b"); + auto* pos = Var("pos", ty.vec3(), struct_access); + + Func("main", tint::Empty, ty.void_(), + Vector{ + Decl(runtime_value), + Decl(pos), + }, + Vector{ + Stage(ast::PipelineStage::kFragment), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + + auto got = gen.Result(); + auto* expect = R"(#version 310 es +precision highp float; +precision highp int; + +struct S { + int a; + vec3 b; + int c; +}; + +void tint_symbol() { + float runtime_value = 3.0f; + S tint_symbol_1 = S(1, vec3(2.0f, runtime_value, 4.0f), 4); + vec3 pos = tint_symbol_1.b; +} + +void main() { + tint_symbol(); + return; +} +)"; + EXPECT_EQ(expect, got); +} + +TEST_F(GlslSanitizerTest, SimplifyPointersBasic) { + // var v : i32; + // let p : ptr = &v; + // let x : i32 = *p; + auto* v = Var("v", ty.i32()); + auto* p = Let("p", ty.ptr(), AddressOf(v)); + auto* x = Var("x", ty.i32(), Deref(p)); + + Func("main", tint::Empty, ty.void_(), + Vector{ + Decl(v), + Decl(p), + Decl(x), + }, + Vector{ + Stage(ast::PipelineStage::kFragment), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + + auto got = gen.Result(); + auto* expect = R"(#version 310 es +precision highp float; +precision highp int; + +void tint_symbol() { + int v = 0; + int x = v; +} + +void main() { + tint_symbol(); + return; +} +)"; + EXPECT_EQ(expect, got); +} + +TEST_F(GlslSanitizerTest, SimplifyPointersComplexChain) { + // var a : array, 4u>; + // let ap : ptr, 4u>> = &a; + // let mp : ptr> = &(*ap)[3i]; + // let vp : ptr> = &(*mp)[2i]; + // let v : vec4 = *vp; + auto* a = Var("a", ty.array(ty.mat4x4(), 4_u)); + auto* ap = Let("ap", ty.ptr, 4>>(), AddressOf(a)); + auto* mp = Let("mp", ty.ptr>(), AddressOf(IndexAccessor(Deref(ap), 3_i))); + auto* vp = Let("vp", ty.ptr>(), AddressOf(IndexAccessor(Deref(mp), 2_i))); + auto* v = Var("v", ty.vec4(), Deref(vp)); + + Func("main", tint::Empty, ty.void_(), + Vector{ + Decl(a), + Decl(ap), + Decl(mp), + Decl(vp), + Decl(v), + }, + Vector{ + Stage(ast::PipelineStage::kFragment), + }); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + + auto got = gen.Result(); + auto* expect = R"(#version 310 es +precision highp float; +precision highp int; + +void tint_symbol() { + mat4 a[4] = mat4[4](mat4(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), mat4(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), mat4(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), mat4(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)); + vec4 v = a[3][2]; +} + +void main() { + tint_symbol(); + return; +} +)"; + EXPECT_EQ(expect, got); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/storage_buffer_test.cc b/references/writer/ast_printer/storage_buffer_test.cc new file mode 100644 index 00000000..b016ef11 --- /dev/null +++ b/references/writer/ast_printer/storage_buffer_test.cc @@ -0,0 +1,108 @@ +// Copyright 2022 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/core/number.h" +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" + +#include "gmock/gmock.h" + +using ::testing::HasSubstr; +using namespace tint::core::number_suffixes; // NOLINT + +namespace tint::glsl::writer { +namespace { + +using GlslASTPrinterTest_StorageBuffer = TestHelper; + +void TestAlign(ProgramBuilder* ctx) { + // struct Nephews { + // @align(256) huey : f32; + // @align(256) dewey : f32; + // @align(256) louie : f32; + // }; + // @group(0) @binding(0) var nephews : Nephews; + auto* nephews = ctx->Structure( + "Nephews", Vector{ + ctx->Member("huey", ctx->ty.f32(), Vector{ctx->MemberAlign(256_i)}), + ctx->Member("dewey", ctx->ty.f32(), Vector{ctx->MemberAlign(256_i)}), + ctx->Member("louie", ctx->ty.f32(), Vector{ctx->MemberAlign(256_i)}), + }); + ctx->GlobalVar("nephews", ctx->ty.Of(nephews), core::AddressSpace::kStorage, ctx->Binding(0_a), + ctx->Group(0_a)); +} + +TEST_F(GlslASTPrinterTest_StorageBuffer, Align) { + TestAlign(this); + + ASTPrinter& gen = Build(); + + // TODO(crbug.com/tint/1421) offsets do not currently work on GLSL ES. + // They will likely require manual padding. + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +struct Nephews { + float huey; + float dewey; + float louie; +}; + +layout(binding = 0, std430) buffer Nephews_ssbo { + float huey; + float dewey; + float louie; +} nephews; + +)"); +} + +TEST_F(GlslASTPrinterTest_StorageBuffer, Align_Desktop) { + TestAlign(this); + + ASTPrinter& gen = Build(Version(Version::Standard::kDesktop, 4, 4)); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 440 + +struct Nephews { + float huey; + float dewey; + float louie; +}; + +layout(binding = 0, std430) buffer Nephews_ssbo { + float huey; + float dewey; + float louie; +} nephews; + +)"); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/switch_test.cc b/references/writer/ast_printer/switch_test.cc new file mode 100644 index 00000000..99b0cdc1 --- /dev/null +++ b/references/writer/ast_printer/switch_test.cc @@ -0,0 +1,92 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" + +#include "gmock/gmock.h" + +using namespace tint::core::number_suffixes; // NOLINT + +namespace tint::glsl::writer { +namespace { + +using GlslASTPrinterTest_Switch = TestHelper; + +TEST_F(GlslASTPrinterTest_Switch, Emit_Switch) { + GlobalVar("cond", ty.i32(), core::AddressSpace::kPrivate); + + auto* def_body = Block(create()); + auto* def = create(Vector{DefaultCaseSelector()}, def_body); + + auto* case_body = Block(create()); + auto* case_stmt = create(Vector{CaseSelector(5_i)}, case_body); + + auto* cond = Expr("cond"); + auto* s = Switch(cond, Vector{case_stmt, def}); + WrapInFunction(s); + + ASTPrinter& gen = Build(); + gen.IncrementIndent(); + gen.EmitStatement(s); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( switch(cond) { + case 5: { + break; + } + default: { + break; + } + } +)"); +} + +TEST_F(GlslASTPrinterTest_Switch, Emit_Switch_MixedDefault) { + GlobalVar("cond", ty.i32(), core::AddressSpace::kPrivate); + + auto* def_body = Block(create()); + auto* def = + create(Vector{CaseSelector(5_i), DefaultCaseSelector()}, def_body); + + auto* cond = Expr("cond"); + auto* s = Switch(cond, Vector{def}); + WrapInFunction(s); + + ASTPrinter& gen = Build(); + gen.IncrementIndent(); + gen.EmitStatement(s); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"( switch(cond) { + case 5: + default: { + break; + } + } +)"); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/type_test.cc b/references/writer/ast_printer/type_test.cc new file mode 100644 index 00000000..8c1c09b7 --- /dev/null +++ b/references/writer/ast_printer/type_test.cc @@ -0,0 +1,594 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/core/type/depth_texture.h" +#include "src/tint/lang/core/type/multisampled_texture.h" +#include "src/tint/lang/core/type/sampled_texture.h" +#include "src/tint/lang/core/type/sampler.h" +#include "src/tint/lang/core/type/storage_texture.h" +#include "src/tint/lang/core/type/texture_dimension.h" +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" +#include "src/tint/lang/wgsl/ast/call_statement.h" +#include "src/tint/lang/wgsl/ast/stage_attribute.h" +#include "src/tint/utils/text/string_stream.h" + +#include "gmock/gmock.h" + +using ::testing::HasSubstr; + +using namespace tint::core::number_suffixes; // NOLINT + +namespace tint::glsl::writer { +namespace { + +using GlslASTPrinterTest_Type = TestHelper; + +TEST_F(GlslASTPrinterTest_Type, EmitType_Array) { + auto arr = ty.array(); + ast::Type ty = GlobalVar("G", arr, core::AddressSpace::kPrivate)->type; + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitType(out, program->TypeOf(ty), core::AddressSpace::kUndefined, core::Access::kReadWrite, + "ary"); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "bool ary[4]"); +} + +TEST_F(GlslASTPrinterTest_Type, EmitType_ArrayOfArray) { + auto arr = ty.array(ty.array(), 5_u); + ast::Type ty = GlobalVar("G", arr, core::AddressSpace::kPrivate)->type; + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitType(out, program->TypeOf(ty), core::AddressSpace::kUndefined, core::Access::kReadWrite, + "ary"); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "bool ary[5][4]"); +} + +TEST_F(GlslASTPrinterTest_Type, EmitType_ArrayOfArrayOfArray) { + auto arr = ty.array(ty.array(ty.array(), 5_u), 6_u); + ast::Type ty = GlobalVar("G", arr, core::AddressSpace::kPrivate)->type; + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitType(out, program->TypeOf(ty), core::AddressSpace::kUndefined, core::Access::kReadWrite, + "ary"); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "bool ary[6][5][4]"); +} + +TEST_F(GlslASTPrinterTest_Type, EmitType_Array_WithoutName) { + auto arr = ty.array(); + ast::Type ty = GlobalVar("G", arr, core::AddressSpace::kPrivate)->type; + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitType(out, program->TypeOf(ty), core::AddressSpace::kUndefined, core::Access::kReadWrite, + ""); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "bool[4]"); +} + +TEST_F(GlslASTPrinterTest_Type, EmitType_Bool) { + auto* bool_ = create(); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitType(out, bool_, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "bool"); +} + +TEST_F(GlslASTPrinterTest_Type, EmitType_F32) { + auto* f32 = create(); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitType(out, f32, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "float"); +} + +TEST_F(GlslASTPrinterTest_Type, EmitType_F16) { + Enable(wgsl::Extension::kF16); + + auto* f16 = create(); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitType(out, f16, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "float16_t"); +} + +TEST_F(GlslASTPrinterTest_Type, EmitType_I32) { + auto* i32 = create(); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitType(out, i32, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "int"); +} + +TEST_F(GlslASTPrinterTest_Type, EmitType_Matrix_F32) { + auto* f32 = create(); + auto* vec3 = create(f32, 3u); + auto* mat2x3 = create(vec3, 2u); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitType(out, mat2x3, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "mat2x3"); +} + +TEST_F(GlslASTPrinterTest_Type, EmitType_Matrix_F16) { + Enable(wgsl::Extension::kF16); + + auto* f16 = create(); + auto* vec3 = create(f16, 3u); + auto* mat2x3 = create(vec3, 2u); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitType(out, mat2x3, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "f16mat2x3"); +} + +TEST_F(GlslASTPrinterTest_Type, EmitType_StructDecl) { + auto* s = Structure("S", Vector{ + Member("a", ty.i32()), + Member("b", ty.f32()), + }); + GlobalVar("g", ty.Of(s), core::AddressSpace::kPrivate); + + ASTPrinter& gen = Build(); + + tint::TextGenerator::TextBuffer buf; + auto* str = program->TypeOf(s)->As(); + gen.EmitStructType(&buf, str); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(buf.String(), R"(struct S { + int a; + float b; +}; + +)"); +} + +TEST_F(GlslASTPrinterTest_Type, EmitType_Struct) { + auto* s = Structure("S", Vector{ + Member("a", ty.i32()), + Member("b", ty.f32()), + }); + GlobalVar("g", ty.Of(s), core::AddressSpace::kPrivate); + + ASTPrinter& gen = Build(); + + auto* str = program->TypeOf(s)->As(); + StringStream out; + gen.EmitType(out, str, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "S"); +} + +TEST_F(GlslASTPrinterTest_Type, EmitType_Struct_NameCollision) { + auto* s = Structure("S", Vector{ + Member("double", ty.i32()), + Member("float", ty.f32()), + }); + GlobalVar("g", ty.Of(s), core::AddressSpace::kPrivate); + + ASTPrinter& gen = SanitizeAndBuild(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr(R"(struct S { + int tint_symbol; + float tint_symbol_1; +}; +)")); +} + +TEST_F(GlslASTPrinterTest_Type, EmitType_Struct_WithOffsetAttributes) { + auto* s = Structure("S", Vector{ + Member("a", ty.i32(), Vector{MemberOffset(0_a)}), + Member("b", ty.f32(), Vector{MemberOffset(8_a)}), + }); + GlobalVar("g", ty.Of(s), core::AddressSpace::kPrivate); + + ASTPrinter& gen = Build(); + + tint::TextGenerator::TextBuffer buf; + auto* str = program->TypeOf(s)->As(); + gen.EmitStructType(&buf, str); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(buf.String(), R"(struct S { + int a; + float b; +}; + +)"); +} + +TEST_F(GlslASTPrinterTest_Type, EmitType_U32) { + auto* u32 = create(); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitType(out, u32, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "uint"); +} + +TEST_F(GlslASTPrinterTest_Type, EmitType_Vector_F32) { + auto* f32 = create(); + auto* vec3 = create(f32, 3u); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitType(out, vec3, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "vec3"); +} + +TEST_F(GlslASTPrinterTest_Type, EmitType_Vector_F16) { + Enable(wgsl::Extension::kF16); + + auto* f16 = create(); + auto* vec3 = create(f16, 3u); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitType(out, vec3, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "f16vec3"); +} + +TEST_F(GlslASTPrinterTest_Type, EmitType_Void) { + auto* void_ = create(); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitType(out, void_, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "void"); +} + +TEST_F(GlslASTPrinterTest_Type, EmitSampler) { + auto* sampler = create(core::type::SamplerKind::kSampler); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitType(out, sampler, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); +} + +TEST_F(GlslASTPrinterTest_Type, EmitSamplerComparison) { + auto* sampler = create(core::type::SamplerKind::kComparisonSampler); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitType(out, sampler, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); +} + +struct GlslDepthTextureData { + core::type::TextureDimension dim; + std::string result; +}; +inline std::ostream& operator<<(std::ostream& out, GlslDepthTextureData data) { + StringStream s; + s << data.dim; + out << s.str(); + return out; +} +using GlslDepthTexturesTest = TestParamHelper; +TEST_P(GlslDepthTexturesTest, Emit) { + auto params = GetParam(); + + auto t = ty.depth_texture(params.dim); + + GlobalVar("tex", t, Binding(1_a), Group(2_a)); + + Func("main", tint::Empty, ty.void_(), + Vector{ + Decl(Var("v", Call("textureDimensions", "tex"))), + }, + Vector{ + Stage(ast::PipelineStage::kFragment), + }); + + ASTPrinter& gen = Build(); + + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr(params.result)); +} +INSTANTIATE_TEST_SUITE_P( + GlslASTPrinterTest_Type, + GlslDepthTexturesTest, + testing::Values( + GlslDepthTextureData{core::type::TextureDimension::k2d, "sampler2DShadow tex;"}, + GlslDepthTextureData{core::type::TextureDimension::k2dArray, "sampler2DArrayShadow tex;"}, + GlslDepthTextureData{core::type::TextureDimension::kCube, "samplerCubeShadow tex;"}, + GlslDepthTextureData{core::type::TextureDimension::kCubeArray, + "samplerCubeArrayShadow tex;"})); + +using GlslDepthMultisampledTexturesTest = TestHelper; +TEST_F(GlslDepthMultisampledTexturesTest, Emit) { + auto t = ty.depth_multisampled_texture(core::type::TextureDimension::k2d); + + GlobalVar("tex", t, Binding(1_a), Group(2_a)); + + Func("main", tint::Empty, ty.void_(), + Vector{ + Decl(Var("v", Call("textureDimensions", "tex"))), + }, + Vector{ + Stage(ast::PipelineStage::kFragment), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("sampler2DMS tex;")); +} + +enum class TextureDataType { F32, U32, I32 }; +struct GlslSampledTextureData { + core::type::TextureDimension dim; + TextureDataType datatype; + std::string result; +}; +inline std::ostream& operator<<(std::ostream& out, GlslSampledTextureData data) { + StringStream str; + str << data.dim; + out << str.str(); + return out; +} +using GlslSampledTexturesTest = TestParamHelper; +TEST_P(GlslSampledTexturesTest, Emit) { + auto params = GetParam(); + + ast::Type datatype; + switch (params.datatype) { + case TextureDataType::F32: + datatype = ty.f32(); + break; + case TextureDataType::U32: + datatype = ty.u32(); + break; + case TextureDataType::I32: + datatype = ty.i32(); + break; + } + ast::Type t = ty.sampled_texture(params.dim, datatype); + + GlobalVar("tex", t, Binding(1_a), Group(2_a)); + + Func("main", tint::Empty, ty.void_(), + Vector{ + Decl(Var("v", Call("textureDimensions", "tex"))), + }, + Vector{ + Stage(ast::PipelineStage::kFragment), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr(params.result)); +} +INSTANTIATE_TEST_SUITE_P(GlslASTPrinterTest_Type, + GlslSampledTexturesTest, + testing::Values( + GlslSampledTextureData{ + core::type::TextureDimension::k1d, + TextureDataType::F32, + "sampler1D tex;", + }, + GlslSampledTextureData{ + core::type::TextureDimension::k2d, + TextureDataType::F32, + "sampler2D tex;", + }, + GlslSampledTextureData{ + core::type::TextureDimension::k2dArray, + TextureDataType::F32, + "sampler2DArray tex;", + }, + GlslSampledTextureData{ + core::type::TextureDimension::k3d, + TextureDataType::F32, + "sampler3D tex;", + }, + GlslSampledTextureData{ + core::type::TextureDimension::kCube, + TextureDataType::F32, + "samplerCube tex;", + }, + GlslSampledTextureData{ + core::type::TextureDimension::kCubeArray, + TextureDataType::F32, + "samplerCubeArray tex;", + }, + GlslSampledTextureData{ + core::type::TextureDimension::k1d, + TextureDataType::U32, + "usampler1D tex;", + }, + GlslSampledTextureData{ + core::type::TextureDimension::k2d, + TextureDataType::U32, + "usampler2D tex;", + }, + GlslSampledTextureData{ + core::type::TextureDimension::k2dArray, + TextureDataType::U32, + "usampler2DArray tex;", + }, + GlslSampledTextureData{ + core::type::TextureDimension::k3d, + TextureDataType::U32, + "usampler3D tex;", + }, + GlslSampledTextureData{ + core::type::TextureDimension::kCube, + TextureDataType::U32, + "usamplerCube tex;", + }, + GlslSampledTextureData{ + core::type::TextureDimension::kCubeArray, + TextureDataType::U32, + "usamplerCubeArray tex;", + }, + GlslSampledTextureData{ + core::type::TextureDimension::k1d, + TextureDataType::I32, + "isampler1D tex;", + }, + GlslSampledTextureData{ + core::type::TextureDimension::k2d, + TextureDataType::I32, + "isampler2D tex;", + }, + GlslSampledTextureData{ + core::type::TextureDimension::k2dArray, + TextureDataType::I32, + "isampler2DArray tex;", + }, + GlslSampledTextureData{ + core::type::TextureDimension::k3d, + TextureDataType::I32, + "isampler3D tex;", + }, + GlslSampledTextureData{ + core::type::TextureDimension::kCube, + TextureDataType::I32, + "isamplerCube tex;", + }, + GlslSampledTextureData{ + core::type::TextureDimension::kCubeArray, + TextureDataType::I32, + "isamplerCubeArray tex;", + })); + +TEST_F(GlslASTPrinterTest_Type, EmitMultisampledTexture) { + auto* f32 = create(); + auto* s = create(core::type::TextureDimension::k2d, f32); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitType(out, s, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "highp sampler2DMS"); +} + +struct GlslStorageTextureData { + core::type::TextureDimension dim; + core::TexelFormat imgfmt; + std::string result; +}; +inline std::ostream& operator<<(std::ostream& out, GlslStorageTextureData data) { + StringStream str; + str << data.dim; + return out << str.str(); +} +using GlslStorageTexturesTest = TestParamHelper; +TEST_P(GlslStorageTexturesTest, Emit) { + auto params = GetParam(); + + auto t = ty.storage_texture(params.dim, params.imgfmt, core::Access::kWrite); + + GlobalVar("tex", t, Binding(1_a), Group(2_a)); + + Func("main", tint::Empty, ty.void_(), + Vector{ + Decl(Var("v", Call("textureDimensions", "tex"))), + }, + Vector{ + Stage(ast::PipelineStage::kFragment), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr(params.result)); +} +INSTANTIATE_TEST_SUITE_P( + GlslASTPrinterTest_Type, + GlslStorageTexturesTest, + testing::Values(GlslStorageTextureData{core::type::TextureDimension::k1d, + core::TexelFormat::kRgba8Unorm, "image1D tex;"}, + GlslStorageTextureData{core::type::TextureDimension::k2d, + core::TexelFormat::kRgba16Float, "image2D tex;"}, + GlslStorageTextureData{core::type::TextureDimension::k2dArray, + core::TexelFormat::kR32Float, "image2DArray tex;"}, + GlslStorageTextureData{core::type::TextureDimension::k3d, + core::TexelFormat::kRg32Float, "image3D tex;"}, + GlslStorageTextureData{core::type::TextureDimension::k1d, + core::TexelFormat::kRgba32Float, "image1D tex;"}, + GlslStorageTextureData{core::type::TextureDimension::k2d, + core::TexelFormat::kRgba16Uint, "image2D tex;"}, + GlslStorageTextureData{core::type::TextureDimension::k2dArray, + core::TexelFormat::kR32Uint, "image2DArray tex;"}, + GlslStorageTextureData{core::type::TextureDimension::k3d, + core::TexelFormat::kRg32Uint, "image3D tex;"}, + GlslStorageTextureData{core::type::TextureDimension::k1d, + core::TexelFormat::kRgba32Uint, "image1D tex;"}, + GlslStorageTextureData{core::type::TextureDimension::k2d, + core::TexelFormat::kRgba16Sint, "image2D tex;"}, + GlslStorageTextureData{core::type::TextureDimension::k2dArray, + core::TexelFormat::kR32Sint, "image2DArray tex;"}, + GlslStorageTextureData{core::type::TextureDimension::k3d, + core::TexelFormat::kRg32Sint, "image3D tex;"}, + GlslStorageTextureData{core::type::TextureDimension::k1d, + core::TexelFormat::kRgba32Sint, "image1D tex;"})); + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/unary_op_test.cc b/references/writer/ast_printer/unary_op_test.cc new file mode 100644 index 00000000..c2df9cb9 --- /dev/null +++ b/references/writer/ast_printer/unary_op_test.cc @@ -0,0 +1,120 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/core/fluent_types.h" +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" +#include "src/tint/utils/text/string_stream.h" + +#include "gmock/gmock.h" + +using namespace tint::core::fluent_types; // NOLINT + +namespace tint::glsl::writer { +namespace { + +using GlslUnaryOpTest = TestHelper; + +TEST_F(GlslUnaryOpTest, AddressOf) { + GlobalVar("expr", ty.f32(), core::AddressSpace::kPrivate); + auto* op = create(core::UnaryOp::kAddressOf, Expr("expr")); + WrapInFunction(op); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, op); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "expr"); +} + +TEST_F(GlslUnaryOpTest, Complement) { + GlobalVar("expr", ty.u32(), core::AddressSpace::kPrivate); + auto* op = create(core::UnaryOp::kComplement, Expr("expr")); + WrapInFunction(op); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, op); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "~(expr)"); +} + +TEST_F(GlslUnaryOpTest, Indirection) { + GlobalVar("G", ty.f32(), core::AddressSpace::kPrivate); + auto* p = Let("expr", create(core::UnaryOp::kAddressOf, Expr("G"))); + auto* op = create(core::UnaryOp::kIndirection, Expr("expr")); + WrapInFunction(p, op); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, op); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "expr"); +} + +TEST_F(GlslUnaryOpTest, Not) { + GlobalVar("expr", ty.bool_(), core::AddressSpace::kPrivate); + auto* op = create(core::UnaryOp::kNot, Expr("expr")); + WrapInFunction(op); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, op); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "!(expr)"); +} + +TEST_F(GlslUnaryOpTest, Negation) { + GlobalVar("expr", ty.i32(), core::AddressSpace::kPrivate); + auto* op = create(core::UnaryOp::kNegation, Expr("expr")); + WrapInFunction(op); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, op); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "-(expr)"); +} + +TEST_F(GlslUnaryOpTest, IntMin) { + auto* op = Expr(i32(std::numeric_limits::min())); + WrapInFunction(op); + + ASTPrinter& gen = Build(); + + StringStream out; + gen.EmitExpression(out, op); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(out.str(), "(-2147483647 - 1)"); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/uniform_buffer_test.cc b/references/writer/ast_printer/uniform_buffer_test.cc new file mode 100644 index 00000000..bcc90091 --- /dev/null +++ b/references/writer/ast_printer/uniform_buffer_test.cc @@ -0,0 +1,82 @@ +// Copyright 2022 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" + +#include "gmock/gmock.h" + +using ::testing::HasSubstr; + +namespace tint::glsl::writer { +namespace { + +using namespace tint::core::number_suffixes; // NOLINT + +using GlslASTPrinterTest_UniformBuffer = TestHelper; + +TEST_F(GlslASTPrinterTest_UniformBuffer, Simple) { + auto* simple = Structure("Simple", Vector{Member("member", ty.f32())}); + GlobalVar("simple", ty.Of(simple), core::AddressSpace::kUniform, Group(0_a), Binding(0_a)); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +struct Simple { + float member; +}; + +layout(binding = 0, std140) uniform Simple_ubo { + float member; +} simple; + +)"); +} + +TEST_F(GlslASTPrinterTest_UniformBuffer, Simple_Desktop) { + auto* simple = Structure("Simple", Vector{Member("member", ty.f32())}); + GlobalVar("simple", ty.Of(simple), core::AddressSpace::kUniform, Group(0_a), Binding(0_a)); + + ASTPrinter& gen = Build(Version(Version::Standard::kDesktop, 4, 4)); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 440 + +struct Simple { + float member; +}; + +layout(binding = 0, std140) uniform Simple_ubo { + float member; +} simple; + +)"); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/variable_decl_statement_test.cc b/references/writer/ast_printer/variable_decl_statement_test.cc new file mode 100644 index 00000000..3c3c8d83 --- /dev/null +++ b/references/writer/ast_printer/variable_decl_statement_test.cc @@ -0,0 +1,540 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/wgsl/ast/variable_decl_statement.h" +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" + +#include "gmock/gmock.h" + +namespace tint::glsl::writer { +namespace { + +using ::testing::HasSubstr; +using namespace tint::core::fluent_types; // NOLINT +using namespace tint::core::number_suffixes; // NOLINT + +using GlslASTPrinterTest_VariableDecl = TestHelper; + +TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement) { + auto* var = Var("a", ty.f32()); + auto* stmt = Decl(var); + WrapInFunction(stmt); + + ASTPrinter& gen = Build(); + gen.IncrementIndent(); + gen.EmitStatement(stmt); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), " float a = 0.0f;\n"); +} + +TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Let) { + auto* var = Let("a", ty.f32(), Call()); + auto* stmt = Decl(var); + WrapInFunction(stmt); + + ASTPrinter& gen = Build(); + gen.IncrementIndent(); + gen.EmitStatement(stmt); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), " float a = 0.0f;\n"); +} + +TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const) { + auto* var = Const("a", ty.f32(), Call()); + auto* stmt = Decl(var); + WrapInFunction(stmt); + + ASTPrinter& gen = Build(); + gen.IncrementIndent(); + gen.EmitStatement(stmt); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), ""); // Not a mistake - 'const' is inlined +} + +TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_AInt) { + auto* C = Const("C", Expr(1_a)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(C), + Decl(Let("l", Expr(C))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void f() { + int l = 1; +} + +)"); +} + +TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_AFloat) { + auto* C = Const("C", Expr(1._a)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(C), + Decl(Let("l", Expr(C))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void f() { + float l = 1.0f; +} + +)"); +} + +TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_i32) { + auto* C = Const("C", Expr(1_i)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(C), + Decl(Let("l", Expr(C))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void f() { + int l = 1; +} + +)"); +} + +TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_u32) { + auto* C = Const("C", Expr(1_u)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(C), + Decl(Let("l", Expr(C))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void f() { + uint l = 1u; +} + +)"); +} + +TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_f32) { + auto* C = Const("C", Expr(1_f)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(C), + Decl(Let("l", Expr(C))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void f() { + float l = 1.0f; +} + +)"); +} + +TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_f16) { + Enable(wgsl::Extension::kF16); + + auto* C = Const("C", Expr(1_h)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(C), + Decl(Let("l", Expr(C))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +#extension GL_AMD_gpu_shader_half_float : require + +void f() { + float16_t l = 1.0hf; +} + +)"); +} + +TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_vec3_AInt) { + auto* C = Const("C", Call>(1_a, 2_a, 3_a)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(C), + Decl(Let("l", Expr(C))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void f() { + ivec3 l = ivec3(1, 2, 3); +} + +)"); +} + +TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_vec3_AFloat) { + auto* C = Const("C", Call>(1._a, 2._a, 3._a)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(C), + Decl(Let("l", Expr(C))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void f() { + vec3 l = vec3(1.0f, 2.0f, 3.0f); +} + +)"); +} + +TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_vec3_f32) { + auto* C = Const("C", Call>(1_f, 2_f, 3_f)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(C), + Decl(Let("l", Expr(C))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void f() { + vec3 l = vec3(1.0f, 2.0f, 3.0f); +} + +)"); +} + +TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_vec3_f16) { + Enable(wgsl::Extension::kF16); + + auto* C = Const("C", Call>(1_h, 2_h, 3_h)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(C), + Decl(Let("l", Expr(C))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +#extension GL_AMD_gpu_shader_half_float : require + +void f() { + f16vec3 l = f16vec3(1.0hf, 2.0hf, 3.0hf); +} + +)"); +} + +TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_mat2x3_AFloat) { + auto* C = Const("C", Call>(1._a, 2._a, 3._a, 4._a, 5._a, 6._a)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(C), + Decl(Let("l", Expr(C))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void f() { + mat2x3 l = mat2x3(vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f)); +} + +)"); +} + +TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_mat2x3_f32) { + auto* C = Const("C", Call>(1_f, 2_f, 3_f, 4_f, 5_f, 6_f)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(C), + Decl(Let("l", Expr(C))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void f() { + mat2x3 l = mat2x3(vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f)); +} + +)"); +} + +TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_mat2x3_f16) { + Enable(wgsl::Extension::kF16); + + auto* C = Const("C", Call>(1_h, 2_h, 3_h, 4_h, 5_h, 6_h)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(C), + Decl(Let("l", Expr(C))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es +#extension GL_AMD_gpu_shader_half_float : require + +void f() { + f16mat2x3 l = f16mat2x3(f16vec3(1.0hf, 2.0hf, 3.0hf), f16vec3(4.0hf, 5.0hf, 6.0hf)); +} + +)"); +} + +TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_arr_f32) { + auto* C = Const("C", Call>(1_f, 2_f, 3_f)); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(C), + Decl(Let("l", Expr(C))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void f() { + float l[3] = float[3](1.0f, 2.0f, 3.0f); +} + +)"); +} + +TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_arr_f32_zero) { + auto* C = Const("C", Call>()); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(C), + Decl(Let("l", Expr(C))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void f() { + float l[2] = float[2](0.0f, 0.0f); +} + +)"); +} + +TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_arr_arr_f32_zero) { + auto* C = Const("C", Call, 3>>()); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(C), + Decl(Let("l", Expr(C))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void f() { + float l[3][2] = float[3][2](float[2](0.0f, 0.0f), float[2](0.0f, 0.0f), float[2](0.0f, 0.0f)); +} + +)"); +} + +TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_arr_struct_zero) { + Structure("S", Vector{Member("a", ty.i32()), Member("b", ty.f32())}); + auto* C = Const("C", Call(ty.array(ty("S"), 2_i))); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(C), + Decl(Let("l", Expr(C))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +struct S { + int a; + float b; +}; + +void f() { + S l[2] = S[2](S(0, 0.0f), S(0, 0.0f)); +} + +)"); +} + +TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_arr_vec2_bool) { + auto* C = Const("C", Call, 3>>( // + Call>(true, false), // + Call>(false, true), // + Call>(true, true))); + Func("f", tint::Empty, ty.void_(), + Vector{ + Decl(C), + Decl(Let("l", Expr(C))), + }); + + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(#version 310 es + +void f() { + bvec2 l[3] = bvec2[3](bvec2(true, false), bvec2(false, true), bvec2(true)); +} + +)"); +} + +TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Array) { + auto* var = Var("a", ty.array()); + + WrapInFunction(var, Expr("a")); + + ASTPrinter& gen = Build(); + gen.IncrementIndent(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), + HasSubstr(" float a[5] = float[5](0.0f, 0.0f, 0.0f, 0.0f, 0.0f);\n")); +} + +TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Private) { + GlobalVar("a", ty.f32(), core::AddressSpace::kPrivate); + + WrapInFunction(Expr("a")); + + ASTPrinter& gen = Build(); + gen.IncrementIndent(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr(" float a = 0.0f;\n")); +} + +TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Initializer_ZeroVec_f32) { + auto* var = Var("a", ty.vec3(), Call>()); + + auto* stmt = Decl(var); + WrapInFunction(stmt); + + ASTPrinter& gen = Build(); + gen.EmitStatement(stmt); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(vec3 a = vec3(0.0f); +)"); +} + +TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Initializer_ZeroVec_f16) { + Enable(wgsl::Extension::kF16); + + auto* var = Var("a", ty.vec3(), Call>()); + + auto* stmt = Decl(var); + WrapInFunction(stmt); + + ASTPrinter& gen = Build(); + gen.EmitStatement(stmt); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), R"(f16vec3 a = f16vec3(0.0hf); +)"); +} + +TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Initializer_ZeroMat_f32) { + auto* var = Var("a", ty.mat2x3(), Call>()); + + auto* stmt = Decl(var); + WrapInFunction(stmt); + + ASTPrinter& gen = Build(); + gen.EmitStatement(stmt); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), + R"(mat2x3 a = mat2x3(vec3(0.0f), vec3(0.0f)); +)"); +} + +TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Initializer_ZeroMat_f16) { + Enable(wgsl::Extension::kF16); + + auto* var = Var("a", ty.mat2x3(), Call>()); + + auto* stmt = Decl(var); + WrapInFunction(stmt); + + ASTPrinter& gen = Build(); + gen.EmitStatement(stmt); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_EQ(gen.Result(), + R"(f16mat2x3 a = f16mat2x3(f16vec3(0.0hf), f16vec3(0.0hf)); +)"); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/workgroup_var_test.cc b/references/writer/ast_printer/workgroup_var_test.cc new file mode 100644 index 00000000..227a27e3 --- /dev/null +++ b/references/writer/ast_printer/workgroup_var_test.cc @@ -0,0 +1,74 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" +#include "src/tint/lang/wgsl/ast/id_attribute.h" +#include "src/tint/lang/wgsl/ast/stage_attribute.h" + +#include "gmock/gmock.h" + +using ::testing::HasSubstr; + +using namespace tint::core::number_suffixes; // NOLINT + +namespace tint::glsl::writer { +namespace { + +using GlslASTPrinterTest_WorkgroupVar = TestHelper; + +TEST_F(GlslASTPrinterTest_WorkgroupVar, Basic) { + GlobalVar("wg", ty.f32(), core::AddressSpace::kWorkgroup); + + Func("main", tint::Empty, ty.void_(), Vector{Assign("wg", 1.2_f)}, + Vector{ + Stage(ast::PipelineStage::kCompute), + WorkgroupSize(1_i), + }); + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("shared float wg;\n")); +} + +TEST_F(GlslASTPrinterTest_WorkgroupVar, Aliased) { + auto* alias = Alias("F32", ty.f32()); + + GlobalVar("wg", ty.Of(alias), core::AddressSpace::kWorkgroup); + + Func("main", tint::Empty, ty.void_(), Vector{Assign("wg", 1.2_f)}, + Vector{ + Stage(ast::PipelineStage::kCompute), + WorkgroupSize(1_i), + }); + ASTPrinter& gen = Build(); + gen.Generate(); + EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); + EXPECT_THAT(gen.Result(), HasSubstr("shared float wg;\n")); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_raise/BUILD.bazel b/references/writer/ast_raise/BUILD.bazel new file mode 100644 index 00000000..49e220fc --- /dev/null +++ b/references/writer/ast_raise/BUILD.bazel @@ -0,0 +1,183 @@ +# Copyright 2023 The Dawn & Tint Authors +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +################################################################################ +# File generated by 'tools/src/cmd/gen' using the template: +# tools/src/cmd/gen/build/BUILD.bazel.tmpl +# +# To regenerate run: './tools/run gen' +# +# Do not modify this file directly +################################################################################ + +load("//src/tint:flags.bzl", "COPTS") +load("@bazel_skylib//lib:selects.bzl", "selects") +cc_library( + name = "ast_raise", + srcs = [ + "combine_samplers.cc", + "pad_structs.cc", + "texture_1d_to_2d.cc", + "texture_builtins_from_uniform.cc", + ], + hdrs = [ + "combine_samplers.h", + "pad_structs.h", + "texture_1d_to_2d.h", + "texture_builtins_from_uniform.h", + ], + deps = [ + "//src/tint/api/common", + "//src/tint/api/options", + "//src/tint/lang/core", + "//src/tint/lang/core/constant", + "//src/tint/lang/core/type", + "//src/tint/lang/wgsl", + "//src/tint/lang/wgsl/ast", + "//src/tint/lang/wgsl/ast/transform", + "//src/tint/lang/wgsl/common", + "//src/tint/lang/wgsl/features", + "//src/tint/lang/wgsl/program", + "//src/tint/lang/wgsl/resolver", + "//src/tint/lang/wgsl/sem", + "//src/tint/utils/containers", + "//src/tint/utils/diagnostic", + "//src/tint/utils/ice", + "//src/tint/utils/id", + "//src/tint/utils/macros", + "//src/tint/utils/math", + "//src/tint/utils/memory", + "//src/tint/utils/reflection", + "//src/tint/utils/result", + "//src/tint/utils/rtti", + "//src/tint/utils/symbol", + "//src/tint/utils/text", + "//src/tint/utils/traits", + ] + select({ + ":tint_build_glsl_writer": [ + "//src/tint/lang/glsl/writer/common", + ], + "//conditions:default": [], + }), + copts = COPTS, + visibility = ["//visibility:public"], +) +cc_library( + name = "test", + alwayslink = True, + srcs = [ + "combine_samplers_test.cc", + "pad_structs_test.cc", + "texture_1d_to_2d_test.cc", + "texture_builtins_from_uniform_test.cc", + ], + deps = [ + "//src/tint/api/common", + "//src/tint/api/options", + "//src/tint/lang/core", + "//src/tint/lang/core/constant", + "//src/tint/lang/core/ir", + "//src/tint/lang/core/type", + "//src/tint/lang/wgsl", + "//src/tint/lang/wgsl/ast", + "//src/tint/lang/wgsl/ast/transform", + "//src/tint/lang/wgsl/common", + "//src/tint/lang/wgsl/features", + "//src/tint/lang/wgsl/program", + "//src/tint/lang/wgsl/sem", + "//src/tint/lang/wgsl/writer/ir_to_program", + "//src/tint/utils/containers", + "//src/tint/utils/diagnostic", + "//src/tint/utils/ice", + "//src/tint/utils/id", + "//src/tint/utils/macros", + "//src/tint/utils/math", + "//src/tint/utils/memory", + "//src/tint/utils/reflection", + "//src/tint/utils/result", + "//src/tint/utils/rtti", + "//src/tint/utils/symbol", + "//src/tint/utils/text", + "//src/tint/utils/traits", + "@gtest", + ] + select({ + ":tint_build_glsl_writer": [ + "//src/tint/lang/glsl/writer/ast_raise", + "//src/tint/lang/glsl/writer/common", + ], + "//conditions:default": [], + }) + select({ + ":tint_build_wgsl_reader": [ + "//src/tint/lang/wgsl/reader", + ], + "//conditions:default": [], + }) + select({ + ":tint_build_wgsl_reader_and_tint_build_wgsl_writer": [ + "//src/tint/lang/wgsl/ast/transform:test", + ], + "//conditions:default": [], + }) + select({ + ":tint_build_wgsl_writer": [ + "//src/tint/lang/wgsl/writer", + ], + "//conditions:default": [], + }), + copts = COPTS, + visibility = ["//visibility:public"], +) + +alias( + name = "tint_build_glsl_writer", + actual = "//src/tint:tint_build_glsl_writer_true", +) + +alias( + name = "tint_build_wgsl_reader", + actual = "//src/tint:tint_build_wgsl_reader_true", +) + +alias( + name = "tint_build_wgsl_writer", + actual = "//src/tint:tint_build_wgsl_writer_true", +) + +selects.config_setting_group( + name = "tint_build_glsl_writer_and_tint_build_wgsl_reader_and_tint_build_wgsl_writer", + match_all = [ + ":tint_build_glsl_writer", + ":tint_build_wgsl_reader", + ":tint_build_wgsl_writer", + ], +) +selects.config_setting_group( + name = "tint_build_wgsl_reader_and_tint_build_wgsl_writer", + match_all = [ + ":tint_build_wgsl_reader", + ":tint_build_wgsl_writer", + ], +) + diff --git a/references/writer/ast_raise/BUILD.cfg b/references/writer/ast_raise/BUILD.cfg new file mode 100644 index 00000000..375c295f --- /dev/null +++ b/references/writer/ast_raise/BUILD.cfg @@ -0,0 +1,6 @@ +{ + "condition": "tint_build_glsl_writer", + "test": { + "condition": "tint_build_wgsl_reader && tint_build_wgsl_writer", + } +} diff --git a/references/writer/ast_raise/BUILD.cmake b/references/writer/ast_raise/BUILD.cmake new file mode 100644 index 00000000..ab41d85e --- /dev/null +++ b/references/writer/ast_raise/BUILD.cmake @@ -0,0 +1,162 @@ +# Copyright 2023 The Dawn & Tint Authors +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +################################################################################ +# File generated by 'tools/src/cmd/gen' using the template: +# tools/src/cmd/gen/build/BUILD.cmake.tmpl +# +# To regenerate run: './tools/run gen' +# +# Do not modify this file directly +################################################################################ + +if(TINT_BUILD_GLSL_WRITER) +################################################################################ +# Target: tint_lang_glsl_writer_ast_raise +# Kind: lib +# Condition: TINT_BUILD_GLSL_WRITER +################################################################################ +tint_add_target(tint_lang_glsl_writer_ast_raise lib + lang/glsl/writer/ast_raise/combine_samplers.cc + lang/glsl/writer/ast_raise/combine_samplers.h + lang/glsl/writer/ast_raise/pad_structs.cc + lang/glsl/writer/ast_raise/pad_structs.h + lang/glsl/writer/ast_raise/texture_1d_to_2d.cc + lang/glsl/writer/ast_raise/texture_1d_to_2d.h + lang/glsl/writer/ast_raise/texture_builtins_from_uniform.cc + lang/glsl/writer/ast_raise/texture_builtins_from_uniform.h +) + +tint_target_add_dependencies(tint_lang_glsl_writer_ast_raise lib + tint_api_common + tint_api_options + tint_lang_core + tint_lang_core_constant + tint_lang_core_type + tint_lang_wgsl + tint_lang_wgsl_ast + tint_lang_wgsl_ast_transform + tint_lang_wgsl_common + tint_lang_wgsl_features + tint_lang_wgsl_program + tint_lang_wgsl_resolver + tint_lang_wgsl_sem + tint_utils_containers + tint_utils_diagnostic + tint_utils_ice + tint_utils_id + tint_utils_macros + tint_utils_math + tint_utils_memory + tint_utils_reflection + tint_utils_result + tint_utils_rtti + tint_utils_symbol + tint_utils_text + tint_utils_traits +) + +if(TINT_BUILD_GLSL_WRITER) + tint_target_add_dependencies(tint_lang_glsl_writer_ast_raise lib + tint_lang_glsl_writer_common + ) +endif(TINT_BUILD_GLSL_WRITER) + +endif(TINT_BUILD_GLSL_WRITER) +if(TINT_BUILD_GLSL_WRITER AND TINT_BUILD_WGSL_READER AND TINT_BUILD_WGSL_WRITER) +################################################################################ +# Target: tint_lang_glsl_writer_ast_raise_test +# Kind: test +# Condition: TINT_BUILD_GLSL_WRITER AND TINT_BUILD_WGSL_READER AND TINT_BUILD_WGSL_WRITER +################################################################################ +tint_add_target(tint_lang_glsl_writer_ast_raise_test test + lang/glsl/writer/ast_raise/combine_samplers_test.cc + lang/glsl/writer/ast_raise/pad_structs_test.cc + lang/glsl/writer/ast_raise/texture_1d_to_2d_test.cc + lang/glsl/writer/ast_raise/texture_builtins_from_uniform_test.cc +) + +tint_target_add_dependencies(tint_lang_glsl_writer_ast_raise_test test + tint_api_common + tint_api_options + tint_lang_core + tint_lang_core_constant + tint_lang_core_ir + tint_lang_core_type + tint_lang_wgsl + tint_lang_wgsl_ast + tint_lang_wgsl_ast_transform + tint_lang_wgsl_common + tint_lang_wgsl_features + tint_lang_wgsl_program + tint_lang_wgsl_sem + tint_lang_wgsl_writer_ir_to_program + tint_utils_containers + tint_utils_diagnostic + tint_utils_ice + tint_utils_id + tint_utils_macros + tint_utils_math + tint_utils_memory + tint_utils_reflection + tint_utils_result + tint_utils_rtti + tint_utils_symbol + tint_utils_text + tint_utils_traits +) + +tint_target_add_external_dependencies(tint_lang_glsl_writer_ast_raise_test test + "gtest" +) + +if(TINT_BUILD_GLSL_WRITER) + tint_target_add_dependencies(tint_lang_glsl_writer_ast_raise_test test + tint_lang_glsl_writer_ast_raise + tint_lang_glsl_writer_common + ) +endif(TINT_BUILD_GLSL_WRITER) + +if(TINT_BUILD_WGSL_READER) + tint_target_add_dependencies(tint_lang_glsl_writer_ast_raise_test test + tint_lang_wgsl_reader + ) +endif(TINT_BUILD_WGSL_READER) + +if(TINT_BUILD_WGSL_READER AND TINT_BUILD_WGSL_WRITER) + tint_target_add_dependencies(tint_lang_glsl_writer_ast_raise_test test + tint_lang_wgsl_ast_transform_test + ) +endif(TINT_BUILD_WGSL_READER AND TINT_BUILD_WGSL_WRITER) + +if(TINT_BUILD_WGSL_WRITER) + tint_target_add_dependencies(tint_lang_glsl_writer_ast_raise_test test + tint_lang_wgsl_writer + ) +endif(TINT_BUILD_WGSL_WRITER) + +endif(TINT_BUILD_GLSL_WRITER AND TINT_BUILD_WGSL_READER AND TINT_BUILD_WGSL_WRITER) \ No newline at end of file diff --git a/references/writer/ast_raise/BUILD.gn b/references/writer/ast_raise/BUILD.gn new file mode 100644 index 00000000..75216fd4 --- /dev/null +++ b/references/writer/ast_raise/BUILD.gn @@ -0,0 +1,151 @@ +# Copyright 2023 The Dawn & Tint Authors +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +################################################################################ +# File generated by 'tools/src/cmd/gen' using the template: +# tools/src/cmd/gen/build/BUILD.gn.tmpl +# +# To regenerate run: './tools/run gen' +# +# Do not modify this file directly +################################################################################ + +import("../../../../../../scripts/tint_overrides_with_defaults.gni") + +import("${tint_src_dir}/tint.gni") + +if (tint_build_unittests || tint_build_benchmarks) { + import("//testing/test.gni") +} +if (tint_build_glsl_writer) { + libtint_source_set("ast_raise") { + sources = [ + "combine_samplers.cc", + "combine_samplers.h", + "pad_structs.cc", + "pad_structs.h", + "texture_1d_to_2d.cc", + "texture_1d_to_2d.h", + "texture_builtins_from_uniform.cc", + "texture_builtins_from_uniform.h", + ] + deps = [ + "${tint_src_dir}/api/common", + "${tint_src_dir}/api/options", + "${tint_src_dir}/lang/core", + "${tint_src_dir}/lang/core/constant", + "${tint_src_dir}/lang/core/type", + "${tint_src_dir}/lang/wgsl", + "${tint_src_dir}/lang/wgsl/ast", + "${tint_src_dir}/lang/wgsl/ast/transform", + "${tint_src_dir}/lang/wgsl/common", + "${tint_src_dir}/lang/wgsl/features", + "${tint_src_dir}/lang/wgsl/program", + "${tint_src_dir}/lang/wgsl/resolver", + "${tint_src_dir}/lang/wgsl/sem", + "${tint_src_dir}/utils/containers", + "${tint_src_dir}/utils/diagnostic", + "${tint_src_dir}/utils/ice", + "${tint_src_dir}/utils/id", + "${tint_src_dir}/utils/macros", + "${tint_src_dir}/utils/math", + "${tint_src_dir}/utils/memory", + "${tint_src_dir}/utils/reflection", + "${tint_src_dir}/utils/result", + "${tint_src_dir}/utils/rtti", + "${tint_src_dir}/utils/symbol", + "${tint_src_dir}/utils/text", + "${tint_src_dir}/utils/traits", + ] + + if (tint_build_glsl_writer) { + deps += [ "${tint_src_dir}/lang/glsl/writer/common" ] + } + } +} +if (tint_build_unittests) { + if (tint_build_glsl_writer && tint_build_wgsl_reader && + tint_build_wgsl_writer) { + tint_unittests_source_set("unittests") { + sources = [ + "combine_samplers_test.cc", + "pad_structs_test.cc", + "texture_1d_to_2d_test.cc", + "texture_builtins_from_uniform_test.cc", + ] + deps = [ + "${tint_src_dir}:gmock_and_gtest", + "${tint_src_dir}/api/common", + "${tint_src_dir}/api/options", + "${tint_src_dir}/lang/core", + "${tint_src_dir}/lang/core/constant", + "${tint_src_dir}/lang/core/ir", + "${tint_src_dir}/lang/core/type", + "${tint_src_dir}/lang/wgsl", + "${tint_src_dir}/lang/wgsl/ast", + "${tint_src_dir}/lang/wgsl/ast/transform", + "${tint_src_dir}/lang/wgsl/common", + "${tint_src_dir}/lang/wgsl/features", + "${tint_src_dir}/lang/wgsl/program", + "${tint_src_dir}/lang/wgsl/sem", + "${tint_src_dir}/lang/wgsl/writer/ir_to_program", + "${tint_src_dir}/utils/containers", + "${tint_src_dir}/utils/diagnostic", + "${tint_src_dir}/utils/ice", + "${tint_src_dir}/utils/id", + "${tint_src_dir}/utils/macros", + "${tint_src_dir}/utils/math", + "${tint_src_dir}/utils/memory", + "${tint_src_dir}/utils/reflection", + "${tint_src_dir}/utils/result", + "${tint_src_dir}/utils/rtti", + "${tint_src_dir}/utils/symbol", + "${tint_src_dir}/utils/text", + "${tint_src_dir}/utils/traits", + ] + + if (tint_build_glsl_writer) { + deps += [ + "${tint_src_dir}/lang/glsl/writer/ast_raise", + "${tint_src_dir}/lang/glsl/writer/common", + ] + } + + if (tint_build_wgsl_reader) { + deps += [ "${tint_src_dir}/lang/wgsl/reader" ] + } + + if (tint_build_wgsl_reader && tint_build_wgsl_writer) { + deps += [ "${tint_src_dir}/lang/wgsl/ast/transform:unittests" ] + } + + if (tint_build_wgsl_writer) { + deps += [ "${tint_src_dir}/lang/wgsl/writer" ] + } + } + } +} diff --git a/references/writer/ast_raise/combine_samplers.cc b/references/writer/ast_raise/combine_samplers.cc new file mode 100644 index 00000000..66f5236f --- /dev/null +++ b/references/writer/ast_raise/combine_samplers.cc @@ -0,0 +1,455 @@ +// Copyright 2022 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_raise/combine_samplers.h" + +#include +#include +#include +#include + +#include "src/tint/lang/glsl/writer/common/options.h" +#include "src/tint/lang/wgsl/program/clone_context.h" +#include "src/tint/lang/wgsl/program/program_builder.h" +#include "src/tint/lang/wgsl/resolver/resolve.h" +#include "src/tint/lang/wgsl/sem/function.h" +#include "src/tint/lang/wgsl/sem/statement.h" + +#include "src/tint/utils/containers/map.h" + +TINT_INSTANTIATE_TYPEINFO(tint::glsl::writer::CombineSamplers); +TINT_INSTANTIATE_TYPEINFO(tint::glsl::writer::CombineSamplersInfo); + +namespace { + +bool IsGlobal(const tint::sem::VariablePair& pair) { + return (!pair.first || tint::Is(pair.first)) && + (!pair.second || tint::Is(pair.second)); +} + +} // namespace + +namespace tint::glsl::writer { + +using namespace tint::core::number_suffixes; // NOLINT + // +CombineSamplersInfo::CombineSamplersInfo() = default; + +CombineSamplersInfo::CombineSamplersInfo(CombinedTextureSamplerInfo map, BindingPoint placeholder) + : sampler_texture_to_name(std::move(map)), placeholder_sampler_binding(placeholder) {} + +CombineSamplersInfo::~CombineSamplersInfo() = default; + +/// PIMPL state for the transform +struct CombineSamplers::State { + /// The source program + const Program& src; + /// The target program builder + ProgramBuilder b; + /// The clone context + program::CloneContext ctx = {&b, &src, /* auto_clone_symbols */ true}; + + /// The combined sampler information + const CombineSamplersInfo* combined_samplers_info; + + /// Map from a texture/sampler pair to the corresponding combined sampler + /// variable + using CombinedTextureSamplerMap = std::unordered_map; + + /// A map of all global texture/sampler variable pairs to the global + /// combined sampler variable that will replace it. + CombinedTextureSamplerMap global_combined_texture_samplers_; + + /// A map of all texture/sampler variable pairs that contain a function + /// parameter to the combined sampler function paramter that will replace it. + std::unordered_map + function_combined_texture_samplers_; + + /// Placeholder global samplers used when a function contains texture-only + /// references (one comparison sampler, one regular). These are also used as + /// temporary sampler parameters to the texture builtins to satisfy the WGSL + /// resolver, but are then ignored and removed by the GLSL writer. + const ast::Variable* placeholder_samplers_[2] = {}; + + /// Group and binding attributes used by all combined sampler globals. + /// Group 0 and binding 0 are used, with collisions disabled. + /// @returns the newly-created attribute list + auto Attributes() const { + Vector attributes{ctx.dst->Group(0_a), ctx.dst->Binding(0_a)}; + attributes.Push(ctx.dst->Disable(ast::DisabledValidation::kBindingPointCollision)); + return attributes; + } + + /// Constructor + /// @param program the source program + /// @param info the binding map information + State(const Program& program, const CombineSamplersInfo* info) + : src(program), combined_samplers_info(info) {} + + /// Creates a combined sampler global variables. + /// (Note this is actually a Texture node at the AST level, but it will be + /// written as the corresponding sampler (eg., sampler2D) on GLSL output.) + /// @param texture_var the texture (global) variable + /// @param sampler_var the sampler (global) variable + /// @param name the default name to use (may be overridden by map lookup) + /// @returns the newly-created global variable + const ast::Variable* CreateCombinedGlobal(const sem::Variable* texture_var, + const sem::Variable* sampler_var, + std::string name) { + binding::CombinedTextureSamplerPair st_pair; + st_pair.texture = texture_var + ? *texture_var->As()->Attributes().binding_point + : combined_samplers_info->placeholder_sampler_binding; + st_pair.sampler = sampler_var + ? *sampler_var->As()->Attributes().binding_point + : combined_samplers_info->placeholder_sampler_binding; + auto it = combined_samplers_info->sampler_texture_to_name.find(st_pair); + if (it != combined_samplers_info->sampler_texture_to_name.end()) { + name = it->second; + } + ast::Type type = CreateCombinedASTTypeFor(texture_var, sampler_var); + Symbol symbol = ctx.dst->Symbols().New(name); + return ctx.dst->GlobalVar(symbol, type, Attributes()); + } + + /// Creates placeholder global sampler variables. + /// @param kind the sampler kind to create for + /// @returns the newly-created global variable + const ast::Variable* CreatePlaceholder(core::type::SamplerKind kind) { + ast::Type type = ctx.dst->ty.sampler(kind); + const char* name = kind == core::type::SamplerKind::kComparisonSampler + ? "placeholder_comparison_sampler" + : "placeholder_sampler"; + Symbol symbol = ctx.dst->Symbols().New(name); + return ctx.dst->GlobalVar(symbol, type, Attributes()); + } + + /// Creates Identifier for a given texture and sampler variable pair. + /// Depth textures with no samplers are turned into the corresponding + /// f32 texture (e.g., texture_depth_2d -> texture_2d). + /// Either texture or sampler could be nullptr, but cannot be nullptr at the same time. + /// The texture can only be nullptr, when the sampler is a dangling function parameter. + /// @param texture the texture variable of interest + /// @param sampler the texture variable of interest + /// @returns the newly-created type + ast::Type CreateCombinedASTTypeFor(const sem::Variable* texture, const sem::Variable* sampler) { + if (texture) { + const core::type::Type* texture_type = texture->Type()->UnwrapRef(); + const core::type::DepthTexture* depth = texture_type->As(); + if (depth && !sampler) { + return ctx.dst->ty.sampled_texture(depth->dim(), ctx.dst->ty.f32()); + } else { + return CreateASTTypeFor(ctx, texture_type); + } + } else { + TINT_ASSERT(sampler != nullptr); + const core::type::Type* sampler_type = sampler->Type()->UnwrapRef(); + return CreateASTTypeFor(ctx, sampler_type); + } + } + + /// Insert a new texture/sampler pair into the combined samplers maps (global or local, as + /// appropriate). If local, also add a function parameter to "params". + /// @param pair the texture/sampler pair to insert + /// @param fn the function scope in which to insert (if local) + /// @param params the calling function's parameter list to modify (if local) + void InsertPair(sem::VariablePair pair, + const sem::Function* fn, + tint::Vector* params) { + const sem::Variable* texture_var = pair.first; + const sem::Variable* sampler_var = pair.second; + std::string name = ""; + if (texture_var) { + name = texture_var->Declaration()->name->symbol.Name(); + } + if (sampler_var) { + if (!name.empty()) { + name += "_"; + } + name += sampler_var->Declaration()->name->symbol.Name(); + } + if (IsGlobal(pair)) { + // Both texture and sampler are global; add a new global variable + // to represent the combined sampler (if not already created). + GetOrAdd(global_combined_texture_samplers_, pair, + [&] { return CreateCombinedGlobal(texture_var, sampler_var, name); }); + } else { + // Either texture or sampler (or both) is a function parameter; + // add a new function parameter to represent the combined sampler. + ast::Type type = CreateCombinedASTTypeFor(texture_var, sampler_var); + auto* var = ctx.dst->Param(ctx.dst->Symbols().New(name), type); + params->Push(var); + function_combined_texture_samplers_[fn][pair] = var; + } + } + + /// For a given texture, find any texture/sampler pair with a non-null sampler in the given + /// function scope. + /// @param texture_var the texture variable of interest + /// @param fn the function scope in which to search + /// @returns the full pair, if found + const sem::VariablePair* FindFullTextureSamplerPair(const sem::Variable* texture_var, + const sem::Function* fn) { + for (auto pairIter = fn->TextureSamplerPairs().begin(); + pairIter != fn->TextureSamplerPairs().end(); pairIter++) { + if (pairIter->first == texture_var && pairIter->second) { + return pairIter; + } + } + return nullptr; + } + + /// Runs the transform + /// @returns the new program or SkipTransform if the transform is not required + ApplyResult Run() { + auto& sem = ctx.src->Sem(); + + // Remove all texture and sampler global variables. These will be replaced + // by combined samplers. + for (auto* global : ctx.src->AST().GlobalVariables()) { + auto* global_sem = sem.Get(global)->As(); + auto* type = ctx.src->TypeOf(global->type); + if (tint::IsAnyOf(type) && + !type->Is()) { + ctx.Remove(ctx.src->AST().GlobalDeclarations(), global); + } else if (auto binding_point = global_sem->Attributes().binding_point) { + if (binding_point->group == 0 && binding_point->binding == 0) { + auto* attribute = + ctx.dst->Disable(ast::DisabledValidation::kBindingPointCollision); + ctx.InsertFront(global->attributes, attribute); + } + } + } + + // Rewrite all function signatures to use combined samplers, and remove + // separate textures & samplers. Create new combined globals where found. + ctx.ReplaceAll([&](const ast::Function* ast_fn) -> const ast::Function* { + if (auto* fn = sem.Get(ast_fn)) { + auto pairs = fn->TextureSamplerPairs(); + if (pairs.IsEmpty()) { + return nullptr; + } + Vector params; + for (auto pair : fn->TextureSamplerPairs()) { + if (!pair.second) { + continue; + } + InsertPair(pair, fn, ¶ms); + } + for (auto pair : fn->TextureSamplerPairs()) { + if (pair.second) { + continue; + } + // Look for another pair with a non-null sampler. + // NOTE: this is O(N^2) in the number of pairs, since + // FindFullTextureSamplerPair() also loops over all pairs. If this proves + // problematic, it could be optimized. + if (const sem::VariablePair* fullPair = + FindFullTextureSamplerPair(pair.first, fn)) { + if (IsGlobal(pair)) { + global_combined_texture_samplers_[pair] = + global_combined_texture_samplers_[*fullPair]; + } else { + auto* var = function_combined_texture_samplers_[fn][*fullPair]; + function_combined_texture_samplers_[fn][pair] = var; + } + } else { + InsertPair(pair, fn, ¶ms); + } + } + // Filter out separate textures and samplers from the original + // function signature. + for (auto* param : fn->Parameters()) { + if (!param->Type()->IsAnyOf()) { + params.Push(ctx.Clone(param->Declaration())); + } + } + // Create a new function signature that differs only in the parameter + // list. + auto name = ctx.Clone(ast_fn->name); + auto return_type = ctx.Clone(ast_fn->return_type); + auto* body = ctx.Clone(ast_fn->body); + auto attributes = ctx.Clone(ast_fn->attributes); + auto return_type_attributes = ctx.Clone(ast_fn->return_type_attributes); + return ctx.dst->create(name, params, return_type, body, + std::move(attributes), + std::move(return_type_attributes)); + } + return nullptr; + }); + + // Replace all function call expressions containing texture or + // sampler parameters to use the current function's combined samplers or + // the combined global samplers, as appropriate. + ctx.ReplaceAll([&](const ast::CallExpression* expr) -> const ast::Expression* { + if (auto* call = sem.Get(expr)->UnwrapMaterialize()->As()) { + Vector args; + // Replace all texture builtin calls. + if (auto* builtin = call->Target()->As()) { + const auto& signature = builtin->Signature(); + auto sampler_index = signature.IndexOf(core::ParameterUsage::kSampler); + auto texture_index = signature.IndexOf(core::ParameterUsage::kTexture); + if (texture_index == -1) { + return nullptr; + } + const sem::ValueExpression* texture = + call->Arguments()[static_cast(texture_index)]; + // We don't want to combine storage textures with anything, since + // they never have associated samplers in GLSL. + if (texture->Type()->UnwrapRef()->Is()) { + return nullptr; + } + const sem::ValueExpression* sampler = + sampler_index != -1 ? call->Arguments()[static_cast(sampler_index)] + : nullptr; + auto* texture_var = texture->UnwrapLoad()->As()->Variable(); + auto* sampler_var = + sampler ? sampler->UnwrapLoad()->As()->Variable() + : nullptr; + sem::VariablePair new_pair(texture_var, sampler_var); + for (auto* arg : expr->args) { + auto* type = ctx.src->TypeOf(arg)->UnwrapRef(); + if (type->Is()) { + const ast::Variable* var = + IsGlobal(new_pair) + ? global_combined_texture_samplers_[new_pair] + : function_combined_texture_samplers_[call->Stmt()->Function()] + [new_pair]; + args.Push(ctx.dst->Expr(var->name->symbol)); + } else if (auto* sampler_type = type->As()) { + core::type::SamplerKind kind = sampler_type->kind(); + int index = (kind == core::type::SamplerKind::kSampler) ? 0 : 1; + const ast::Variable*& p = placeholder_samplers_[index]; + if (!p) { + p = CreatePlaceholder(kind); + } + args.Push(ctx.dst->Expr(p->name->symbol)); + } else { + args.Push(ctx.Clone(arg)); + } + } + const ast::Expression* value = ctx.dst->Call(ctx.Clone(expr->target), args); + if (builtin->Fn() == wgsl::BuiltinFn::kTextureLoad && + texture_var->Type()->UnwrapRef()->Is() && + !call->Stmt()->Declaration()->Is()) { + value = ctx.dst->MemberAccessor(value, "x"); + } + return value; + } + // Replace all function calls. + if (auto* callee = call->Target()->As()) { + auto make_arg = [&](const sem::Variable* texture_var, + const sem::Variable* sampler_var) { + if (auto* param = tint::As(texture_var)) { + const sem::ValueExpression* texture = call->Arguments()[param->Index()]; + texture_var = + texture->UnwrapLoad()->As()->Variable(); + } + if (auto* param = tint::As(sampler_var)) { + const sem::ValueExpression* sampler = call->Arguments()[param->Index()]; + sampler_var = + sampler->UnwrapLoad()->As()->Variable(); + } + sem::VariablePair new_pair(texture_var, sampler_var); + // If both texture and sampler are (now) global, pass that + // global variable to the callee. Otherwise use the caller's + // function parameter for this pair. + const ast::Variable* var = + IsGlobal(new_pair) + ? global_combined_texture_samplers_[new_pair] + : function_combined_texture_samplers_[call->Stmt()->Function()] + [new_pair]; + return ctx.dst->Expr(var->name->symbol); + }; + + for (auto pair : callee->TextureSamplerPairs()) { + if (!pair.second) { + continue; + } + // Global pairs used by the callee do not require a function + // parameter at the call site. + if (IsGlobal(pair)) { + continue; + } + + args.Push(make_arg(pair.first, pair.second)); + } + for (auto pair : callee->TextureSamplerPairs()) { + if (pair.second) { + continue; + } + // Global pairs used by the callee do not require a function + // parameter at the call site. + if (IsGlobal(pair)) { + continue; + } + // Texture-only pairs do not require a function parameter if they've been + // replaced by a real pair. + if (FindFullTextureSamplerPair(pair.first, callee)) { + continue; + } + + args.Push(make_arg(pair.first, nullptr)); + } + // Append all of the remaining non-texture and non-sampler + // parameters. + for (auto* arg : expr->args) { + if (!ctx.src->TypeOf(arg) + ->UnwrapRef() + ->IsAnyOf()) { + args.Push(ctx.Clone(arg)); + } + } + return ctx.dst->Call(ctx.Clone(expr->target), args); + } + } + return nullptr; + }); + + ctx.Clone(); + return resolver::Resolve(b); + } +}; + +CombineSamplers::CombineSamplers() = default; + +CombineSamplers::~CombineSamplers() = default; + +ast::transform::Transform::ApplyResult CombineSamplers::Apply(const Program& src, + const ast::transform::DataMap& inputs, + ast::transform::DataMap&) const { + auto* info = inputs.Get(); + if (!info) { + ProgramBuilder b; + b.Diagnostics().AddError(Source{}) << "missing transform data for " << TypeInfo().name; + return resolver::Resolve(b); + } + + return State(src, info).Run(); +} + +} // namespace tint::glsl::writer diff --git a/references/writer/ast_raise/combine_samplers.h b/references/writer/ast_raise/combine_samplers.h new file mode 100644 index 00000000..24b53547 --- /dev/null +++ b/references/writer/ast_raise/combine_samplers.h @@ -0,0 +1,86 @@ +// Copyright 2022 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef SRC_TINT_LANG_GLSL_WRITER_AST_RAISE_COMBINE_SAMPLERS_H_ +#define SRC_TINT_LANG_GLSL_WRITER_AST_RAISE_COMBINE_SAMPLERS_H_ + +#include +#include + +#include "src/tint/lang/wgsl/ast/transform/transform.h" + +namespace tint::glsl::writer { + +/// This transform converts all separate texture/sampler refences in a +/// program into combined texture/samplers. This is required for GLSL, +/// which does not support separate texture/samplers. +/// +/// It utilizes the texture/sampler information collected by the +/// Resolver and stored on each sem::Function. For each function, all +/// separate texture/sampler parameters in the function signature are +/// removed. For each unique pair, if both texture and sampler are +/// global variables, the function passes the corresponding combined +/// global stored in global_combined_texture_samplers_ at the call +/// site. Otherwise, either the texture or sampler must be a function +/// parameter. In this case, a new parameter is added to the function +/// signature. All separate texture/sampler parameters are removed. +/// +/// All texture builtin callsites are modified to pass the combined +/// texture/sampler as the first argument, and separate texture/sampler +/// arguments are removed. +/// +/// Note that the sampler may be null, indicating that only a texture +/// reference was required (e.g., textureLoad). In this case, a +/// placeholder global sampler is used at the AST level. This will be +/// combined with the original texture to give a combined global, and +/// the placeholder removed (ignored) by the GLSL writer. +/// +/// Note that the combined samplers are actually represented by a +/// Texture node at the AST level, since this contains all the +/// information needed to represent a combined sampler in GLSL +/// (dimensionality, component type, etc). The GLSL writer outputs such +/// (Tint) Textures as (GLSL) Samplers. +class CombineSamplers final : public Castable { + public: + /// Constructor + CombineSamplers(); + + /// Destructor + ~CombineSamplers() override; + + /// @copydoc ast::transform::Transform::Apply + ApplyResult Apply(const Program& program, + const ast::transform::DataMap& inputs, + ast::transform::DataMap& outputs) const override; + + private: + struct State; +}; + +} // namespace tint::glsl::writer + +#endif // SRC_TINT_LANG_GLSL_WRITER_AST_RAISE_COMBINE_SAMPLERS_H_ diff --git a/references/writer/ast_raise/combine_samplers_test.cc b/references/writer/ast_raise/combine_samplers_test.cc new file mode 100644 index 00000000..575f428e --- /dev/null +++ b/references/writer/ast_raise/combine_samplers_test.cc @@ -0,0 +1,1218 @@ +// Copyright 2022 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_raise/combine_samplers.h" + +#include +#include + +#include "src/tint/lang/glsl/writer/common/options.h" +#include "src/tint/lang/wgsl/ast/transform/helper_test.h" + +namespace tint::glsl::writer { +namespace { + +using CombineSamplersTest = ast::transform::TransformTest; + +TEST_F(CombineSamplersTest, EmptyModule) { + auto* src = ""; + auto* expect = ""; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, SimplePair) { + auto* src = R"( +@group(0) @binding(0) var t : texture_2d; + +@group(0) @binding(1) var s : sampler; + +fn main() -> vec4 { + return textureSample(t, s, vec2(1.0, 2.0)); +} +)"; + auto* expect = R"( +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s : texture_2d; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; + +fn main() -> vec4 { + return textureSample(t_s, placeholder_sampler, vec2(1.0, 2.0)); +} +)"; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, SimplePair_OutOfOrder) { + auto* src = R"( +fn main() -> vec4 { + return textureSample(t, s, vec2(1.0, 2.0)); +} + +@group(0) @binding(0) var t : texture_2d; + +@group(0) @binding(1) var s : sampler; +)"; + auto* expect = R"( +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s : texture_2d; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; + +fn main() -> vec4 { + return textureSample(t_s, placeholder_sampler, vec2(1.0, 2.0)); +} +)"; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, SimplePairInAFunction) { + auto* src = R"( +@group(0) @binding(0) var t : texture_2d; + +@group(0) @binding(1) var s : sampler; + +fn sample(t : texture_2d, s : sampler, coords : vec2) -> vec4 { + return textureSample(t, s, coords); +} + +fn main() -> vec4 { + return sample(t, s, vec2(1.0, 2.0)); +} +)"; + auto* expect = R"( +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; + +fn sample(t_s : texture_2d, coords : vec2) -> vec4 { + return textureSample(t_s, placeholder_sampler, coords); +} + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s_1 : texture_2d; + +fn main() -> vec4 { + return sample(t_s_1, vec2(1.0, 2.0)); +} +)"; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, SimplePairInAFunction_OutOfOrder) { + auto* src = R"( +fn main() -> vec4 { + return sample(t, s, vec2(1.0, 2.0)); +} + +fn sample(t : texture_2d, s : sampler, coords : vec2) -> vec4 { + return textureSample(t, s, coords); +} + +@group(0) @binding(1) var s : sampler; + +@group(0) @binding(0) var t : texture_2d; +)"; + auto* expect = R"( +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s : texture_2d; + +fn main() -> vec4 { + return sample(t_s, vec2(1.0, 2.0)); +} + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; + +fn sample(t_s_1 : texture_2d, coords : vec2) -> vec4 { + return textureSample(t_s_1, placeholder_sampler, coords); +} +)"; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, SimplePairRename) { + auto* src = R"( +@group(0) @binding(1) var t : texture_2d; + +@group(2) @binding(3) var s : sampler; + +fn main() -> vec4 { + return textureSample(t, s, vec2(1.0, 2.0)); +} +)"; + auto* expect = R"( +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var fuzzy : texture_2d; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; + +fn main() -> vec4 { + return textureSample(fuzzy, placeholder_sampler, vec2(1.0, 2.0)); +} +)"; + + ast::transform::DataMap data; + CombinedTextureSamplerInfo map; + binding::CombinedTextureSamplerPair pair; + pair.texture = {0, 1}; + pair.sampler = {2, 3}; + map[pair] = "fuzzy"; + + BindingPoint placeholder{1024, 0}; + data.Add(map, placeholder); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, SimplePairRenameMiss) { + auto* src = R"( +@group(0) @binding(1) var t : texture_2d; + +@group(2) @binding(3) var s : sampler; + +fn main() -> vec4 { + return textureSample(t, s, vec2(1.0, 2.0)); +} +)"; + auto* expect = R"( +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s : texture_2d; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; + +fn main() -> vec4 { + return textureSample(t_s, placeholder_sampler, vec2(1.0, 2.0)); +} +)"; + + ast::transform::DataMap data; + CombinedTextureSamplerInfo map; + binding::CombinedTextureSamplerPair pair; + pair.texture = {3, 2}; + pair.sampler = {1, 0}; + map[pair] = "fuzzy"; + + BindingPoint placeholder{1024, 0}; + data.Add(map, placeholder); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, AliasedTypes) { + auto* src = R"( + +alias Tex2d = texture_2d; + +@group(0) @binding(0) var t : Tex2d; + +@group(0) @binding(1) var s : sampler; + +fn sample(t : Tex2d, s : sampler, coords : vec2) -> vec4 { + return textureSample(t, s, coords); +} + +fn main() -> vec4 { + return sample(t, s, vec2(1.0, 2.0)); +} +)"; + auto* expect = R"( +alias Tex2d = texture_2d; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; + +fn sample(t_s : texture_2d, coords : vec2) -> vec4 { + return textureSample(t_s, placeholder_sampler, coords); +} + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s_1 : texture_2d; + +fn main() -> vec4 { + return sample(t_s_1, vec2(1.0, 2.0)); +} +)"; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, AliasedTypes_OutOfOrder) { + auto* src = R"( +fn main() -> vec4 { + return sample(t, s, vec2(1.0, 2.0)); +} + +fn sample(t : Tex2d, s : sampler, coords : vec2) -> vec4 { + return textureSample(t, s, coords); +} + +@group(0) @binding(0) var t : Tex2d; +@group(0) @binding(1) var s : sampler; + +alias Tex2d = texture_2d; +)"; + auto* expect = R"( +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s : texture_2d; + +fn main() -> vec4 { + return sample(t_s, vec2(1.0, 2.0)); +} + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; + +fn sample(t_s_1 : texture_2d, coords : vec2) -> vec4 { + return textureSample(t_s_1, placeholder_sampler, coords); +} + +alias Tex2d = texture_2d; +)"; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, SimplePairInTwoFunctions) { + auto* src = R"( +@group(0) @binding(0) var t : texture_2d; + +@group(0) @binding(1) var s : sampler; + +fn g(t : texture_2d, s : sampler, coords : vec2) -> vec4 { + return textureSample(t, s, coords); +} + +fn f(t : texture_2d, s : sampler, coords : vec2) -> vec4 { + return g(t, s, coords); +} + +fn main() -> vec4 { + return f(t, s, vec2(1.0, 2.0)); +} +)"; + auto* expect = R"( +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; + +fn g(t_s : texture_2d, coords : vec2) -> vec4 { + return textureSample(t_s, placeholder_sampler, coords); +} + +fn f(t_s_1 : texture_2d, coords : vec2) -> vec4 { + return g(t_s_1, coords); +} + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s_2 : texture_2d; + +fn main() -> vec4 { + return f(t_s_2, vec2(1.0, 2.0)); +} +)"; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, SimplePairInTwoFunctions_OutOfOrder) { + auto* src = R"( +fn main() -> vec4 { + return f(t, s, vec2(1.0, 2.0)); +} + +fn f(t : texture_2d, s : sampler, coords : vec2) -> vec4 { + return g(t, s, coords); +} + +fn g(t : texture_2d, s : sampler, coords : vec2) -> vec4 { + return textureSample(t, s, coords); +} + +@group(0) @binding(1) var s : sampler; +@group(0) @binding(0) var t : texture_2d; +)"; + auto* expect = R"( +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s : texture_2d; + +fn main() -> vec4 { + return f(t_s, vec2(1.0, 2.0)); +} + +fn f(t_s_1 : texture_2d, coords : vec2) -> vec4 { + return g(t_s_1, coords); +} + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; + +fn g(t_s_2 : texture_2d, coords : vec2) -> vec4 { + return textureSample(t_s_2, placeholder_sampler, coords); +} +)"; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, TwoFunctionsGenerateSamePair) { + auto* src = R"( +@group(1) @binding(0) var tex : texture_2d; + +@group(1) @binding(1) var samp : sampler; + +fn f() -> vec4 { + return textureSample(tex, samp, vec2(1.0, 2.0)); +} + +fn g() -> vec4 { + return textureSample(tex, samp, vec2(3.0, 4.0)); +} + +fn main() -> vec4 { + return f() + g(); +} +)"; + auto* expect = R"( +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp : texture_2d; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; + +fn f() -> vec4 { + return textureSample(tex_samp, placeholder_sampler, vec2(1.0, 2.0)); +} + +fn g() -> vec4 { + return textureSample(tex_samp, placeholder_sampler, vec2(3.0, 4.0)); +} + +fn main() -> vec4 { + return (f() + g()); +} +)"; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, ThreeTexturesThreeSamplers) { + auto* src = R"( +@group(0) @binding(0) var tex1 : texture_2d; +@group(0) @binding(1) var tex2 : texture_2d; +@group(0) @binding(2) var tex3 : texture_2d; + +@group(1) @binding(0) var samp1 : sampler; +@group(1) @binding(1) var samp2: sampler; +@group(1) @binding(2) var samp3: sampler; + +fn sample(t : texture_2d, s : sampler) -> vec4 { + return textureSample(t, s, vec2(1.0, 2.0)); +} + +fn main() -> vec4 { + return sample(tex1, samp1) + + sample(tex1, samp2) + + sample(tex1, samp3) + + sample(tex2, samp1) + + sample(tex2, samp2) + + sample(tex2, samp3) + + sample(tex3, samp1) + + sample(tex3, samp2) + + sample(tex3, samp3); +} +)"; + auto* expect = R"( +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; + +fn sample(t_s : texture_2d) -> vec4 { + return textureSample(t_s, placeholder_sampler, vec2(1.0, 2.0)); +} + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex1_samp1 : texture_2d; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex1_samp2 : texture_2d; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex1_samp3 : texture_2d; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex2_samp1 : texture_2d; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex2_samp2 : texture_2d; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex2_samp3 : texture_2d; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex3_samp1 : texture_2d; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex3_samp2 : texture_2d; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex3_samp3 : texture_2d; + +fn main() -> vec4 { + return ((((((((sample(tex1_samp1) + sample(tex1_samp2)) + sample(tex1_samp3)) + sample(tex2_samp1)) + sample(tex2_samp2)) + sample(tex2_samp3)) + sample(tex3_samp1)) + sample(tex3_samp2)) + sample(tex3_samp3)); +} +)"; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, TwoFunctionsTwoTexturesDiamond) { + auto* src = R"( +@group(0) @binding(0) var tex1 : texture_2d; + +@group(0) @binding(1) var tex2 : texture_2d; + +@group(0) @binding(2) var samp : sampler; + +fn sample(t : texture_2d, s : sampler, coords : vec2) -> vec4 { + return textureSample(t, s, coords); +} + +fn f(t1 : texture_2d, t2 : texture_2d, s : sampler, coords : vec2) -> vec4 { + return sample(t1, s, coords) + sample(t2, s, coords); +} + +fn main() -> vec4 { + return f(tex1, tex2, samp, vec2(1.0, 2.0)); +} +)"; + auto* expect = R"( +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; + +fn sample(t_s : texture_2d, coords : vec2) -> vec4 { + return textureSample(t_s, placeholder_sampler, coords); +} + +fn f(t1_s : texture_2d, t2_s : texture_2d, coords : vec2) -> vec4 { + return (sample(t1_s, coords) + sample(t2_s, coords)); +} + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex1_samp : texture_2d; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex2_samp : texture_2d; + +fn main() -> vec4 { + return f(tex1_samp, tex2_samp, vec2(1.0, 2.0)); +} +)"; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, TwoFunctionsTwoSamplersDiamond) { + auto* src = R"( +@group(0) @binding(0) var tex : texture_2d; + +@group(0) @binding(1) var samp1 : sampler; + +@group(0) @binding(2) var samp2 : sampler; + +fn sample(t : texture_2d, s : sampler, coords : vec2) -> vec4 { + return textureSample(t, s, coords); +} + +fn f(t : texture_2d, s1 : sampler, s2 : sampler, coords : vec2) -> vec4 { + return sample(t, s1, coords) + sample(t, s2, coords); +} + +fn main() -> vec4 { + return f(tex, samp1, samp2, vec2(1.0, 2.0)); +} +)"; + auto* expect = R"( +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; + +fn sample(t_s : texture_2d, coords : vec2) -> vec4 { + return textureSample(t_s, placeholder_sampler, coords); +} + +fn f(t_s1 : texture_2d, t_s2 : texture_2d, coords : vec2) -> vec4 { + return (sample(t_s1, coords) + sample(t_s2, coords)); +} + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp1 : texture_2d; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp2 : texture_2d; + +fn main() -> vec4 { + return f(tex_samp1, tex_samp2, vec2(1.0, 2.0)); +} +)"; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, GlobalTextureLocalSampler) { + auto* src = R"( +@group(0) @binding(0) var tex : texture_2d; + +@group(0) @binding(1) var samp1 : sampler; + +@group(0) @binding(2) var samp2 : sampler; + +fn f(s1 : sampler, s2 : sampler, coords : vec2) -> vec4 { + return textureSample(tex, s1, coords) + textureSample(tex, s2, coords); +} + +fn main() -> vec4 { + return f(samp1, samp2, vec2(1.0, 2.0)); +} +)"; + auto* expect = R"( +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; + +fn f(tex_s1 : texture_2d, tex_s2 : texture_2d, coords : vec2) -> vec4 { + return (textureSample(tex_s1, placeholder_sampler, coords) + textureSample(tex_s2, placeholder_sampler, coords)); +} + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp1 : texture_2d; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp2 : texture_2d; + +fn main() -> vec4 { + return f(tex_samp1, tex_samp2, vec2(1.0, 2.0)); +} +)"; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, GlobalTextureLocalSampler_OutOfOrder) { + auto* src = R"( +fn main() -> vec4 { + return f(samp1, samp2, vec2(1.0, 2.0)); +} + +fn f(s1 : sampler, s2 : sampler, coords : vec2) -> vec4 { + return textureSample(tex, s1, coords) + textureSample(tex, s2, coords); +} + +@group(0) @binding(1) var samp1 : sampler; +@group(0) @binding(2) var samp2 : sampler; +@group(0) @binding(0) var tex : texture_2d; +)"; + auto* expect = R"( +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp1 : texture_2d; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp2 : texture_2d; + +fn main() -> vec4 { + return f(tex_samp1, tex_samp2, vec2(1.0, 2.0)); +} + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; + +fn f(tex_s1 : texture_2d, tex_s2 : texture_2d, coords : vec2) -> vec4 { + return (textureSample(tex_s1, placeholder_sampler, coords) + textureSample(tex_s2, placeholder_sampler, coords)); +} +)"; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, LocalTextureGlobalSampler) { + auto* src = R"( +@group(0) @binding(0) var tex1 : texture_2d; + +@group(0) @binding(1) var tex2 : texture_2d; + +@group(0) @binding(2) var samp : sampler; + +fn f(t1 : texture_2d, t2 : texture_2d, coords : vec2) -> vec4 { + return textureSample(t1, samp, coords) + textureSample(t2, samp, coords); +} + +fn main() -> vec4 { + return f(tex1, tex2, vec2(1.0, 2.0)); +} +)"; + auto* expect = R"( +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; + +fn f(t1_samp : texture_2d, t2_samp : texture_2d, coords : vec2) -> vec4 { + return (textureSample(t1_samp, placeholder_sampler, coords) + textureSample(t2_samp, placeholder_sampler, coords)); +} + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex1_samp : texture_2d; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex2_samp : texture_2d; + +fn main() -> vec4 { + return f(tex1_samp, tex2_samp, vec2(1.0, 2.0)); +} +)"; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, LocalTextureGlobalSampler_OutOfOrder) { + auto* src = R"( +fn main() -> vec4 { + return f(tex1, tex2, vec2(1.0, 2.0)); +} + +fn f(t1 : texture_2d, t2 : texture_2d, coords : vec2) -> vec4 { + return textureSample(t1, samp, coords) + textureSample(t2, samp, coords); +} + +@group(0) @binding(2) var samp : sampler; +@group(0) @binding(0) var tex1 : texture_2d; +@group(0) @binding(1) var tex2 : texture_2d; +)"; + auto* expect = R"( +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex1_samp : texture_2d; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex2_samp : texture_2d; + +fn main() -> vec4 { + return f(tex1_samp, tex2_samp, vec2(1.0, 2.0)); +} + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; + +fn f(t1_samp : texture_2d, t2_samp : texture_2d, coords : vec2) -> vec4 { + return (textureSample(t1_samp, placeholder_sampler, coords) + textureSample(t2_samp, placeholder_sampler, coords)); +} +)"; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, TextureLoadNoSampler) { + auto* src = R"( +@group(0) @binding(0) var tex : texture_2d; + +fn f(t : texture_2d, coords : vec2) -> vec4 { + return textureLoad(t, coords, 0); +} + +fn main() -> vec4 { + return f(tex, vec2(1, 2)); +} +)"; + auto* expect = R"( +fn f(t_1 : texture_2d, coords : vec2) -> vec4 { + return textureLoad(t_1, coords, 0); +} + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var fred : texture_2d; + +fn main() -> vec4 { + return f(fred, vec2(1, 2)); +} +)"; + + CombinedTextureSamplerInfo map; + binding::CombinedTextureSamplerPair pair; + pair.texture = {0, 0}; + pair.sampler = {1024, 0}; + map[pair] = "fred"; + + BindingPoint placeholder{pair.sampler.group, pair.sampler.binding}; + + ast::transform::DataMap data; + data.Add(map, placeholder); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, TextureWithAndWithoutSampler) { + auto* src = R"( +@group(0) @binding(0) var tex : texture_2d; +@group(0) @binding(1) var samp : sampler; + +fn main() -> vec4 { + return textureLoad(tex, vec2(), 0) + + textureSample(tex, samp, vec2()); +} +)"; + auto* expect = R"( +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var barney : texture_2d; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; + +fn main() -> vec4 { + return (textureLoad(barney, vec2(), 0) + textureSample(barney, placeholder_sampler, vec2())); +} +)"; + + BindingPoint placeholder{1024, 0}; + BindingPoint tex{0, 0}; + BindingPoint samp{0, 1}; + + CombinedTextureSamplerInfo map; + binding::CombinedTextureSamplerPair pair; + pair.texture = {tex.group, tex.binding}; + pair.sampler = {samp.group, samp.binding}; + + binding::CombinedTextureSamplerPair placeholder_pair; + placeholder_pair.texture = {tex.group, tex.binding}; + placeholder_pair.sampler = {placeholder.group, placeholder.binding}; + + map[pair] = "barney"; + map[placeholder_pair] = "fred"; + + ast::transform::DataMap data; + data.Add(map, placeholder); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, TextureSampleCompare) { + auto* src = R"( +@group(0) @binding(0) var tex : texture_depth_2d; + +@group(0) @binding(1) var samp : sampler_comparison; + +fn main() -> vec4 { + return vec4(textureSampleCompare(tex, samp, vec2(1.0, 2.0), 0.5)); +} +)"; + auto* expect = R"( +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp : texture_depth_2d; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_comparison_sampler : sampler_comparison; + +fn main() -> vec4 { + return vec4(textureSampleCompare(tex_samp, placeholder_comparison_sampler, vec2(1.0, 2.0), 0.5)); +} +)"; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, TextureSampleCompareInAFunction) { + auto* src = R"( +@group(0) @binding(0) var tex : texture_depth_2d; + +@group(0) @binding(1) var samp : sampler_comparison; + +fn f(t : texture_depth_2d, s : sampler_comparison, coords : vec2) -> f32 { + return textureSampleCompare(t, s, coords, 5.0f); +} + +fn main() -> vec4 { + return vec4(f(tex, samp, vec2(1.0, 2.0))); +} +)"; + auto* expect = R"( +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_comparison_sampler : sampler_comparison; + +fn f(t_s : texture_depth_2d, coords : vec2) -> f32 { + return textureSampleCompare(t_s, placeholder_comparison_sampler, coords, 5.0f); +} + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp : texture_depth_2d; + +fn main() -> vec4 { + return vec4(f(tex_samp, vec2(1.0, 2.0))); +} +)"; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, TextureSampleCompareInAFunction_OutOfOrder) { + auto* src = R"( +fn main() -> vec4 { + return vec4(f(tex, samp, vec2(1.0, 2.0))); +} + +fn f(t : texture_depth_2d, s : sampler_comparison, coords : vec2) -> f32 { + return textureSampleCompare(t, s, coords, 5.0f); +} + +@group(0) @binding(0) var tex : texture_depth_2d; +@group(0) @binding(1) var samp : sampler_comparison; +)"; + auto* expect = R"( +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp : texture_depth_2d; + +fn main() -> vec4 { + return vec4(f(tex_samp, vec2(1.0, 2.0))); +} + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_comparison_sampler : sampler_comparison; + +fn f(t_s : texture_depth_2d, coords : vec2) -> f32 { + return textureSampleCompare(t_s, placeholder_comparison_sampler, coords, 5.0f); +} +)"; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, BindingPointCollision) { + auto* src = R"( +@group(1) @binding(0) var tex : texture_2d; + +@group(1) @binding(1) var samp : sampler; + +@group(0) @binding(0) var gcoords : vec2; + +fn main() -> vec4 { + return textureSample(tex, samp, gcoords); +} +)"; + auto* expect = R"( +@internal(disable_validation__binding_point_collision) @group(0) @binding(0) var gcoords : vec2; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp : texture_2d; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; + +fn main() -> vec4 { + return textureSample(tex_samp, placeholder_sampler, gcoords); +} +)"; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, BindingPointCollision_OutOfOrder) { + auto* src = R"( +fn main() -> vec4 { + return textureSample(tex, samp, gcoords); +} + +@group(1) @binding(1) var samp : sampler; +@group(0) @binding(0) var gcoords : vec2; +@group(1) @binding(0) var tex : texture_2d; + +)"; + auto* expect = R"( +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp : texture_2d; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; + +fn main() -> vec4 { + return textureSample(tex_samp, placeholder_sampler, gcoords); +} + +@internal(disable_validation__binding_point_collision) @group(0) @binding(0) var gcoords : vec2; +)"; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, UnusedTextureFunctionParameter) { + auto* src = R"( +@group(0) @binding(0) var t : texture_2d; + +fn f(tex: texture_2d) -> u32 { + return 1u; +} + +fn main() { + _ = f(t); +} +)"; + auto* expect = R"( +fn f(tex_1 : texture_2d) -> u32 { + return 1u; +} + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_1 : texture_2d; + +fn main() { + _ = f(t_1); +} +)"; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, UnusedSamplerFunctionParameter) { + auto* src = R"( +@group(0) @binding(0) var s : sampler; + +fn f(sampler1: sampler) -> u32 { + return 1u; +} + +fn main() { + _ = f(s); +} +)"; + auto* expect = R"( +fn f(sampler1_1 : sampler) -> u32 { + return 1u; +} + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var s_1 : sampler; + +fn main() { + _ = f(s_1); +} +)"; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, UnusedTextureAndSamplerFunctionParameter) { + auto* src = R"( +@group(0) @binding(0) var t : texture_2d; + +@group(0) @binding(1) var s : sampler; + +fn f(tex: texture_2d, sampler1: sampler) -> u32 { + return 1u; +} + +fn main() { + _ = f(t, s); +} +)"; + auto* expect = R"( +fn f(sampler1_1 : sampler, tex_1 : texture_2d) -> u32 { + return 1u; +} + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var s_1 : sampler; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_1 : texture_2d; + +fn main() { + _ = f(s_1, t_1); +} +)"; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, UnusedTextureFunctionParameter_Multiple) { + auto* src = R"( +@group(0) @binding(0) var t1 : texture_2d; + +@group(0) @binding(1) var t2 : texture_2d_array; + +@group(0) @binding(2) var s : sampler; + +fn f(tex1: texture_2d, tex2: texture_2d, tex3: texture_2d_array, sampler1: sampler) -> u32 { + return 1u + textureNumLayers(tex3); +} + +fn main() { + _ = f(t1, t1, t2, s); +} +)"; + auto* expect = R"( +fn f(sampler1_1 : sampler, tex3_1 : texture_2d_array, tex1_1 : texture_2d) -> u32 { + return (1u + textureNumLayers(tex3_1)); +} + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var s_1 : sampler; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t2_1 : texture_2d_array; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t1_1 : texture_2d; + +fn main() { + _ = f(s_1, t2_1, t1_1); +} +)"; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, UnusedTextureFunctionParameter_Nested) { + auto* src = R"( +@group(0) @binding(0) var t : texture_2d; + +fn f_nested(tex: texture_2d) -> u32 { + return 1u; +} + +fn f(tex: texture_2d) -> u32 { + return f_nested(tex); +} + +fn main() { + _ = f(t); +} +)"; + auto* expect = R"( +fn f_nested(tex_1 : texture_2d) -> u32 { + return 1u; +} + +fn f(tex_2 : texture_2d) -> u32 { + return f_nested(tex_2); +} + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_1 : texture_2d; + +fn main() { + _ = f(t_1); +} +)"; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CombineSamplersTest, UnusedTextureAndSamplerFunctionParameter_Nested) { + auto* src = R"( +@group(0) @binding(0) var t : texture_2d; + +@group(0) @binding(1) var s : sampler; + +fn f_nested(tex: texture_2d, sampler1: sampler) -> u32 { + return 1u; +} + +fn f(tex: texture_2d, sampler1: sampler) -> u32 { + return f_nested(tex, sampler1); +} + +fn main() { + _ = f(t, s); +} +)"; + auto* expect = + R"( +fn f_nested(sampler1_1 : sampler, tex_1 : texture_2d) -> u32 { + return 1u; +} + +fn f(sampler1_2 : sampler, tex_2 : texture_2d) -> u32 { + return f_nested(sampler1_2, tex_2); +} + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var s_1 : sampler; + +@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_1 : texture_2d; + +fn main() { + _ = f(s_1, t_1); +} +)"; + + ast::transform::DataMap data; + data.Add(CombinedTextureSamplerInfo(), BindingPoint()); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_raise/pad_structs.cc b/references/writer/ast_raise/pad_structs.cc new file mode 100644 index 00000000..08c83610 --- /dev/null +++ b/references/writer/ast_raise/pad_structs.cc @@ -0,0 +1,191 @@ +// Copyright 2022 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_raise/pad_structs.h" + +#include +#include +#include + +#include "src/tint/lang/wgsl/ast/disable_validation_attribute.h" +#include "src/tint/lang/wgsl/ast/parameter.h" +#include "src/tint/lang/wgsl/program/clone_context.h" +#include "src/tint/lang/wgsl/program/program_builder.h" +#include "src/tint/lang/wgsl/resolver/resolve.h" +#include "src/tint/lang/wgsl/sem/call.h" +#include "src/tint/lang/wgsl/sem/module.h" +#include "src/tint/lang/wgsl/sem/value_constructor.h" + +using namespace tint::core::number_suffixes; // NOLINT + +TINT_INSTANTIATE_TYPEINFO(tint::glsl::writer::PadStructs); + +namespace tint::glsl::writer { + +namespace { + +void CreatePadding(Vector* new_members, + Hashset* padding_members, + Hashset* member_names, + int& last_padding_index, + ast::Builder* b, + uint32_t bytes) { + const size_t count = bytes / 4u; + padding_members->Reserve(count); + new_members->Reserve(count); + member_names->Reserve(count); + std::string name = "pad"; + for (uint32_t i = 0; i < count; ++i) { + while (member_names->Contains(name)) { + name = "pad_" + std::to_string(++last_padding_index); + } + member_names->Add(name); + auto* member = b->Member(name, b->ty.u32()); + padding_members->Add(member); + new_members->Push(member); + } +} + +} // namespace + +PadStructs::PadStructs() = default; + +PadStructs::~PadStructs() = default; + +ast::transform::Transform::ApplyResult PadStructs::Apply(const Program& src, + const ast::transform::DataMap&, + ast::transform::DataMap&) const { + ProgramBuilder b; + program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true}; + auto& sem = src.Sem(); + + std::unordered_map replaced_structs; + Hashset padding_members; + + ctx.ReplaceAll([&](const ast::Struct* ast_str) -> const ast::Struct* { + auto* str = sem.Get(ast_str); + if (!str || !str->IsHostShareable()) { + return nullptr; + } + uint32_t offset = 0; + bool has_runtime_sized_array = false; + tint::Vector new_members; + Hashset member_names; + for (auto* mem : str->Members()) { + member_names.Add(mem->Name().Name()); + } + + int last_padding_index = 0; + for (auto* mem : str->Members()) { + auto name = mem->Name().Name(); + + if (offset < mem->Offset()) { + CreatePadding(&new_members, &padding_members, &member_names, last_padding_index, + ctx.dst, mem->Offset() - offset); + offset = mem->Offset(); + } + + auto* ty = mem->Type(); + auto type = CreateASTTypeFor(ctx, ty); + + new_members.Push(b.Member(name, type)); + + uint32_t size = ty->Size(); + if (ty->Is() && str->UsedAs(core::AddressSpace::kUniform)) { + // std140 structs should be padded out to 16 bytes. + size = tint::RoundUp(16u, size); + } else if (auto* array_ty = ty->As()) { + if (array_ty->Count()->Is()) { + has_runtime_sized_array = true; + } + } + offset += size; + } + + // Add any required padding after the last member, if it's not a runtime-sized array. + uint32_t struct_size = str->Size(); + if (str->UsedAs(core::AddressSpace::kUniform)) { + struct_size = tint::RoundUp(16u, struct_size); + } + if (offset < struct_size && !has_runtime_sized_array) { + CreatePadding(&new_members, &padding_members, &member_names, last_padding_index, + ctx.dst, struct_size - offset); + } + + tint::Vector struct_attribs = ctx.Clone(ast_str->attributes); + if (!padding_members.IsEmpty()) { + struct_attribs.Push(b.Disable(ast::DisabledValidation::kIgnoreStructMemberLimit)); + } + + auto* new_struct = b.create(ctx.Clone(ast_str->name), std::move(new_members), + std::move(struct_attribs)); + replaced_structs[ast_str] = new_struct; + return new_struct; + }); + + ctx.ReplaceAll([&](const ast::CallExpression* ast_call) -> const ast::CallExpression* { + if (ast_call->args.Length() == 0) { + return nullptr; + } + + auto* call = sem.Get(ast_call); + if (!call) { + return nullptr; + } + auto* cons = call->Target()->As(); + if (!cons) { + return nullptr; + } + auto* str = cons->ReturnType()->As(); + if (!str) { + return nullptr; + } + + auto* new_struct = replaced_structs[str->Declaration()]; + if (!new_struct) { + return nullptr; + } + + tint::Vector new_args; + + auto arg = ast_call->args.begin(); + for (auto* member : new_struct->members) { + if (padding_members.Contains(member)) { + new_args.Push(b.Expr(0_u)); + } else { + new_args.Push(ctx.Clone(*arg)); + ++arg; + } + } + return b.Call(CreateASTTypeFor(ctx, str), new_args); + }); + + ctx.Clone(); + return resolver::Resolve(b); +} + +} // namespace tint::glsl::writer diff --git a/references/writer/ast_raise/pad_structs.h b/references/writer/ast_raise/pad_structs.h new file mode 100644 index 00000000..3f2acbe9 --- /dev/null +++ b/references/writer/ast_raise/pad_structs.h @@ -0,0 +1,56 @@ +// Copyright 2022 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef SRC_TINT_LANG_GLSL_WRITER_AST_RAISE_PAD_STRUCTS_H_ +#define SRC_TINT_LANG_GLSL_WRITER_AST_RAISE_PAD_STRUCTS_H_ + +#include "src/tint/lang/wgsl/ast/transform/transform.h" + +namespace tint::glsl::writer { + +/// This transform turns all explicit alignment and sizing into padding +/// members of structs. This is required for GLSL ES, since it not support +/// the offset= decoration. +/// +/// @note This transform requires the CanonicalizeEntryPointIO transform to have been run first. +class PadStructs final : public Castable { + public: + /// Constructor + PadStructs(); + + /// Destructor + ~PadStructs() override; + + /// @copydoc ast::transform::Transform::Apply + ApplyResult Apply(const Program& program, + const ast::transform::DataMap& inputs, + ast::transform::DataMap& outputs) const override; +}; + +} // namespace tint::glsl::writer + +#endif // SRC_TINT_LANG_GLSL_WRITER_AST_RAISE_PAD_STRUCTS_H_ diff --git a/references/writer/ast_raise/pad_structs_test.cc b/references/writer/ast_raise/pad_structs_test.cc new file mode 100644 index 00000000..91107c55 --- /dev/null +++ b/references/writer/ast_raise/pad_structs_test.cc @@ -0,0 +1,757 @@ +// Copyright 2022 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_raise/pad_structs.h" + +#include +#include + +#include "src/tint/lang/wgsl/ast/transform/helper_test.h" + +namespace tint::glsl::writer { +namespace { + +using PadStructsTest = ast::transform::TransformTest; + +TEST_F(PadStructsTest, EmptyModule) { + auto* src = ""; + auto* expect = src; + + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(PadStructsTest, Uniform) { + auto* src = R"( +struct S { + x : i32, +} + +@group(0) @binding(0) var u : S; + +fn main() { + let x = u.x; +} +)"; + auto* expect = R"( +@internal(disable_validation__ignore_struct_member) +struct S { + x : i32, + pad : u32, + pad_1 : u32, + pad_2 : u32, +} + +@group(0) @binding(0) var u : S; + +fn main() { + let x = u.x; +} +)"; + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(PadStructsTest, Size) { + auto* src = R"( +struct S { + @size(12) + x : i32, + y : i32, +} + +@group(0) @binding(0) var u : S; + +fn main() { + let x = u.x; +} +)"; + auto* expect = R"( +@internal(disable_validation__ignore_struct_member) +struct S { + x : i32, + pad : u32, + pad_1 : u32, + y : i32, +} + +@group(0) @binding(0) var u : S; + +fn main() { + let x = u.x; +} +)"; + + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(PadStructsTest, SizeUniformAndPrivate) { + auto* src = R"( +struct S { + @size(12) + x : i32, + y : i32, +} + +@group(0) @binding(0) var u : S; + +var p : S; + +fn main() { + p.x = u.x; +} +)"; + auto* expect = R"( +@internal(disable_validation__ignore_struct_member) +struct S { + x : i32, + pad : u32, + pad_1 : u32, + y : i32, +} + +@group(0) @binding(0) var u : S; + +var p : S; + +fn main() { + p.x = u.x; +} +)"; + + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(PadStructsTest, SizeStorageAndPrivate) { + auto* src = R"( +struct S { + @size(12) + x : i32, + y : i32, +} + +@group(0) @binding(0) var s : S; + +var p : S; + +fn main() { + p.x = 123; + s.x = p.x; +} +)"; + auto* expect = R"( +@internal(disable_validation__ignore_struct_member) +struct S { + x : i32, + pad : u32, + pad_1 : u32, + y : i32, +} + +@group(0) @binding(0) var s : S; + +var p : S; + +fn main() { + p.x = 123; + s.x = p.x; +} +)"; + + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(PadStructsTest, SizeUniformAndStorage) { + auto* src = R"( +struct S { + @size(12) + x : i32, + y : i32, +} + +@group(0) @binding(0) var u : S; + +@group(0) @binding(1) var s : S; + +fn main() { + s.x = u.x; +} +)"; + auto* expect = R"( +@internal(disable_validation__ignore_struct_member) +struct S { + x : i32, + pad : u32, + pad_1 : u32, + y : i32, +} + +@group(0) @binding(0) var u : S; + +@group(0) @binding(1) var s : S; + +fn main() { + s.x = u.x; +} +)"; + + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(PadStructsTest, SizePrivateOnly) { + // Structs that are not host-visible should have no explicit padding. + auto* src = R"( +struct S { + @size(12) + x : i32, + y : i32, +} + +var p : S; + +fn main() { + p.x = 123; +} +)"; + auto* expect = R"( +struct S { + @size(12) + x : i32, + y : i32, +} + +var p : S; + +fn main() { + p.x = 123; +} +)"; + + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(PadStructsTest, AlignUniformAndPrivate) { + auto* src = R"( +struct S { + a : i32, + @align(16) + b : i32, +} + +@group(0) @binding(0) var u : S; + +var p : S; + +fn main() { + p.a = u.b; + p.b = u.a; +} +)"; + auto* expect = R"( +@internal(disable_validation__ignore_struct_member) +struct S { + a : i32, + pad : u32, + pad_1 : u32, + pad_2 : u32, + b : i32, + pad_3 : u32, + pad_4 : u32, + pad_5 : u32, +} + +@group(0) @binding(0) var u : S; + +var p : S; + +fn main() { + p.a = u.b; + p.b = u.a; +} +)"; + + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(PadStructsTest, AlignStorageAndPrivate) { + auto* src = R"( +struct S { + a : i32, + @align(16) + b : i32, +} + +@group(0) @binding(0) var s : S; + +var p : S; + +fn main() { + p.a = 123; + p.b = 321; + s.a = p.b; + s.b = p.a; +} +)"; + auto* expect = R"( +@internal(disable_validation__ignore_struct_member) +struct S { + a : i32, + pad : u32, + pad_1 : u32, + pad_2 : u32, + b : i32, + pad_3 : u32, + pad_4 : u32, + pad_5 : u32, +} + +@group(0) @binding(0) var s : S; + +var p : S; + +fn main() { + p.a = 123; + p.b = 321; + s.a = p.b; + s.b = p.a; +} +)"; + + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(PadStructsTest, AlignUniformAndStorage) { + auto* src = R"( +struct S { + a : i32, + @align(16) + b : i32, +} + +@group(0) @binding(0) var u : S; + +@group(0) @binding(1) var s : S; + +fn main() { + s.a = u.b; + s.b = u.a; +} +)"; + auto* expect = R"( +@internal(disable_validation__ignore_struct_member) +struct S { + a : i32, + pad : u32, + pad_1 : u32, + pad_2 : u32, + b : i32, + pad_3 : u32, + pad_4 : u32, + pad_5 : u32, +} + +@group(0) @binding(0) var u : S; + +@group(0) @binding(1) var s : S; + +fn main() { + s.a = u.b; + s.b = u.a; +} +)"; + + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(PadStructsTest, AlignPrivateOnly) { + // Structs that are not host-visible should have no explicit padding. + auto* src = R"( +struct S { + a : i32, + @align(16) + b : i32, +} + +var p : S; + +fn main() { + p.a = 123; + p.b = 321; +} +)"; + auto* expect = R"( +struct S { + a : i32, + @align(16) + b : i32, +} + +var p : S; + +fn main() { + p.a = 123; + p.b = 321; +} +)"; + + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(PadStructsTest, LastMemberRuntimeSizeArray) { + // Structs with runtime-sized arrays should not be padded after the + // last member. + auto* src = R"( +struct T { + a : f32, + b : i32, +} + +struct S { + a : vec4, + b : array, +} + +@group(0) @binding(0) var s : S; + +fn main() { + s.b[0] = T(1.0f, 23); +} +)"; + auto* expect = R"( +struct T { + a : f32, + b : i32, +} + +struct S { + a : vec4, + b : array, +} + +@group(0) @binding(0) var s : S; + +fn main() { + s.b[0] = T(1.0f, 23); +} +)"; + + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(PadStructsTest, LastMemberFixedSizeArray) { + // Structs without runtime-sized arrays should be padded after the last + // member. + auto* src = R"( +struct T { + a : f32, + b : i32, +} + +struct S { + a : vec4, + b : array, +} + +@group(0) @binding(0) var s : S; + +fn main() { + s.b[0] = T(1.0f, 23); +} +)"; + auto* expect = R"( +struct T { + a : f32, + b : i32, +} + +@internal(disable_validation__ignore_struct_member) +struct S { + a : vec4, + b : array, + pad : u32, + pad_1 : u32, +} + +@group(0) @binding(0) var s : S; + +fn main() { + s.b[0] = T(1.0f, 23); +} +)"; + + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(PadStructsTest, Initializer) { + // Calls to a initializer of a padded struct must be modified to initialize the padding. + auto* src = R"( +struct S { + a : f32, + @align(8) + b : i32, +} + +@group(0) @binding(0) var s : S; + +fn main() { + s = S(1.0f, 2); +} +)"; + auto* expect = R"( +@internal(disable_validation__ignore_struct_member) +struct S { + a : f32, + pad : u32, + b : i32, + pad_1 : u32, +} + +@group(0) @binding(0) var s : S; + +fn main() { + s = S(1.0f, 0u, 2, 0u); +} +)"; + + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(PadStructsTest, MemberNamedPad) { + auto* src = R"( +struct S { + @align(8) pad_5 : u32, + @align(8) pad_3 : u32, + @align(8) pad : u32, + @align(8) pad_1 : u32, +} + +@group(0) @binding(0) var s : S; + +fn main() { + _ = s; +} +)"; + auto* expect = R"( +@internal(disable_validation__ignore_struct_member) +struct S { + pad_5 : u32, + pad_2 : u32, + pad_3 : u32, + pad_4 : u32, + pad : u32, + pad_6 : u32, + pad_1 : u32, + pad_7 : u32, +} + +@group(0) @binding(0) var s : S; + +fn main() { + _ = s; +} +)"; + + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(PadStructsTest, TwoStructs) { + auto* src = R"( +struct S { + @align(8) i : i32, + @align(8) u : u32, +} + +struct T { + @align(8) i : i32, + @align(8) u : u32, +} + +@group(0) @binding(0) var s : S; +@group(0) @binding(1) var t : T; + +fn main() { + _ = s; + _ = t; +} +)"; + auto* expect = R"( +@internal(disable_validation__ignore_struct_member) +struct S { + i : i32, + pad : u32, + u : u32, + pad_1 : u32, +} + +@internal(disable_validation__ignore_struct_member) +struct T { + i : i32, + pad : u32, + u : u32, + pad_1 : u32, +} + +@group(0) @binding(0) var s : S; + +@group(0) @binding(1) var t : T; + +fn main() { + _ = s; + _ = t; +} +)"; + + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(PadStructsTest, GlobalVariableNamedPad) { + auto* src = R"( +@group(0) @binding(0) var pad : u32; +@group(0) @binding(1) var pad_1 : u32; + +struct S { + @align(8) i : u32, + @align(8) j : u32, +} + +@group(0) @binding(1) var s : S; + +fn main() { + _ = s; +} +)"; + auto* expect = R"( +@group(0) @binding(0) var pad : u32; + +@group(0) @binding(1) var pad_1 : u32; + +@internal(disable_validation__ignore_struct_member) +struct S { + i : u32, + pad : u32, + j : u32, + pad_1 : u32, +} + +@group(0) @binding(1) var s : S; + +fn main() { + _ = s; +} +)"; + + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(PadStructsTest, InitializerZeroArgs) { + // Calls to a zero-argument initializer of a padded struct should not be modified. + auto* src = R"( +struct S { + a : f32, + @align(8) + b : i32, +} + +@group(0) @binding(0) var s : S; + +fn main() { + s = S(); +} +)"; + auto* expect = R"( +@internal(disable_validation__ignore_struct_member) +struct S { + a : f32, + pad : u32, + b : i32, + pad_1 : u32, +} + +@group(0) @binding(0) var s : S; + +fn main() { + s = S(); +} +)"; + + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_raise/texture_1d_to_2d.cc b/references/writer/ast_raise/texture_1d_to_2d.cc new file mode 100644 index 00000000..df5452aa --- /dev/null +++ b/references/writer/ast_raise/texture_1d_to_2d.cc @@ -0,0 +1,204 @@ +// Copyright 2022 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_raise/texture_1d_to_2d.h" + +#include + +#include "src/tint/lang/core/type/texture_dimension.h" +#include "src/tint/lang/wgsl/program/clone_context.h" +#include "src/tint/lang/wgsl/program/program_builder.h" +#include "src/tint/lang/wgsl/resolver/resolve.h" +#include "src/tint/lang/wgsl/sem/function.h" +#include "src/tint/lang/wgsl/sem/statement.h" +#include "src/tint/lang/wgsl/sem/type_expression.h" +#include "src/tint/utils/rtti/switch.h" + +TINT_INSTANTIATE_TYPEINFO(tint::glsl::writer::Texture1DTo2D); + +using namespace tint::core::number_suffixes; // NOLINT + +namespace tint::glsl::writer { + +namespace { + +bool ShouldRun(const Program& program) { + for (auto* fn : program.AST().Functions()) { + if (auto* sem_fn = program.Sem().Get(fn)) { + for (auto* builtin : sem_fn->DirectlyCalledBuiltins()) { + const auto& signature = builtin->Signature(); + auto texture = signature.Parameter(core::ParameterUsage::kTexture); + if (texture) { + auto* tex = texture->Type()->As(); + if (tex->dim() == core::type::TextureDimension::k1d) { + return true; + } + } + } + } + } + for (auto* var : program.AST().GlobalVariables()) { + if (Switch( + program.Sem().Get(var)->Type()->UnwrapRef(), + [&](const core::type::SampledTexture* tex) { + return tex->dim() == core::type::TextureDimension::k1d; + }, + [&](const core::type::StorageTexture* storage_tex) { + return storage_tex->dim() == core::type::TextureDimension::k1d; + })) { + return true; + } + } + return false; +} + +} // namespace + +/// PIMPL state for the transform +struct Texture1DTo2D::State { + /// The source program + const Program& src; + /// The target program builder + ProgramBuilder b; + /// The clone context + program::CloneContext ctx = {&b, &src, /* auto_clone_symbols */ true}; + + /// Constructor + /// @param program the source program + explicit State(const Program& program) : src(program) {} + + /// Runs the transform + /// @returns the new program or SkipTransform if the transform is not required + ApplyResult Run() { + auto& sem = src.Sem(); + + if (!ShouldRun(src)) { + return SkipTransform; + } + + auto create_var = [&](const ast::Variable* v, ast::Type type) -> const ast::Variable* { + if (v->As()) { + return ctx.dst->Param(ctx.Clone(v->name->symbol), type, ctx.Clone(v->attributes)); + } else { + return ctx.dst->Var(ctx.Clone(v->name->symbol), type, ctx.Clone(v->attributes)); + } + }; + + ctx.ReplaceAll([&](const ast::Variable* v) -> const ast::Variable* { + const ast::Variable* r = Switch( + sem.Get(v)->Type()->UnwrapRef(), + [&](const core::type::SampledTexture* tex) -> const ast::Variable* { + if (tex->dim() == core::type::TextureDimension::k1d) { + auto type = ctx.dst->ty.sampled_texture(core::type::TextureDimension::k2d, + CreateASTTypeFor(ctx, tex->type())); + return create_var(v, type); + } else { + return nullptr; + } + }, + [&](const core::type::StorageTexture* storage_tex) -> const ast::Variable* { + if (storage_tex->dim() == core::type::TextureDimension::k1d) { + auto type = ctx.dst->ty.storage_texture(core::type::TextureDimension::k2d, + storage_tex->texel_format(), + storage_tex->access()); + return create_var(v, type); + } else { + return nullptr; + } + }, + [](Default) { return nullptr; }); + return r; + }); + + ctx.ReplaceAll([&](const ast::CallExpression* c) -> const ast::Expression* { + auto* call = sem.Get(c)->UnwrapMaterialize()->As(); + if (!call) { + return nullptr; + } + auto* builtin = call->Target()->As(); + if (!builtin) { + return nullptr; + } + const auto& signature = builtin->Signature(); + auto* texture = signature.Parameter(core::ParameterUsage::kTexture); + if (!texture) { + return nullptr; + } + auto* tex = texture->Type()->As(); + if (tex->dim() != core::type::TextureDimension::k1d) { + return nullptr; + } + + if (builtin->Fn() == wgsl::BuiltinFn::kTextureDimensions) { + auto* new_call = ctx.CloneWithoutTransform(c); + return ctx.dst->MemberAccessor(new_call, "x"); + } + + auto coords_index = signature.IndexOf(core::ParameterUsage::kCoords); + if (coords_index == -1) { + return nullptr; + } + + tint::Vector args; + int index = 0; + for (auto* arg : c->args) { + if (index == coords_index) { + auto* ctype = call->Arguments()[static_cast(coords_index)]->Type(); + auto* coords = c->args[static_cast(coords_index)]; + + const ast::LiteralExpression* half = nullptr; + if (ctype->is_integer_scalar()) { + half = ctx.dst->Expr(0_a); + } else { + half = ctx.dst->Expr(0.5_a); + } + args.Push( + ctx.dst->vec(CreateASTTypeFor(ctx, ctype), 2u, ctx.Clone(coords), half)); + } else { + args.Push(ctx.Clone(arg)); + } + index++; + } + return ctx.dst->Call(ctx.Clone(c->target), args); + }); + + ctx.Clone(); + return resolver::Resolve(b); + } +}; + +Texture1DTo2D::Texture1DTo2D() = default; + +Texture1DTo2D::~Texture1DTo2D() = default; + +ast::transform::Transform::ApplyResult Texture1DTo2D::Apply(const Program& src, + const ast::transform::DataMap&, + ast::transform::DataMap&) const { + return State(src).Run(); +} + +} // namespace tint::glsl::writer diff --git a/references/writer/ast_raise/texture_1d_to_2d.h b/references/writer/ast_raise/texture_1d_to_2d.h new file mode 100644 index 00000000..53237082 --- /dev/null +++ b/references/writer/ast_raise/texture_1d_to_2d.h @@ -0,0 +1,56 @@ +// Copyright 2022 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef SRC_TINT_LANG_GLSL_WRITER_AST_RAISE_TEXTURE_1D_TO_2D_H_ +#define SRC_TINT_LANG_GLSL_WRITER_AST_RAISE_TEXTURE_1D_TO_2D_H_ + +#include "src/tint/lang/wgsl/ast/transform/transform.h" + +namespace tint::glsl::writer { + +/// This transform converts all 1D texture types and accesses to 2D. +/// This is required for GLSL ES, which does not support 1D textures. +class Texture1DTo2D final : public Castable { + public: + /// Constructor + Texture1DTo2D(); + + /// Destructor + ~Texture1DTo2D() override; + + /// @copydoc ast::transform::Transform::Apply + ApplyResult Apply(const Program& program, + const ast::transform::DataMap& inputs, + ast::transform::DataMap& outputs) const override; + + private: + struct State; +}; + +} // namespace tint::glsl::writer + +#endif // SRC_TINT_LANG_GLSL_WRITER_AST_RAISE_TEXTURE_1D_TO_2D_H_ diff --git a/references/writer/ast_raise/texture_1d_to_2d_test.cc b/references/writer/ast_raise/texture_1d_to_2d_test.cc new file mode 100644 index 00000000..7441fe32 --- /dev/null +++ b/references/writer/ast_raise/texture_1d_to_2d_test.cc @@ -0,0 +1,318 @@ +// Copyright 2022 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_raise/texture_1d_to_2d.h" + +#include "src/tint/lang/wgsl/ast/transform/helper_test.h" + +namespace tint::glsl::writer { +namespace { + +using Texture1DTo2DTest = ast::transform::TransformTest; + +TEST_F(Texture1DTo2DTest, EmptyModule) { + auto* src = ""; + + ast::transform::DataMap data; + EXPECT_FALSE(ShouldRun(src, data)); +} + +TEST_F(Texture1DTo2DTest, Global1DDecl) { + auto* src = R"( +@group(0) @binding(0) var t : texture_1d; + +@group(0) @binding(1) var s : sampler; +)"; + auto* expect = R"( +@group(0) @binding(0) var t : texture_2d; + +@group(0) @binding(1) var s : sampler; +)"; + + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(Texture1DTo2DTest, Global1DDeclAndSample) { + auto* src = R"( +@group(0) @binding(0) var t : texture_1d; + +@group(0) @binding(1) var s : sampler; + +fn main() -> vec4 { + return textureSample(t, s, 0.5); +} +)"; + auto* expect = R"( +@group(0) @binding(0) var t : texture_2d; + +@group(0) @binding(1) var s : sampler; + +fn main() -> vec4 { + return textureSample(t, s, vec2(0.5, 0.5)); +} +)"; + + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(Texture1DTo2DTest, Global1DDeclAndLoad) { + auto* src = R"( +@group(0) @binding(0) var t : texture_1d; + +fn main() -> vec4 { + return textureLoad(t, 1, 0); +} +)"; + auto* expect = R"( +@group(0) @binding(0) var t : texture_2d; + +fn main() -> vec4 { + return textureLoad(t, vec2(1, 0), 0); +} +)"; + + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(Texture1DTo2DTest, Global1DDeclAndTextureDimensions) { + auto* src = R"( +@group(0) @binding(0) var t : texture_1d; + +fn main() -> u32 { + return textureDimensions(t); +} +)"; + auto* expect = R"( +@group(0) @binding(0) var t : texture_2d; + +fn main() -> u32 { + return textureDimensions(t).x; +} +)"; + + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(Texture1DTo2DTest, Global1DDeclAndTextureNumLevels) { + auto* src = R"( +@group(0) @binding(0) var t : texture_1d; + +fn main() -> u32 { + return textureNumLevels(t); +} +)"; + auto* expect = R"( +@group(0) @binding(0) var t : texture_2d; + +fn main() -> u32 { + return textureNumLevels(t); +} +)"; + + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(Texture1DTo2DTest, Global1DDeclAndTextureDimensionsInCallStmt) { + auto* src = R"( +@group(0) @binding(0) var st : texture_storage_1d; + +@group(0) @binding(1) var t : texture_1d; + +fn main() { + textureStore(st, clamp(5, 0, i32((textureDimensions(st) - 1))), vec4u(0, 0, 0, 0)); +} +)"; + auto* expect = R"( +@group(0) @binding(0) var st : texture_storage_2d; + +@group(0) @binding(1) var t : texture_2d; + +fn main() { + textureStore(st, vec2(clamp(5, 0, i32((textureDimensions(st).x - 1))), 0), vec4u(0, 0, 0, 0)); +} +)"; + + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(Texture1DTo2DTest, GlobalStorage1DDecl) { + auto* src = R"( +@group(0) @binding(0) var t : texture_storage_1d; +)"; + auto* expect = R"( +@group(0) @binding(0) var t : texture_storage_2d; +)"; + + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(Texture1DTo2DTest, Global2DDeclAndSample) { + auto* src = R"( +@group(0) @binding(0) var t : texture_2d; + +@group(0) @binding(1) var s : sampler; + +fn main() -> vec4 { + return textureSample(t, s, vec2(0.5, 1.5)); +} +)"; + + ast::transform::DataMap data; + EXPECT_FALSE(ShouldRun(src, data)); +} + +TEST_F(Texture1DTo2DTest, PrivateIntNoop) { + auto* src = R"( +var i : i32; +)"; + + ast::transform::DataMap data; + EXPECT_FALSE(ShouldRun(src, data)); +} + +TEST_F(Texture1DTo2DTest, GlobalMatrixNoop) { + auto* src = R"( +@group(0) @binding(0) var m : mat2x2; +)"; + + ast::transform::DataMap data; + EXPECT_FALSE(ShouldRun(src, data)); +} + +TEST_F(Texture1DTo2DTest, Texture1DFuncParam) { + auto* src = R"( +@group(0) @binding(0) var tex : texture_1d; + +@group(0) @binding(1) var samp : sampler; + +fn f(t : texture_1d, s : sampler) -> vec4 { + return textureSample(t, s, 0.7); +} + +fn main() -> vec4 { + return f(tex, samp); +} +)"; + auto* expect = R"( +@group(0) @binding(0) var tex : texture_2d; + +@group(0) @binding(1) var samp : sampler; + +fn f(t : texture_2d, s : sampler) -> vec4 { + return textureSample(t, s, vec2(0.69999999999999995559, 0.5)); +} + +fn main() -> vec4 { + return f(tex, samp); +} +)"; + + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(Texture1DTo2DTest, TextureStorage1DFuncParam) { + auto* src = R"( +@group(0) @binding(0) var tex : texture_storage_1d; + +fn f(t : texture_storage_1d) { + textureStore(t, 3, vec4(42.0, 21.0, 84.0, 10.5)); +} + +fn main() { + f(tex); +} +)"; + + auto* expect = R"( +@group(0) @binding(0) var tex : texture_storage_2d; + +fn f(t : texture_storage_2d) { + textureStore(t, vec2(3, 0), vec4(42.0, 21.0, 84.0, 10.5)); +} + +fn main() { + f(tex); +} +)"; + + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(Texture1DTo2DTest, TextureAndNonTextureBuiltin) { + auto* src = R"( +@group(0) @binding(0) var tex : texture_1d; + +fn d() { + _ = textureLoad(tex, 1, 0); + let l = sin(3.0); +} +)"; + + auto* expect = R"( +@group(0) @binding(0) var tex : texture_2d; + +fn d() { + _ = textureLoad(tex, vec2(1, 0), 0); + let l = sin(3.0); +} +)"; + + ast::transform::DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/ast_raise/texture_builtins_from_uniform.cc b/references/writer/ast_raise/texture_builtins_from_uniform.cc new file mode 100644 index 00000000..450499a4 --- /dev/null +++ b/references/writer/ast_raise/texture_builtins_from_uniform.cc @@ -0,0 +1,426 @@ +// Copyright 2023 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_raise/texture_builtins_from_uniform.h" + +#include +#include +#include +#include + +#include "src/tint/lang/wgsl/program/clone_context.h" +#include "src/tint/lang/wgsl/program/program_builder.h" +#include "src/tint/lang/wgsl/resolver/resolve.h" +#include "src/tint/lang/wgsl/sem/call.h" +#include "src/tint/lang/wgsl/sem/function.h" +#include "src/tint/lang/wgsl/sem/module.h" +#include "src/tint/lang/wgsl/sem/statement.h" +#include "src/tint/lang/wgsl/sem/variable.h" + +#include "src/tint/utils/containers/hashmap.h" +#include "src/tint/utils/containers/vector.h" +#include "src/tint/utils/rtti/switch.h" + +TINT_INSTANTIATE_TYPEINFO(tint::glsl::writer::TextureBuiltinsFromUniform); +TINT_INSTANTIATE_TYPEINFO(tint::glsl::writer::TextureBuiltinsFromUniform::Config); + +namespace tint::glsl::writer { + +namespace { + +/// The member name of the texture builtin values. +constexpr std::string_view kTextureBuiltinValuesMemberNamePrefix = "texture_builtin_value_"; + +} // namespace + +TextureBuiltinsFromUniform::TextureBuiltinsFromUniform() = default; + +TextureBuiltinsFromUniform::~TextureBuiltinsFromUniform() = default; + +/// PIMPL state for the transform +struct TextureBuiltinsFromUniform::State { + /// Constructor + /// @param program the source program + /// @param in the input transform data + explicit State(const Program& program, const ast::transform::DataMap& in) + : src(program), inputs(in) {} + + /// Runs the transform + /// @returns the new program or SkipTransform if the transform is not required + ApplyResult Run() { + auto* cfg = inputs.Get(); + if (cfg == nullptr) { + b.Diagnostics().AddError(Source{}) + << "missing transform data for " + << tint::TypeInfo::Of().name; + return resolver::Resolve(b); + } + ubo_bindingpoint_ordering = cfg->ubo_bindingpoint_ordering; + + // If there's no interested texture builtin at all, skip the transform. + if (ubo_bindingpoint_ordering.empty()) { + return SkipTransform; + } + + // The dependency order declarations guaranteed that we traverse interested functions in the + // following order: + // 1. texture builtins + // 2. user function directly calls texture builtins + // 3. user function calls 2. + // 4. user function calls 3. + // ... + // n. entry point function. + for (auto* fn_decl : sem.Module()->DependencyOrderedDeclarations()) { + if (auto* fn = sem.Get(fn_decl)) { + for (auto* call : fn->DirectCalls()) { + auto* call_expr = call->Declaration(); + + tint::Switch( + call->Target(), + [&](const sem::BuiltinFn* builtin) { + if (builtin->Fn() != wgsl::BuiltinFn::kTextureNumLevels && + builtin->Fn() != wgsl::BuiltinFn::kTextureNumSamples) { + return; + } + if (auto* call_stmt = + call->Stmt()->Declaration()->As()) { + if (call_stmt->expr == call->Declaration()) { + // textureNumLevels() / textureNumSamples() is used as a + // statement. The argument expression must be side-effect free, + // so just drop the statement. + RemoveStatement(ctx, call_stmt); + return; + } + } + + auto* texture_expr = call->Declaration()->args[0]; + auto* texture_sem = sem.GetVal(texture_expr)->RootIdentifier(); + TINT_ASSERT(texture_sem); + + tint::Switch( + texture_sem, + [&](const sem::GlobalVariable* global) { + // This texture variable is a global variable. + auto binding = GetGlobalBinding(global); + // Record the call and binding to be replaced later. + builtin_to_replace.Add(call_expr, binding); + }, + [&](const sem::Variable* variable) { + // This texture variable is a user function parameter. + auto new_param = GetAndRecordFunctionParameter(fn, variable); + // Record the call and new_param to be replaced later. + builtin_to_replace.Add(call_expr, new_param); + }, // + TINT_ICE_ON_NO_MATCH); + }, + [&](const sem::Function* user_fn) { + auto user_param_to_info = fn_to_data.Get(user_fn); + if (!user_param_to_info) { + // Uninterested function not calling texture builtins with function + // texture param. + return; + } + TINT_ASSERT(call->Arguments().Length() == + user_fn->Declaration()->params.Length()); + for (size_t i = 0; i < call->Arguments().Length(); i++) { + auto param = user_fn->Declaration()->params[i]; + if (auto info = user_param_to_info->Get(param)) { + auto* arg = call->Arguments()[i]; + auto* texture_sem = arg->RootIdentifier(); + auto& args = call_to_data.GetOrAdd(call_expr, [&] { + return Vector< + std::variant, 4>(); + }); + + tint::Switch( + texture_sem, + [&](const sem::GlobalVariable* global) { + // This texture variable is a global variable. + auto binding = GetGlobalBinding(global); + // Record the binding to add to args. + args.Push(binding); + }, + [&](const sem::Variable* variable) { + // This texture variable is a user function parameter. + auto new_param = + GetAndRecordFunctionParameter(fn, variable); + // Record adding extra function parameter + args.Push(new_param); + }, // + TINT_ICE_ON_NO_MATCH); + } + } + }); + } + } + } + + // If any functions need extra params, add them now. + if (!fn_to_data.IsEmpty()) { + for (auto& pair : fn_to_data) { + auto* fn = pair.key.Value(); + + // Reorder the param to a vector to make sure params are in the correct order. + Vector extra_params_in_order; + extra_params_in_order.Resize(pair.value.Count()); + for (auto t_p : pair.value) { + TINT_ASSERT(t_p.value.extra_idx < extra_params_in_order.Length()); + extra_params_in_order[t_p.value.extra_idx] = t_p.value.param; + } + + for (auto p : extra_params_in_order) { + ctx.InsertBack(fn->Declaration()->params, p); + } + } + } + + // Replace all interested texture builtin calls. + for (auto& pair : builtin_to_replace) { + auto call = pair.key.Value(); + if (std::holds_alternative(pair.value)) { + // This texture is a global variable with binding point. + // Read builtin value from uniform buffer. + auto* builtin_value = GetUniformValue(std::get(pair.value)); + ctx.Replace(call, builtin_value); + } else { + // Otherwise this value comes from a function param + auto* param = std::get(pair.value); + ctx.Replace(call, b.Expr(param)); + } + } + + // Insert all extra args to interested function calls. + for (auto& pair : call_to_data) { + auto call = pair.key.Value(); + for (auto new_arg_info : pair.value) { + if (std::holds_alternative(new_arg_info)) { + // This texture is a global variable with binding point. + // Read builtin value from uniform buffer. + auto* builtin_value = GetUniformValue(std::get(new_arg_info)); + ctx.InsertBack(call->args, builtin_value); + } else { + // Otherwise this value comes from a function param + auto* param = std::get(new_arg_info); + ctx.InsertBack(call->args, b.Expr(param)); + } + } + } + + ctx.Clone(); + return resolver::Resolve(b); + } + + private: + /// The source program + const Program& src; + /// The transform inputs + const ast::transform::DataMap& inputs; + /// The target program builder + ProgramBuilder b; + /// The clone context + program::CloneContext ctx = {&b, &src, /* auto_clone_symbols */ true}; + /// Alias to the semantic info in ctx.src + const sem::Info& sem = src.Sem(); + + /// Ordered list of binding points for where they appear in the UBO + std::vector ubo_bindingpoint_ordering; + + struct FunctionExtraParamInfo { + // The extra passed in param that corresponds to the texture param. + const ast::Parameter* param = nullptr; + + // id of this extra param e.g. f(t0, foo, t1, e0, e1) e0 and e1 are extra params, their + // extra_idx are 0 and 1. This is to help sort extra ids in the correct order. + size_t extra_idx = 0; + }; + + /// Store a map from function to a collection of extra params that need adding. + /// The value of the map is made a map instead of a vector to make it easier to find the param. + /// for call sites. e.g. fn f(t: texture_2d) -> u32 { + /// return textureNumLevels(t); + /// } + /// -> + /// fn f(t : texture_2d, tint_symbol : u32) -> u32 { + /// return tint_symbol; + /// } + Hashmap, 8> + fn_to_data; + + /// For each callsite of the above functions, record a vector of extra call args that need + /// inserting. e.g. f(tex) + /// -> + /// f(tex, internal_uniform.texture_builtin_value), if tex is from a global + /// variable, store the BindingPoint. or f(tex, extra_param_tex), if tex is from a function + /// param, store the texture function parameter pointer. + Hashmap, 4>, + 8> + call_to_data; + + /// Texture builtin calls to be replaced by either uniform values or function parameters. + Hashmap, 8> + builtin_to_replace; + + /// A map from global texture bindpoint to the symbol storing its builtin value in the uniform + /// buffer struct. + Hashmap bindpoint_to_syms; + + /// The internal uniform buffer + const ast::Variable* ubo = nullptr; + + /// Get or create a UBO including u32 scalars for texture builtin values. + /// @returns the symbol of the uniform buffer variable. + Symbol GetUboSym() { + if (ubo) { + // Already created + return ubo->name->symbol; + } + + auto* cfg = inputs.Get(); + + Vector new_members; + new_members.Resize(ubo_bindingpoint_ordering.size()); + + for (size_t i = 0; i < ubo_bindingpoint_ordering.size(); ++i) { + // Append the vector index with the variable name to avoid unstable naming issue. + auto sym = b.Symbols().New(std::string(kTextureBuiltinValuesMemberNamePrefix) + + std::to_string(i)); + bindpoint_to_syms.Add(ubo_bindingpoint_ordering[i], sym); + new_members[i] = b.Member(sym, b.ty.u32()); + } + + // Find if there's any existing global variable using the same cfg->ubo_binding + for (auto* var : src.AST().Globals()) { + if (var->HasBindingPoint()) { + auto* global_sem = sem.Get(var); + + // The original binding point + BindingPoint binding_point = *global_sem->Attributes().binding_point; + + if (binding_point == cfg->ubo_binding) { + // This ubo_binding struct already exists. + // which should only be added by other *FromUniform transforms. + // Replace it with a new struct including the new_member. + // Then remove the old structure global declaration. + + ubo = var->As(); + + auto* ty = global_sem->Type()->UnwrapRef(); + auto* str = ty->As(); + if (TINT_UNLIKELY(!str)) { + TINT_ICE() + << "existing ubo binding " << cfg->ubo_binding << " is not a struct."; + } + + for (auto new_member : new_members) { + ctx.InsertBack(str->Declaration()->members, new_member); + } + return ctx.Clone(ubo->name->symbol); + } + } + } + + auto* buffer_struct = b.Structure(b.Sym(), std::move(new_members)); + ubo = b.GlobalVar(b.Sym(), b.ty.Of(buffer_struct), core::AddressSpace::kUniform, + b.Group(core::AInt(cfg->ubo_binding.group)), + b.Binding(core::AInt(cfg->ubo_binding.binding))); + return ubo->name->symbol; + } + + /// Get the expression of retrieving the builtin value from the uniform buffer. + /// @param binding of the global variable. + /// @returns an expression of the builtin value. + const ast::Expression* GetUniformValue(const BindingPoint& binding) { + // Make sure GetUboSym() is called first to initialize the uniform buffer struct. + auto ubo_sym = GetUboSym(); + + // Load the builtin value from the UBO. + auto member_sym = bindpoint_to_syms.Get(binding); + TINT_ASSERT(member_sym); + + return b.MemberAccessor(ubo_sym, *member_sym); + } + + /// Get and return the binding of the global texture variable. + /// @param global global variable of the texture variable. + /// @returns binding of the global variable. + BindingPoint GetGlobalBinding(const sem::GlobalVariable* global) { + return global->Attributes().binding_point.value(); + } + + /// Find which function param is the given texture variable. + /// Add a new u32 param relates to this texture param. Record in fn_to_data if first + /// visited. + /// @param fn the current function scope. + /// @param var the texture variable. + /// @returns the new u32 function parameter. + const ast::Parameter* GetAndRecordFunctionParameter(const sem::Function* fn, + const sem::Variable* var) { + auto& param_to_info = fn_to_data.GetOrAdd( + fn, [&] { return Hashmap(); }); + + const ast::Parameter* param = nullptr; + for (auto p : fn->Declaration()->params) { + if (p->As() == var->Declaration()) { + param = p; + break; + } + } + TINT_ASSERT(param); + + // Get or record a new u32 param to this function if first visited. + auto entry = param_to_info.Get(param); + if (entry) { + return entry->param; + } + + const ast::Parameter* new_param = b.Param(b.Sym(), b.ty.u32()); + size_t idx = param_to_info.Count(); + param_to_info.Add(param, FunctionExtraParamInfo{new_param, idx}); + return new_param; + } +}; + +ast::transform::Transform::ApplyResult TextureBuiltinsFromUniform::Apply( + const Program& src, + const ast::transform::DataMap& inputs, + ast::transform::DataMap&) const { + return State{src, inputs}.Run(); +} + +TextureBuiltinsFromUniform::Config::Config(BindingPoint ubo_bp, + const std::vector& ordering) + : ubo_binding(ubo_bp), ubo_bindingpoint_ordering(ordering) {} + +TextureBuiltinsFromUniform::Config::Config(const Config&) = default; + +TextureBuiltinsFromUniform::Config& TextureBuiltinsFromUniform::Config::operator=(const Config&) = + default; + +TextureBuiltinsFromUniform::Config::~Config() = default; + +} // namespace tint::glsl::writer diff --git a/references/writer/ast_raise/texture_builtins_from_uniform.h b/references/writer/ast_raise/texture_builtins_from_uniform.h new file mode 100644 index 00000000..550e0f83 --- /dev/null +++ b/references/writer/ast_raise/texture_builtins_from_uniform.h @@ -0,0 +1,115 @@ +// Copyright 2023 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef SRC_TINT_LANG_GLSL_WRITER_AST_RAISE_TEXTURE_BUILTINS_FROM_UNIFORM_H_ +#define SRC_TINT_LANG_GLSL_WRITER_AST_RAISE_TEXTURE_BUILTINS_FROM_UNIFORM_H_ + +#include + +#include "src/tint/api/common/binding_point.h" +#include "src/tint/lang/wgsl/ast/transform/transform.h" + +// Forward declarations +namespace tint { +class CloneContext; +} // namespace tint + +namespace tint::glsl::writer { + +/// TextureBuiltinsFromUniform is a transform that implements calls to textureNumLevels() and +/// textureNumSamples() by retrieving the texture information from a uniform buffer, as those +/// builtin functions are not available in some version of GLSL. +/// +/// The generated uniform buffer will have the form: +/// +/// ``` +/// struct internal_uniform { +/// texture_builtin_value_0 : u32, +/// }; +/// +/// @group(0) @binding(0) var tex : texture_2d; +/// ``` +/// +/// The binding group and number used for this uniform buffer are provided via +/// the `Config` transform input. +/// +/// The transform coverts the texture builtins calls into values lookup from the internal buffer. If +/// the texture is a function parameter instead of a global variable, this transform also takes care +/// of adding extra parameters and arguments to these functions and their call-sites. +/// +/// This transform must run before `CombineSamplers` transform so that the binding point of the +/// original texture object can be preserved. +/// +class TextureBuiltinsFromUniform final + : public Castable { + public: + /// Constructor + TextureBuiltinsFromUniform(); + /// Destructor + ~TextureBuiltinsFromUniform() override; + + /// Configuration options for the TextureBuiltinsFromUniform transform. + struct Config final : public Castable { + /// Constructor + /// @param ubo_bp the binding point to use for the generated uniform buffer. + /// @param ordering the ordered list of binding points to appear in the UBO + Config(BindingPoint ubo_bp, const std::vector& ordering); + + /// Copy constructor + Config(const Config&); + + /// Copy assignment + /// @return this Config + Config& operator=(const Config&); + + /// Destructor + ~Config() override; + + /// The binding point to use for the generated uniform buffer. + BindingPoint ubo_binding; + + /// The set of binding points which will be in the extra UBO buffer. The binding points are + /// provided in the order that the data will be in the UBO buffer. + /// + /// Note, a given `BindingPoint` _must_ only appear once in the ordering list. This works + /// because the two types of calls we're substituting `textureNumLevels` and + /// `textureNumSamples` work on a disjoint set of texture types. + std::vector ubo_bindingpoint_ordering; + }; + + /// @copydoc ast::transform::Transform::Apply + ApplyResult Apply(const Program& program, + const ast::transform::DataMap& inputs, + ast::transform::DataMap& outputs) const override; + + private: + struct State; +}; + +} // namespace tint::glsl::writer + +#endif // SRC_TINT_LANG_GLSL_WRITER_AST_RAISE_TEXTURE_BUILTINS_FROM_UNIFORM_H_ diff --git a/references/writer/ast_raise/texture_builtins_from_uniform_test.cc b/references/writer/ast_raise/texture_builtins_from_uniform_test.cc new file mode 100644 index 00000000..3ee97751 --- /dev/null +++ b/references/writer/ast_raise/texture_builtins_from_uniform_test.cc @@ -0,0 +1,575 @@ +// Copyright 2023 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/ast_raise/texture_builtins_from_uniform.h" + +#include + +#include "src/tint/lang/wgsl/ast/transform/helper_test.h" + +namespace tint::glsl::writer { +namespace { + +using TextureBuiltinsFromUniformTest = ast::transform::TransformTest; + +TEST_F(TextureBuiltinsFromUniformTest, Error_MissingTransformData) { + auto* src = R"( +@group(0) @binding(0) var t : texture_2d; + +@compute @workgroup_size(1) +fn main() { + var len : u32 = textureNumLevels(t); +} +)"; + + auto* expect = + "error: missing transform data for tint::glsl::writer::TextureBuiltinsFromUniform"; + + auto got = Run(src); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(TextureBuiltinsFromUniformTest, BasicTextureNumLevels) { + auto* src = R"( +@group(0) @binding(0) var t : texture_2d; + +@compute @workgroup_size(1) +fn main() { + var len : u32 = textureNumLevels(t); +} +)"; + + auto* expect = R"( +struct tint_symbol { + texture_builtin_value_0 : u32, +} + +@group(0) @binding(30) var tint_symbol_1 : tint_symbol; + +@group(0) @binding(0) var t : texture_2d; + +@compute @workgroup_size(1) +fn main() { + var len : u32 = tint_symbol_1.texture_builtin_value_0; +} +)"; + + std::vector bps = {{0, 0}}; + TextureBuiltinsFromUniform::Config cfg({0, 30u}, bps); + + ast::transform::DataMap data; + data.Add(std::move(cfg)); + + auto got = Run(src, data); + EXPECT_EQ(expect, str(got)); +} + +TEST_F(TextureBuiltinsFromUniformTest, BasicTextureNumSamples) { + auto* src = R"( +@group(0) @binding(0) var t : texture_multisampled_2d; + +@compute @workgroup_size(1) +fn main() { + var samples : u32 = textureNumSamples(t); +} +)"; + + auto* expect = R"( +struct tint_symbol { + texture_builtin_value_0 : u32, +} + +@group(0) @binding(30) var tint_symbol_1 : tint_symbol; + +@group(0) @binding(0) var t : texture_multisampled_2d; + +@compute @workgroup_size(1) +fn main() { + var samples : u32 = tint_symbol_1.texture_builtin_value_0; +} +)"; + + std::vector bps = {{0, 0}}; + TextureBuiltinsFromUniform::Config cfg({0, 30u}, bps); + + ast::transform::DataMap data; + data.Add(std::move(cfg)); + + auto got = Run(src, data); + EXPECT_EQ(expect, str(got)); +} + +TEST_F(TextureBuiltinsFromUniformTest, SameBuiltinCalledMultipleTimes) { + auto* src = R"( +@group(0) @binding(0) var tex : texture_2d; + +@compute @workgroup_size(1) +fn main() { + var len : u32 = textureNumLevels(tex); + len = textureNumLevels(tex); +} +)"; + + auto* expect = R"( +struct tint_symbol { + texture_builtin_value_0 : u32, +} + +@group(0) @binding(30) var tint_symbol_1 : tint_symbol; + +@group(0) @binding(0) var tex : texture_2d; + +@compute @workgroup_size(1) +fn main() { + var len : u32 = tint_symbol_1.texture_builtin_value_0; + len = tint_symbol_1.texture_builtin_value_0; +} +)"; + + std::vector bps = {{0, 0}}; + TextureBuiltinsFromUniform::Config cfg({0, 30u}, bps); + + ast::transform::DataMap data; + data.Add(std::move(cfg)); + + auto got = Run(src, data); + EXPECT_EQ(expect, str(got)); +} + +TEST_F(TextureBuiltinsFromUniformTest, SameBuiltinCalledMultipleTimesTextureNumSamples) { + auto* src = R"( +@group(0) @binding(0) var tex : texture_multisampled_2d; + +@compute @workgroup_size(1) +fn main() { + var len : u32 = textureNumSamples(tex); + len = textureNumSamples(tex); +} +)"; + + auto* expect = R"( +struct tint_symbol { + texture_builtin_value_0 : u32, +} + +@group(0) @binding(30) var tint_symbol_1 : tint_symbol; + +@group(0) @binding(0) var tex : texture_multisampled_2d; + +@compute @workgroup_size(1) +fn main() { + var len : u32 = tint_symbol_1.texture_builtin_value_0; + len = tint_symbol_1.texture_builtin_value_0; +} +)"; + + std::vector bps = {{0, 0}}; + TextureBuiltinsFromUniform::Config cfg({0, 30u}, bps); + + ast::transform::DataMap data; + data.Add(std::move(cfg)); + + auto got = Run(src, data); + EXPECT_EQ(expect, str(got)); +} + +TEST_F(TextureBuiltinsFromUniformTest, TextureAsFunctionParameterBasic) { + auto* src = R"( +@group(0) @binding(0) var tex : texture_2d; + +fn f(t: texture_2d) -> u32 { + return textureNumLevels(t); +} + +@compute @workgroup_size(1) +fn main() { + var len : u32 = f(tex); +} +)"; + + auto* expect = R"( +struct tint_symbol_1 { + texture_builtin_value_0 : u32, +} + +@group(0) @binding(30) var tint_symbol_2 : tint_symbol_1; + +@group(0) @binding(0) var tex : texture_2d; + +fn f(t : texture_2d, tint_symbol : u32) -> u32 { + return tint_symbol; +} + +@compute @workgroup_size(1) +fn main() { + var len : u32 = f(tex, tint_symbol_2.texture_builtin_value_0); +} +)"; + + std::vector bps = {{0, 0}}; + TextureBuiltinsFromUniform::Config cfg({0, 30u}, bps); + + ast::transform::DataMap data; + data.Add(std::move(cfg)); + + auto got = Run(src, data); + EXPECT_EQ(expect, str(got)); +} + +TEST_F(TextureBuiltinsFromUniformTest, TextureAsFunctionParameterUsedTwice) { + auto* src = R"( +@group(0) @binding(0) var tex : texture_2d; + +fn f(t: texture_2d) -> u32 { + var len = textureNumLevels(t); + len += textureNumLevels(t); + return len; +} + +@compute @workgroup_size(1) +fn main() { + var len : u32 = f(tex); +} +)"; + + auto* expect = R"( +struct tint_symbol_1 { + texture_builtin_value_0 : u32, +} + +@group(0) @binding(30) var tint_symbol_2 : tint_symbol_1; + +@group(0) @binding(0) var tex : texture_2d; + +fn f(t : texture_2d, tint_symbol : u32) -> u32 { + var len = tint_symbol; + len += tint_symbol; + return len; +} + +@compute @workgroup_size(1) +fn main() { + var len : u32 = f(tex, tint_symbol_2.texture_builtin_value_0); +} +)"; + + std::vector bps = {{0, 0}}; + TextureBuiltinsFromUniform::Config cfg({0, 30u}, bps); + + ast::transform::DataMap data; + data.Add(std::move(cfg)); + + auto got = Run(src, data); + EXPECT_EQ(expect, str(got)); +} + +TEST_F(TextureBuiltinsFromUniformTest, TextureAsFunctionParameterMultipleParameters) { + auto* src = R"( +@group(0) @binding(0) var tex1 : texture_2d; +@group(0) @binding(1) var tex2 : texture_2d; +@group(0) @binding(2) var tex3 : texture_2d; + +fn f(t1: texture_2d, t2: texture_2d, t3: texture_2d) -> u32 { + return textureNumLevels(t1) + textureNumLevels(t2) + textureNumLevels(t3); +} + +@compute @workgroup_size(1) +fn main() { + var len : u32 = f(tex1, tex2, tex3); +} +)"; + + auto* expect = R"( +struct tint_symbol_3 { + texture_builtin_value_0 : u32, + texture_builtin_value_1 : u32, + texture_builtin_value_2 : u32, +} + +@group(0) @binding(30) var tint_symbol_4 : tint_symbol_3; + +@group(0) @binding(0) var tex1 : texture_2d; + +@group(0) @binding(1) var tex2 : texture_2d; + +@group(0) @binding(2) var tex3 : texture_2d; + +fn f(t1 : texture_2d, t2 : texture_2d, t3 : texture_2d, tint_symbol : u32, tint_symbol_1 : u32, tint_symbol_2 : u32) -> u32 { + return ((tint_symbol + tint_symbol_1) + tint_symbol_2); +} + +@compute @workgroup_size(1) +fn main() { + var len : u32 = f(tex1, tex2, tex3, tint_symbol_4.texture_builtin_value_0, tint_symbol_4.texture_builtin_value_1, tint_symbol_4.texture_builtin_value_2); +} +)"; + + std::vector bps = {{0, 0}, {0, 1}, {0, 2}}; + TextureBuiltinsFromUniform::Config cfg({0, 30u}, bps); + + ast::transform::DataMap data; + data.Add(std::move(cfg)); + + auto got = Run(src, data); + EXPECT_EQ(expect, str(got)); +} + +TEST_F(TextureBuiltinsFromUniformTest, TextureAsFunctionParameterNested) { + auto* src = R"( +@group(0) @binding(0) var tex : texture_2d; + +fn f2(tt: texture_2d) -> u32 { + return textureNumLevels(tt); +} + +fn f1(t: texture_2d) -> u32 { + return f2(t); +} + +@compute @workgroup_size(1) +fn main() { + var len : u32 = f1(tex); +} +)"; + + auto* expect = R"( +struct tint_symbol_2 { + texture_builtin_value_0 : u32, +} + +@group(0) @binding(30) var tint_symbol_3 : tint_symbol_2; + +@group(0) @binding(0) var tex : texture_2d; + +fn f2(tt : texture_2d, tint_symbol : u32) -> u32 { + return tint_symbol; +} + +fn f1(t : texture_2d, tint_symbol_1 : u32) -> u32 { + return f2(t, tint_symbol_1); +} + +@compute @workgroup_size(1) +fn main() { + var len : u32 = f1(tex, tint_symbol_3.texture_builtin_value_0); +} +)"; + + std::vector bps = {{0, 0}}; + TextureBuiltinsFromUniform::Config cfg({0, 30u}, bps); + + ast::transform::DataMap data; + data.Add(std::move(cfg)); + + auto got = Run(src, data); + EXPECT_EQ(expect, str(got)); +} + +TEST_F(TextureBuiltinsFromUniformTest, TextureAsFunctionParameterMixed) { + auto* src = R"( +@group(0) @binding(0) var tex0 : texture_2d; +@group(0) @binding(1) var tex1 : texture_2d; +@group(0) @binding(2) var tex2 : texture_2d; +@group(0) @binding(3) var tex3 : texture_2d; +@group(0) @binding(4) var tex4 : texture_2d_array; // unused for textureNumLevels + +fn f_nested(t1: texture_2d, t2: texture_2d) -> u32 { + return textureNumLevels(t1) + textureNumLevels(t2); +} + +fn f1(a: u32, t: texture_2d) -> u32 { + return a + f_nested(t, tex1) + textureNumLevels(tex3); +} + +@compute @workgroup_size(1) +fn main() { + _ = textureNumLayers(tex4); + _ = f1(9u, tex0); + _ = f_nested(tex2, tex2); + _ = f_nested(tex1, tex0); +} +)"; + + auto* expect = R"( +struct tint_symbol_3 { + texture_builtin_value_0 : u32, + texture_builtin_value_1 : u32, + texture_builtin_value_2 : u32, + texture_builtin_value_3 : u32, +} + +@group(0) @binding(30) var tint_symbol_4 : tint_symbol_3; + +@group(0) @binding(0) var tex0 : texture_2d; + +@group(0) @binding(1) var tex1 : texture_2d; + +@group(0) @binding(2) var tex2 : texture_2d; + +@group(0) @binding(3) var tex3 : texture_2d; + +@group(0) @binding(4) var tex4 : texture_2d_array; + +fn f_nested(t1 : texture_2d, t2 : texture_2d, tint_symbol : u32, tint_symbol_1 : u32) -> u32 { + return (tint_symbol + tint_symbol_1); +} + +fn f1(a : u32, t : texture_2d, tint_symbol_2 : u32) -> u32 { + return ((a + f_nested(t, tex1, tint_symbol_2, tint_symbol_4.texture_builtin_value_0)) + tint_symbol_4.texture_builtin_value_1); +} + +@compute @workgroup_size(1) +fn main() { + _ = textureNumLayers(tex4); + _ = f1(9u, tex0, tint_symbol_4.texture_builtin_value_2); + _ = f_nested(tex2, tex2, tint_symbol_4.texture_builtin_value_3, tint_symbol_4.texture_builtin_value_3); + _ = f_nested(tex1, tex0, tint_symbol_4.texture_builtin_value_0, tint_symbol_4.texture_builtin_value_2); +} +)"; + + std::vector bps = {{0, 1}, {0, 3}, {0, 0}, {0, 2}}; + TextureBuiltinsFromUniform::Config cfg({0, 30u}, bps); + + ast::transform::DataMap data; + data.Add(std::move(cfg)); + + auto got = Run(src, data); + EXPECT_EQ(expect, str(got)); +} + +TEST_F(TextureBuiltinsFromUniformTest, MultipleTextures) { + auto* src = R"( +@group(0) @binding(0) var t0 : texture_2d; +@group(0) @binding(1) var t1 : texture_multisampled_2d; +@group(0) @binding(2) var t2 : texture_2d_array; +@group(0) @binding(3) var t3 : texture_cube; +@group(0) @binding(4) var t4 : texture_depth_2d; +@group(1) @binding(0) var t5 : texture_depth_multisampled_2d; + +@compute @workgroup_size(1) +fn main() { + _ = textureNumLevels(t0); + _ = textureNumSamples(t1); + _ = textureNumLevels(t2); + _ = textureNumLevels(t3); + _ = textureNumLevels(t4); + _ = textureNumSamples(t5); +} +)"; + + auto* expect = R"( +struct tint_symbol { + texture_builtin_value_0 : u32, + texture_builtin_value_1 : u32, + texture_builtin_value_2 : u32, + texture_builtin_value_3 : u32, + texture_builtin_value_4 : u32, + texture_builtin_value_5 : u32, +} + +@group(0) @binding(30) var tint_symbol_1 : tint_symbol; + +@group(0) @binding(0) var t0 : texture_2d; + +@group(0) @binding(1) var t1 : texture_multisampled_2d; + +@group(0) @binding(2) var t2 : texture_2d_array; + +@group(0) @binding(3) var t3 : texture_cube; + +@group(0) @binding(4) var t4 : texture_depth_2d; + +@group(1) @binding(0) var t5 : texture_depth_multisampled_2d; + +@compute @workgroup_size(1) +fn main() { + _ = tint_symbol_1.texture_builtin_value_0; + _ = tint_symbol_1.texture_builtin_value_1; + _ = tint_symbol_1.texture_builtin_value_2; + _ = tint_symbol_1.texture_builtin_value_3; + _ = tint_symbol_1.texture_builtin_value_4; + _ = tint_symbol_1.texture_builtin_value_5; +} +)"; + + std::vector bps = {{0, 0}, {0, 1}, {0, 2}, {0, 3}, {0, 4}, {1, 0}}; + TextureBuiltinsFromUniform::Config cfg({0, 30u}, bps); + + ast::transform::DataMap data; + data.Add(std::move(cfg)); + + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(TextureBuiltinsFromUniformTest, BindingPointExist) { + auto* src = R"( +struct tint_symbol { + foo : array, 1u>, +} + +@group(0) @binding(30) var tint_symbol_1 : tint_symbol; + +@group(0) @binding(0) var t : texture_2d; + +@compute @workgroup_size(1) +fn main() { + var len : u32 = textureNumLevels(t); +} +)"; + + auto* expect = R"( +struct tint_symbol { + foo : array, 1u>, + texture_builtin_value_0 : u32, +} + +@group(0) @binding(30) var tint_symbol_1 : tint_symbol; + +@group(0) @binding(0) var t : texture_2d; + +@compute @workgroup_size(1) +fn main() { + var len : u32 = tint_symbol_1.texture_builtin_value_0; +} +)"; + + std::vector bps = {{0, 0}}; + TextureBuiltinsFromUniform::Config cfg({0, 30u}, bps); + + ast::transform::DataMap data; + data.Add(std::move(cfg)); + + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/common/BUILD.bazel b/references/writer/common/BUILD.bazel new file mode 100644 index 00000000..a3df8659 --- /dev/null +++ b/references/writer/common/BUILD.bazel @@ -0,0 +1,125 @@ +# Copyright 2023 The Dawn & Tint Authors +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +################################################################################ +# File generated by 'tools/src/cmd/gen' using the template: +# tools/src/cmd/gen/build/BUILD.bazel.tmpl +# +# To regenerate run: './tools/run gen' +# +# Do not modify this file directly +################################################################################ + +load("//src/tint:flags.bzl", "COPTS") +load("@bazel_skylib//lib:selects.bzl", "selects") +cc_library( + name = "common", + srcs = [ + "options.cc", + "printer_support.cc", + ], + hdrs = [ + "options.h", + "printer_support.h", + "version.h", + ], + deps = [ + "//src/tint/api/common", + "//src/tint/api/options", + "//src/tint/lang/core", + "//src/tint/lang/core/constant", + "//src/tint/lang/core/type", + "//src/tint/lang/wgsl", + "//src/tint/lang/wgsl/ast", + "//src/tint/lang/wgsl/ast/transform", + "//src/tint/lang/wgsl/program", + "//src/tint/lang/wgsl/sem", + "//src/tint/utils/containers", + "//src/tint/utils/diagnostic", + "//src/tint/utils/ice", + "//src/tint/utils/id", + "//src/tint/utils/macros", + "//src/tint/utils/math", + "//src/tint/utils/memory", + "//src/tint/utils/reflection", + "//src/tint/utils/result", + "//src/tint/utils/rtti", + "//src/tint/utils/strconv", + "//src/tint/utils/symbol", + "//src/tint/utils/text", + "//src/tint/utils/traits", + ], + copts = COPTS, + visibility = ["//visibility:public"], +) +cc_library( + name = "test", + alwayslink = True, + srcs = [ + "options_test.cc", + "version_test.cc", + ], + deps = [ + "//src/tint/api/common", + "//src/tint/api/options", + "//src/tint/lang/core", + "//src/tint/lang/core/constant", + "//src/tint/lang/core/type", + "//src/tint/lang/wgsl", + "//src/tint/lang/wgsl/ast", + "//src/tint/lang/wgsl/ast/transform", + "//src/tint/lang/wgsl/program", + "//src/tint/lang/wgsl/sem", + "//src/tint/utils/containers", + "//src/tint/utils/diagnostic", + "//src/tint/utils/ice", + "//src/tint/utils/id", + "//src/tint/utils/macros", + "//src/tint/utils/math", + "//src/tint/utils/memory", + "//src/tint/utils/reflection", + "//src/tint/utils/result", + "//src/tint/utils/rtti", + "//src/tint/utils/symbol", + "//src/tint/utils/text", + "//src/tint/utils/traits", + "@gtest", + ] + select({ + ":tint_build_glsl_writer": [ + "//src/tint/lang/glsl/writer/common", + ], + "//conditions:default": [], + }), + copts = COPTS, + visibility = ["//visibility:public"], +) + +alias( + name = "tint_build_glsl_writer", + actual = "//src/tint:tint_build_glsl_writer_true", +) + diff --git a/references/writer/common/BUILD.cfg b/references/writer/common/BUILD.cfg new file mode 100644 index 00000000..7459430e --- /dev/null +++ b/references/writer/common/BUILD.cfg @@ -0,0 +1,3 @@ +{ + "condition": "tint_build_glsl_writer" +} diff --git a/references/writer/common/BUILD.cmake b/references/writer/common/BUILD.cmake new file mode 100644 index 00000000..19ce2763 --- /dev/null +++ b/references/writer/common/BUILD.cmake @@ -0,0 +1,126 @@ +# Copyright 2023 The Dawn & Tint Authors +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +################################################################################ +# File generated by 'tools/src/cmd/gen' using the template: +# tools/src/cmd/gen/build/BUILD.cmake.tmpl +# +# To regenerate run: './tools/run gen' +# +# Do not modify this file directly +################################################################################ + +if(TINT_BUILD_GLSL_WRITER) +################################################################################ +# Target: tint_lang_glsl_writer_common +# Kind: lib +# Condition: TINT_BUILD_GLSL_WRITER +################################################################################ +tint_add_target(tint_lang_glsl_writer_common lib + lang/glsl/writer/common/options.cc + lang/glsl/writer/common/options.h + lang/glsl/writer/common/printer_support.cc + lang/glsl/writer/common/printer_support.h + lang/glsl/writer/common/version.h +) + +tint_target_add_dependencies(tint_lang_glsl_writer_common lib + tint_api_common + tint_api_options + tint_lang_core + tint_lang_core_constant + tint_lang_core_type + tint_lang_wgsl + tint_lang_wgsl_ast + tint_lang_wgsl_ast_transform + tint_lang_wgsl_program + tint_lang_wgsl_sem + tint_utils_containers + tint_utils_diagnostic + tint_utils_ice + tint_utils_id + tint_utils_macros + tint_utils_math + tint_utils_memory + tint_utils_reflection + tint_utils_result + tint_utils_rtti + tint_utils_strconv + tint_utils_symbol + tint_utils_text + tint_utils_traits +) + +endif(TINT_BUILD_GLSL_WRITER) +if(TINT_BUILD_GLSL_WRITER) +################################################################################ +# Target: tint_lang_glsl_writer_common_test +# Kind: test +# Condition: TINT_BUILD_GLSL_WRITER +################################################################################ +tint_add_target(tint_lang_glsl_writer_common_test test + lang/glsl/writer/common/options_test.cc + lang/glsl/writer/common/version_test.cc +) + +tint_target_add_dependencies(tint_lang_glsl_writer_common_test test + tint_api_common + tint_api_options + tint_lang_core + tint_lang_core_constant + tint_lang_core_type + tint_lang_wgsl + tint_lang_wgsl_ast + tint_lang_wgsl_ast_transform + tint_lang_wgsl_program + tint_lang_wgsl_sem + tint_utils_containers + tint_utils_diagnostic + tint_utils_ice + tint_utils_id + tint_utils_macros + tint_utils_math + tint_utils_memory + tint_utils_reflection + tint_utils_result + tint_utils_rtti + tint_utils_symbol + tint_utils_text + tint_utils_traits +) + +tint_target_add_external_dependencies(tint_lang_glsl_writer_common_test test + "gtest" +) + +if(TINT_BUILD_GLSL_WRITER) + tint_target_add_dependencies(tint_lang_glsl_writer_common_test test + tint_lang_glsl_writer_common + ) +endif(TINT_BUILD_GLSL_WRITER) + +endif(TINT_BUILD_GLSL_WRITER) \ No newline at end of file diff --git a/references/writer/common/BUILD.gn b/references/writer/common/BUILD.gn new file mode 100644 index 00000000..2994d84e --- /dev/null +++ b/references/writer/common/BUILD.gn @@ -0,0 +1,120 @@ +# Copyright 2023 The Dawn & Tint Authors +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +################################################################################ +# File generated by 'tools/src/cmd/gen' using the template: +# tools/src/cmd/gen/build/BUILD.gn.tmpl +# +# To regenerate run: './tools/run gen' +# +# Do not modify this file directly +################################################################################ + +import("../../../../../../scripts/tint_overrides_with_defaults.gni") + +import("${tint_src_dir}/tint.gni") + +if (tint_build_unittests || tint_build_benchmarks) { + import("//testing/test.gni") +} +if (tint_build_glsl_writer) { + libtint_source_set("common") { + sources = [ + "options.cc", + "options.h", + "printer_support.cc", + "printer_support.h", + "version.h", + ] + deps = [ + "${tint_src_dir}/api/common", + "${tint_src_dir}/api/options", + "${tint_src_dir}/lang/core", + "${tint_src_dir}/lang/core/constant", + "${tint_src_dir}/lang/core/type", + "${tint_src_dir}/lang/wgsl", + "${tint_src_dir}/lang/wgsl/ast", + "${tint_src_dir}/lang/wgsl/ast/transform", + "${tint_src_dir}/lang/wgsl/program", + "${tint_src_dir}/lang/wgsl/sem", + "${tint_src_dir}/utils/containers", + "${tint_src_dir}/utils/diagnostic", + "${tint_src_dir}/utils/ice", + "${tint_src_dir}/utils/id", + "${tint_src_dir}/utils/macros", + "${tint_src_dir}/utils/math", + "${tint_src_dir}/utils/memory", + "${tint_src_dir}/utils/reflection", + "${tint_src_dir}/utils/result", + "${tint_src_dir}/utils/rtti", + "${tint_src_dir}/utils/strconv", + "${tint_src_dir}/utils/symbol", + "${tint_src_dir}/utils/text", + "${tint_src_dir}/utils/traits", + ] + } +} +if (tint_build_unittests) { + if (tint_build_glsl_writer) { + tint_unittests_source_set("unittests") { + sources = [ + "options_test.cc", + "version_test.cc", + ] + deps = [ + "${tint_src_dir}:gmock_and_gtest", + "${tint_src_dir}/api/common", + "${tint_src_dir}/api/options", + "${tint_src_dir}/lang/core", + "${tint_src_dir}/lang/core/constant", + "${tint_src_dir}/lang/core/type", + "${tint_src_dir}/lang/wgsl", + "${tint_src_dir}/lang/wgsl/ast", + "${tint_src_dir}/lang/wgsl/ast/transform", + "${tint_src_dir}/lang/wgsl/program", + "${tint_src_dir}/lang/wgsl/sem", + "${tint_src_dir}/utils/containers", + "${tint_src_dir}/utils/diagnostic", + "${tint_src_dir}/utils/ice", + "${tint_src_dir}/utils/id", + "${tint_src_dir}/utils/macros", + "${tint_src_dir}/utils/math", + "${tint_src_dir}/utils/memory", + "${tint_src_dir}/utils/reflection", + "${tint_src_dir}/utils/result", + "${tint_src_dir}/utils/rtti", + "${tint_src_dir}/utils/symbol", + "${tint_src_dir}/utils/text", + "${tint_src_dir}/utils/traits", + ] + + if (tint_build_glsl_writer) { + deps += [ "${tint_src_dir}/lang/glsl/writer/common" ] + } + } + } +} diff --git a/references/writer/common/options.cc b/references/writer/common/options.cc new file mode 100644 index 00000000..3b0eb9b3 --- /dev/null +++ b/references/writer/common/options.cc @@ -0,0 +1,38 @@ +// Copyright 2023 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/common/options.h" + +namespace tint::glsl::writer { + +Options::Options() = default; + +Options::~Options() = default; + +Options::Options(const Options&) = default; + +} // namespace tint::glsl::writer diff --git a/references/writer/common/options.h b/references/writer/common/options.h new file mode 100644 index 00000000..3ba7540a --- /dev/null +++ b/references/writer/common/options.h @@ -0,0 +1,190 @@ +// Copyright 2023 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef SRC_TINT_LANG_GLSL_WRITER_COMMON_OPTIONS_H_ +#define SRC_TINT_LANG_GLSL_WRITER_COMMON_OPTIONS_H_ + +#include +#include +#include + +#include "src/tint/api/options/binding_remapper.h" +#include "src/tint/api/options/depth_range_offsets.h" +#include "src/tint/api/options/external_texture.h" +#include "src/tint/api/options/texture_builtins_from_uniform.h" +#include "src/tint/lang/glsl/writer/common/version.h" +#include "src/tint/lang/wgsl/ast/transform/transform.h" + +namespace tint::glsl::writer::binding { + +/// A combined texture/sampler pair +// Note, these are the WGSL binding points that are used to create the combined samplers +struct CombinedTextureSamplerPair { + /// The WGSL texture binding + BindingPoint texture = {}; + /// The WGSL sampler binding + BindingPoint sampler = {}; + + /// Equality operator + /// @param rhs the CombinedTextureSamplerPair to compare against + /// @returns true if this CombinedTextureSamplerPair is equal to `rhs` + inline bool operator==(const CombinedTextureSamplerPair& rhs) const { + return texture == rhs.texture && sampler == rhs.sampler; + } + + /// Less then operator + /// @param rhs the CombinedTextureSamplerPair to compare against + /// @returns if this is less then rhs + inline bool operator<(const CombinedTextureSamplerPair& rhs) const { + if (texture < rhs.texture) { + return true; + } + if (texture == rhs.texture) { + return sampler < rhs.sampler; + } + return false; + } + + /// Reflect the fields of this class so that it can be used by tint::ForeachField() + TINT_REFLECT(CombinedTextureSamplerPair, texture, sampler); +}; + +} // namespace tint::glsl::writer::binding + +namespace std { +/// Custom std::hash specialization for tint::glsl::writer::binding::CombinedTextureSamplerPair +template <> +class hash { + public: + /// @param n the combined sampler texture pair + /// @return the hash value + inline std::size_t operator()( + const tint::glsl::writer::binding::CombinedTextureSamplerPair& n) const { + return tint::Hash(n.texture, n.sampler); + } +}; + +} // namespace std + +namespace tint::glsl::writer { + +using CombinedTextureSamplerInfo = + std::unordered_map; + +struct CombineSamplersInfo final + : public Castable { + /// Constructor + CombineSamplersInfo(); + + /// Constructor + /// @param map the texture/sampler to name map + /// @param placeholder the sampler placeholder binding point + CombineSamplersInfo(CombinedTextureSamplerInfo map, BindingPoint placeholder); + + CombineSamplersInfo(const CombineSamplersInfo&) = default; + CombineSamplersInfo(CombineSamplersInfo&&) = default; + + /// Destructor + ~CombineSamplersInfo() override; + + CombineSamplersInfo& operator=(const CombineSamplersInfo&) = default; + CombineSamplersInfo& operator=(CombineSamplersInfo&&) = default; + + /// A map of SamplerTexturePair to combined sampler names for the + /// CombineSamplers transform + CombinedTextureSamplerInfo sampler_texture_to_name; + + /// The binding point to use for placeholder samplers. + BindingPoint placeholder_sampler_binding; + + TINT_REFLECT(CombineSamplersInfo, sampler_texture_to_name, placeholder_sampler_binding); +}; + +/// Configuration options used for generating GLSL. +struct Options { + /// Constructor + Options(); + + /// Destructor + ~Options(); + + /// Copy constructor + Options(const Options&); + + /// Set to `true` to disable software robustness that prevents out-of-bounds accesses. + bool disable_robustness = false; + + /// Set to `true` to disable workgroup memory zero initialization + bool disable_workgroup_init = false; + + /// Set to `true` to disable the polyfills on integer division and modulo. + bool disable_polyfill_integer_div_mod = false; + + /// The GLSL version to emit + Version version; + + /// Combine Samplers transform information + CombineSamplersInfo combined_samplers_info = {}; + + /// Options used in the bindings remapper + BindingRemapperOptions binding_remapper_options = {}; + + /// Options used in the binding mappings for external textures + ExternalTextureOptions external_texture_options = {}; + + /// Offset of the firstVertex push constant. + std::optional first_vertex_offset; + + /// Offset of the firstInstance push constant. + std::optional first_instance_offset; + + /// Offsets of the minDepth and maxDepth push constants. + std::optional depth_range_offsets; + + /// Options used to map WGSL textureNumLevels/textureNumSamples builtins to internal uniform + /// buffer values. If not specified, emits corresponding GLSL builtins + /// textureQueryLevels/textureSamples directly. + TextureBuiltinsFromUniformOptions texture_builtins_from_uniform = {}; + + /// Reflect the fields of this class so that it can be used by tint::ForeachField() + TINT_REFLECT(Options, + disable_robustness, + disable_workgroup_init, + disable_polyfill_integer_div_mod, + version, + combined_samplers_info, + binding_remapper_options, + external_texture_options, + first_vertex_offset, + first_instance_offset, + depth_range_offsets, + texture_builtins_from_uniform); +}; + +} // namespace tint::glsl::writer + +#endif // SRC_TINT_LANG_GLSL_WRITER_COMMON_OPTIONS_H_ diff --git a/references/writer/common/options_test.cc b/references/writer/common/options_test.cc new file mode 100644 index 00000000..0309f316 --- /dev/null +++ b/references/writer/common/options_test.cc @@ -0,0 +1,40 @@ +// Copyright 2024 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/common/options.h" + +#include + +namespace tint { +namespace { + +TEST(TintCheckAllFieldsReflected, GlslWriterCommonOptionsTest) { + TINT_ASSERT_ALL_FIELDS_REFLECTED(tint::glsl::writer::Options); +} + +} // namespace +} // namespace tint diff --git a/references/writer/common/printer_support.cc b/references/writer/common/printer_support.cc new file mode 100644 index 00000000..d4c62de3 --- /dev/null +++ b/references/writer/common/printer_support.cc @@ -0,0 +1,68 @@ +// Copyright 2023 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/common/printer_support.h" + +#include +#include + +#include "src/tint/utils/strconv/float_to_string.h" + +namespace tint::glsl::writer { + +void PrintI32(StringStream& out, int32_t value) { + // GLSL parses `-2147483648` as a unary minus and `2147483648` as separate tokens, and the + // latter doesn't fit into an (32-bit) `int`. Emit `(-2147483647 - 1)` instead, which ensures + // the expression type is `int`. + if (auto int_min = std::numeric_limits::min(); value == int_min) { + out << "(" << int_min + 1 << " - 1)"; + } else { + out << value; + } +} + +void PrintF32(StringStream& out, float value) { + if (std::isinf(value)) { + out << "0.0f " << (value >= 0 ? "/* inf */" : "/* -inf */"); + } else if (std::isnan(value)) { + out << "0.0f /* nan */"; + } else { + out << tint::strconv::FloatToString(value) << "f"; + } +} + +void PrintF16(StringStream& out, float value) { + if (std::isinf(value)) { + out << "0.0hf " << (value >= 0 ? "/* inf */" : "/* -inf */"); + } else if (std::isnan(value)) { + out << "0.0hf /* nan */"; + } else { + out << tint::strconv::FloatToString(value) << "hf"; + } +} + +} // namespace tint::glsl::writer diff --git a/references/writer/common/printer_support.h b/references/writer/common/printer_support.h new file mode 100644 index 00000000..d36a1d8a --- /dev/null +++ b/references/writer/common/printer_support.h @@ -0,0 +1,54 @@ +// Copyright 2023 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef SRC_TINT_LANG_GLSL_WRITER_COMMON_PRINTER_SUPPORT_H_ +#define SRC_TINT_LANG_GLSL_WRITER_COMMON_PRINTER_SUPPORT_H_ + +#include + +#include "src/tint/utils/text/string_stream.h" + +namespace tint::glsl::writer { + +/// Prints a float32 to the output stream +/// @param out the stream to write too +/// @param value the float32 value +void PrintF32(StringStream& out, float value); + +/// Prints a float16 to the output stream +/// @param out the stream to write too +/// @param value the float16 value +void PrintF16(StringStream& out, float value); + +/// Prints an int32 to the output stream +/// @param out the stream to write too +/// @param value the int32 value +void PrintI32(StringStream& out, int32_t value); + +} // namespace tint::glsl::writer + +#endif // SRC_TINT_LANG_GLSL_WRITER_COMMON_PRINTER_SUPPORT_H_ diff --git a/references/writer/common/version.h b/references/writer/common/version.h new file mode 100644 index 00000000..182b4f2d --- /dev/null +++ b/references/writer/common/version.h @@ -0,0 +1,83 @@ +// Copyright 2022 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef SRC_TINT_LANG_GLSL_WRITER_COMMON_VERSION_H_ +#define SRC_TINT_LANG_GLSL_WRITER_COMMON_VERSION_H_ + +#include + +#include "src/tint/utils/reflection/reflection.h" + +namespace tint::glsl::writer { + +/// A structure representing the version of GLSL to be generated. +struct Version { + /// Is this version desktop GLSL, or GLSL ES? + enum class Standard { + kDesktop, + kES, + }; + + /// Constructor + /// @param standard_ Desktop or ES + /// @param major_ the major version + /// @param minor_ the minor version + Version(Standard standard_, uint32_t major_, uint32_t minor_) + : standard(standard_), major_version(major_), minor_version(minor_) {} + + /// Default constructor (see default values below) + Version() = default; + + /// @returns true if this version is GLSL ES + bool IsES() const { return standard == Standard::kES; } + + /// @returns true if this version is Desktop GLSL + bool IsDesktop() const { return standard == Standard::kDesktop; } + + /// Desktop or ES + Standard standard = Standard::kES; + + /// Major GLSL version + uint32_t major_version = 3; + + /// Minor GLSL version + uint32_t minor_version = 1; + + /// Reflect the fields of this class so that it can be used by tint::ForeachField() + TINT_REFLECT(Version, standard, major_version, minor_version); +}; + +} // namespace tint::glsl::writer + +namespace tint { + +/// Relect enum information for Version +TINT_REFLECT_ENUM_RANGE(glsl::writer::Version::Standard, kDesktop, kES); + +} // namespace tint + +#endif // SRC_TINT_LANG_GLSL_WRITER_COMMON_VERSION_H_ diff --git a/references/writer/common/version_test.cc b/references/writer/common/version_test.cc new file mode 100644 index 00000000..7110ff6d --- /dev/null +++ b/references/writer/common/version_test.cc @@ -0,0 +1,40 @@ +// Copyright 2024 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/common/version.h" + +#include + +namespace tint { +namespace { + +TEST(TintCheckAllFieldsReflected, GlslWriterCommonVersionTest) { + TINT_ASSERT_ALL_FIELDS_REFLECTED(tint::glsl::writer::Version); +} + +} // namespace +} // namespace tint diff --git a/references/writer/output.cc b/references/writer/output.cc new file mode 100644 index 00000000..26c78e93 --- /dev/null +++ b/references/writer/output.cc @@ -0,0 +1,38 @@ +// Copyright 2023 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/output.h" + +namespace tint::glsl::writer { + +Output::Output() = default; + +Output::~Output() = default; + +Output::Output(const Output&) = default; + +} // namespace tint::glsl::writer diff --git a/references/writer/output.h b/references/writer/output.h new file mode 100644 index 00000000..8f205d14 --- /dev/null +++ b/references/writer/output.h @@ -0,0 +1,56 @@ +// Copyright 2023 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef SRC_TINT_LANG_GLSL_WRITER_OUTPUT_H_ +#define SRC_TINT_LANG_GLSL_WRITER_OUTPUT_H_ + +#include +#include +#include + +#include "src/tint/lang/wgsl/ast/pipeline_stage.h" + +namespace tint::glsl::writer { + +/// The output produced when generating GLSL. +struct Output { + /// Constructor + Output(); + + /// Destructor + ~Output(); + + /// Copy constructor + Output(const Output&); + + /// The generated GLSL. + std::string glsl = ""; +}; + +} // namespace tint::glsl::writer + +#endif // SRC_TINT_LANG_GLSL_WRITER_OUTPUT_H_ diff --git a/references/writer/printer/BUILD.bazel b/references/writer/printer/BUILD.bazel new file mode 100644 index 00000000..8961c7aa --- /dev/null +++ b/references/writer/printer/BUILD.bazel @@ -0,0 +1,121 @@ +# Copyright 2023 The Dawn & Tint Authors +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +################################################################################ +# File generated by 'tools/src/cmd/gen' using the template: +# tools/src/cmd/gen/build/BUILD.bazel.tmpl +# +# To regenerate run: './tools/run gen' +# +# Do not modify this file directly +################################################################################ + +load("//src/tint:flags.bzl", "COPTS") +load("@bazel_skylib//lib:selects.bzl", "selects") +cc_library( + name = "printer", + srcs = [ + "printer.cc", + ], + hdrs = [ + "printer.h", + ], + deps = [ + "//src/tint/api/common", + "//src/tint/lang/core", + "//src/tint/lang/core/constant", + "//src/tint/lang/core/ir", + "//src/tint/lang/core/type", + "//src/tint/utils/containers", + "//src/tint/utils/diagnostic", + "//src/tint/utils/generator", + "//src/tint/utils/ice", + "//src/tint/utils/id", + "//src/tint/utils/macros", + "//src/tint/utils/math", + "//src/tint/utils/memory", + "//src/tint/utils/reflection", + "//src/tint/utils/result", + "//src/tint/utils/rtti", + "//src/tint/utils/symbol", + "//src/tint/utils/text", + "//src/tint/utils/traits", + ] + select({ + ":tint_build_glsl_writer": [ + "//src/tint/lang/glsl/writer/common", + ], + "//conditions:default": [], + }), + copts = COPTS, + visibility = ["//visibility:public"], +) +cc_library( + name = "test", + alwayslink = True, + srcs = [ + "constant_test.cc", + "function_test.cc", + "helper_test.h", + ], + deps = [ + "//src/tint/api/common", + "//src/tint/lang/core", + "//src/tint/lang/core/constant", + "//src/tint/lang/core/intrinsic", + "//src/tint/lang/core/ir", + "//src/tint/lang/core/type", + "//src/tint/lang/glsl/writer/raise", + "//src/tint/utils/containers", + "//src/tint/utils/diagnostic", + "//src/tint/utils/ice", + "//src/tint/utils/id", + "//src/tint/utils/macros", + "//src/tint/utils/math", + "//src/tint/utils/memory", + "//src/tint/utils/reflection", + "//src/tint/utils/result", + "//src/tint/utils/rtti", + "//src/tint/utils/symbol", + "//src/tint/utils/text", + "//src/tint/utils/traits", + "@gtest", + ] + select({ + ":tint_build_glsl_writer": [ + "//src/tint/lang/glsl/writer/common", + "//src/tint/lang/glsl/writer/printer", + ], + "//conditions:default": [], + }), + copts = COPTS, + visibility = ["//visibility:public"], +) + +alias( + name = "tint_build_glsl_writer", + actual = "//src/tint:tint_build_glsl_writer_true", +) + diff --git a/references/writer/printer/BUILD.cfg b/references/writer/printer/BUILD.cfg new file mode 100644 index 00000000..7459430e --- /dev/null +++ b/references/writer/printer/BUILD.cfg @@ -0,0 +1,3 @@ +{ + "condition": "tint_build_glsl_writer" +} diff --git a/references/writer/printer/BUILD.cmake b/references/writer/printer/BUILD.cmake new file mode 100644 index 00000000..31486ddf --- /dev/null +++ b/references/writer/printer/BUILD.cmake @@ -0,0 +1,123 @@ +# Copyright 2023 The Dawn & Tint Authors +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +################################################################################ +# File generated by 'tools/src/cmd/gen' using the template: +# tools/src/cmd/gen/build/BUILD.cmake.tmpl +# +# To regenerate run: './tools/run gen' +# +# Do not modify this file directly +################################################################################ + +if(TINT_BUILD_GLSL_WRITER) +################################################################################ +# Target: tint_lang_glsl_writer_printer +# Kind: lib +# Condition: TINT_BUILD_GLSL_WRITER +################################################################################ +tint_add_target(tint_lang_glsl_writer_printer lib + lang/glsl/writer/printer/printer.cc + lang/glsl/writer/printer/printer.h +) + +tint_target_add_dependencies(tint_lang_glsl_writer_printer lib + tint_api_common + tint_lang_core + tint_lang_core_constant + tint_lang_core_ir + tint_lang_core_type + tint_utils_containers + tint_utils_diagnostic + tint_utils_generator + tint_utils_ice + tint_utils_id + tint_utils_macros + tint_utils_math + tint_utils_memory + tint_utils_reflection + tint_utils_result + tint_utils_rtti + tint_utils_symbol + tint_utils_text + tint_utils_traits +) + +if(TINT_BUILD_GLSL_WRITER) + tint_target_add_dependencies(tint_lang_glsl_writer_printer lib + tint_lang_glsl_writer_common + ) +endif(TINT_BUILD_GLSL_WRITER) + +endif(TINT_BUILD_GLSL_WRITER) +if(TINT_BUILD_GLSL_WRITER) +################################################################################ +# Target: tint_lang_glsl_writer_printer_test +# Kind: test +# Condition: TINT_BUILD_GLSL_WRITER +################################################################################ +tint_add_target(tint_lang_glsl_writer_printer_test test + lang/glsl/writer/printer/constant_test.cc + lang/glsl/writer/printer/function_test.cc + lang/glsl/writer/printer/helper_test.h +) + +tint_target_add_dependencies(tint_lang_glsl_writer_printer_test test + tint_api_common + tint_lang_core + tint_lang_core_constant + tint_lang_core_intrinsic + tint_lang_core_ir + tint_lang_core_type + tint_lang_glsl_writer_raise + tint_utils_containers + tint_utils_diagnostic + tint_utils_ice + tint_utils_id + tint_utils_macros + tint_utils_math + tint_utils_memory + tint_utils_reflection + tint_utils_result + tint_utils_rtti + tint_utils_symbol + tint_utils_text + tint_utils_traits +) + +tint_target_add_external_dependencies(tint_lang_glsl_writer_printer_test test + "gtest" +) + +if(TINT_BUILD_GLSL_WRITER) + tint_target_add_dependencies(tint_lang_glsl_writer_printer_test test + tint_lang_glsl_writer_common + tint_lang_glsl_writer_printer + ) +endif(TINT_BUILD_GLSL_WRITER) + +endif(TINT_BUILD_GLSL_WRITER) \ No newline at end of file diff --git a/references/writer/printer/BUILD.gn b/references/writer/printer/BUILD.gn new file mode 100644 index 00000000..59f838a2 --- /dev/null +++ b/references/writer/printer/BUILD.gn @@ -0,0 +1,117 @@ +# Copyright 2023 The Dawn & Tint Authors +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +################################################################################ +# File generated by 'tools/src/cmd/gen' using the template: +# tools/src/cmd/gen/build/BUILD.gn.tmpl +# +# To regenerate run: './tools/run gen' +# +# Do not modify this file directly +################################################################################ + +import("../../../../../../scripts/tint_overrides_with_defaults.gni") + +import("${tint_src_dir}/tint.gni") + +if (tint_build_unittests || tint_build_benchmarks) { + import("//testing/test.gni") +} +if (tint_build_glsl_writer) { + libtint_source_set("printer") { + sources = [ + "printer.cc", + "printer.h", + ] + deps = [ + "${tint_src_dir}/api/common", + "${tint_src_dir}/lang/core", + "${tint_src_dir}/lang/core/constant", + "${tint_src_dir}/lang/core/ir", + "${tint_src_dir}/lang/core/type", + "${tint_src_dir}/utils/containers", + "${tint_src_dir}/utils/diagnostic", + "${tint_src_dir}/utils/generator", + "${tint_src_dir}/utils/ice", + "${tint_src_dir}/utils/id", + "${tint_src_dir}/utils/macros", + "${tint_src_dir}/utils/math", + "${tint_src_dir}/utils/memory", + "${tint_src_dir}/utils/reflection", + "${tint_src_dir}/utils/result", + "${tint_src_dir}/utils/rtti", + "${tint_src_dir}/utils/symbol", + "${tint_src_dir}/utils/text", + "${tint_src_dir}/utils/traits", + ] + + if (tint_build_glsl_writer) { + deps += [ "${tint_src_dir}/lang/glsl/writer/common" ] + } + } +} +if (tint_build_unittests) { + if (tint_build_glsl_writer) { + tint_unittests_source_set("unittests") { + sources = [ + "constant_test.cc", + "function_test.cc", + "helper_test.h", + ] + deps = [ + "${tint_src_dir}:gmock_and_gtest", + "${tint_src_dir}/api/common", + "${tint_src_dir}/lang/core", + "${tint_src_dir}/lang/core/constant", + "${tint_src_dir}/lang/core/intrinsic", + "${tint_src_dir}/lang/core/ir", + "${tint_src_dir}/lang/core/type", + "${tint_src_dir}/lang/glsl/writer/raise", + "${tint_src_dir}/utils/containers", + "${tint_src_dir}/utils/diagnostic", + "${tint_src_dir}/utils/ice", + "${tint_src_dir}/utils/id", + "${tint_src_dir}/utils/macros", + "${tint_src_dir}/utils/math", + "${tint_src_dir}/utils/memory", + "${tint_src_dir}/utils/reflection", + "${tint_src_dir}/utils/result", + "${tint_src_dir}/utils/rtti", + "${tint_src_dir}/utils/symbol", + "${tint_src_dir}/utils/text", + "${tint_src_dir}/utils/traits", + ] + + if (tint_build_glsl_writer) { + deps += [ + "${tint_src_dir}/lang/glsl/writer/common", + "${tint_src_dir}/lang/glsl/writer/printer", + ] + } + } + } +} diff --git a/references/writer/printer/constant_test.cc b/references/writer/printer/constant_test.cc new file mode 100644 index 00000000..55eb4db2 --- /dev/null +++ b/references/writer/printer/constant_test.cc @@ -0,0 +1,41 @@ +// Copyright 2023 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/printer/helper_test.h" + +using namespace tint::core::number_suffixes; // NOLINT +using namespace tint::core::fluent_types; // NOLINT + +namespace tint::glsl::writer { +namespace { + +TEST_F(GlslPrinterTest, DISABLED_Constant_Bool_True) { + FAIL(); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/printer/function_test.cc b/references/writer/printer/function_test.cc new file mode 100644 index 00000000..763a051c --- /dev/null +++ b/references/writer/printer/function_test.cc @@ -0,0 +1,45 @@ +// Copyright 2023 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/printer/helper_test.h" + +namespace tint::glsl::writer { +namespace { + +TEST_F(GlslPrinterTest, Function_Empty) { + auto* func = b.Function("foo", ty.void_()); + func->Block()->Append(b.Return(func)); + + ASSERT_TRUE(Generate()) << err_ << output_; + EXPECT_EQ(output_, GlslHeader() + R"( +void foo() { +} +)"); +} + +} // namespace +} // namespace tint::glsl::writer diff --git a/references/writer/printer/helper_test.h b/references/writer/printer/helper_test.h new file mode 100644 index 00000000..2dbcd000 --- /dev/null +++ b/references/writer/printer/helper_test.h @@ -0,0 +1,101 @@ +// Copyright 2023 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef SRC_TINT_LANG_GLSL_WRITER_PRINTER_HELPER_TEST_H_ +#define SRC_TINT_LANG_GLSL_WRITER_PRINTER_HELPER_TEST_H_ + +#include + +#include "gtest/gtest.h" +#include "src/tint/lang/core/ir/builder.h" +#include "src/tint/lang/core/ir/validator.h" +#include "src/tint/lang/glsl/writer/common/version.h" +#include "src/tint/lang/glsl/writer/printer/printer.h" +#include "src/tint/lang/glsl/writer/raise/raise.h" + +namespace tint::glsl::writer { + +/// Base helper class for testing the GLSL generator implementation. +template +class GlslPrinterTestHelperBase : public BASE { + public: + /// The test module. + core::ir::Module mod; + /// The test builder. + core::ir::Builder b{mod}; + /// The type manager. + core::type::Manager& ty{mod.Types()}; + /// The GLSL version + Version version{}; + + protected: + /// Validation errors + std::string err_; + + /// Generated GLSL + std::string output_; + + /// Run the writer on the IR module and validate the result. + /// @returns true if generation and validation succeeded + bool Generate() { + if (auto raised = Raise(mod); raised != Success) { + err_ = raised.Failure().reason.Str(); + return false; + } + + auto result = Print(mod, version); + if (result != Success) { + err_ = result.Failure().reason.Str(); + return false; + } + output_ = result.Get(); + + return true; + } + + /// @returns the metal header string + std::string GlslHeader() const { + std::stringstream ver; + ver << "#version " << version.major_version << version.minor_version << "0"; + if (version.IsES()) { + ver << " es"; + } + ver << "\n"; + return ver.str(); + } +}; + +/// Test class +using GlslPrinterTest = GlslPrinterTestHelperBase; + +/// Test param class +template +using GlslPrinterTestWithParam = GlslPrinterTestHelperBase>; + +} // namespace tint::glsl::writer + +#endif // SRC_TINT_LANG_GLSL_WRITER_PRINTER_HELPER_TEST_H_ diff --git a/references/writer/printer/printer.cc b/references/writer/printer/printer.cc new file mode 100644 index 00000000..dfee449b --- /dev/null +++ b/references/writer/printer/printer.cc @@ -0,0 +1,176 @@ +// Copyright 2023 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/printer/printer.h" + +#include +#include + +#include "src/tint/lang/core/ir/function.h" +#include "src/tint/lang/core/ir/module.h" +#include "src/tint/lang/core/ir/return.h" +#include "src/tint/lang/core/ir/unreachable.h" +#include "src/tint/lang/core/ir/validator.h" +#include "src/tint/lang/glsl/writer/common/version.h" +#include "src/tint/utils/generator/text_generator.h" +#include "src/tint/utils/macros/scoped_assignment.h" +#include "src/tint/utils/rtti/switch.h" + +using namespace tint::core::fluent_types; // NOLINT + +namespace tint::glsl::writer { +namespace { + +/// PIMPL class for the MSL generator +class Printer : public tint::TextGenerator { + public: + /// Constructor + /// @param module the Tint IR module to generate + explicit Printer(const core::ir::Module& module) : ir_(module) {} + + /// @param version the GLSL version information + /// @returns the generated GLSL shader + tint::Result Generate(const Version& version) { + auto valid = core::ir::ValidateAndDumpIfNeeded(ir_, "GLSL writer"); + if (valid != Success) { + return std::move(valid.Failure()); + } + + { + TINT_SCOPED_ASSIGNMENT(current_buffer_, &preamble_buffer_); + + auto out = Line(); + out << "#version " << version.major_version << version.minor_version << "0"; + if (version.IsES()) { + out << " es"; + } + } + + // Emit module-scope declarations. + EmitBlockInstructions(ir_.root_block); + + // Emit functions. + for (auto& func : ir_.functions) { + EmitFunction(func); + } + + StringStream ss; + ss << preamble_buffer_.String() << '\n' << main_buffer_.String(); + return ss.str(); + } + + private: + const core::ir::Module& ir_; + + /// The buffer holding preamble text + TextBuffer preamble_buffer_; + + /// The current function being emitted + const core::ir::Function* current_function_ = nullptr; + /// The current block being emitted + const core::ir::Block* current_block_ = nullptr; + + /// Emit the function + /// @param func the function to emit + void EmitFunction(const core::ir::Function* func) { + TINT_SCOPED_ASSIGNMENT(current_function_, func); + + { + auto out = Line(); + + // TODO(dsinclair): Emit function stage if any + // TODO(dsinclair): Handle return type attributes + + EmitType(out, func->ReturnType()); + out << " " << ir_.NameOf(func).Name() << "() {"; + + // TODO(dsinclair): Emit Function parameters + } + { + ScopedIndent si(current_buffer_); + EmitBlock(func->Block()); + } + + Line() << "}"; + } + + /// Emit a block + /// @param block the block to emit + void EmitBlock(const core::ir::Block* block) { + // TODO(dsinclair): Handle marking inline + // MarkInlinable(block); + + EmitBlockInstructions(block); + } + + /// Emit the instructions in a block + /// @param block the block with the instructions to emit + void EmitBlockInstructions(const core::ir::Block* block) { + TINT_SCOPED_ASSIGNMENT(current_block_, block); + + for (auto* inst : *block) { + Switch( + inst, // + [&](const core::ir::Return* r) { EmitReturn(r); }, // + [&](const core::ir::Unreachable*) { EmitUnreachable(); }, // + TINT_ICE_ON_NO_MATCH); + } + } + + /// Emit a type + /// @param out the stream to emit too + /// @param ty the type to emit + void EmitType(StringStream& out, [[maybe_unused]] const core::type::Type* ty) { out << "void"; } + + /// Emit a return instruction + /// @param r the return instruction + void EmitReturn(const core::ir::Return* r) { + // If this return has no arguments and the current block is for the function which is + // being returned, skip the return. + if (current_block_ == current_function_->Block() && r->Args().IsEmpty()) { + return; + } + + auto out = Line(); + out << "return"; + // TODO(dsinclair): Handle return args + // if (!r->Args().IsEmpty()) { + // out << " " << Expr(r->Args().Front()); + // } + out << ";"; + } + + /// Emit an unreachable instruction + void EmitUnreachable() { Line() << "/* unreachable */"; } +}; +} // namespace + +Result Print(const core::ir::Module& module, const Version& version) { + return Printer{module}.Generate(version); +} + +} // namespace tint::glsl::writer diff --git a/references/writer/printer/printer.h b/references/writer/printer/printer.h new file mode 100644 index 00000000..6f75b39c --- /dev/null +++ b/references/writer/printer/printer.h @@ -0,0 +1,52 @@ +// Copyright 2023 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef SRC_TINT_LANG_GLSL_WRITER_PRINTER_PRINTER_H_ +#define SRC_TINT_LANG_GLSL_WRITER_PRINTER_PRINTER_H_ + +#include + +#include "src/tint/utils/result/result.h" + +// Forward declarations +namespace tint::core::ir { +class Module; +} // namespace tint::core::ir +namespace tint::glsl::writer { +struct Version; +} // namespace tint::glsl::writer + +namespace tint::glsl::writer { + +/// @returns the generated GLSL shader on success, or failure +/// @param module the Tint IR module to generate +/// @param version the GLSL version information +Result Print(const core::ir::Module& module, const Version& version); + +} // namespace tint::glsl::writer + +#endif // SRC_TINT_LANG_GLSL_WRITER_PRINTER_PRINTER_H_ diff --git a/references/writer/raise/BUILD.bazel b/references/writer/raise/BUILD.bazel new file mode 100644 index 00000000..ab458ab8 --- /dev/null +++ b/references/writer/raise/BUILD.bazel @@ -0,0 +1,62 @@ +# Copyright 2023 The Dawn & Tint Authors +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +################################################################################ +# File generated by 'tools/src/cmd/gen' using the template: +# tools/src/cmd/gen/build/BUILD.bazel.tmpl +# +# To regenerate run: './tools/run gen' +# +# Do not modify this file directly +################################################################################ + +load("//src/tint:flags.bzl", "COPTS") +load("@bazel_skylib//lib:selects.bzl", "selects") +cc_library( + name = "raise", + srcs = [ + "raise.cc", + ], + hdrs = [ + "raise.h", + ], + deps = [ + "//src/tint/utils/containers", + "//src/tint/utils/diagnostic", + "//src/tint/utils/ice", + "//src/tint/utils/macros", + "//src/tint/utils/math", + "//src/tint/utils/memory", + "//src/tint/utils/result", + "//src/tint/utils/rtti", + "//src/tint/utils/text", + "//src/tint/utils/traits", + ], + copts = COPTS, + visibility = ["//visibility:public"], +) + diff --git a/references/writer/raise/BUILD.cmake b/references/writer/raise/BUILD.cmake new file mode 100644 index 00000000..90ff6d39 --- /dev/null +++ b/references/writer/raise/BUILD.cmake @@ -0,0 +1,57 @@ +# Copyright 2023 The Dawn & Tint Authors +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +################################################################################ +# File generated by 'tools/src/cmd/gen' using the template: +# tools/src/cmd/gen/build/BUILD.cmake.tmpl +# +# To regenerate run: './tools/run gen' +# +# Do not modify this file directly +################################################################################ + +################################################################################ +# Target: tint_lang_glsl_writer_raise +# Kind: lib +################################################################################ +tint_add_target(tint_lang_glsl_writer_raise lib + lang/glsl/writer/raise/raise.cc + lang/glsl/writer/raise/raise.h +) + +tint_target_add_dependencies(tint_lang_glsl_writer_raise lib + tint_utils_containers + tint_utils_diagnostic + tint_utils_ice + tint_utils_macros + tint_utils_math + tint_utils_memory + tint_utils_result + tint_utils_rtti + tint_utils_text + tint_utils_traits +) diff --git a/references/writer/raise/BUILD.gn b/references/writer/raise/BUILD.gn new file mode 100644 index 00000000..bd20a514 --- /dev/null +++ b/references/writer/raise/BUILD.gn @@ -0,0 +1,58 @@ +# Copyright 2023 The Dawn & Tint Authors +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +################################################################################ +# File generated by 'tools/src/cmd/gen' using the template: +# tools/src/cmd/gen/build/BUILD.gn.tmpl +# +# To regenerate run: './tools/run gen' +# +# Do not modify this file directly +################################################################################ + +import("../../../../../../scripts/tint_overrides_with_defaults.gni") + +import("${tint_src_dir}/tint.gni") + +libtint_source_set("raise") { + sources = [ + "raise.cc", + "raise.h", + ] + deps = [ + "${tint_src_dir}/utils/containers", + "${tint_src_dir}/utils/diagnostic", + "${tint_src_dir}/utils/ice", + "${tint_src_dir}/utils/macros", + "${tint_src_dir}/utils/math", + "${tint_src_dir}/utils/memory", + "${tint_src_dir}/utils/result", + "${tint_src_dir}/utils/rtti", + "${tint_src_dir}/utils/text", + "${tint_src_dir}/utils/traits", + ] +} diff --git a/references/writer/raise/raise.cc b/references/writer/raise/raise.cc new file mode 100644 index 00000000..1985ec7e --- /dev/null +++ b/references/writer/raise/raise.cc @@ -0,0 +1,44 @@ +// Copyright 2023 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/raise/raise.h" + +namespace tint::glsl::writer { + +Result Raise(core::ir::Module&) { + // #define RUN_TRANSFORM(name) + // do { + // auto result = core::ir::transform::name(module); + // if (!result) { + // return result; + // } + // } while (false) + + return Success; +} + +} // namespace tint::glsl::writer diff --git a/references/writer/raise/raise.h b/references/writer/raise/raise.h new file mode 100644 index 00000000..846790dc --- /dev/null +++ b/references/writer/raise/raise.h @@ -0,0 +1,47 @@ +// Copyright 2023 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef SRC_TINT_LANG_GLSL_WRITER_RAISE_RAISE_H_ +#define SRC_TINT_LANG_GLSL_WRITER_RAISE_RAISE_H_ + +#include "src/tint/utils/result/result.h" + +// Forward declarations +namespace tint::core::ir { +class Module; +} // namespace tint::core::ir + +namespace tint::glsl::writer { + +/// Raise a core IR module to the MSL dialect of the IR. +/// @param mod the core IR module to raise to MSL dialect +/// @returns success or failure +Result Raise(core::ir::Module& mod); + +} // namespace tint::glsl::writer + +#endif // SRC_TINT_LANG_GLSL_WRITER_RAISE_RAISE_H_ diff --git a/references/writer/writer.cc b/references/writer/writer.cc new file mode 100644 index 00000000..17bd7efd --- /dev/null +++ b/references/writer/writer.cc @@ -0,0 +1,83 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/glsl/writer/writer.h" + +#include +#include + +#include "src/tint/lang/glsl/writer/ast_printer/ast_printer.h" +#include "src/tint/lang/glsl/writer/printer/printer.h" +#include "src/tint/lang/glsl/writer/raise/raise.h" + +namespace tint::glsl::writer { + +Result Generate(core::ir::Module& ir, const Options& options, const std::string&) { + Output output; + + // Raise from core-dialect to GLSL-dialect. + if (auto res = Raise(ir); res != Success) { + return res.Failure(); + } + + // Generate the GLSL code. + auto result = Print(ir, options.version); + if (result != Success) { + return result.Failure(); + } + output.glsl = result.Get(); + + return output; +} + +Result Generate(const Program& program, + const Options& options, + const std::string& entry_point) { + if (!program.IsValid()) { + return Failure{program.Diagnostics()}; + } + + Output output; + + // Sanitize the program. + auto sanitized_result = Sanitize(program, options, entry_point); + if (!sanitized_result.program.IsValid()) { + return Failure{sanitized_result.program.Diagnostics()}; + } + + // Generate the GLSL code. + auto impl = std::make_unique(sanitized_result.program, options.version); + if (!impl->Generate()) { + return Failure{impl->Diagnostics()}; + } + + output.glsl = impl->Result(); + + return output; +} + +} // namespace tint::glsl::writer diff --git a/references/writer/writer.h b/references/writer/writer.h new file mode 100644 index 00000000..299ee080 --- /dev/null +++ b/references/writer/writer.h @@ -0,0 +1,72 @@ +// Copyright 2021 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef SRC_TINT_LANG_GLSL_WRITER_WRITER_H_ +#define SRC_TINT_LANG_GLSL_WRITER_WRITER_H_ + +#include + +#include "src/tint/lang/glsl/writer/common/options.h" +#include "src/tint/lang/glsl/writer/output.h" +#include "src/tint/utils/diagnostic/diagnostic.h" +#include "src/tint/utils/result/result.h" + +// Forward declarations +namespace tint { +class Program; +} // namespace tint +namespace tint::core::ir { +class Module; +} // namespace tint::core::ir + +namespace tint::glsl::writer { + +/// Generate GLSL for a program, according to a set of configuration options. +/// The result will contain the GLSL and supplementary information, or failure. +/// information. +/// @param ir the IR module to translate to GLSL +/// @param options the configuration options to use when generating GLSL +/// @param entry_point the entry point to generate GLSL for +/// @returns the resulting GLSL and supplementary information, or failure +Result Generate(core::ir::Module& ir, + const Options& options, + const std::string& entry_point); + +/// Generate GLSL for a program, according to a set of configuration options. +/// The result will contain the GLSL and supplementary information, or failure. +/// information. +/// @param program the program to translate to GLSL +/// @param options the configuration options to use when generating GLSL +/// @param entry_point the entry point to generate GLSL for +/// @returns the resulting GLSL and supplementary information, or failure +Result Generate(const Program& program, + const Options& options, + const std::string& entry_point); + +} // namespace tint::glsl::writer + +#endif // SRC_TINT_LANG_GLSL_WRITER_WRITER_H_ diff --git a/references/writer/writer_ast_fuzz.cc b/references/writer/writer_ast_fuzz.cc new file mode 100644 index 00000000..e555a758 --- /dev/null +++ b/references/writer/writer_ast_fuzz.cc @@ -0,0 +1,81 @@ +// Copyright 2024 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// GEN_BUILD:CONDITION(tint_build_wgsl_reader) + +#include "src/tint/cmd/fuzz/wgsl/fuzz.h" +#include "src/tint/lang/glsl/writer/writer.h" +#include "src/tint/lang/wgsl/ast/module.h" +#include "src/tint/lang/wgsl/inspector/inspector.h" + +namespace tint::glsl::writer { +namespace { + +bool CanRun(const tint::Program& program, const Options& options) { + if (program.AST().HasOverrides()) { + return false; // Overrides are not supported. + } + + // Excessive values can cause OOM / timeouts in the PadStructs transform. + static constexpr uint32_t kMaxOffset = 0x1000; + + if (options.first_instance_offset && options.first_instance_offset > kMaxOffset) { + return false; + } + + if (options.first_vertex_offset && options.first_vertex_offset > kMaxOffset) { + return false; + } + + if (options.depth_range_offsets) { + if (options.depth_range_offsets->max > kMaxOffset || + options.depth_range_offsets->min > kMaxOffset) { + return false; + } + } + + return true; +} + +void ASTFuzzer(const tint::Program& program, const Options& options) { + if (!CanRun(program, options)) { + return; + } + + auto inspector = tint::inspector::Inspector(program); + auto entrypoints = inspector.GetEntryPoints(); + + // Test all of the entry points as GLSL requires specifying which one to generate. + for (const auto& ep : entrypoints) { + [[maybe_unused]] auto res = tint::glsl::writer::Generate(program, options, ep.name); + } +} + +} // namespace +} // namespace tint::glsl::writer + +TINT_WGSL_PROGRAM_FUZZER(tint::glsl::writer::ASTFuzzer); diff --git a/references/writer/writer_bench.cc b/references/writer/writer_bench.cc new file mode 100644 index 00000000..8505dac2 --- /dev/null +++ b/references/writer/writer_bench.cc @@ -0,0 +1,65 @@ +// Copyright 2022 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include "src/tint/cmd/bench/bench.h" +#include "src/tint/lang/glsl/writer/writer.h" +#include "src/tint/lang/wgsl/ast/identifier.h" +#include "src/tint/lang/wgsl/ast/module.h" + +namespace tint::glsl::writer { +namespace { + +void GenerateGLSL(benchmark::State& state, std::string input_name) { + auto res = bench::LoadProgram(input_name); + if (res != Success) { + state.SkipWithError(res.Failure().reason.Str()); + return; + } + auto& program = res->program; + std::vector entry_points; + for (auto& fn : program.AST().Functions()) { + if (fn->IsEntryPoint()) { + entry_points.emplace_back(fn->name->symbol.Name()); + } + } + + for (auto _ : state) { + for (auto& ep : entry_points) { + auto gen_res = Generate(program, {}, ep); + if (gen_res != Success) { + state.SkipWithError(gen_res.Failure().reason.Str()); + } + } + } +} + +TINT_BENCHMARK_PROGRAMS(GenerateGLSL); + +} // namespace +} // namespace tint::glsl::writer diff --git a/src/expr_fuse_test.cpp b/src/expr_fuse_test.cpp index f3d04602..7c1bb89a 100644 --- a/src/expr_fuse_test.cpp +++ b/src/expr_fuse_test.cpp @@ -183,6 +183,48 @@ int run_non_recording(CpuEngine& cpu, GpuEngine& gpu) return ok ? 0 : 1; } +// ── vec4 向量化路径验证(cols%4==0)────────────────────────────────────── +// 用已登记的 vec4 合格表达式(Linear × RowBroadcast × const,即 FusedChain +// backward 的 grad*gamma*2)在 cols 为 4 的倍数时运行,确保 glsl_gen 的 vec4 +// 快速路径(vec4 加载/广播/运算/存储)数值与 CPU 参考一致。尾部与非 4 倍数 +// cols 的标量回退由 run_non_recording(C=6)覆盖。 +int run_vec4_path(CpuEngine& cpu, GpuEngine& gpu) +{ + const std::size_t R = 4; + int fail = 0; + for (const std::size_t C : {std::size_t{4}, std::size_t{8}, std::size_t{12}}) + { + std::mt19937 rng(42 + static_cast(C)); + std::uniform_real_distribution dist(-1.0f, 1.0f); + Matrix x(R, C); + for (auto& v : x.span()) v = dist(rng); + Matrix g(R, 1); + for (auto& v : g.span()) v = 0.5f + 0.01f * static_cast(rng() % 10); + + const Tensor xt = Tensor::from_matrix(Matrix(x)); + const Tensor gt = Tensor::from_matrix(Matrix(g)); + auto cr = nn::dsl::compute(cpu, + nn::dsl::leaf(xt) * nn::dsl::row_broadcast(gt) * Scalar{2}, R, C); + auto gr = nn::dsl::compute(gpu, + nn::dsl::leaf(xt) * nn::dsl::row_broadcast(gt) * Scalar{2}, R, C); + if (!cr) { std::cerr << " CPU 求值失败\n"; return 1; } + if (!gr) + { + std::cerr << " GPU vec4 路径失败(未命中 AOT shader?): " + << gr.error().message << "\n"; + return 1; + } + auto gm = gpu.to_matrix(*gr); + if (!gm) { std::cerr << " GPU 结果下载失败\n"; return 1; } + const Scalar err = max_abs_diff(cr->cpu_matrix(), *gm); + const bool ok = err < 1e-4f; + std::cout << "[" << (ok ? "PASS" : "FAIL") << "] vec4 向量化路径 (cols=" << C << ")" + << " err=" << std::scientific << std::setprecision(2) << err << "\n"; + fail += ok ? 0 : 1; + } + return fail == 0 ? 0 : 1; +} + int main() { std::cout << "========================================\n" @@ -204,6 +246,7 @@ int main() fail += run_fused_chain(*cpu_engine, *gpu_engine); fail += run_manual_recording(*cpu_engine, *gpu_engine); fail += run_non_recording(*cpu_engine, *gpu_engine); + fail += run_vec4_path(*cpu_engine, *gpu_engine); std::cout << (fail == 0 ? "ALL PASS\n" : "FAILED\n"); return fail == 0 ? 0 : 1; diff --git a/tools/gen_fused.cpp b/tools/gen_fused.cpp index c8c59c8f..af4da0cb 100644 --- a/tools/gen_fused.cpp +++ b/tools/gen_fused.cpp @@ -42,8 +42,10 @@ namespace { // 仅给 glslc 路径加引号;src/dst 为无空格路径(out_dir 由 CMake 控制), // 正斜杠相对路径不加引号,避免 cmd/system 解析问题。 + // --target-env=vulkan1.2 → SPIR-V 1.5:subgroup 算子(GL_KHR_shader_subgroup_*) + // 需 SPIR-V ≥1.3;现基本找不到 <1.2 的显卡,直接定 vulkan1.2。 const std::string cmd = - "\"" + glslc + "\" -fshader-stage=compute -o " + dst + " " + src; + "\"" + glslc + "\" -fshader-stage=compute --target-env=vulkan1.2 -o " + dst + " " + src; return std::system(cmd.c_str()) == 0; } @@ -150,6 +152,7 @@ int main(int argc, char* argv[]) H << " std::size_t spirv_words;\n"; H << " int reduce_axis; // -1=逐元素, 0=行归约, 1=列归约\n"; H << " std::uint32_t view_param_count; // 运行时视图参数个数(RowMod/RotateHalf 的 push constant vp 槽)\n"; + H << " std::uint32_t vec_width; // 每线程处理元素数(1=标量, 4=vec4)\n"; H << "};\n\n"; for (const auto& spec : reg.specs) @@ -216,11 +219,13 @@ int main(int argc, char* argv[]) if (raxis == -2) continue; // 与上方跳过保持一致 const std::string key = nn::expr_spec_key(spec); + const std::uint32_t vecw = + (raxis < 0 && nn::glsl_vec4_eligible(spec)) ? 4u : 1u; H << " { \"" << key << "\",\n " << emit_spec(spec) << ",\n" << " kSpirv_" << key << ", sizeof(kSpirv_" << key << ")/sizeof(std::uint32_t), " << raxis << ", " - << nn::expr_spec_runtime_view_param_count(spec) << " },\n"; + << nn::expr_spec_runtime_view_param_count(spec) << ", " << vecw << " },\n"; } H << "};\n"; H << "inline constexpr std::size_t kFusedShaderCount =\n" From 3b0f2fa302ef65d855950eb5eb462b85be98de7c Mon Sep 17 00:00:00 2001 From: EthanPeng-2048 Date: Tue, 25 Aug 2026 01:36:09 +0800 Subject: [PATCH 6/9] =?UTF-8?q?warp-shuffle=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 3 +- include/neuralnet.cpp/glsl_gen.hpp | 1 + src/reduce_bench.cpp | 132 +++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 src/reduce_bench.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 72d7d11f..829cea73 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,7 +107,8 @@ list(APPEND NN_APP_TARGETS text_train text_infer tokenizer_train tokenizer_infer # ── 计算性能基准工具 ───────────────────────────────────────────────────── nn_add_executable(compute_bench src/compute_bench.cpp) nn_add_executable(bench_thresholds src/bench_thresholds.cpp) -list(APPEND NN_APP_TARGETS compute_bench bench_thresholds) +nn_add_executable(reduce_bench src/reduce_bench.cpp) +list(APPEND NN_APP_TARGETS compute_bench bench_thresholds reduce_bench) # ── 测试 / 验证目标(默认关闭,NN_ENABLE_TESTS=ON 时构建)───────────── if(NN_ENABLE_TESTS) diff --git a/include/neuralnet.cpp/glsl_gen.hpp b/include/neuralnet.cpp/glsl_gen.hpp index f81fd062..816e986d 100644 --- a/include/neuralnet.cpp/glsl_gen.hpp +++ b/include/neuralnet.cpp/glsl_gen.hpp @@ -629,6 +629,7 @@ inline std::string generate_glsl(const std::string& name, const ExprSpec& spec) // 归约:warp shuffle 蝴蝶归约(subgroup)替代共享内存树形归约。 // 第 1 步:warp 内 subgroupAdd/subgroupMax → 每 warp 一个部分和(零共享/屏障) // 第 2 步:首 warp 归约全部 warp 部分和 → s_red[slot][0](屏障后全线程可见) + // 真机(NVIDIA, prime-run)大矩阵实测有效(combine 步骤不再是多步 barrier 串行)。 const auto emit_tree_reduce = [&](int slot, bool is_max) { const std::string s = "s_red[" + std::to_string(slot) + "]"; diff --git a/src/reduce_bench.cpp b/src/reduce_bench.cpp new file mode 100644 index 00000000..c0349037 --- /dev/null +++ b/src/reduce_bench.cpp @@ -0,0 +1,132 @@ +// ─────────────────────────────────────────────────────────────────────────── +// reduce_bench.cpp — 归约 kernel 性能对比(旧共享内存树 vs 新 warp shuffle) +// +// 旧算法:GpuEngine::row_reduce_sum / col_reduce_sum +// → 独立 reduce.comp(单累加器 + 共享内存树形归约) +// 新算法:dsl::compute 触发 AOT 融合归约 shader(fused_*) +// → 单累加器 + warp shuffle(subgroup) 归约 +// 两者均为"单累加器 + 逐线程跨步累加",唯一差异是最终树形归约实现 +// (共享内存树 vs subgroup 蝴蝶归约)→ 干净隔离 warp shuffle 收益。 +// +// 指标:GB/s = 读入整矩阵字节数 / 耗时。归约 kernel 主要受内存带宽限制。 +// +// 用法:reduce_bench [shapes...] (默认若干大形状) +// ─────────────────────────────────────────────────────────────────────────── + +#include + +#include +#include +#include +#include +#include +#include +#include + +using nn::Scalar; +namespace dsl = nn::dsl; + +#ifndef NN_HAS_VULKAN +int main() +{ + std::printf("[SKIP] 需要 Vulkan SDK(-DNN_HAS_VULKAN)\n"); + return 0; +} +#else +#include + +using nn::GpuBackend; +using nn::GpuEngine; +using nn::Tensor; + +namespace +{ + +// 计时一个可空返回的 GPU 调用,返回单次平均耗时(秒) +template +double time_call(F&& f, int iters) +{ + // warmup + f(); + auto t0 = std::chrono::steady_clock::now(); + for (int i = 0; i < iters; ++i) + { + auto r = f(); + if (!r) { std::printf("[ERR] 调用失败: %s\n", r.error().message.c_str()); return -1.0; } + } + auto t1 = std::chrono::steady_clock::now(); + return std::chrono::duration(t1 - t0).count() / iters; +} + +void bench_shape(GpuEngine& gpu, std::size_t rows, std::size_t cols, int iters) +{ + const double bytes = static_cast(rows) * cols * sizeof(Scalar); + const double mbytes = bytes / 1e6; // MB + + std::mt19937 rng(12345); + std::uniform_real_distribution dist(-1.0f, 1.0f); + Tensor x = Tensor::cpu(rows, cols); + for (auto& v : x.cpu_matrix().span()) v = dist(rng); + const Tensor xt = x; + + // ── 行求和:旧(reduce_gpu) vs 新(融合 row_reduce_sum)── + // 新路径用 compute_reduce(输出归约向量 (rows,1)),与旧路径输出对齐, + // 避免 compute() 把归约结果广播写到整个 (rows,cols)(多一次整矩阵写)。 + // leaf() 走表达式重载 → 归约**指令**,命中融合 shader。 + const double old_rs = time_call([&]{ return gpu.row_reduce_sum(xt); }, iters); + const double new_rs = time_call([&]{ + return dsl::compute_reduce(gpu, dsl::row_reduce_sum(dsl::leaf(xt)), rows, cols); }, iters); + + // ── 列求和:旧(reduce_gpu) vs 新(融合 col_reduce_sum)── + const double old_cs = time_call([&]{ return gpu.col_reduce_sum(xt); }, iters); + const double new_cs = time_call([&]{ + return dsl::compute_reduce(gpu, dsl::col_reduce_sum(dsl::leaf(xt)), rows, cols); }, iters); + + std::printf("shape %zux%-8zu (%.0f MB)\n", rows, cols, mbytes); + std::printf(" 行求和: 旧 %.2f GB/s 新 %.2f GB/s 加速 %s\n", + old_rs > 0 ? bytes / old_rs / 1e9 : 0.0, + new_rs > 0 ? bytes / new_rs / 1e9 : 0.0, + (old_rs > 0 && new_rs > 0) ? (std::to_string(old_rs / new_rs) + "x").c_str() : "-"); + std::printf(" 列求和: 旧 %.2f GB/s 新 %.2f GB/s 加速 %s\n", + old_cs > 0 ? bytes / old_cs / 1e9 : 0.0, + new_cs > 0 ? bytes / new_cs / 1e9 : 0.0, + (old_cs > 0 && new_cs > 0) ? (std::to_string(old_cs / new_cs) + "x").c_str() : "-"); +} + +} // namespace + +int main(int argc, char** argv) +{ + std::printf("reduce_bench — 旧(共享内存树) vs 新(warp shuffle) 归约吞吐\n"); + auto& backend = GpuBackend::instance(); + auto init_r = backend.initialize(); + if (!init_r) + { + std::printf("[ERR] GPU 初始化失败: %s\n", init_r.error().message.c_str()); + return 1; + } + GpuEngine gpu(backend); + + // 默认形状;可用命令行覆盖:reduce_bench 4096,4096 8192,8192 ... + std::vector> shapes; + if (argc > 1) + { + for (int i = 1; i < argc; ++i) + { + std::size_t r = 0, c = 0; + if (std::sscanf(argv[i], "%zu,%zu", &r, &c) == 2) + shapes.push_back({r, c}); + } + } + if (shapes.empty()) + shapes = {{4096, 4096}, {8192, 8192}, {1024, 65536}, {65536, 1024}}; + + for (auto& [r, c] : shapes) + { + // 迭代次数随规模缩放,避免总耗时过长 + const int iters = (r * c >= 256ull * 1024 * 1024) ? 3 : 10; + bench_shape(gpu, r, c, iters); + } + return 0; +} +#endif // NN_HAS_VULKAN From fb37a296069a5c080a7b91b7e061cdcf83f2fcf9 Mon Sep 17 00:00:00 2001 From: EthanPeng-2048 Date: Tue, 25 Aug 2026 01:56:42 +0800 Subject: [PATCH 7/9] 1 --- include/neuralnet.cpp/backend/vk_backend.hpp | 5 +- include/neuralnet.cpp/glsl_gen.hpp | 283 +- references/nir_opt_vectorize.c | 754 ---- references/nir_opt_vectorize_io.c | 671 ---- references/nir_opt_vectorize_io_vars.c | 636 ---- references/writer/BUILD.bazel | 140 - references/writer/BUILD.cfg | 3 - references/writer/BUILD.cmake | 198 -- references/writer/BUILD.gn | 176 - references/writer/ast_printer/BUILD.bazel | 167 - references/writer/ast_printer/BUILD.cfg | 3 - references/writer/ast_printer/BUILD.cmake | 169 - references/writer/ast_printer/BUILD.gn | 165 - .../writer/ast_printer/array_accessor_test.cc | 57 - references/writer/ast_printer/assign_test.cc | 53 - references/writer/ast_printer/ast_printer.cc | 3028 ----------------- references/writer/ast_printer/ast_printer.h | 500 --- .../writer/ast_printer/ast_printer_test.cc | 147 - references/writer/ast_printer/binary_test.cc | 901 ----- references/writer/ast_printer/bitcast_test.cc | 204 -- references/writer/ast_printer/block_test.cc | 53 - references/writer/ast_printer/break_test.cc | 50 - references/writer/ast_printer/builtin_test.cc | 1680 --------- .../ast_printer/builtin_texture_test.cc | 318 -- references/writer/ast_printer/call_test.cc | 98 - references/writer/ast_printer/case_test.cc | 108 - references/writer/ast_printer/cast_test.cc | 66 - .../writer/ast_printer/constructor_test.cc | 437 --- .../writer/ast_printer/continue_test.cc | 57 - references/writer/ast_printer/discard_test.cc | 51 - .../writer/ast_printer/function_test.cc | 977 ------ references/writer/ast_printer/helper_test.h | 140 - .../writer/ast_printer/identifier_test.cc | 53 - references/writer/ast_printer/if_test.cc | 137 - references/writer/ast_printer/import_test.cc | 296 -- references/writer/ast_printer/loop_test.cc | 493 --- .../ast_printer/member_accessor_test.cc | 917 ----- .../ast_printer/module_constant_test.cc | 349 -- references/writer/ast_printer/return_test.cc | 62 - .../writer/ast_printer/sanitizer_test.cc | 339 -- .../writer/ast_printer/storage_buffer_test.cc | 108 - references/writer/ast_printer/switch_test.cc | 92 - references/writer/ast_printer/type_test.cc | 594 ---- .../writer/ast_printer/unary_op_test.cc | 120 - .../writer/ast_printer/uniform_buffer_test.cc | 82 - .../variable_decl_statement_test.cc | 540 --- .../writer/ast_printer/workgroup_var_test.cc | 74 - references/writer/ast_raise/BUILD.bazel | 183 - references/writer/ast_raise/BUILD.cfg | 6 - references/writer/ast_raise/BUILD.cmake | 162 - references/writer/ast_raise/BUILD.gn | 151 - .../writer/ast_raise/combine_samplers.cc | 455 --- .../writer/ast_raise/combine_samplers.h | 86 - .../writer/ast_raise/combine_samplers_test.cc | 1218 ------- references/writer/ast_raise/pad_structs.cc | 191 -- references/writer/ast_raise/pad_structs.h | 56 - .../writer/ast_raise/pad_structs_test.cc | 757 ----- .../writer/ast_raise/texture_1d_to_2d.cc | 204 -- .../writer/ast_raise/texture_1d_to_2d.h | 56 - .../writer/ast_raise/texture_1d_to_2d_test.cc | 318 -- .../texture_builtins_from_uniform.cc | 426 --- .../ast_raise/texture_builtins_from_uniform.h | 115 - .../texture_builtins_from_uniform_test.cc | 575 ---- references/writer/common/BUILD.bazel | 125 - references/writer/common/BUILD.cfg | 3 - references/writer/common/BUILD.cmake | 126 - references/writer/common/BUILD.gn | 120 - references/writer/common/options.cc | 38 - references/writer/common/options.h | 190 -- references/writer/common/options_test.cc | 40 - references/writer/common/printer_support.cc | 68 - references/writer/common/printer_support.h | 54 - references/writer/common/version.h | 83 - references/writer/common/version_test.cc | 40 - references/writer/output.cc | 38 - references/writer/output.h | 56 - references/writer/printer/BUILD.bazel | 121 - references/writer/printer/BUILD.cfg | 3 - references/writer/printer/BUILD.cmake | 123 - references/writer/printer/BUILD.gn | 117 - references/writer/printer/constant_test.cc | 41 - references/writer/printer/function_test.cc | 45 - references/writer/printer/helper_test.h | 101 - references/writer/printer/printer.cc | 176 - references/writer/printer/printer.h | 52 - references/writer/raise/BUILD.bazel | 62 - references/writer/raise/BUILD.cmake | 57 - references/writer/raise/BUILD.gn | 58 - references/writer/raise/raise.cc | 44 - references/writer/raise/raise.h | 47 - references/writer/writer.cc | 83 - references/writer/writer.h | 72 - references/writer/writer_ast_fuzz.cc | 81 - references/writer/writer_bench.cc | 65 - src/reduce_bench.cpp | 63 +- 95 files changed, 234 insertions(+), 23668 deletions(-) delete mode 100644 references/nir_opt_vectorize.c delete mode 100644 references/nir_opt_vectorize_io.c delete mode 100644 references/nir_opt_vectorize_io_vars.c delete mode 100644 references/writer/BUILD.bazel delete mode 100644 references/writer/BUILD.cfg delete mode 100644 references/writer/BUILD.cmake delete mode 100644 references/writer/BUILD.gn delete mode 100644 references/writer/ast_printer/BUILD.bazel delete mode 100644 references/writer/ast_printer/BUILD.cfg delete mode 100644 references/writer/ast_printer/BUILD.cmake delete mode 100644 references/writer/ast_printer/BUILD.gn delete mode 100644 references/writer/ast_printer/array_accessor_test.cc delete mode 100644 references/writer/ast_printer/assign_test.cc delete mode 100644 references/writer/ast_printer/ast_printer.cc delete mode 100644 references/writer/ast_printer/ast_printer.h delete mode 100644 references/writer/ast_printer/ast_printer_test.cc delete mode 100644 references/writer/ast_printer/binary_test.cc delete mode 100644 references/writer/ast_printer/bitcast_test.cc delete mode 100644 references/writer/ast_printer/block_test.cc delete mode 100644 references/writer/ast_printer/break_test.cc delete mode 100644 references/writer/ast_printer/builtin_test.cc delete mode 100644 references/writer/ast_printer/builtin_texture_test.cc delete mode 100644 references/writer/ast_printer/call_test.cc delete mode 100644 references/writer/ast_printer/case_test.cc delete mode 100644 references/writer/ast_printer/cast_test.cc delete mode 100644 references/writer/ast_printer/constructor_test.cc delete mode 100644 references/writer/ast_printer/continue_test.cc delete mode 100644 references/writer/ast_printer/discard_test.cc delete mode 100644 references/writer/ast_printer/function_test.cc delete mode 100644 references/writer/ast_printer/helper_test.h delete mode 100644 references/writer/ast_printer/identifier_test.cc delete mode 100644 references/writer/ast_printer/if_test.cc delete mode 100644 references/writer/ast_printer/import_test.cc delete mode 100644 references/writer/ast_printer/loop_test.cc delete mode 100644 references/writer/ast_printer/member_accessor_test.cc delete mode 100644 references/writer/ast_printer/module_constant_test.cc delete mode 100644 references/writer/ast_printer/return_test.cc delete mode 100644 references/writer/ast_printer/sanitizer_test.cc delete mode 100644 references/writer/ast_printer/storage_buffer_test.cc delete mode 100644 references/writer/ast_printer/switch_test.cc delete mode 100644 references/writer/ast_printer/type_test.cc delete mode 100644 references/writer/ast_printer/unary_op_test.cc delete mode 100644 references/writer/ast_printer/uniform_buffer_test.cc delete mode 100644 references/writer/ast_printer/variable_decl_statement_test.cc delete mode 100644 references/writer/ast_printer/workgroup_var_test.cc delete mode 100644 references/writer/ast_raise/BUILD.bazel delete mode 100644 references/writer/ast_raise/BUILD.cfg delete mode 100644 references/writer/ast_raise/BUILD.cmake delete mode 100644 references/writer/ast_raise/BUILD.gn delete mode 100644 references/writer/ast_raise/combine_samplers.cc delete mode 100644 references/writer/ast_raise/combine_samplers.h delete mode 100644 references/writer/ast_raise/combine_samplers_test.cc delete mode 100644 references/writer/ast_raise/pad_structs.cc delete mode 100644 references/writer/ast_raise/pad_structs.h delete mode 100644 references/writer/ast_raise/pad_structs_test.cc delete mode 100644 references/writer/ast_raise/texture_1d_to_2d.cc delete mode 100644 references/writer/ast_raise/texture_1d_to_2d.h delete mode 100644 references/writer/ast_raise/texture_1d_to_2d_test.cc delete mode 100644 references/writer/ast_raise/texture_builtins_from_uniform.cc delete mode 100644 references/writer/ast_raise/texture_builtins_from_uniform.h delete mode 100644 references/writer/ast_raise/texture_builtins_from_uniform_test.cc delete mode 100644 references/writer/common/BUILD.bazel delete mode 100644 references/writer/common/BUILD.cfg delete mode 100644 references/writer/common/BUILD.cmake delete mode 100644 references/writer/common/BUILD.gn delete mode 100644 references/writer/common/options.cc delete mode 100644 references/writer/common/options.h delete mode 100644 references/writer/common/options_test.cc delete mode 100644 references/writer/common/printer_support.cc delete mode 100644 references/writer/common/printer_support.h delete mode 100644 references/writer/common/version.h delete mode 100644 references/writer/common/version_test.cc delete mode 100644 references/writer/output.cc delete mode 100644 references/writer/output.h delete mode 100644 references/writer/printer/BUILD.bazel delete mode 100644 references/writer/printer/BUILD.cfg delete mode 100644 references/writer/printer/BUILD.cmake delete mode 100644 references/writer/printer/BUILD.gn delete mode 100644 references/writer/printer/constant_test.cc delete mode 100644 references/writer/printer/function_test.cc delete mode 100644 references/writer/printer/helper_test.h delete mode 100644 references/writer/printer/printer.cc delete mode 100644 references/writer/printer/printer.h delete mode 100644 references/writer/raise/BUILD.bazel delete mode 100644 references/writer/raise/BUILD.cmake delete mode 100644 references/writer/raise/BUILD.gn delete mode 100644 references/writer/raise/raise.cc delete mode 100644 references/writer/raise/raise.h delete mode 100644 references/writer/writer.cc delete mode 100644 references/writer/writer.h delete mode 100644 references/writer/writer_ast_fuzz.cc delete mode 100644 references/writer/writer_bench.cc diff --git a/include/neuralnet.cpp/backend/vk_backend.hpp b/include/neuralnet.cpp/backend/vk_backend.hpp index 0c6e34d9..edb19369 100644 --- a/include/neuralnet.cpp/backend/vk_backend.hpp +++ b/include/neuralnet.cpp/backend/vk_backend.hpp @@ -3173,12 +3173,13 @@ class GpuBackend vkCmdPushConstants(cmd, pipeline.pipeline_layout(), VK_SHADER_STAGE_COMPUTE_BIT, 0, static_cast(pc.size()), pc.data()); - // dispatch:逐元素 = ceil(count/(256*vec_width));行归约 = rows 个工作组;列归约 = cols 个工作组 + // dispatch:逐元素 = ceil(count/(256*vec_width));行归约 = rows 个工作组; + // 列归约(tile) = ceil(cols/256) 个工作组(每工作组 256 列) const std::uint32_t vec_width = fused_vec_width_.count(shader_name) ? fused_vec_width_.at(shader_name) : 1u; const std::uint32_t wg_count = (raxis == 0) ? static_cast(rows) - : (raxis == 1) ? static_cast(cols) + : (raxis == 1) ? (static_cast(cols) + 255u) / 256u : (count + 256u * vec_width - 1u) / (256u * vec_width); vkCmdDispatch(cmd, wg_count, 1, 1); record_output_barrier(cmd, output.buffer().impl()); diff --git a/include/neuralnet.cpp/glsl_gen.hpp b/include/neuralnet.cpp/glsl_gen.hpp index 816e986d..805380fa 100644 --- a/include/neuralnet.cpp/glsl_gen.hpp +++ b/include/neuralnet.cpp/glsl_gen.hpp @@ -512,8 +512,12 @@ inline std::string generate_glsl(const std::string& name, const ExprSpec& spec) L << "void main()\n{\n"; L << " const uint tid = gl_LocalInvocationID.x;\n"; L << " const uint idx = gl_WorkGroupID.x;\n"; - L << (is_row ? " if (idx >= rows) return;\n" - : " if (idx >= cols) return;\n"); + // 行归约:工作组=单行,idx= rows) return;\n" : ""); + + // 归约结果索引:行=工作组单行(s_red[slot][0]);列 tile=每线程一列(s_red[slot][tid]) + const std::string red_idx = is_row ? "0" : "tid"; // ── 操作数求值(当前元素由 row/col 变量给出) ── const auto operand = [&](const ExprOperand& op) -> std::string @@ -527,13 +531,13 @@ inline std::string generate_glsl(const std::string& name, const ExprSpec& spec) case static_cast(ExprOperandKind::Const): return "c" + std::to_string(op.idx); case static_cast(ExprOperandKind::Reduce): - return "s_red[" + std::to_string(slot_of_instr[op.idx]) + "][0]"; + return "s_red[" + std::to_string(slot_of_instr[op.idx]) + "][" + red_idx + "]"; case static_cast(ExprOperandKind::Input): { const ExprView& v = spec.views[op.idx]; const ExprViewKind vk = static_cast(v.kind); if (expr_view_is_reduce(vk)) - return "s_red[" + std::to_string(slot_of_view[op.idx]) + "][0]"; + return "s_red[" + std::to_string(slot_of_view[op.idx]) + "][" + red_idx + "]"; if (vk == ExprViewKind::RowBroadcast) return "b" + std::to_string(op.idx) + "[row]"; if (vk == ExprViewKind::ColBroadcast) @@ -651,117 +655,186 @@ inline std::string generate_glsl(const std::string& name, const ExprSpec& spec) L << " barrier();\n"; }; - // 元素索引声明(行归约:固定行、循环列;列归约:循环行、固定列) - const std::string loop_decl = is_row - ? " for (uint c = tid; c < cols; c += 256u) {\n const uint row = idx;\n const uint col = c;\n" - : " for (uint r = tid; r < rows; r += 256u) {\n const uint row = r;\n const uint col = idx;\n"; - const std::string out_idx = "row*cols + col"; + // 组合 4 路累加器(sum/max) + const auto combine4 = [&](bool is_max, const std::string& a0, const std::string& a1, + const std::string& a2, const std::string& a3) { + return combine(is_max, combine(is_max, combine(is_max, a0, a1), a2), a3); + }; - // ── 归约视图归约 pass(多路独立累加器,破串行累积延迟)── - // view_val(t):跨步索引 t 处的输入值(行归约:row=idx 固定、col=t; - // 列归约:col=idx 固定、row=t)。多累加器 stride 4*256。 - for (std::size_t k = 0; k < n_inputs; ++k) + if (is_row) { - const ExprView& v = spec.views[k]; - if (!expr_view_is_reduce(static_cast(v.kind))) - continue; - const int slot = slot_of_view[k]; - const bool is_max = slot_is_max[static_cast(slot)]; - const std::string init = is_max ? "uintBitsToFloat(0xff800000u)" : "0.0"; - const auto view_val = [&](const std::string& t) { - return is_row - ? "b" + std::to_string(k) + "[idx*cols + " + t + "]" - : "b" + std::to_string(k) + "[" + t + "*cols + idx]"; - }; - L << "\n // 归约视图 " << k << " (槽 " << slot << ")\n"; - L << " {\n"; - L << " float acc0 = " << init << ", acc1 = " << init - << ", acc2 = " << init << ", acc3 = " << init << ";\n"; - L << " const uint len = " << (is_row ? "cols" : "rows") << ";\n"; - L << " uint i = tid;\n"; - L << " for (; i + 3u*256u < len; i += 4u*256u) {\n"; - L << " acc0 = " << combine(is_max, "acc0", view_val("i")) << ";\n"; - L << " acc1 = " << combine(is_max, "acc1", view_val("i+256u")) << ";\n"; - L << " acc2 = " << combine(is_max, "acc2", view_val("i+512u")) << ";\n"; - L << " acc3 = " << combine(is_max, "acc3", view_val("i+768u")) << ";\n"; + // ═══ 行归约(工作组单行、跨线程跨步 + 树形归约;合并访问)═══ + const std::string loop_decl = + " for (uint c = tid; c < cols; c += 256u) {\n const uint row = idx;\n const uint col = c;\n"; + const std::string out_idx = "row*cols + col"; + + // ── 归约视图 pass(多累加器,跨步 4*256)── + for (std::size_t k = 0; k < n_inputs; ++k) + { + const ExprView& v = spec.views[k]; + if (!expr_view_is_reduce(static_cast(v.kind))) + continue; + const int slot = slot_of_view[k]; + const bool is_max = slot_is_max[static_cast(slot)]; + const std::string init = is_max ? "uintBitsToFloat(0xff800000u)" : "0.0"; + L << "\n // 归约视图 " << k << " (槽 " << slot << ")\n"; + L << " {\n"; + L << " float acc0 = " << init << ", acc1 = " << init + << ", acc2 = " << init << ", acc3 = " << init << ";\n"; + L << " uint i = tid;\n"; + L << " for (; i + 3u*256u < cols; i += 4u*256u) {\n"; + L << " acc0 = " << combine(is_max, "acc0", "b" + std::to_string(k) + "[idx*cols + i]") << ";\n"; + L << " acc1 = " << combine(is_max, "acc1", "b" + std::to_string(k) + "[idx*cols + i+256u]") << ";\n"; + L << " acc2 = " << combine(is_max, "acc2", "b" + std::to_string(k) + "[idx*cols + i+512u]") << ";\n"; + L << " acc3 = " << combine(is_max, "acc3", "b" + std::to_string(k) + "[idx*cols + i+768u]") << ";\n"; + L << " }\n"; + L << " for (; i < cols; i += 256u)\n"; + L << " acc0 = " << combine(is_max, "acc0", "b" + std::to_string(k) + "[idx*cols + i]") << ";\n"; + L << " s_red[" << slot << "][tid] = " + << combine4(is_max, "acc0", "acc1", "acc2", "acc3") << ";\n"; + L << " }\n"; + emit_tree_reduce(slot, is_max); + } + + // ── 归约指令 pass(跨步循环 + 树形归约)── + for (std::size_t ri = 0; ri < spec.instrs.size(); ++ri) + { + const ExprInstr& R = spec.instrs[ri]; + if (!expr_op_is_reduce(static_cast(R.op))) + continue; + const int slot = slot_of_instr[R.dst]; + const bool is_max = slot_is_max[static_cast(slot)]; + const bool src_is_reg = + (R.a.kind == static_cast(ExprOperandKind::Reg) || + R.a.kind == static_cast(ExprOperandKind::Fanout)); + const std::string src = src_is_reg + ? "r" + std::to_string(static_cast(R.a.idx)) : operand(R.a); + L << "\n // 归约指令 " << ri << " (槽 " << slot << ")\n"; + L << " {\n"; + L << " float acc = " << (is_max ? "uintBitsToFloat(0xff800000u)" : "0.0") << ";\n"; + L << loop_decl; + emit_reg_decl(); + emit_instrs(0, ri); + L << " acc = " << combine(is_max, "acc", src) << ";\n"; + L << " }\n"; + L << " s_red[" << slot << "][tid] = acc;\n"; + L << " }\n"; + emit_tree_reduce(slot, is_max); + } + + // ── 输出 pass(行)── + L << "\n // 输出\n"; + L << " if (vector_out == 1u) {\n"; + L << " if (tid == 0u) {\n"; + if (last_is_reduce) + L << " bout[idx] = s_red[" << slot_of_instr[spec.instrs.back().dst] << "][0];\n"; + else + { + L << " const uint row = idx;\n const uint col = 0u;\n"; + emit_reg_decl(); + emit_instrs(0, spec.instrs.size()); + L << " bout[idx] = r" << static_cast(spec.instrs.back().dst) << ";\n"; + } L << " }\n"; - L << " for (; i < len; i += 256u)\n"; - L << " acc0 = " << combine(is_max, "acc0", view_val("i")) << ";\n"; - L << " s_red[" << slot << "][tid] = " - << combine(is_max, combine(is_max, combine(is_max, "acc0", "acc1"), "acc2"), "acc3") - << ";\n"; + L << " return;\n"; L << " }\n"; - emit_tree_reduce(slot, is_max); - } - - // ── 归约指令 pass(按指令序,源可引用更早归约结果) ── - // 寄存器声明直接放在 for 循环体内(无需额外 {} 包裹),使源寄存器在 - // combine 处可见;不同 pass 各自循环体作用域隔离,无重声明冲突。 - for (std::size_t ri = 0; ri < spec.instrs.size(); ++ri) - { - const ExprInstr& R = spec.instrs[ri]; - const ExprOp rop = static_cast(R.op); - if (!expr_op_is_reduce(rop)) - continue; - const int slot = slot_of_instr[R.dst]; - const bool is_max = slot_is_max[static_cast(slot)]; - const bool src_is_reg = - (R.a.kind == static_cast(ExprOperandKind::Reg) || - R.a.kind == static_cast(ExprOperandKind::Fanout)); - const std::string src = src_is_reg - ? "r" + std::to_string(static_cast(R.a.idx)) - : operand(R.a); - L << "\n // 归约指令 " << ri << " (槽 " << slot << ")\n"; - L << " {\n"; - L << " float acc = " - << (is_max ? "uintBitsToFloat(0xff800000u)" : "0.0") << ";\n"; L << loop_decl; - emit_reg_decl(); - emit_instrs(0, ri); - L << " acc = " << combine(is_max, "acc", src) << ";\n"; - L << " }\n"; - L << " s_red[" << slot << "][tid] = acc;\n"; + if (last_is_reduce) + L << " bout[" << out_idx << "] = s_red[" << slot_of_instr[spec.instrs.back().dst] << "][0];\n"; + else + { + emit_reg_decl(); + emit_instrs(0, spec.instrs.size()); + L << " bout[" << out_idx << "] = r" << static_cast(spec.instrs.back().dst) << ";\n"; + } L << " }\n"; - emit_tree_reduce(slot, is_max); - } - - // ── 输出 pass ── - // vector_out=1:输出归约向量(thread 0 写代表元素,输出形状由后端分配 - // (rows,1)/(1,cols));vector_out=0:广播到 (rows,cols)。 - L << "\n // 输出\n"; - L << " if (vector_out == 1u) {\n"; - L << " const uint row = " << (is_row ? "idx" : "0u") << ";\n"; - L << " const uint col = " << (is_row ? "0u" : "idx") << ";\n"; - L << " if (tid == 0u) {\n"; - if (last_is_reduce) - { - L << " bout[" << (is_row ? "row" : "col") << "] = s_red[" - << slot_of_instr[spec.instrs.back().dst] << "][0];\n"; } else { - emit_reg_decl(); - emit_instrs(0, spec.instrs.size()); - L << " bout[" << (is_row ? "row" : "col") << "] = r" - << static_cast(spec.instrs.back().dst) << ";\n"; - } - L << " }\n"; - L << " return;\n"; - L << " }\n"; - L << loop_decl; - if (last_is_reduce) - { - L << " bout[" << out_idx << "] = s_red[" - << slot_of_instr[spec.instrs.back().dst] << "][0];\n"; - } - else - { - emit_reg_decl(); - emit_instrs(0, spec.instrs.size()); - L << " bout[" << out_idx << "] = r" - << static_cast(spec.instrs.back().dst) << ";\n"; + // ═══ 列归约 tile(合并访问):每工作组 256 列、每线程一整列,无需跨线程归约 ═══ + L << " const uint col = idx * 256u + tid;\n"; + L << " if (col >= cols) return;\n"; + + // ── 归约视图 pass(tile:每线程一整列,多累加器跨行顺序读,合并访问)── + for (std::size_t k = 0; k < n_inputs; ++k) + { + const ExprView& v = spec.views[k]; + if (!expr_view_is_reduce(static_cast(v.kind))) + continue; + const int slot = slot_of_view[k]; + const bool is_max = slot_is_max[static_cast(slot)]; + const std::string init = is_max ? "uintBitsToFloat(0xff800000u)" : "0.0"; + L << "\n // 归约视图 " << k << " (槽 " << slot << ")\n"; + L << " {\n"; + L << " float acc0 = " << init << ", acc1 = " << init + << ", acc2 = " << init << ", acc3 = " << init << ";\n"; + L << " uint i = 0u;\n"; + L << " for (; i + 3u < rows; i += 4u) {\n"; + L << " acc0 = " << combine(is_max, "acc0", "b" + std::to_string(k) + "[i*cols + col]") << ";\n"; + L << " acc1 = " << combine(is_max, "acc1", "b" + std::to_string(k) + "[(i+1u)*cols + col]") << ";\n"; + L << " acc2 = " << combine(is_max, "acc2", "b" + std::to_string(k) + "[(i+2u)*cols + col]") << ";\n"; + L << " acc3 = " << combine(is_max, "acc3", "b" + std::to_string(k) + "[(i+3u)*cols + col]") << ";\n"; + L << " }\n"; + L << " for (; i < rows; ++i)\n"; + L << " acc0 = " << combine(is_max, "acc0", "b" + std::to_string(k) + "[i*cols + col]") << ";\n"; + L << " s_red[" << slot << "][tid] = " + << combine4(is_max, "acc0", "acc1", "acc2", "acc3") << ";\n"; + L << " }\n"; + } + + // ── 归约指令 pass(tile:单累加器,跨行顺序读,合并访问)── + for (std::size_t ri = 0; ri < spec.instrs.size(); ++ri) + { + const ExprInstr& R = spec.instrs[ri]; + if (!expr_op_is_reduce(static_cast(R.op))) + continue; + const int slot = slot_of_instr[R.dst]; + const bool is_max = slot_is_max[static_cast(slot)]; + const bool src_is_reg = + (R.a.kind == static_cast(ExprOperandKind::Reg) || + R.a.kind == static_cast(ExprOperandKind::Fanout)); + const std::string src = src_is_reg + ? "r" + std::to_string(static_cast(R.a.idx)) : operand(R.a); + L << "\n // 归约指令 " << ri << " (槽 " << slot << ")\n"; + L << " {\n"; + L << " float acc = " << (is_max ? "uintBitsToFloat(0xff800000u)" : "0.0") << ";\n"; + L << " for (uint row = 0u; row < rows; ++row) {\n"; + emit_reg_decl(); + emit_instrs(0, ri); + L << " acc = " << combine(is_max, "acc", src) << ";\n"; + L << " }\n"; + L << " s_red[" << slot << "][tid] = acc;\n"; + L << " }\n"; + } + + // ── 输出 pass(列 tile)── + L << "\n // 输出\n"; + L << " if (vector_out == 1u) {\n"; + if (last_is_reduce) + L << " bout[col] = s_red[" << slot_of_instr[spec.instrs.back().dst] << "][tid];\n"; + else + { + L << " const uint row = 0u;\n"; + emit_reg_decl(); + emit_instrs(0, spec.instrs.size()); + L << " bout[col] = r" << static_cast(spec.instrs.back().dst) << ";\n"; + } + L << " return;\n"; + L << " }\n"; + if (last_is_reduce) + { + L << " for (uint row = 0u; row < rows; ++row)\n"; + L << " bout[row*cols + col] = s_red[" << slot_of_instr[spec.instrs.back().dst] << "][tid];\n"; + } + else + { + L << " for (uint row = 0u; row < rows; ++row) {\n"; + emit_reg_decl(); + emit_instrs(0, spec.instrs.size()); + L << " bout[row*cols + col] = r" << static_cast(spec.instrs.back().dst) << ";\n"; + L << " }\n"; + } } - L << " }\n"; L << "}\n"; return L.str(); } diff --git a/references/nir_opt_vectorize.c b/references/nir_opt_vectorize.c deleted file mode 100644 index 28cf42b8..00000000 --- a/references/nir_opt_vectorize.c +++ /dev/null @@ -1,754 +0,0 @@ -/* - * Copyright © 2015 Connor Abbott - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - * - */ - -/** - * nir_opt_vectorize() aims to vectorize ALU instructions. - * - * The default vectorization width is 4. - * If desired, a callback function which returns the max vectorization width - * per instruction can be provided. - * - * The max vectorization width must be a power of 2. - */ - -#include "util/u_dynarray.h" -#include "util/u_qsort.h" -#include "nir.h" -#include "nir_builder.h" -#include "nir_vla.h" - -#define XXH_INLINE_ALL -#include "util/xxhash.h" - -#define HASH(hash, data) XXH32(&data, sizeof(data), hash) - -static uint32_t -hash_src(uint32_t hash, const nir_src *src) -{ - void *hash_data = nir_src_is_const(*src) ? NULL : src->ssa; - - return HASH(hash, hash_data); -} - -static uint32_t -hash_alu_src(uint32_t hash, const nir_alu_src *src, - uint32_t num_components, uint32_t max_vec) -{ - /* hash whether a swizzle accesses elements beyond the maximum - * vectorization factor: - * For example accesses to .x and .y are considered different variables - * compared to accesses to .z and .w for 16-bit vec2. - */ - uint32_t swizzle = (src->swizzle[0] & ~(max_vec - 1)); - hash = HASH(hash, swizzle); - - return hash_src(hash, &src->src); -} - -static bool -phi_src_is_compatible(nir_def *def, uint32_t max_vec) -{ - /* hash_phi_src() and phi_srcs_equal() look at component 0 after chasing - * movs. For multi-component sources, make sure that component represents - * the whole source: all components are constants, or all components come - * from the same def and remain within the same max_vec. - */ - const nir_scalar first = nir_scalar_chase_movs(nir_get_scalar(def, 0)); - const bool first_is_const = nir_scalar_is_const(first); - const uint32_t mask = ~(max_vec - 1); - - for (uint8_t i = 1; i < def->num_components; i++) { - const nir_scalar chased = nir_scalar_chase_movs(nir_get_scalar(def, i)); - const bool chased_is_const = nir_scalar_is_const(chased); - - if (first_is_const || chased_is_const) { - if (first_is_const != chased_is_const) - return false; - continue; - } - - if (chased.def != first.def || - (chased.comp & mask) != (first.comp & mask)) - return false; - } - - return true; -} - -static uint32_t -hash_phi_src(uint32_t hash, const nir_phi_instr *phi, const nir_phi_src *src, - uint32_t max_vec) -{ - hash = HASH(hash, src->pred); - - nir_scalar chased = nir_scalar_chase_movs(nir_get_scalar(src->src.ssa, 0)); - uint32_t swizzle = chased.comp & ~(max_vec - 1); - hash = HASH(hash, swizzle); - - if (nir_scalar_is_const(chased)) { - void *data = NULL; - hash = HASH(hash, data); - } else if (src->pred->index < phi->instr.block->index) { - hash = HASH(hash, chased.def); - } else { - nir_instr *chased_instr = nir_def_instr(chased.def); - hash = HASH(hash, chased_instr->type); - - if (chased_instr->type == nir_instr_type_alu) - hash = HASH(hash, nir_instr_as_alu(chased_instr)->op); - } - - return hash; -} - -static uint32_t -hash_instr(const void *data) -{ - const nir_instr *instr = (nir_instr *)data; - uint32_t hash = HASH(0, instr->type); - - if (instr->type == nir_instr_type_phi) { - nir_phi_instr *phi = nir_instr_as_phi(instr); - - hash = HASH(hash, instr->block); - hash = HASH(hash, phi->def.bit_size); - - /* The order of phi sources is not guaranteed so hash commutatively. */ - nir_foreach_phi_src(src, phi) - hash *= hash_phi_src(0, phi, src, instr->pass_flags); - - return hash; - } - - assert(instr->type == nir_instr_type_alu); - nir_alu_instr *alu = nir_instr_as_alu(instr); - - hash = HASH(hash, alu->op); - hash = HASH(hash, alu->def.bit_size); - - unsigned i = 0; - if (nir_op_infos[alu->op].algebraic_properties & NIR_OP_IS_2SRC_COMMUTATIVE) { - uint32_t src0_hash = hash_alu_src(hash, &alu->src[0], - alu->def.num_components, - instr->pass_flags); - uint32_t src1_hash = hash_alu_src(hash, &alu->src[1], - alu->def.num_components, - instr->pass_flags); - - hash = src0_hash * src1_hash; - i = 2; - } - - for (; i < nir_op_infos[alu->op].num_inputs; i++) - hash = hash_alu_src(hash, &alu->src[i], - alu->def.num_components, - instr->pass_flags); - - return hash; -} - -static bool -srcs_equal(const nir_src *src1, const nir_src *src2) -{ - - return src1->ssa == src2->ssa || - (nir_src_is_const(*src1) && nir_src_is_const(*src2)); -} - -static bool -alu_srcs_equal(const nir_alu_src *src1, const nir_alu_src *src2, - uint32_t max_vec) -{ - uint32_t mask = ~(max_vec - 1); - if ((src1->swizzle[0] & mask) != (src2->swizzle[0] & mask)) - return false; - - return srcs_equal(&src1->src, &src2->src); -} - -static bool -alu_srcs_match_swapped(const nir_alu_instr *alu1, const nir_alu_instr *alu2, - uint32_t max_vec) -{ - return (nir_op_infos[alu1->op].algebraic_properties & NIR_OP_IS_2SRC_COMMUTATIVE) && - alu_srcs_equal(&alu1->src[0], &alu2->src[1], max_vec) && - alu_srcs_equal(&alu1->src[1], &alu2->src[0], max_vec); -} - -static bool -phi_srcs_equal(nir_block *block, const nir_phi_src *src1, - const nir_phi_src *src2, uint32_t max_vec) -{ - if (src1->pred != src2->pred) - return false; - - /* Since phi sources don't have swizzles, they are swizzled using movs. - * Get the real sources first. - */ - nir_scalar chased1 = nir_scalar_chase_movs(nir_get_scalar(src1->src.ssa, 0)); - nir_scalar chased2 = nir_scalar_chase_movs(nir_get_scalar(src2->src.ssa, 0)); - - if (nir_scalar_is_const(chased1) && nir_scalar_is_const(chased2)) - return true; - - uint32_t mask = ~(max_vec - 1); - if ((chased1.comp & mask) != (chased2.comp & mask)) - return false; - - /* For phi sources whose defs we have already processed, we require that - * they point to the same def like we do for ALU instructions. - */ - if (src1->pred->index < block->index) - return chased1.def == chased2.def; - - /* Otherwise (i.e., for loop back-edges), we haven't processed the sources - * yet so they haven't been vectorized. In this case, try to guess if they - * could be vectorized later. Keep it simple for now: if they are the same - * type of instruction and, if ALU, have the same operation, assume they - * might be vectorized later. Although this won't be true in general, this - * heuristic is probable good enough in practice: since we check that other - * (forward-edge) sources are vectorized, chances are the back-edge will - * also be vectorized. - */ - nir_instr *chased_instr1 = nir_def_instr(chased1.def); - nir_instr *chased_instr2 = nir_def_instr(chased2.def); - - if (chased_instr1->type != chased_instr2->type) - return false; - - if (chased_instr1->type != nir_instr_type_alu) - return true; - - return nir_instr_as_alu(chased_instr1)->op == - nir_instr_as_alu(chased_instr2)->op; -} - -static bool -instrs_equal(const void *data1, const void *data2) -{ - const nir_instr *instr1 = (nir_instr *)data1; - const nir_instr *instr2 = (nir_instr *)data2; - - if (instr1->type != instr2->type) - return false; - - if (instr1->type == nir_instr_type_phi) { - if (instr1->block != instr2->block) - return false; - - nir_phi_instr *phi1 = nir_instr_as_phi(instr1); - nir_phi_instr *phi2 = nir_instr_as_phi(instr2); - - if (phi1->def.bit_size != phi2->def.bit_size) - return false; - - nir_foreach_phi_src(src1, phi1) { - nir_phi_src *src2 = nir_phi_get_src_from_block(phi2, src1->pred); - - if (!phi_srcs_equal(instr1->block, src1, src2, instr1->pass_flags)) - return false; - } - - return true; - } - - assert(instr1->type == nir_instr_type_alu); - assert(instr2->type == nir_instr_type_alu); - - nir_alu_instr *alu1 = nir_instr_as_alu(instr1); - nir_alu_instr *alu2 = nir_instr_as_alu(instr2); - - if (alu1->op != alu2->op) - return false; - - if (alu1->def.bit_size != alu2->def.bit_size) - return false; - - unsigned i = 0; - if (alu_srcs_match_swapped(alu1, alu2, instr1->pass_flags)) - i = 2; - - for (; i < nir_op_infos[alu1->op].num_inputs; i++) { - if (!alu_srcs_equal(&alu1->src[i], &alu2->src[i], instr1->pass_flags)) - return false; - } - - return true; -} - -static bool -instr_can_rewrite(nir_instr *instr) -{ - switch (instr->type) { - case nir_instr_type_alu: { - nir_alu_instr *alu = nir_instr_as_alu(instr); - - /* Don't try and vectorize mov's. Either they'll be handled by copy - * prop, or they're actually necessary and trying to vectorize them - * would result in fighting with copy prop. - */ - if (alu->op == nir_op_mov) - return false; - - /* no need to hash instructions which are already vectorized */ - if (alu->def.num_components >= instr->pass_flags) - return false; - - if (nir_op_infos[alu->op].output_size != 0) - return false; - - for (unsigned i = 0; i < nir_op_infos[alu->op].num_inputs; i++) { - if (nir_op_infos[alu->op].input_sizes[i] != 0) - return false; - - /* don't hash instructions which are already swizzled - * outside of max_components: these should better be scalarized */ - uint32_t mask = ~(instr->pass_flags - 1); - for (unsigned j = 1; j < alu->def.num_components; j++) { - if ((alu->src[i].swizzle[0] & mask) != (alu->src[i].swizzle[j] & mask)) - return false; - } - } - - return true; - } - - case nir_instr_type_phi: { - nir_phi_instr *phi = nir_instr_as_phi(instr); - - if (phi->def.num_components >= instr->pass_flags) - return false; - - nir_foreach_phi_src(src, phi) { - if (!phi_src_is_compatible(src->src.ssa, instr->pass_flags)) - return false; - } - - return true; - } - - default: - break; - } - - return false; -} - -static void -rewrite_uses(nir_builder *b, struct set *instr_set, nir_def *def1, - nir_def *def2, nir_def *new_def) -{ - /* update all ALU uses */ - nir_foreach_use_safe(src, def1) { - nir_instr *user_instr = nir_src_use_instr(src); - if (user_instr->type == nir_instr_type_alu) { - /* Check if user is found in the hashset */ - struct set_entry *entry = _mesa_set_search(instr_set, user_instr); - - /* For ALU instructions, rewrite the source directly to avoid a - * round-trip through copy propagation. - */ - nir_src_rewrite(src, new_def); - - /* Rehash user if it was found in the hashset */ - if (entry && entry->key == user_instr) { - _mesa_set_remove(instr_set, entry); - _mesa_set_add(instr_set, user_instr); - } - } - } - - nir_foreach_use_safe(src, def2) { - if (nir_src_use_instr(src)->type == nir_instr_type_alu) { - /* For ALU instructions, rewrite the source directly to avoid a - * round-trip through copy propagation. - */ - nir_src_rewrite(src, new_def); - - nir_alu_src *alu_src = container_of(src, nir_alu_src, src); - nir_alu_instr *use = nir_instr_as_alu(nir_src_use_instr(src)); - unsigned components = - nir_ssa_alu_instr_src_components(use, alu_src - use->src); - for (unsigned i = 0; i < components; i++) - alu_src->swizzle[i] += def1->num_components; - } - } - - /* update all other uses if there are any */ - unsigned swiz[NIR_MAX_VEC_COMPONENTS]; - - if (!nir_def_is_unused(def1)) { - for (unsigned i = 0; i < def1->num_components; i++) - swiz[i] = i; - nir_def *new_def1 = nir_swizzle(b, new_def, swiz, def1->num_components); - nir_def_rewrite_uses(def1, new_def1); - } - - if (!nir_def_is_unused(def2)) { - for (unsigned i = 0; i < def2->num_components; i++) - swiz[i] = i + def1->num_components; - nir_def *new_def2 = nir_swizzle(b, new_def, swiz, def2->num_components); - nir_def_rewrite_uses(def2, new_def2); - } - - nir_instr_remove(nir_def_instr(def1)); - nir_instr_remove(nir_def_instr(def2)); -} - -static nir_block * -get_first_non_const_phi_pred(nir_phi_instr *phi) -{ - nir_block *result = NULL; - nir_foreach_phi_src(src, phi) { - /* Loop back edge source might not be vectors. */ - if (phi->instr.block->index < src->pred->index) - continue; - - /* Find the first predecessor in source order. */ - if (result && src->pred->index > result->index) - continue; - - /* Ignore constant phi sources. */ - if (nir_scalar_is_const(nir_scalar_resolved(src->src.ssa, 0))) - continue; - - result = src->pred; - } - - return result; -} - -static int -compare_scalar(const void *p1, const void *p2, void *data) -{ - uint8_t idx1 = *(const uint8_t *)p1; - uint8_t idx2 = *(const uint8_t *)p2; - - const nir_scalar *scalars = data; - - if (scalars[idx1].comp == scalars[idx2].comp) - return idx1 - idx2; - - return scalars[idx1].comp - scalars[idx2].comp; -} - -static nir_instr * -instr_try_combine_phi(struct set *instr_set, nir_phi_instr *phi1, nir_phi_instr *phi2) -{ - assert(phi1->def.bit_size == phi2->def.bit_size); - unsigned phi1_components = phi1->def.num_components; - unsigned phi2_components = phi2->def.num_components; - unsigned total_components = phi1_components + phi2_components; - - assert(phi1->instr.pass_flags == phi2->instr.pass_flags); - if (total_components > phi1->instr.pass_flags) - return NULL; - - assert(phi1->instr.block == phi2->instr.block); - nir_block *block = phi1->instr.block; - - nir_builder b = nir_builder_at(nir_after_instr(&phi1->instr)); - nir_phi_instr *new_phi = nir_phi_instr_create(b.shader); - nir_def_init(&new_phi->instr, &new_phi->def, total_components, - phi1->def.bit_size); - nir_builder_instr_insert(&b, &new_phi->instr); - new_phi->instr.pass_flags = phi1->instr.pass_flags; - - assert(exec_list_length(&phi1->srcs) == exec_list_length(&phi2->srcs)); - - uint8_t dest_swizzle[NIR_MAX_VEC_COMPONENTS] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; - - nir_block *swizzle_pred = get_first_non_const_phi_pred(phi1); - /* Try to avoid reverse swizzles at the new phi sources. */ - if (swizzle_pred) { - nir_phi_src *src1 = nir_phi_get_src_from_block(phi1, swizzle_pred); - nir_phi_src *src2 = nir_phi_get_src_from_block(phi2, swizzle_pred); - - nir_scalar new_srcs[NIR_MAX_VEC_COMPONENTS]; - - for (unsigned i = 0; i < phi1_components; i++) - new_srcs[i] = nir_scalar_resolved(src1->src.ssa, i); - - for (unsigned i = 0; i < phi2_components; i++) - new_srcs[phi1_components + i] = nir_scalar_resolved(src2->src.ssa, i); - - util_qsort_r(dest_swizzle, total_components, sizeof(dest_swizzle[0]), - compare_scalar, new_srcs); - } - - nir_foreach_phi_src(src1, phi1) { - nir_phi_src *src2 = nir_phi_get_src_from_block(phi2, src1->pred); - nir_block *pred_block = src1->pred; - - nir_scalar new_srcs[NIR_MAX_VEC_COMPONENTS]; - - for (unsigned i = 0; i < total_components; i++) { - unsigned comp = dest_swizzle[i]; - if (comp < phi1_components) - new_srcs[i] = nir_scalar_resolved(src1->src.ssa, comp); - else - new_srcs[i] = nir_scalar_resolved(src2->src.ssa, comp - phi1_components); - } - - nir_def *new_src; - - if (nir_scalar_is_const(new_srcs[0])) { - nir_const_value value[NIR_MAX_VEC_COMPONENTS]; - - for (unsigned i = 0; i < total_components; i++) { - assert(nir_scalar_is_const(new_srcs[i])); - value[i] = nir_scalar_as_const_value(new_srcs[i]); - } - - b.cursor = nir_after_block_before_jump(pred_block); - unsigned bit_size = src1->src.ssa->bit_size; - new_src = nir_build_imm(&b, total_components, bit_size, value); - } else if (pred_block->index < block->index) { - nir_def *def = new_srcs[0].def; - unsigned src_swizzle[NIR_MAX_VEC_COMPONENTS]; - - for (unsigned i = 0; i < total_components; i++) { - assert(new_srcs[i].def == def); - src_swizzle[i] = new_srcs[i].comp; - } - - b.cursor = nir_after_instr_and_phis(nir_def_instr(def)); - new_src = nir_swizzle(&b, def, src_swizzle, total_components); - } else { - /* This is a loop back-edge so we haven't vectorized the sources yet. - * Combine them in a vec which, if they are vectorized later, will be - * cleaned up by copy propagation. - */ - b.cursor = nir_after_block_before_jump(pred_block); - new_src = nir_vec_scalars(&b, new_srcs, total_components); - } - - nir_phi_src *new_phi_src = - nir_phi_instr_add_src(new_phi, src1->pred, new_src); - list_addtail(&new_phi_src->src.use_link, &new_src->uses); - } - - b.cursor = nir_after_phis(block); - - nir_alu_src replace_phi1 = { nir_src_for_ssa(&new_phi->def) }; - nir_alu_src replace_phi2 = { nir_src_for_ssa(&new_phi->def) }; - - for (unsigned i = 0; i < total_components; i++) { - unsigned comp = dest_swizzle[i]; - if (comp < phi1_components) - replace_phi1.swizzle[comp] = i; - else - replace_phi2.swizzle[comp - phi1_components] = i; - } - - nir_def_rewrite_uses_with_alu_src(&b, &phi1->def, replace_phi1); - nir_def_rewrite_uses_with_alu_src(&b, &phi2->def, replace_phi2); - - nir_instr_remove(&phi1->instr); - nir_instr_remove(&phi2->instr); - - return &new_phi->instr; -} - -static nir_instr * -instr_try_combine_alu(struct set *instr_set, nir_alu_instr *alu1, nir_alu_instr *alu2) -{ - assert(alu1->def.bit_size == alu2->def.bit_size); - unsigned alu1_components = alu1->def.num_components; - unsigned alu2_components = alu2->def.num_components; - unsigned total_components = alu1_components + alu2_components; - - assert(alu1->instr.pass_flags == alu2->instr.pass_flags); - if (total_components > alu1->instr.pass_flags) - return NULL; - - nir_builder b = nir_builder_at(nir_after_instr(&alu1->instr)); - - nir_alu_instr *new_alu = nir_alu_instr_create(b.shader, alu1->op); - nir_def_init(&new_alu->instr, &new_alu->def, total_components, - alu1->def.bit_size); - new_alu->instr.pass_flags = alu1->instr.pass_flags; - - /* fp_math_ctrl is a set of restrictions, take the union of both. - */ - new_alu->fp_math_ctrl = alu1->fp_math_ctrl | alu2->fp_math_ctrl; - - /* If all channels don't wrap, we can say that the whole vector doesn't - * wrap. - */ - new_alu->no_signed_wrap = alu1->no_signed_wrap && alu2->no_signed_wrap; - new_alu->no_unsigned_wrap = alu1->no_unsigned_wrap && alu2->no_unsigned_wrap; - - bool srcs_swapped = alu_srcs_match_swapped(alu1, alu2, alu1->instr.pass_flags); - - for (unsigned i = 0; i < nir_op_infos[alu1->op].num_inputs; i++) { - unsigned alu2_src_idx = srcs_swapped && i < 2 ? 1 - i : i; - const nir_alu_src *alu2_src = &alu2->src[alu2_src_idx]; - - /* handle constant merging case */ - if (alu1->src[i].src.ssa != alu2_src->src.ssa) { - nir_const_value *c1 = nir_src_as_const_value(alu1->src[i].src); - nir_const_value *c2 = nir_src_as_const_value(alu2_src->src); - assert(c1 && c2); - nir_const_value value[NIR_MAX_VEC_COMPONENTS]; - unsigned bit_size = alu1->src[i].src.ssa->bit_size; - - for (unsigned j = 0; j < total_components; j++) { - value[j].u64 = j < alu1_components ? c1[alu1->src[i].swizzle[j]].u64 : c2[alu2_src->swizzle[j - alu1_components]].u64; - } - nir_def *def = nir_build_imm(&b, total_components, bit_size, value); - - new_alu->src[i].src = nir_src_for_ssa(def); - for (unsigned j = 0; j < total_components; j++) - new_alu->src[i].swizzle[j] = j; - continue; - } - - new_alu->src[i].src = alu1->src[i].src; - - for (unsigned j = 0; j < alu1_components; j++) - new_alu->src[i].swizzle[j] = alu1->src[i].swizzle[j]; - - for (unsigned j = 0; j < alu2_components; j++) { - new_alu->src[i].swizzle[j + alu1_components] = - alu2_src->swizzle[j]; - } - } - - nir_builder_instr_insert(&b, &new_alu->instr); - rewrite_uses(&b, instr_set, &alu1->def, &alu2->def, &new_alu->def); - - return &new_alu->instr; -} - -/* - * Tries to combine two instructions whose sources are different components of - * the same instructions into one vectorized instruction. Note that instr1 - * should dominate instr2. - */ -static nir_instr * -instr_try_combine(struct set *instr_set, nir_instr *instr1, nir_instr *instr2) -{ - switch (instr1->type) { - case nir_instr_type_alu: - assert(instr2->type == nir_instr_type_alu); - return instr_try_combine_alu(instr_set, nir_instr_as_alu(instr1), - nir_instr_as_alu(instr2)); - - case nir_instr_type_phi: - assert(instr2->type == nir_instr_type_phi); - return instr_try_combine_phi(instr_set, nir_instr_as_phi(instr1), - nir_instr_as_phi(instr2)); - - default: - UNREACHABLE("Unsupported instruction type"); - } -} - -static void -vec_instr_set_init(struct set *instr_set) -{ - _mesa_set_init(instr_set, NULL, hash_instr, instrs_equal); -} - -static void -vec_instr_set_fini(struct set *instr_set) -{ - _mesa_set_fini(instr_set, NULL); -} - -static bool -vec_instr_set_add_or_rewrite(struct set *instr_set, nir_instr *instr, - nir_vectorize_cb filter, void *data) -{ - /* set max vector to instr pass flags: this is used to hash swizzles */ - instr->pass_flags = filter ? filter(instr, data) : 4; - assert(util_is_power_of_two_or_zero(instr->pass_flags)); - - if (!instr_can_rewrite(instr)) - return false; - - struct set_entry *entry = _mesa_set_search(instr_set, instr); - if (entry) { - nir_instr *old_instr = (nir_instr *)entry->key; - - /* We cannot combine the instructions if the old one doesn't dominate - * the new one. Since we will never encounter a block again that is - * dominated by the old instruction, overwrite it with the new one in - * the instruction set. - */ - if (!nir_block_dominates(old_instr->block, instr->block)) { - entry->key = instr; - return false; - } - - _mesa_set_remove(instr_set, entry); - nir_instr *new_instr = instr_try_combine(instr_set, old_instr, instr); - if (new_instr) { - if (instr_can_rewrite(new_instr)) - _mesa_set_add(instr_set, new_instr); - return true; - } - } - - _mesa_set_add(instr_set, instr); - return false; -} - -static bool -nir_opt_vectorize_impl(nir_function_impl *impl, - nir_vectorize_cb filter, void *data) -{ - struct set instr_set; - vec_instr_set_init(&instr_set); - - nir_metadata_require(impl, - nir_metadata_block_index | nir_metadata_dominance); - - bool progress = false; - - nir_foreach_block(block, impl) { - nir_foreach_instr_safe(instr, block) { - progress |= vec_instr_set_add_or_rewrite(&instr_set, instr, filter, data); - } - } - - nir_progress(progress, impl, nir_metadata_control_flow); - - vec_instr_set_fini(&instr_set); - return progress; -} - -bool -nir_opt_vectorize(nir_shader *shader, nir_vectorize_cb filter, - void *data) -{ - bool progress = false; - - nir_foreach_function_impl(impl, shader) { - progress |= nir_opt_vectorize_impl(impl, filter, data); - } - - return progress; -} diff --git a/references/nir_opt_vectorize_io.c b/references/nir_opt_vectorize_io.c deleted file mode 100644 index 0669b6e9..00000000 --- a/references/nir_opt_vectorize_io.c +++ /dev/null @@ -1,671 +0,0 @@ -/* - * Copyright 2024 Advanced Micro Devices, Inc. - * - * SPDX-License-Identifier: MIT - */ - -/** - * This pass: - * - vectorizes lowered input/output loads and stores - * - vectorizes low and high 16-bit loads and stores by merging them into - * a single 32-bit load or store (except load_interpolated_input and XFB, - * which have to keep bit_size=16) - * - performs DCE of output stores that overwrite the previous value by writing - * into the same slot and component. - * - * Vectorization is only local within basic blocks. No vectorization occurs - * across basic block boundaries, barriers (only TCS outputs), emits (only - * GS outputs), and output load <-> output store dependencies. - * - * All loads and stores must be scalar. 64-bit loads and stores are forbidden. - * - * For each basic block, the time complexity is O(n*log(n)) where n is - * the number of IO instructions within that block. - */ - -#include "util/u_dynarray.h" -#include "nir.h" -#include "nir_builder.h" - -/* Return 0 if loads/stores are vectorizable. Return 1 or -1 to define - * an ordering between non-vectorizable instructions. This is used by qsort, - * to sort all gathered instructions into groups of vectorizable instructions. - */ -static int -compare_is_not_vectorizable(nir_intrinsic_instr *a, nir_intrinsic_instr *b) -{ - if (a->intrinsic != b->intrinsic) - return a->intrinsic > b->intrinsic ? 1 : -1; - - nir_src *offset0 = nir_get_io_offset_src(a); - nir_src *offset1 = nir_get_io_offset_src(b); - if (offset0 && offset0->ssa != offset1->ssa) - return offset0->ssa->index > offset1->ssa->index ? 1 : -1; - - nir_src *array_idx0 = nir_get_io_arrayed_index_src(a); - nir_src *array_idx1 = nir_get_io_arrayed_index_src(b); - if (array_idx0 && array_idx0->ssa != array_idx1->ssa) - return array_idx0->ssa->index > array_idx1->ssa->index ? 1 : -1; - - /* Compare barycentrics or vertex index. */ - if ((a->intrinsic == nir_intrinsic_load_interpolated_input || - a->intrinsic == nir_intrinsic_load_input_vertex) && - a->src[0].ssa != b->src[0].ssa) - return a->src[0].ssa->index > b->src[0].ssa->index ? 1 : -1; - - nir_io_semantics sem0 = nir_intrinsic_io_semantics(a); - nir_io_semantics sem1 = nir_intrinsic_io_semantics(b); - if (sem0.location != sem1.location) - return sem0.location > sem1.location ? 1 : -1; - - /* The mediump flag isn't mergable. */ - if (sem0.medium_precision != sem1.medium_precision) - return sem0.medium_precision > sem1.medium_precision ? 1 : -1; - - /* Don't merge per-view attributes with non-per-view attributes. */ - if (sem0.per_view != sem1.per_view) - return sem0.per_view > sem1.per_view ? 1 : -1; - - if (sem0.interp_explicit_strict != sem1.interp_explicit_strict) - return sem0.interp_explicit_strict > sem1.interp_explicit_strict ? 1 : -1; - - /* Only load_interpolated_input can't merge low and high halves of 16-bit - * loads/stores. - */ - if (a->intrinsic == nir_intrinsic_load_interpolated_input && - sem0.high_16bits != sem1.high_16bits) - return sem0.high_16bits > sem1.high_16bits ? 1 : -1; - - /* TODO: vectorize (f32, f32, f16vec2, f16vec2) -> vec4 - * For now, different bit sizes are not vectorized together. - */ - if (nir_intrinsic_has_src_type(a)) { - /* Stores. */ - if (a->src[0].ssa->bit_size != b->src[0].ssa->bit_size) - return a->src[0].ssa->bit_size > b->src[0].ssa->bit_size ? 1 : -1; - } else { - /* Loads. */ - if (a->def.bit_size != b->def.bit_size) - return a->def.bit_size > b->def.bit_size ? 1 : -1; - } - - nir_shader *shader = a->instr.block->impl->function->shader; - - /* Compare the types. */ - if (!(shader->options->io_options & nir_io_vectorizer_ignores_types)) { - unsigned type_a, type_b; - - if (nir_intrinsic_has_src_type(a)) { - type_a = nir_intrinsic_src_type(a); - type_b = nir_intrinsic_src_type(b); - } else { - type_a = nir_intrinsic_dest_type(a); - type_b = nir_intrinsic_dest_type(b); - } - - if (type_a != type_b) - return type_a > type_b ? 1 : -1; - } - - return 0; -} - -static int -compare_intr(const void *xa, const void *xb) -{ - nir_intrinsic_instr *a = *(nir_intrinsic_instr **)xa; - nir_intrinsic_instr *b = *(nir_intrinsic_instr **)xb; - - int comp = compare_is_not_vectorizable(a, b); - if (comp) - return comp; - - /* qsort isn't stable. This ensures that later stores aren't moved before earlier stores. */ - return a->instr.index > b->instr.index ? 1 : -1; -} - -typedef enum { - merge_low_high_16_to_32, - vectorize_high_16_separately, - vectorize_the_rest, -} nir_vectorize_op_step; - -static bool -apply_radv_workaround(nir_builder *b) -{ - return b->shader->options->io_options & - nir_io_radv_intrinsic_component_workaround; -} - -static void -vectorize_load(nir_intrinsic_instr *chan[8], unsigned start, unsigned count, - nir_vectorize_op_step step) -{ - nir_intrinsic_instr *first = NULL; - - /* Find the first instruction where the vectorized load will be - * inserted. - */ - for (unsigned i = start; i < start + count; i++) { - if (!chan[i]) - continue; - - first = !first || chan[i]->instr.index < first->instr.index ? chan[i] : first; - if (step == merge_low_high_16_to_32) { - first = !first || chan[4 + i]->instr.index < first->instr.index ? chan[4 + i] : first; - } - } - - /* Insert the vectorized load. */ - nir_builder b = nir_builder_at(nir_before_instr(&first->instr)); - nir_intrinsic_instr *new_intr = - nir_intrinsic_instr_create(b.shader, first->intrinsic); - - new_intr->num_components = count; - nir_def_init(&new_intr->instr, &new_intr->def, count, - step == merge_low_high_16_to_32 ? 32 : first->def.bit_size); - memcpy(new_intr->src, first->src, - nir_intrinsic_infos[first->intrinsic].num_srcs * sizeof(nir_src)); - nir_intrinsic_copy_const_indices(new_intr, first); - if (apply_radv_workaround(&b)) - nir_intrinsic_set_component(new_intr, start); - else - nir_intrinsic_set_component(new_intr, start & 0x3); /* Bits 4..7 should map to 0..3 */ - assert(start % 4 + count <= 4); - - nir_io_semantics sem = nir_intrinsic_io_semantics(new_intr); - - for (unsigned i = start; i < start + count; i++) { - if (chan[i]) { - if (!nir_intrinsic_io_semantics(chan[i]).no_signed_zero) - sem.no_signed_zero = 0; - } - - if (step == merge_low_high_16_to_32 && chan[4 + i]) { - if (!nir_intrinsic_io_semantics(chan[4 + i]).no_signed_zero) - sem.no_signed_zero = 0; - } - } - - if (step == vectorize_high_16_separately) { - assert(start >= 4); - sem.high_16bits = 1; - } else { - assert(start <= 3); - } - - if (step == merge_low_high_16_to_32) { - sem.high_16bits = 0; - nir_intrinsic_set_dest_type(new_intr, - (nir_intrinsic_dest_type(new_intr) & ~16) | 32); - } - - nir_intrinsic_set_io_semantics(new_intr, sem); - - nir_builder_instr_insert(&b, &new_intr->instr); - nir_def *def = &new_intr->def; - - /* Replace the scalar loads. */ - if (step == merge_low_high_16_to_32) { - for (unsigned i = start; i < start + count; i++) { - nir_def *comp = nir_channel(&b, def, i - start); - - nir_def_rewrite_uses(&chan[i]->def, - nir_unpack_32_2x16_split_x(&b, comp)); - nir_def_rewrite_uses(&chan[4 + i]->def, - nir_unpack_32_2x16_split_y(&b, comp)); - nir_instr_remove(&chan[i]->instr); - nir_instr_remove(&chan[4 + i]->instr); - } - } else { - for (unsigned i = start; i < start + count; i++) { - if (chan[i]) - nir_def_replace(&chan[i]->def, nir_channel(&b, def, i - start)); - } - } -} - -static void -vectorize_store(nir_intrinsic_instr *chan[8], unsigned start, unsigned count, - nir_vectorize_op_step step) -{ - nir_intrinsic_instr *last = NULL; - - /* Find the last instruction where the vectorized store will be - * inserted. - */ - for (unsigned i = start; i < start + count; i++) { - last = !last || chan[i]->instr.index > last->instr.index ? chan[i] : last; - if (step == merge_low_high_16_to_32) { - last = !last || chan[4 + i]->instr.index > last->instr.index ? chan[4 + i] : last; - } - } - - /* Change the last instruction to a vectorized store. Update xfb first - * because we need to read some info from "last" before overwriting it. - */ - if (nir_intrinsic_has_io_xfb(last)) { - /* 0 = low/full channels - * 1 = high channels - */ - nir_io_xfb xfb[2] = { { { { 0 } } } }; - - for (unsigned i = start; i < start + count; i++) { - xfb[i / 4].out[i % 4] = nir_intrinsic_io_xfb(chan[i]).out[i % 4]; - - /* Merging low and high 16 bits to 32 bits is not possible - * with xfb in some cases. - */ - assert(!xfb[i / 4].out[i % 4].num_components || - step != merge_low_high_16_to_32); - } - - /* Now vectorize xfb info by merging the individual elements. */ - for (unsigned i = start; i < start + count; i++) { - /* mediump means that xfb upconverts to 32 bits when writing to - * memory. - */ - unsigned xfb_comp_size = - nir_intrinsic_io_semantics(chan[i]).medium_precision ? 32 : chan[i]->src[0].ssa->bit_size; - - for (unsigned j = i + 1; j < start + count; j++) { - if (xfb[i / 4].out[i % 4].buffer != xfb[j / 4].out[j % 4].buffer || - xfb[i / 4].out[i % 4].offset != xfb[j / 4].out[j % 4].offset + - xfb_comp_size * (j - i)) - break; - - xfb[i / 4].out[i % 4].num_components++; - memset(&xfb[j / 4].out[j % 4], 0, sizeof(xfb[j / 4].out[j % 4])); - } - } - - if (start >= 4) { - nir_intrinsic_set_io_xfb(last, xfb[1]); - } else { - assert(start + count <= 4); - nir_intrinsic_set_io_xfb(last, xfb[0]); - } - } - - /* Update gs_streams. */ - unsigned gs_streams = 0; - for (unsigned i = start; i < start + count; i++) { - gs_streams |= (nir_intrinsic_io_semantics(chan[i]).gs_streams & 0x3) << ((i - start) * 2); - } - - nir_io_semantics sem = nir_intrinsic_io_semantics(last); - sem.gs_streams = gs_streams; - - if (step == vectorize_high_16_separately) { - assert(start >= 4); - sem.high_16bits = 1; - } else { - assert(start <= 3); - } - - /* Update other flags. */ - for (unsigned i = start; i < start + count; i++) { - if (!nir_intrinsic_io_semantics(chan[i]).no_sysval_output) - sem.no_sysval_output = 0; - if (!nir_intrinsic_io_semantics(chan[i]).no_varying) - sem.no_varying = 0; - if (!nir_intrinsic_io_semantics(chan[i]).no_signed_zero) - sem.no_signed_zero = 0; - } - - if (step == merge_low_high_16_to_32) { - /* Update "no" flags for high bits. */ - for (unsigned i = start; i < start + count; i++) { - if (!nir_intrinsic_io_semantics(chan[4 + i]).no_sysval_output) - sem.no_sysval_output = 0; - if (!nir_intrinsic_io_semantics(chan[4 + i]).no_varying) - sem.no_varying = 0; - if (!nir_intrinsic_io_semantics(chan[4 + i]).no_signed_zero) - sem.no_signed_zero = 0; - } - - /* Update the type. */ - sem.high_16bits = 0; - nir_intrinsic_set_src_type(last, - (nir_intrinsic_src_type(last) & ~16) | 32); - } - - /* TODO: Merge names? */ - - nir_builder b = nir_builder_at(nir_before_instr(&last->instr)); - - /* Update the rest. */ - nir_intrinsic_set_io_semantics(last, sem); - if (apply_radv_workaround(&b)) - nir_intrinsic_set_component(last, start); - else - nir_intrinsic_set_component(last, start & 0x3); /* Bits 4..7 should map to 0..3 */ - assert(start % 4 + count <= 4); - nir_intrinsic_set_write_mask(last, BITFIELD_MASK(count)); - last->num_components = count; - - /* Replace the stored scalar with the vector. */ - if (step == merge_low_high_16_to_32) { - nir_def *value[4]; - for (unsigned i = start; i < start + count; i++) { - value[i] = nir_pack_32_2x16_split(&b, chan[i]->src[0].ssa, - chan[4 + i]->src[0].ssa); - } - - nir_src_rewrite(&last->src[0], nir_vec(&b, &value[start], count)); - } else { - nir_def *value[8]; - for (unsigned i = start; i < start + count; i++) - value[i] = chan[i]->src[0].ssa; - - nir_src_rewrite(&last->src[0], nir_vec(&b, &value[start], count)); - } - - /* Remove the scalar stores. */ - for (unsigned i = start; i < start + count; i++) { - if (chan[i] != last) - nir_instr_remove(&chan[i]->instr); - if (step == merge_low_high_16_to_32 && chan[4 + i] != last) - nir_instr_remove(&chan[4 + i]->instr); - } -} - -/* Vectorize a vector of scalar instructions. chan[8] are the channels. - * (the last 4 are the high 16-bit channels) - */ -static bool -vectorize_slot(nir_intrinsic_instr *chan[8], unsigned mask, bool allow_holes) -{ - bool progress = false; - assert(mask); - bool is_load = nir_intrinsic_infos[chan[ffs(mask) - 1]->intrinsic].has_dest; - - /* First, merge low and high 16-bit halves into 32 bits separately when - * possible. Then vectorize what's left. - */ - for (nir_vectorize_op_step step = merge_low_high_16_to_32; - step <= vectorize_the_rest; step++) { - unsigned scan_mask; - - if (step == merge_low_high_16_to_32) { - /* Get the subset of the mask where both low and high bits are set. */ - scan_mask = 0; - for (unsigned i = 0; i < 4; i++) { - unsigned low_high_bits = BITFIELD_BIT(i) | BITFIELD_BIT(i + 4); - - if ((mask & low_high_bits) == low_high_bits) { - /* Merging low and high 16 bits to 32 bits is not possible - * with xfb in some cases. - */ - if (nir_intrinsic_has_io_xfb(chan[i])) { - unsigned hi = i + 4; - - if (nir_intrinsic_io_xfb(chan[i]).out[i].num_components || - nir_intrinsic_io_xfb(chan[hi]).out[i].num_components) - continue; - } - - /* The GS stream must be the same for both halves. */ - if ((nir_intrinsic_io_semantics(chan[i]).gs_streams & 0x3) != - (nir_intrinsic_io_semantics(chan[4 + i]).gs_streams & 0x3)) - continue; - - scan_mask |= BITFIELD_BIT(i); - mask &= ~low_high_bits; - } - } - } else if (step == vectorize_high_16_separately) { - scan_mask = mask & BITFIELD_RANGE(4, 4); - mask &= ~scan_mask; - - if (is_load && allow_holes && scan_mask) { - unsigned num = util_last_bit(scan_mask); - unsigned start = ffs(scan_mask) - 1; - scan_mask = BITFIELD_RANGE(start, num - start); - } - } else { - scan_mask = mask; - - if (is_load && allow_holes && scan_mask) { - unsigned num = util_last_bit(scan_mask); - unsigned start = ffs(scan_mask) - 1; - scan_mask = BITFIELD_RANGE(start, num - start); - } - } - - while (scan_mask) { - int start, count; - - u_bit_scan_consecutive_range(&scan_mask, &start, &count); - - if (count == 1 && step != merge_low_high_16_to_32) - continue; /* There is nothing to vectorize. */ - - if (is_load) - vectorize_load(chan, start, count, step); - else - vectorize_store(chan, start, count, step); - - progress = true; - } - } - - return progress; -} - -static bool -vectorize_batch(struct util_dynarray *io_instructions, bool allow_holes) -{ - unsigned num_instr = util_dynarray_num_elements(io_instructions, void *); - - /* We need to at least 2 instructions to have something to do. */ - if (num_instr <= 1) { - /* Clear the array. The next block will reuse it. */ - util_dynarray_clear(io_instructions); - return false; - } - - /* The instructions are sorted such that groups of vectorizable - * instructions are next to each other. Multiple incompatible - * groups of vectorizable instructions can occur in this array. - * The reason why 2 groups would be incompatible is that they - * could have a different intrinsic, indirect index, array index, - * vertex index, barycentrics, or location. Each group is vectorized - * separately. - * - * This reorders instructions in the array, but not in the shader. - */ - qsort(io_instructions->data, num_instr, sizeof(void *), compare_intr); - - nir_intrinsic_instr *chan[8] = { 0 }, *prev = NULL; - unsigned chan_mask = 0; - bool progress = false; - - /* Vectorize all groups. - * - * The channels for each group are gathered. If 2 stores overwrite - * the same channel, the earlier store is DCE'd here. - */ - util_dynarray_foreach(io_instructions, nir_intrinsic_instr *, intr) { - /* If the next instruction is not vectorizable, vectorize what - * we have gathered so far. - */ - if (prev && compare_is_not_vectorizable(prev, *intr)) { - /* We need at least 2 instructions to have something to do. */ - if (util_bitcount(chan_mask) > 1) - progress |= vectorize_slot(chan, chan_mask, allow_holes); - - prev = NULL; - memset(chan, 0, sizeof(chan)); - chan_mask = 0; - } - - /* This performs DCE of output stores because the previous value - * is being overwritten. - */ - unsigned index = nir_intrinsic_io_semantics(*intr).high_16bits * 4 + - nir_intrinsic_component(*intr); - bool is_store = !nir_intrinsic_infos[(*intr)->intrinsic].has_dest; - if (is_store && chan[index]) - nir_instr_remove(&chan[index]->instr); - - /* Gather the channel. */ - chan[index] = *intr; - prev = *intr; - chan_mask |= BITFIELD_BIT(index); - } - - /* Vectorize the last group. */ - if (prev && util_bitcount(chan_mask) > 1) - progress |= vectorize_slot(chan, chan_mask, allow_holes); - - /* Clear the array. The next block will reuse it. */ - util_dynarray_clear(io_instructions); - return progress; -} - -/* Vectorize lowered IO (load_input/store_output/...). - * - * modes specifies whether to vectorize inputs and/or outputs. - * - * allow_holes enables vectorization of loads with holes, e.g.: - * load X; load W; ==> load XYZW; - * - * This is useful for VS input loads where it might not be possible to skip - * loading unused components, e.g. with AMD where loading W also loads XYZ, - * so if we also load X separately again, it's wasteful. It's better to get - * X from the vector that loads (XYZ)W. - */ -bool -nir_opt_vectorize_io(nir_shader *shader, nir_variable_mode modes, - bool allow_holes) -{ - assert(!(modes & ~(nir_var_shader_in | nir_var_shader_out))); - - if (shader->info.stage == MESA_SHADER_FRAGMENT && - shader->options->io_options & nir_io_prefer_scalar_fs_inputs) - modes &= ~nir_var_shader_in; - - if ((shader->info.stage == MESA_SHADER_TESS_CTRL || - shader->info.stage == MESA_SHADER_GEOMETRY) && - util_bitcount(modes) == 2) { - /* When vectorizing TCS and GS IO, inputs can ignore barriers and emits, - * but that is only done when outputs are ignored, so vectorize them - * separately. - */ - bool progress_in = nir_opt_vectorize_io(shader, nir_var_shader_in, - allow_holes); - bool progress_out = nir_opt_vectorize_io(shader, nir_var_shader_out, - allow_holes); - return progress_in || progress_out; - } - - /* Initialize dynamic arrays. */ - struct util_dynarray io_instructions = UTIL_DYNARRAY_INIT; - bool global_progress = false; - - nir_foreach_function_impl(impl, shader) { - bool progress = false; - nir_metadata_require(impl, nir_metadata_instr_index); - - nir_foreach_block(block, impl) { - BITSET_DECLARE(has_output_loads, NUM_TOTAL_VARYING_SLOTS * 8); - BITSET_DECLARE(has_output_stores, NUM_TOTAL_VARYING_SLOTS * 8); - BITSET_ZERO(has_output_loads); - BITSET_ZERO(has_output_stores); - - /* Gather load/store intrinsics within the block. */ - nir_foreach_instr(instr, block) { - if (instr->type != nir_instr_type_intrinsic) - continue; - - nir_intrinsic_instr *intr = nir_instr_as_intrinsic(instr); - bool is_load = nir_intrinsic_infos[intr->intrinsic].has_dest; - bool is_output = false; - nir_io_semantics sem = { 0 }; - unsigned index = 0; - - if (nir_intrinsic_has_io_semantics(intr)) { - sem = nir_intrinsic_io_semantics(intr); - assert(sem.location < NUM_TOTAL_VARYING_SLOTS); - index = sem.location * 8 + sem.high_16bits * 4; - - if (nir_intrinsic_has_component(intr)) - index += nir_intrinsic_component(intr); - } - - switch (intr->intrinsic) { - case nir_intrinsic_load_input: - case nir_intrinsic_load_per_primitive_input: - case nir_intrinsic_load_input_vertex: - case nir_intrinsic_load_interpolated_input: - case nir_intrinsic_load_per_vertex_input: - if (!(modes & nir_var_shader_in)) - continue; - break; - - case nir_intrinsic_load_output: - case nir_intrinsic_load_per_vertex_output: - case nir_intrinsic_load_per_view_output: - case nir_intrinsic_load_per_primitive_output: - case nir_intrinsic_store_output: - case nir_intrinsic_store_per_vertex_output: - case nir_intrinsic_store_per_view_output: - case nir_intrinsic_store_per_primitive_output: - if (!(modes & nir_var_shader_out)) - continue; - - /* Break the batch if an output load is followed by an output - * store to the same channel and vice versa. - */ - if (BITSET_TEST(is_load ? has_output_stores : has_output_loads, - index)) { - progress |= vectorize_batch(&io_instructions, allow_holes); - BITSET_ZERO(has_output_loads); - BITSET_ZERO(has_output_stores); - } - is_output = true; - break; - - case nir_intrinsic_barrier: - /* Don't vectorize across TCS barriers. */ - if (modes & nir_var_shader_out && - nir_intrinsic_memory_modes(intr) & nir_var_shader_out) { - progress |= vectorize_batch(&io_instructions, allow_holes); - BITSET_ZERO(has_output_loads); - BITSET_ZERO(has_output_stores); - } - continue; - - case nir_intrinsic_emit_vertex: - /* Don't vectorize across GS emits. */ - progress |= vectorize_batch(&io_instructions, allow_holes); - BITSET_ZERO(has_output_loads); - BITSET_ZERO(has_output_stores); - continue; - - default: - continue; - } - - /* Only scalar 16 and 32-bit instructions are allowed. */ - ASSERTED nir_def *value = is_load ? &intr->def : intr->src[0].ssa; - assert(value->num_components == 1); - assert(value->bit_size == 16 || value->bit_size == 32); - - util_dynarray_append(&io_instructions, intr); - if (is_output) - BITSET_SET(is_load ? has_output_loads : has_output_stores, index); - } - - progress |= vectorize_batch(&io_instructions, allow_holes); - } - - nir_progress(progress, impl, - nir_metadata_block_index | nir_metadata_dominance); - global_progress |= progress; - } - util_dynarray_fini(&io_instructions); - - return global_progress; -} diff --git a/references/nir_opt_vectorize_io_vars.c b/references/nir_opt_vectorize_io_vars.c deleted file mode 100644 index dd8c8505..00000000 --- a/references/nir_opt_vectorize_io_vars.c +++ /dev/null @@ -1,636 +0,0 @@ -/* - * Copyright © 2019 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#include "util/u_dynarray.h" -#include "nir.h" -#include "nir_builder.h" -#include "nir_deref.h" - -/** @file nir_opt_vectorize_io_vars.c - * - * Merges compatible input/output variables residing in different components - * of the same location. It's expected that further passes such as - * nir_lower_io_vars_to_temporaries will combine loads and stores of the merged - * variables, producing vector nir_load_input/nir_store_output instructions - * when all is said and done. - */ - -/* FRAG_RESULT_MAX+1 instead of just FRAG_RESULT_MAX because of how this pass - * handles dual source blending */ -#define MAX_SLOTS MAX2(VARYING_SLOT_TESS_MAX, FRAG_RESULT_MAX + 1) - -static unsigned -get_slot(const nir_variable *var) -{ - /* This handling of dual-source blending might not be correct when more than - * one render target is supported, but it seems no driver supports more than - * one. */ - return var->data.location + var->data.index; -} - -static const struct glsl_type * -get_per_vertex_type(const nir_shader *shader, const nir_variable *var, - unsigned *num_vertices) -{ - if (nir_is_arrayed_io(var, shader->info.stage)) { - assert(glsl_type_is_array(var->type)); - if (num_vertices) - *num_vertices = glsl_get_length(var->type); - return glsl_get_array_element(var->type); - } else { - if (num_vertices) - *num_vertices = 0; - return var->type; - } -} - -static const struct glsl_type * -resize_array_vec_type(const struct glsl_type *type, unsigned num_components) -{ - if (glsl_type_is_array(type)) { - const struct glsl_type *arr_elem = - resize_array_vec_type(glsl_get_array_element(type), num_components); - return glsl_array_type(arr_elem, glsl_get_length(type), 0); - } else { - assert(glsl_type_is_vector_or_scalar(type)); - return glsl_vector_type(glsl_get_base_type(type), num_components); - } -} - -static bool -variables_can_merge(const nir_shader *shader, - const nir_variable *a, const nir_variable *b, - bool same_array_structure) -{ - if (a->data.compact || b->data.compact) - return false; - - if (a->data.per_view || b->data.per_view) - return false; - - const struct glsl_type *a_type_tail = a->type; - const struct glsl_type *b_type_tail = b->type; - - if (nir_is_arrayed_io(a, shader->info.stage) != - nir_is_arrayed_io(b, shader->info.stage)) - return false; - - /* They must have the same array structure */ - if (same_array_structure) { - while (glsl_type_is_array(a_type_tail)) { - if (!glsl_type_is_array(b_type_tail)) - return false; - - if (glsl_get_length(a_type_tail) != glsl_get_length(b_type_tail)) - return false; - - a_type_tail = glsl_get_array_element(a_type_tail); - b_type_tail = glsl_get_array_element(b_type_tail); - } - if (glsl_type_is_array(b_type_tail)) - return false; - } else { - a_type_tail = glsl_without_array(a_type_tail); - b_type_tail = glsl_without_array(b_type_tail); - } - - if (!glsl_type_is_vector_or_scalar(a_type_tail) || - !glsl_type_is_vector_or_scalar(b_type_tail)) - return false; - - if (glsl_get_base_type(a_type_tail) != glsl_get_base_type(b_type_tail)) - return false; - - /* TODO: add 64/16bit support ? */ - if (glsl_get_bit_size(a_type_tail) != 32) - return false; - - assert(a->data.mode == b->data.mode); - if (shader->info.stage == MESA_SHADER_FRAGMENT && - a->data.mode == nir_var_shader_in && - (a->data.interpolation != b->data.interpolation || - a->data.centroid != b->data.centroid || - a->data.sample != b->data.sample)) - return false; - - if (shader->info.stage == MESA_SHADER_FRAGMENT && - a->data.mode == nir_var_shader_out && - a->data.index != b->data.index) - return false; - - /* It's tricky to merge XFB-outputs correctly, because we need there - * to not be any overlaps when we get to - * nir_gather_xfb_info_with_varyings later on. We'll end up - * triggering an assert there if we merge here. - */ - if ((shader->info.stage == MESA_SHADER_VERTEX || - shader->info.stage == MESA_SHADER_TESS_EVAL || - shader->info.stage == MESA_SHADER_GEOMETRY) && - a->data.mode == nir_var_shader_out && - (a->data.explicit_xfb_buffer || b->data.explicit_xfb_buffer)) - return false; - - return true; -} - -static const struct glsl_type * -get_flat_type(const nir_shader *shader, nir_variable *old_vars[MAX_SLOTS][4], - unsigned *loc, nir_variable **first_var, unsigned *num_vertices) -{ - unsigned todo = 1; - unsigned slots = 0; - unsigned num_vars = 0; - enum glsl_base_type base = GLSL_TYPE_ERROR; - *num_vertices = 0; - *first_var = NULL; - - while (todo) { - assert(*loc < MAX_SLOTS); - for (unsigned frac = 0; frac < 4; frac++) { - nir_variable *var = old_vars[*loc][frac]; - if (!var) - continue; - if ((*first_var && - !variables_can_merge(shader, var, *first_var, false)) || - var->data.compact) { - (*loc)++; - return NULL; - } - - if (!*first_var) { - if (!glsl_type_is_vector_or_scalar(glsl_without_array(var->type))) { - (*loc)++; - return NULL; - } - *first_var = var; - base = glsl_get_base_type( - glsl_without_array(get_per_vertex_type(shader, var, NULL))); - } - - bool vs_in = shader->info.stage == MESA_SHADER_VERTEX && - var->data.mode == nir_var_shader_in; - unsigned var_slots = glsl_count_attribute_slots( - get_per_vertex_type(shader, var, num_vertices), vs_in); - todo = MAX2(todo, var_slots); - num_vars++; - } - todo--; - slots++; - (*loc)++; - } - - if (num_vars <= 1) - return NULL; - - if (slots == 1) - return glsl_vector_type(base, 4); - else - return glsl_array_type(glsl_vector_type(base, 4), slots, 0); -} - -static bool -create_new_io_vars(nir_shader *shader, nir_variable_mode mode, - nir_variable *new_vars[MAX_SLOTS][4], - bool flat_vars[MAX_SLOTS], - struct util_dynarray *demote_vars) -{ - nir_variable *old_vars[MAX_SLOTS][4] = { { 0 } }; - - bool has_io_var = false; - nir_foreach_variable_with_modes(var, shader, mode) { - unsigned frac = var->data.location_frac; - old_vars[get_slot(var)][frac] = var; - has_io_var = true; - } - - if (!has_io_var) - return false; - - bool merged_any_vars = false; - - for (unsigned loc = 0; loc < MAX_SLOTS; loc++) { - unsigned frac = 0; - while (frac < 4) { - nir_variable *first_var = old_vars[loc][frac]; - if (!first_var) { - frac++; - continue; - } - - int first = frac; - bool found_merge = false; - - while (frac < 4) { - nir_variable *var = old_vars[loc][frac]; - if (!var) - break; - - if (var != first_var) { - if (!variables_can_merge(shader, first_var, var, true)) - break; - - found_merge = true; - } - - const unsigned num_components = - glsl_get_vector_elements(glsl_without_array(var->type)); - if (!num_components) { - assert(frac == 0); - frac++; - break; /* The type was a struct. */ - } - - /* We had better not have any overlapping vars */ - for (unsigned i = 1; i < num_components; i++) - assert(old_vars[loc][frac + i] == NULL); - - frac += num_components; - } - - if (!found_merge) - continue; - - merged_any_vars = true; - - nir_variable *var = nir_variable_clone(old_vars[loc][first], shader); - var->data.location_frac = first; - var->type = resize_array_vec_type(var->type, frac - first); - - nir_shader_add_variable(shader, var); - for (unsigned i = first; i < frac; i++) { - new_vars[loc][i] = var; - if (old_vars[loc][i]) { - util_dynarray_append(demote_vars, old_vars[loc][i]); - old_vars[loc][i] = NULL; - } - } - - old_vars[loc][first] = var; - } - } - - /* "flat" mode: tries to ensure there is at most one variable per slot by - * merging variables into vec4s - */ - for (unsigned loc = 0; loc < MAX_SLOTS;) { - nir_variable *first_var; - unsigned num_vertices; - unsigned new_loc = loc; - const struct glsl_type *flat_type = - get_flat_type(shader, old_vars, &new_loc, &first_var, &num_vertices); - if (flat_type) { - merged_any_vars = true; - - nir_variable *var = nir_variable_clone(first_var, shader); - var->data.location_frac = 0; - if (num_vertices) - var->type = glsl_array_type(flat_type, num_vertices, 0); - else - var->type = flat_type; - - nir_shader_add_variable(shader, var); - unsigned num_slots = - glsl_type_is_array(flat_type) ? glsl_get_length(flat_type) : 1; - for (unsigned i = 0; i < num_slots; i++) { - for (unsigned j = 0; j < 4; j++) - new_vars[loc + i][j] = var; - flat_vars[loc + i] = true; - } - } - loc = new_loc; - } - - return merged_any_vars; -} - -static nir_deref_instr * -build_array_deref_of_new_var(nir_builder *b, nir_variable *new_var, - nir_deref_instr *leader) -{ - if (leader->deref_type == nir_deref_type_var) - return nir_build_deref_var(b, new_var); - - nir_deref_instr *parent = - build_array_deref_of_new_var(b, new_var, nir_deref_instr_parent(leader)); - - return nir_build_deref_follower(b, parent, leader); -} - -static nir_def * -build_array_index(nir_builder *b, nir_deref_instr *deref, nir_def *base, - bool vs_in, bool per_vertex) -{ - switch (deref->deref_type) { - case nir_deref_type_var: - return base; - case nir_deref_type_array: { - nir_def *index = nir_i2iN(b, deref->arr.index.ssa, - deref->def.bit_size); - - if (nir_deref_instr_parent(deref)->deref_type == nir_deref_type_var && - per_vertex) - return base; - - return nir_iadd( - b, build_array_index(b, nir_deref_instr_parent(deref), base, vs_in, per_vertex), - nir_amul_imm(b, index, glsl_count_attribute_slots(deref->type, vs_in))); - } - default: - UNREACHABLE("Invalid deref instruction type"); - } -} - -static nir_deref_instr * -build_array_deref_of_new_var_flat(nir_shader *shader, - nir_builder *b, nir_variable *new_var, - nir_deref_instr *leader, unsigned base) -{ - nir_deref_instr *deref = nir_build_deref_var(b, new_var); - - bool per_vertex = nir_is_arrayed_io(new_var, shader->info.stage); - if (per_vertex) { - nir_deref_path path; - nir_deref_path_init(&path, leader, NULL); - - assert(path.path[0]->deref_type == nir_deref_type_var); - nir_deref_instr *p = path.path[1]; - nir_deref_path_finish(&path); - - nir_def *index = p->arr.index.ssa; - deref = nir_build_deref_array(b, deref, index); - } - - if (!glsl_type_is_array(deref->type)) - return deref; - - bool vs_in = shader->info.stage == MESA_SHADER_VERTEX && - new_var->data.mode == nir_var_shader_in; - return nir_build_deref_array(b, deref, - build_array_index(b, leader, nir_imm_int(b, base), vs_in, per_vertex)); -} - -static bool -nir_opt_vectorize_io_vars_impl(nir_function_impl *impl, nir_variable_mode modes) -{ - assert(!(modes & ~(nir_var_shader_in | nir_var_shader_out))); - - nir_builder b = nir_builder_create(impl); - - nir_metadata_require(impl, nir_metadata_dominance); - - struct util_dynarray demote_vars = UTIL_DYNARRAY_INIT; - - nir_shader *shader = impl->function->shader; - nir_variable *new_inputs[MAX_SLOTS][4] = { { 0 } }; - nir_variable *new_outputs[MAX_SLOTS][4] = { { 0 } }; - bool flat_inputs[MAX_SLOTS] = { 0 }; - bool flat_outputs[MAX_SLOTS] = { 0 }; - - if (modes & nir_var_shader_in) { - /* Vertex shaders support overlapping inputs. We don't do those */ - assert(b.shader->info.stage != MESA_SHADER_VERTEX); - - /* If we don't actually merge any variables, remove that bit from modes - * so we don't bother doing extra non-work. - */ - if (!create_new_io_vars(shader, nir_var_shader_in, - new_inputs, flat_inputs, &demote_vars)) - modes &= ~nir_var_shader_in; - } - - if (modes & nir_var_shader_out) { - /* If we don't actually merge any variables, remove that bit from modes - * so we don't bother doing extra non-work. - */ - if (!create_new_io_vars(shader, nir_var_shader_out, - new_outputs, flat_outputs, &demote_vars)) - modes &= ~nir_var_shader_out; - } - - if (!modes) - return false; - - bool progress = false; - - /* Actually lower all the IO load/store intrinsics. Load instructions are - * lowered to a vector load and an ALU instruction to grab the channels we - * want. Outputs are lowered to a write-masked store of the vector output. - * For non-TCS outputs, we then run nir_lower_io_vars_to_temporaries at the end - * to clean up the partial writes. - */ - nir_foreach_block(block, impl) { - nir_foreach_instr_safe(instr, block) { - if (instr->type != nir_instr_type_intrinsic) - continue; - - nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr); - - switch (intrin->intrinsic) { - case nir_intrinsic_load_deref: - case nir_intrinsic_interp_deref_at_centroid: - case nir_intrinsic_interp_deref_at_sample: - case nir_intrinsic_interp_deref_at_offset: - case nir_intrinsic_interp_deref_at_vertex: { - nir_deref_instr *old_deref = nir_src_as_deref(intrin->src[0]); - if (!nir_deref_mode_is_one_of(old_deref, modes)) - break; - - nir_variable *old_var = nir_deref_instr_get_variable(old_deref); - - const unsigned loc = get_slot(old_var); - const unsigned old_frac = old_var->data.location_frac; - nir_variable *new_var = old_var->data.mode == nir_var_shader_in ? new_inputs[loc][old_frac] : new_outputs[loc][old_frac]; - bool flat = old_var->data.mode == nir_var_shader_in ? flat_inputs[loc] : flat_outputs[loc]; - if (!new_var) - break; - - const unsigned new_frac = new_var->data.location_frac; - - nir_component_mask_t vec4_comp_mask = - ((1 << intrin->num_components) - 1) << old_frac; - - b.cursor = nir_before_instr(&intrin->instr); - - /* Rewrite the load to use the new variable and only select a - * portion of the result. - */ - nir_deref_instr *new_deref; - if (flat) { - new_deref = build_array_deref_of_new_var_flat( - shader, &b, new_var, old_deref, loc - get_slot(new_var)); - } else { - assert(get_slot(new_var) == loc); - new_deref = build_array_deref_of_new_var(&b, new_var, old_deref); - assert(glsl_type_is_vector(new_deref->type)); - } - nir_src_rewrite(&intrin->src[0], &new_deref->def); - - intrin->num_components = - glsl_get_components(new_deref->type); - intrin->def.num_components = intrin->num_components; - - b.cursor = nir_after_instr(&intrin->instr); - - nir_def *new_vec = nir_channels(&b, &intrin->def, - vec4_comp_mask >> new_frac); - nir_def_rewrite_uses_after(&intrin->def, new_vec); - - progress = true; - break; - } - - case nir_intrinsic_store_deref: { - nir_deref_instr *old_deref = nir_src_as_deref(intrin->src[0]); - if (!nir_deref_mode_is(old_deref, nir_var_shader_out)) - break; - - nir_variable *old_var = nir_deref_instr_get_variable(old_deref); - - const unsigned loc = get_slot(old_var); - const unsigned old_frac = old_var->data.location_frac; - nir_variable *new_var = new_outputs[loc][old_frac]; - bool flat = flat_outputs[loc]; - if (!new_var) - break; - - const unsigned new_frac = new_var->data.location_frac; - - b.cursor = nir_before_instr(&intrin->instr); - - /* Rewrite the store to be a masked store to the new variable */ - nir_deref_instr *new_deref; - if (flat) { - new_deref = build_array_deref_of_new_var_flat( - shader, &b, new_var, old_deref, loc - get_slot(new_var)); - } else { - assert(get_slot(new_var) == loc); - new_deref = build_array_deref_of_new_var(&b, new_var, old_deref); - assert(glsl_type_is_vector(new_deref->type)); - } - nir_src_rewrite(&intrin->src[0], &new_deref->def); - - intrin->num_components = - glsl_get_components(new_deref->type); - - nir_component_mask_t old_wrmask = nir_intrinsic_write_mask(intrin); - - nir_def *old_value = intrin->src[1].ssa; - nir_scalar comps[4]; - for (unsigned c = 0; c < intrin->num_components; c++) { - if (new_frac + c >= old_frac && - (old_wrmask & 1 << (new_frac + c - old_frac))) { - comps[c] = nir_get_scalar(old_value, - new_frac + c - old_frac); - } else { - comps[c] = nir_get_scalar(nir_undef(&b, old_value->num_components, - old_value->bit_size), - 0); - } - } - nir_def *new_value = nir_vec_scalars(&b, comps, intrin->num_components); - nir_src_rewrite(&intrin->src[1], new_value); - - nir_intrinsic_set_write_mask(intrin, - old_wrmask << (old_frac - new_frac)); - - progress = true; - break; - } - - default: - break; - } - } - } - - /* Demote the old var to a global, so that things like - * nir_lower_io_vars_to_temporaries() don't trigger on it. - */ - util_dynarray_foreach(&demote_vars, nir_variable *, varp) { - (*varp)->data.mode = nir_var_shader_temp; - } - nir_fixup_deref_modes(b.shader); - util_dynarray_fini(&demote_vars); - - return nir_progress(progress, impl, nir_metadata_control_flow); -} - -bool -nir_opt_vectorize_io_vars(nir_shader *shader, nir_variable_mode modes) -{ - bool progress = false; - - nir_foreach_function_impl(impl, shader) { - progress |= nir_opt_vectorize_io_vars_impl(impl, modes); - } - - return progress; -} - -static bool -is_tess_level_variable(nir_variable *var) -{ - return var->data.location == VARYING_SLOT_TESS_LEVEL_OUTER || - var->data.location == VARYING_SLOT_TESS_LEVEL_INNER; -} - -/* Make the tess factor variables vectors instead of compact arrays, so accesses - * can be combined by nir_opt_cse()/nir_opt_combine_stores(). - */ -bool -nir_lower_tess_level_array_vars_to_vec(nir_shader *shader) -{ - nir_variable_mode mode; - if (shader->info.stage == MESA_SHADER_TESS_CTRL) - mode = nir_var_shader_out; - else if (shader->info.stage == MESA_SHADER_TESS_EVAL) - mode = nir_var_shader_in; - else - return false; - - bool progress = false; - nir_foreach_variable_with_modes(var, shader, mode) { - if (is_tess_level_variable(var)) { - var->type = glsl_vector_type(GLSL_TYPE_FLOAT, glsl_get_length(var->type)); - var->data.compact = false; - progress = true; - } - } - - if (progress) { - nir_fixup_deref_types(shader); - - nir_lower_array_deref_of_vec(shader, mode, is_tess_level_variable, - nir_lower_direct_array_deref_of_vec_load | - nir_lower_indirect_array_deref_of_vec_load | - nir_lower_direct_array_deref_of_vec_store | - nir_lower_indirect_array_deref_of_vec_store); - - /* Remove dead array deref instructions to avoid nir_validate() complain - * array_deref on vector variable. - */ - nir_opt_dce(shader); - } - - return progress; -} diff --git a/references/writer/BUILD.bazel b/references/writer/BUILD.bazel deleted file mode 100644 index 8ecd6b79..00000000 --- a/references/writer/BUILD.bazel +++ /dev/null @@ -1,140 +0,0 @@ -# Copyright 2023 The Dawn & Tint Authors -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -################################################################################ -# File generated by 'tools/src/cmd/gen' using the template: -# tools/src/cmd/gen/build/BUILD.bazel.tmpl -# -# To regenerate run: './tools/run gen' -# -# Do not modify this file directly -################################################################################ - -load("//src/tint:flags.bzl", "COPTS") -load("@bazel_skylib//lib:selects.bzl", "selects") -cc_library( - name = "writer", - srcs = [ - "output.cc", - "writer.cc", - ], - hdrs = [ - "output.h", - "writer.h", - ], - deps = [ - "//src/tint/api/common", - "//src/tint/api/options", - "//src/tint/lang/core", - "//src/tint/lang/core/constant", - "//src/tint/lang/core/type", - "//src/tint/lang/glsl/writer/raise", - "//src/tint/lang/wgsl", - "//src/tint/lang/wgsl/ast", - "//src/tint/lang/wgsl/ast/transform", - "//src/tint/lang/wgsl/features", - "//src/tint/lang/wgsl/program", - "//src/tint/lang/wgsl/sem", - "//src/tint/utils/containers", - "//src/tint/utils/diagnostic", - "//src/tint/utils/generator", - "//src/tint/utils/ice", - "//src/tint/utils/id", - "//src/tint/utils/macros", - "//src/tint/utils/math", - "//src/tint/utils/memory", - "//src/tint/utils/reflection", - "//src/tint/utils/result", - "//src/tint/utils/rtti", - "//src/tint/utils/symbol", - "//src/tint/utils/text", - "//src/tint/utils/traits", - ] + select({ - ":tint_build_glsl_writer": [ - "//src/tint/lang/glsl/writer/ast_printer", - "//src/tint/lang/glsl/writer/common", - "//src/tint/lang/glsl/writer/printer", - ], - "//conditions:default": [], - }), - copts = COPTS, - visibility = ["//visibility:public"], -) -cc_library( - name = "bench", - alwayslink = True, - srcs = [ - "writer_bench.cc", - ], - deps = [ - "//src/tint/api/common", - "//src/tint/api/options", - "//src/tint/cmd/bench:bench", - "//src/tint/lang/core", - "//src/tint/lang/core/constant", - "//src/tint/lang/core/type", - "//src/tint/lang/wgsl", - "//src/tint/lang/wgsl/ast", - "//src/tint/lang/wgsl/ast/transform", - "//src/tint/lang/wgsl/features", - "//src/tint/lang/wgsl/program", - "//src/tint/lang/wgsl/sem", - "//src/tint/utils/containers", - "//src/tint/utils/diagnostic", - "//src/tint/utils/ice", - "//src/tint/utils/id", - "//src/tint/utils/macros", - "//src/tint/utils/math", - "//src/tint/utils/memory", - "//src/tint/utils/reflection", - "//src/tint/utils/result", - "//src/tint/utils/rtti", - "//src/tint/utils/symbol", - "//src/tint/utils/text", - "//src/tint/utils/traits", - "@benchmark", - ] + select({ - ":tint_build_glsl_writer": [ - "//src/tint/lang/glsl/writer", - "//src/tint/lang/glsl/writer/common", - ], - "//conditions:default": [], - }), - copts = COPTS, - visibility = ["//visibility:public"], -) - -alias( - name = "tint_build_glsl_writer", - actual = "//src/tint:tint_build_glsl_writer_true", -) - -alias( - name = "tint_build_wgsl_reader", - actual = "//src/tint:tint_build_wgsl_reader_true", -) - diff --git a/references/writer/BUILD.cfg b/references/writer/BUILD.cfg deleted file mode 100644 index 7459430e..00000000 --- a/references/writer/BUILD.cfg +++ /dev/null @@ -1,3 +0,0 @@ -{ - "condition": "tint_build_glsl_writer" -} diff --git a/references/writer/BUILD.cmake b/references/writer/BUILD.cmake deleted file mode 100644 index 6939a358..00000000 --- a/references/writer/BUILD.cmake +++ /dev/null @@ -1,198 +0,0 @@ -# Copyright 2023 The Dawn & Tint Authors -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -################################################################################ -# File generated by 'tools/src/cmd/gen' using the template: -# tools/src/cmd/gen/build/BUILD.cmake.tmpl -# -# To regenerate run: './tools/run gen' -# -# Do not modify this file directly -################################################################################ - -include(lang/glsl/writer/ast_printer/BUILD.cmake) -include(lang/glsl/writer/ast_raise/BUILD.cmake) -include(lang/glsl/writer/common/BUILD.cmake) -include(lang/glsl/writer/printer/BUILD.cmake) -include(lang/glsl/writer/raise/BUILD.cmake) - -if(TINT_BUILD_GLSL_WRITER) -################################################################################ -# Target: tint_lang_glsl_writer -# Kind: lib -# Condition: TINT_BUILD_GLSL_WRITER -################################################################################ -tint_add_target(tint_lang_glsl_writer lib - lang/glsl/writer/output.cc - lang/glsl/writer/output.h - lang/glsl/writer/writer.cc - lang/glsl/writer/writer.h -) - -tint_target_add_dependencies(tint_lang_glsl_writer lib - tint_api_common - tint_api_options - tint_lang_core - tint_lang_core_constant - tint_lang_core_type - tint_lang_glsl_writer_raise - tint_lang_wgsl - tint_lang_wgsl_ast - tint_lang_wgsl_ast_transform - tint_lang_wgsl_features - tint_lang_wgsl_program - tint_lang_wgsl_sem - tint_utils_containers - tint_utils_diagnostic - tint_utils_generator - tint_utils_ice - tint_utils_id - tint_utils_macros - tint_utils_math - tint_utils_memory - tint_utils_reflection - tint_utils_result - tint_utils_rtti - tint_utils_symbol - tint_utils_text - tint_utils_traits -) - -if(TINT_BUILD_GLSL_WRITER) - tint_target_add_dependencies(tint_lang_glsl_writer lib - tint_lang_glsl_writer_ast_printer - tint_lang_glsl_writer_common - tint_lang_glsl_writer_printer - ) -endif(TINT_BUILD_GLSL_WRITER) - -endif(TINT_BUILD_GLSL_WRITER) -if(TINT_BUILD_GLSL_WRITER) -################################################################################ -# Target: tint_lang_glsl_writer_bench -# Kind: bench -# Condition: TINT_BUILD_GLSL_WRITER -################################################################################ -tint_add_target(tint_lang_glsl_writer_bench bench - lang/glsl/writer/writer_bench.cc -) - -tint_target_add_dependencies(tint_lang_glsl_writer_bench bench - tint_api_common - tint_api_options - tint_cmd_bench_bench - tint_lang_core - tint_lang_core_constant - tint_lang_core_type - tint_lang_wgsl - tint_lang_wgsl_ast - tint_lang_wgsl_ast_transform - tint_lang_wgsl_features - tint_lang_wgsl_program - tint_lang_wgsl_sem - tint_utils_containers - tint_utils_diagnostic - tint_utils_ice - tint_utils_id - tint_utils_macros - tint_utils_math - tint_utils_memory - tint_utils_reflection - tint_utils_result - tint_utils_rtti - tint_utils_symbol - tint_utils_text - tint_utils_traits -) - -tint_target_add_external_dependencies(tint_lang_glsl_writer_bench bench - "google-benchmark" -) - -if(TINT_BUILD_GLSL_WRITER) - tint_target_add_dependencies(tint_lang_glsl_writer_bench bench - tint_lang_glsl_writer - tint_lang_glsl_writer_common - ) -endif(TINT_BUILD_GLSL_WRITER) - -endif(TINT_BUILD_GLSL_WRITER) -if(TINT_BUILD_GLSL_WRITER) -################################################################################ -# Target: tint_lang_glsl_writer_fuzz -# Kind: fuzz -# Condition: TINT_BUILD_GLSL_WRITER -################################################################################ -tint_add_target(tint_lang_glsl_writer_fuzz fuzz -) - -tint_target_add_dependencies(tint_lang_glsl_writer_fuzz fuzz - tint_api_common - tint_api_options - tint_lang_core - tint_lang_core_constant - tint_lang_core_type - tint_lang_wgsl - tint_lang_wgsl_ast - tint_lang_wgsl_ast_transform - tint_lang_wgsl_features - tint_lang_wgsl_inspector - tint_lang_wgsl_program - tint_lang_wgsl_sem - tint_utils_bytes - tint_utils_containers - tint_utils_diagnostic - tint_utils_ice - tint_utils_id - tint_utils_macros - tint_utils_math - tint_utils_memory - tint_utils_reflection - tint_utils_result - tint_utils_rtti - tint_utils_symbol - tint_utils_text - tint_utils_traits -) - -if(TINT_BUILD_GLSL_WRITER) - tint_target_add_dependencies(tint_lang_glsl_writer_fuzz fuzz - tint_lang_glsl_writer - tint_lang_glsl_writer_common - ) -endif(TINT_BUILD_GLSL_WRITER) - -if(TINT_BUILD_WGSL_READER) - tint_target_add_sources(tint_lang_glsl_writer_fuzz fuzz - "lang/glsl/writer/writer_ast_fuzz.cc" - ) - tint_target_add_dependencies(tint_lang_glsl_writer_fuzz fuzz - tint_cmd_fuzz_wgsl_fuzz - ) -endif(TINT_BUILD_WGSL_READER) - -endif(TINT_BUILD_GLSL_WRITER) \ No newline at end of file diff --git a/references/writer/BUILD.gn b/references/writer/BUILD.gn deleted file mode 100644 index 4b6b5145..00000000 --- a/references/writer/BUILD.gn +++ /dev/null @@ -1,176 +0,0 @@ -# Copyright 2023 The Dawn & Tint Authors -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -################################################################################ -# File generated by 'tools/src/cmd/gen' using the template: -# tools/src/cmd/gen/build/BUILD.gn.tmpl -# -# To regenerate run: './tools/run gen' -# -# Do not modify this file directly -################################################################################ - -import("../../../../../scripts/tint_overrides_with_defaults.gni") - -import("${tint_src_dir}/tint.gni") - -if (tint_build_unittests || tint_build_benchmarks) { - import("//testing/test.gni") -} -if (tint_build_glsl_writer) { - libtint_source_set("writer") { - sources = [ - "output.cc", - "output.h", - "writer.cc", - "writer.h", - ] - deps = [ - "${tint_src_dir}/api/common", - "${tint_src_dir}/api/options", - "${tint_src_dir}/lang/core", - "${tint_src_dir}/lang/core/constant", - "${tint_src_dir}/lang/core/type", - "${tint_src_dir}/lang/glsl/writer/raise", - "${tint_src_dir}/lang/wgsl", - "${tint_src_dir}/lang/wgsl/ast", - "${tint_src_dir}/lang/wgsl/ast/transform", - "${tint_src_dir}/lang/wgsl/features", - "${tint_src_dir}/lang/wgsl/program", - "${tint_src_dir}/lang/wgsl/sem", - "${tint_src_dir}/utils/containers", - "${tint_src_dir}/utils/diagnostic", - "${tint_src_dir}/utils/generator", - "${tint_src_dir}/utils/ice", - "${tint_src_dir}/utils/id", - "${tint_src_dir}/utils/macros", - "${tint_src_dir}/utils/math", - "${tint_src_dir}/utils/memory", - "${tint_src_dir}/utils/reflection", - "${tint_src_dir}/utils/result", - "${tint_src_dir}/utils/rtti", - "${tint_src_dir}/utils/symbol", - "${tint_src_dir}/utils/text", - "${tint_src_dir}/utils/traits", - ] - - if (tint_build_glsl_writer) { - deps += [ - "${tint_src_dir}/lang/glsl/writer/ast_printer", - "${tint_src_dir}/lang/glsl/writer/common", - "${tint_src_dir}/lang/glsl/writer/printer", - ] - } - } -} -if (tint_build_benchmarks) { - if (tint_build_glsl_writer) { - tint_unittests_source_set("bench") { - sources = [ "writer_bench.cc" ] - deps = [ - "${tint_src_dir}:google_benchmark", - "${tint_src_dir}/api/common", - "${tint_src_dir}/api/options", - "${tint_src_dir}/cmd/bench:bench", - "${tint_src_dir}/lang/core", - "${tint_src_dir}/lang/core/constant", - "${tint_src_dir}/lang/core/type", - "${tint_src_dir}/lang/wgsl", - "${tint_src_dir}/lang/wgsl/ast", - "${tint_src_dir}/lang/wgsl/ast/transform", - "${tint_src_dir}/lang/wgsl/features", - "${tint_src_dir}/lang/wgsl/program", - "${tint_src_dir}/lang/wgsl/sem", - "${tint_src_dir}/utils/containers", - "${tint_src_dir}/utils/diagnostic", - "${tint_src_dir}/utils/ice", - "${tint_src_dir}/utils/id", - "${tint_src_dir}/utils/macros", - "${tint_src_dir}/utils/math", - "${tint_src_dir}/utils/memory", - "${tint_src_dir}/utils/reflection", - "${tint_src_dir}/utils/result", - "${tint_src_dir}/utils/rtti", - "${tint_src_dir}/utils/symbol", - "${tint_src_dir}/utils/text", - "${tint_src_dir}/utils/traits", - ] - - if (tint_build_glsl_writer) { - deps += [ - "${tint_src_dir}/lang/glsl/writer", - "${tint_src_dir}/lang/glsl/writer/common", - ] - } - } - } -} -if (tint_build_glsl_writer) { - tint_fuzz_source_set("fuzz") { - sources = [] - deps = [ - "${tint_src_dir}/api/common", - "${tint_src_dir}/api/options", - "${tint_src_dir}/lang/core", - "${tint_src_dir}/lang/core/constant", - "${tint_src_dir}/lang/core/type", - "${tint_src_dir}/lang/wgsl", - "${tint_src_dir}/lang/wgsl/ast", - "${tint_src_dir}/lang/wgsl/ast/transform", - "${tint_src_dir}/lang/wgsl/features", - "${tint_src_dir}/lang/wgsl/inspector", - "${tint_src_dir}/lang/wgsl/program", - "${tint_src_dir}/lang/wgsl/sem", - "${tint_src_dir}/utils/bytes", - "${tint_src_dir}/utils/containers", - "${tint_src_dir}/utils/diagnostic", - "${tint_src_dir}/utils/ice", - "${tint_src_dir}/utils/id", - "${tint_src_dir}/utils/macros", - "${tint_src_dir}/utils/math", - "${tint_src_dir}/utils/memory", - "${tint_src_dir}/utils/reflection", - "${tint_src_dir}/utils/result", - "${tint_src_dir}/utils/rtti", - "${tint_src_dir}/utils/symbol", - "${tint_src_dir}/utils/text", - "${tint_src_dir}/utils/traits", - ] - - if (tint_build_glsl_writer) { - deps += [ - "${tint_src_dir}/lang/glsl/writer", - "${tint_src_dir}/lang/glsl/writer/common", - ] - } - - if (tint_build_wgsl_reader) { - sources += [ "writer_ast_fuzz.cc" ] - deps += [ "${tint_src_dir}/cmd/fuzz/wgsl:fuzz" ] - } - } -} diff --git a/references/writer/ast_printer/BUILD.bazel b/references/writer/ast_printer/BUILD.bazel deleted file mode 100644 index 28417196..00000000 --- a/references/writer/ast_printer/BUILD.bazel +++ /dev/null @@ -1,167 +0,0 @@ -# Copyright 2023 The Dawn & Tint Authors -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -################################################################################ -# File generated by 'tools/src/cmd/gen' using the template: -# tools/src/cmd/gen/build/BUILD.bazel.tmpl -# -# To regenerate run: './tools/run gen' -# -# Do not modify this file directly -################################################################################ - -load("//src/tint:flags.bzl", "COPTS") -load("@bazel_skylib//lib:selects.bzl", "selects") -cc_library( - name = "ast_printer", - srcs = [ - "ast_printer.cc", - ], - hdrs = [ - "ast_printer.h", - ], - deps = [ - "//src/tint/api/common", - "//src/tint/api/options", - "//src/tint/lang/core", - "//src/tint/lang/core/constant", - "//src/tint/lang/core/type", - "//src/tint/lang/wgsl", - "//src/tint/lang/wgsl/ast", - "//src/tint/lang/wgsl/ast/transform", - "//src/tint/lang/wgsl/features", - "//src/tint/lang/wgsl/helpers", - "//src/tint/lang/wgsl/program", - "//src/tint/lang/wgsl/sem", - "//src/tint/utils/containers", - "//src/tint/utils/diagnostic", - "//src/tint/utils/generator", - "//src/tint/utils/ice", - "//src/tint/utils/id", - "//src/tint/utils/macros", - "//src/tint/utils/math", - "//src/tint/utils/memory", - "//src/tint/utils/reflection", - "//src/tint/utils/result", - "//src/tint/utils/rtti", - "//src/tint/utils/symbol", - "//src/tint/utils/text", - "//src/tint/utils/traits", - ] + select({ - ":tint_build_glsl_writer": [ - "//src/tint/lang/glsl/writer/ast_raise", - "//src/tint/lang/glsl/writer/common", - ], - "//conditions:default": [], - }), - copts = COPTS, - visibility = ["//visibility:public"], -) -cc_library( - name = "test", - alwayslink = True, - srcs = [ - "array_accessor_test.cc", - "assign_test.cc", - "ast_printer_test.cc", - "binary_test.cc", - "bitcast_test.cc", - "block_test.cc", - "break_test.cc", - "builtin_test.cc", - "builtin_texture_test.cc", - "call_test.cc", - "case_test.cc", - "cast_test.cc", - "constructor_test.cc", - "continue_test.cc", - "discard_test.cc", - "function_test.cc", - "helper_test.h", - "identifier_test.cc", - "if_test.cc", - "import_test.cc", - "loop_test.cc", - "member_accessor_test.cc", - "module_constant_test.cc", - "return_test.cc", - "sanitizer_test.cc", - "storage_buffer_test.cc", - "switch_test.cc", - "type_test.cc", - "unary_op_test.cc", - "uniform_buffer_test.cc", - "variable_decl_statement_test.cc", - "workgroup_var_test.cc", - ], - deps = [ - "//src/tint/api/common", - "//src/tint/api/options", - "//src/tint/lang/core", - "//src/tint/lang/core/constant", - "//src/tint/lang/core/type", - "//src/tint/lang/wgsl", - "//src/tint/lang/wgsl/ast", - "//src/tint/lang/wgsl/ast/transform", - "//src/tint/lang/wgsl/ast:test", - "//src/tint/lang/wgsl/common", - "//src/tint/lang/wgsl/features", - "//src/tint/lang/wgsl/program", - "//src/tint/lang/wgsl/resolver", - "//src/tint/lang/wgsl/sem", - "//src/tint/utils/containers", - "//src/tint/utils/diagnostic", - "//src/tint/utils/generator", - "//src/tint/utils/ice", - "//src/tint/utils/id", - "//src/tint/utils/macros", - "//src/tint/utils/math", - "//src/tint/utils/memory", - "//src/tint/utils/reflection", - "//src/tint/utils/result", - "//src/tint/utils/rtti", - "//src/tint/utils/symbol", - "//src/tint/utils/text", - "//src/tint/utils/traits", - "@gtest", - ] + select({ - ":tint_build_glsl_writer": [ - "//src/tint/lang/glsl/writer", - "//src/tint/lang/glsl/writer/ast_printer", - "//src/tint/lang/glsl/writer/common", - ], - "//conditions:default": [], - }), - copts = COPTS, - visibility = ["//visibility:public"], -) - -alias( - name = "tint_build_glsl_writer", - actual = "//src/tint:tint_build_glsl_writer_true", -) - diff --git a/references/writer/ast_printer/BUILD.cfg b/references/writer/ast_printer/BUILD.cfg deleted file mode 100644 index 7459430e..00000000 --- a/references/writer/ast_printer/BUILD.cfg +++ /dev/null @@ -1,3 +0,0 @@ -{ - "condition": "tint_build_glsl_writer" -} diff --git a/references/writer/ast_printer/BUILD.cmake b/references/writer/ast_printer/BUILD.cmake deleted file mode 100644 index a8e36ccd..00000000 --- a/references/writer/ast_printer/BUILD.cmake +++ /dev/null @@ -1,169 +0,0 @@ -# Copyright 2023 The Dawn & Tint Authors -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -################################################################################ -# File generated by 'tools/src/cmd/gen' using the template: -# tools/src/cmd/gen/build/BUILD.cmake.tmpl -# -# To regenerate run: './tools/run gen' -# -# Do not modify this file directly -################################################################################ - -if(TINT_BUILD_GLSL_WRITER) -################################################################################ -# Target: tint_lang_glsl_writer_ast_printer -# Kind: lib -# Condition: TINT_BUILD_GLSL_WRITER -################################################################################ -tint_add_target(tint_lang_glsl_writer_ast_printer lib - lang/glsl/writer/ast_printer/ast_printer.cc - lang/glsl/writer/ast_printer/ast_printer.h -) - -tint_target_add_dependencies(tint_lang_glsl_writer_ast_printer lib - tint_api_common - tint_api_options - tint_lang_core - tint_lang_core_constant - tint_lang_core_type - tint_lang_wgsl - tint_lang_wgsl_ast - tint_lang_wgsl_ast_transform - tint_lang_wgsl_features - tint_lang_wgsl_helpers - tint_lang_wgsl_program - tint_lang_wgsl_sem - tint_utils_containers - tint_utils_diagnostic - tint_utils_generator - tint_utils_ice - tint_utils_id - tint_utils_macros - tint_utils_math - tint_utils_memory - tint_utils_reflection - tint_utils_result - tint_utils_rtti - tint_utils_symbol - tint_utils_text - tint_utils_traits -) - -if(TINT_BUILD_GLSL_WRITER) - tint_target_add_dependencies(tint_lang_glsl_writer_ast_printer lib - tint_lang_glsl_writer_ast_raise - tint_lang_glsl_writer_common - ) -endif(TINT_BUILD_GLSL_WRITER) - -endif(TINT_BUILD_GLSL_WRITER) -if(TINT_BUILD_GLSL_WRITER) -################################################################################ -# Target: tint_lang_glsl_writer_ast_printer_test -# Kind: test -# Condition: TINT_BUILD_GLSL_WRITER -################################################################################ -tint_add_target(tint_lang_glsl_writer_ast_printer_test test - lang/glsl/writer/ast_printer/array_accessor_test.cc - lang/glsl/writer/ast_printer/assign_test.cc - lang/glsl/writer/ast_printer/ast_printer_test.cc - lang/glsl/writer/ast_printer/binary_test.cc - lang/glsl/writer/ast_printer/bitcast_test.cc - lang/glsl/writer/ast_printer/block_test.cc - lang/glsl/writer/ast_printer/break_test.cc - lang/glsl/writer/ast_printer/builtin_test.cc - lang/glsl/writer/ast_printer/builtin_texture_test.cc - lang/glsl/writer/ast_printer/call_test.cc - lang/glsl/writer/ast_printer/case_test.cc - lang/glsl/writer/ast_printer/cast_test.cc - lang/glsl/writer/ast_printer/constructor_test.cc - lang/glsl/writer/ast_printer/continue_test.cc - lang/glsl/writer/ast_printer/discard_test.cc - lang/glsl/writer/ast_printer/function_test.cc - lang/glsl/writer/ast_printer/helper_test.h - lang/glsl/writer/ast_printer/identifier_test.cc - lang/glsl/writer/ast_printer/if_test.cc - lang/glsl/writer/ast_printer/import_test.cc - lang/glsl/writer/ast_printer/loop_test.cc - lang/glsl/writer/ast_printer/member_accessor_test.cc - lang/glsl/writer/ast_printer/module_constant_test.cc - lang/glsl/writer/ast_printer/return_test.cc - lang/glsl/writer/ast_printer/sanitizer_test.cc - lang/glsl/writer/ast_printer/storage_buffer_test.cc - lang/glsl/writer/ast_printer/switch_test.cc - lang/glsl/writer/ast_printer/type_test.cc - lang/glsl/writer/ast_printer/unary_op_test.cc - lang/glsl/writer/ast_printer/uniform_buffer_test.cc - lang/glsl/writer/ast_printer/variable_decl_statement_test.cc - lang/glsl/writer/ast_printer/workgroup_var_test.cc -) - -tint_target_add_dependencies(tint_lang_glsl_writer_ast_printer_test test - tint_api_common - tint_api_options - tint_lang_core - tint_lang_core_constant - tint_lang_core_type - tint_lang_wgsl - tint_lang_wgsl_ast - tint_lang_wgsl_ast_transform - tint_lang_wgsl_ast_test - tint_lang_wgsl_common - tint_lang_wgsl_features - tint_lang_wgsl_program - tint_lang_wgsl_resolver - tint_lang_wgsl_sem - tint_utils_containers - tint_utils_diagnostic - tint_utils_generator - tint_utils_ice - tint_utils_id - tint_utils_macros - tint_utils_math - tint_utils_memory - tint_utils_reflection - tint_utils_result - tint_utils_rtti - tint_utils_symbol - tint_utils_text - tint_utils_traits -) - -tint_target_add_external_dependencies(tint_lang_glsl_writer_ast_printer_test test - "gtest" -) - -if(TINT_BUILD_GLSL_WRITER) - tint_target_add_dependencies(tint_lang_glsl_writer_ast_printer_test test - tint_lang_glsl_writer - tint_lang_glsl_writer_ast_printer - tint_lang_glsl_writer_common - ) -endif(TINT_BUILD_GLSL_WRITER) - -endif(TINT_BUILD_GLSL_WRITER) \ No newline at end of file diff --git a/references/writer/ast_printer/BUILD.gn b/references/writer/ast_printer/BUILD.gn deleted file mode 100644 index 692ca9b8..00000000 --- a/references/writer/ast_printer/BUILD.gn +++ /dev/null @@ -1,165 +0,0 @@ -# Copyright 2023 The Dawn & Tint Authors -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -################################################################################ -# File generated by 'tools/src/cmd/gen' using the template: -# tools/src/cmd/gen/build/BUILD.gn.tmpl -# -# To regenerate run: './tools/run gen' -# -# Do not modify this file directly -################################################################################ - -import("../../../../../../scripts/tint_overrides_with_defaults.gni") - -import("${tint_src_dir}/tint.gni") - -if (tint_build_unittests || tint_build_benchmarks) { - import("//testing/test.gni") -} -if (tint_build_glsl_writer) { - libtint_source_set("ast_printer") { - sources = [ - "ast_printer.cc", - "ast_printer.h", - ] - deps = [ - "${tint_src_dir}/api/common", - "${tint_src_dir}/api/options", - "${tint_src_dir}/lang/core", - "${tint_src_dir}/lang/core/constant", - "${tint_src_dir}/lang/core/type", - "${tint_src_dir}/lang/wgsl", - "${tint_src_dir}/lang/wgsl/ast", - "${tint_src_dir}/lang/wgsl/ast/transform", - "${tint_src_dir}/lang/wgsl/features", - "${tint_src_dir}/lang/wgsl/helpers", - "${tint_src_dir}/lang/wgsl/program", - "${tint_src_dir}/lang/wgsl/sem", - "${tint_src_dir}/utils/containers", - "${tint_src_dir}/utils/diagnostic", - "${tint_src_dir}/utils/generator", - "${tint_src_dir}/utils/ice", - "${tint_src_dir}/utils/id", - "${tint_src_dir}/utils/macros", - "${tint_src_dir}/utils/math", - "${tint_src_dir}/utils/memory", - "${tint_src_dir}/utils/reflection", - "${tint_src_dir}/utils/result", - "${tint_src_dir}/utils/rtti", - "${tint_src_dir}/utils/symbol", - "${tint_src_dir}/utils/text", - "${tint_src_dir}/utils/traits", - ] - - if (tint_build_glsl_writer) { - deps += [ - "${tint_src_dir}/lang/glsl/writer/ast_raise", - "${tint_src_dir}/lang/glsl/writer/common", - ] - } - } -} -if (tint_build_unittests) { - if (tint_build_glsl_writer) { - tint_unittests_source_set("unittests") { - sources = [ - "array_accessor_test.cc", - "assign_test.cc", - "ast_printer_test.cc", - "binary_test.cc", - "bitcast_test.cc", - "block_test.cc", - "break_test.cc", - "builtin_test.cc", - "builtin_texture_test.cc", - "call_test.cc", - "case_test.cc", - "cast_test.cc", - "constructor_test.cc", - "continue_test.cc", - "discard_test.cc", - "function_test.cc", - "helper_test.h", - "identifier_test.cc", - "if_test.cc", - "import_test.cc", - "loop_test.cc", - "member_accessor_test.cc", - "module_constant_test.cc", - "return_test.cc", - "sanitizer_test.cc", - "storage_buffer_test.cc", - "switch_test.cc", - "type_test.cc", - "unary_op_test.cc", - "uniform_buffer_test.cc", - "variable_decl_statement_test.cc", - "workgroup_var_test.cc", - ] - deps = [ - "${tint_src_dir}:gmock_and_gtest", - "${tint_src_dir}/api/common", - "${tint_src_dir}/api/options", - "${tint_src_dir}/lang/core", - "${tint_src_dir}/lang/core/constant", - "${tint_src_dir}/lang/core/type", - "${tint_src_dir}/lang/wgsl", - "${tint_src_dir}/lang/wgsl/ast", - "${tint_src_dir}/lang/wgsl/ast:unittests", - "${tint_src_dir}/lang/wgsl/ast/transform", - "${tint_src_dir}/lang/wgsl/common", - "${tint_src_dir}/lang/wgsl/features", - "${tint_src_dir}/lang/wgsl/program", - "${tint_src_dir}/lang/wgsl/resolver", - "${tint_src_dir}/lang/wgsl/sem", - "${tint_src_dir}/utils/containers", - "${tint_src_dir}/utils/diagnostic", - "${tint_src_dir}/utils/generator", - "${tint_src_dir}/utils/ice", - "${tint_src_dir}/utils/id", - "${tint_src_dir}/utils/macros", - "${tint_src_dir}/utils/math", - "${tint_src_dir}/utils/memory", - "${tint_src_dir}/utils/reflection", - "${tint_src_dir}/utils/result", - "${tint_src_dir}/utils/rtti", - "${tint_src_dir}/utils/symbol", - "${tint_src_dir}/utils/text", - "${tint_src_dir}/utils/traits", - ] - - if (tint_build_glsl_writer) { - deps += [ - "${tint_src_dir}/lang/glsl/writer", - "${tint_src_dir}/lang/glsl/writer/ast_printer", - "${tint_src_dir}/lang/glsl/writer/common", - ] - } - } - } -} diff --git a/references/writer/ast_printer/array_accessor_test.cc b/references/writer/ast_printer/array_accessor_test.cc deleted file mode 100644 index 5f3daf2f..00000000 --- a/references/writer/ast_printer/array_accessor_test.cc +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" - -#include "src/tint/lang/core/fluent_types.h" -#include "src/tint/utils/text/string_stream.h" - -#include "gmock/gmock.h" - -using namespace tint::core::number_suffixes; // NOLINT -using namespace tint::core::fluent_types; // NOLINT - -namespace tint::glsl::writer { -namespace { - -using GlslASTPrinterTest_Expression = TestHelper; - -TEST_F(GlslASTPrinterTest_Expression, IndexAccessor) { - GlobalVar("ary", ty.array(), core::AddressSpace::kPrivate); - auto* expr = IndexAccessor("ary", 5_i); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "ary[5]"); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/assign_test.cc b/references/writer/ast_printer/assign_test.cc deleted file mode 100644 index 9977e129..00000000 --- a/references/writer/ast_printer/assign_test.cc +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" - -#include "gmock/gmock.h" - -namespace tint::glsl::writer { -namespace { - -using GlslASTPrinterTest_Assign = TestHelper; - -TEST_F(GlslASTPrinterTest_Assign, Emit_Assign) { - GlobalVar("lhs", ty.i32(), core::AddressSpace::kPrivate); - GlobalVar("rhs", ty.i32(), core::AddressSpace::kPrivate); - auto* assign = Assign("lhs", "rhs"); - WrapInFunction(assign); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - - gen.EmitStatement(assign); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), " lhs = rhs;\n"); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/ast_printer.cc b/references/writer/ast_printer/ast_printer.cc deleted file mode 100644 index 29abc934..00000000 --- a/references/writer/ast_printer/ast_printer.cc +++ /dev/null @@ -1,3028 +0,0 @@ -/// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_printer/ast_printer.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "src/tint/lang/core/constant/splat.h" -#include "src/tint/lang/core/constant/value.h" -#include "src/tint/lang/core/fluent_types.h" -#include "src/tint/lang/core/type/array.h" -#include "src/tint/lang/core/type/atomic.h" -#include "src/tint/lang/core/type/depth_multisampled_texture.h" -#include "src/tint/lang/core/type/depth_texture.h" -#include "src/tint/lang/core/type/multisampled_texture.h" -#include "src/tint/lang/core/type/sampled_texture.h" -#include "src/tint/lang/core/type/storage_texture.h" -#include "src/tint/lang/core/type/texture_dimension.h" -#include "src/tint/lang/glsl/writer/ast_raise/combine_samplers.h" -#include "src/tint/lang/glsl/writer/ast_raise/pad_structs.h" -#include "src/tint/lang/glsl/writer/ast_raise/texture_1d_to_2d.h" -#include "src/tint/lang/glsl/writer/ast_raise/texture_builtins_from_uniform.h" -#include "src/tint/lang/glsl/writer/common/options.h" -#include "src/tint/lang/glsl/writer/common/printer_support.h" -#include "src/tint/lang/wgsl/ast/call_statement.h" -#include "src/tint/lang/wgsl/ast/id_attribute.h" -#include "src/tint/lang/wgsl/ast/internal_attribute.h" -#include "src/tint/lang/wgsl/ast/interpolate_attribute.h" -#include "src/tint/lang/wgsl/ast/transform/add_block_attribute.h" -#include "src/tint/lang/wgsl/ast/transform/add_empty_entry_point.h" -#include "src/tint/lang/wgsl/ast/transform/binding_remapper.h" -#include "src/tint/lang/wgsl/ast/transform/builtin_polyfill.h" -#include "src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.h" -#include "src/tint/lang/wgsl/ast/transform/clamp_frag_depth.h" -#include "src/tint/lang/wgsl/ast/transform/demote_to_helper.h" -#include "src/tint/lang/wgsl/ast/transform/direct_variable_access.h" -#include "src/tint/lang/wgsl/ast/transform/disable_uniformity_analysis.h" -#include "src/tint/lang/wgsl/ast/transform/expand_compound_assignment.h" -#include "src/tint/lang/wgsl/ast/transform/fold_constants.h" -#include "src/tint/lang/wgsl/ast/transform/manager.h" -#include "src/tint/lang/wgsl/ast/transform/multiplanar_external_texture.h" -#include "src/tint/lang/wgsl/ast/transform/offset_first_index.h" -#include "src/tint/lang/wgsl/ast/transform/preserve_padding.h" -#include "src/tint/lang/wgsl/ast/transform/promote_initializers_to_let.h" -#include "src/tint/lang/wgsl/ast/transform/promote_side_effects_to_decl.h" -#include "src/tint/lang/wgsl/ast/transform/remove_continue_in_switch.h" -#include "src/tint/lang/wgsl/ast/transform/remove_phonies.h" -#include "src/tint/lang/wgsl/ast/transform/renamer.h" -#include "src/tint/lang/wgsl/ast/transform/robustness.h" -#include "src/tint/lang/wgsl/ast/transform/simplify_pointers.h" -#include "src/tint/lang/wgsl/ast/transform/single_entry_point.h" -#include "src/tint/lang/wgsl/ast/transform/std140.h" -#include "src/tint/lang/wgsl/ast/transform/unshadow.h" -#include "src/tint/lang/wgsl/ast/transform/zero_init_workgroup_memory.h" -#include "src/tint/lang/wgsl/ast/variable_decl_statement.h" -#include "src/tint/lang/wgsl/helpers/append_vector.h" -#include "src/tint/lang/wgsl/helpers/check_supported_extensions.h" -#include "src/tint/lang/wgsl/sem/block_statement.h" -#include "src/tint/lang/wgsl/sem/builtin_enum_expression.h" -#include "src/tint/lang/wgsl/sem/call.h" -#include "src/tint/lang/wgsl/sem/function.h" -#include "src/tint/lang/wgsl/sem/member_accessor_expression.h" -#include "src/tint/lang/wgsl/sem/module.h" -#include "src/tint/lang/wgsl/sem/statement.h" -#include "src/tint/lang/wgsl/sem/struct.h" -#include "src/tint/lang/wgsl/sem/switch_statement.h" -#include "src/tint/lang/wgsl/sem/value_constructor.h" -#include "src/tint/lang/wgsl/sem/value_conversion.h" -#include "src/tint/lang/wgsl/sem/variable.h" -#include "src/tint/utils/containers/map.h" -#include "src/tint/utils/ice/ice.h" -#include "src/tint/utils/macros/defer.h" -#include "src/tint/utils/macros/scoped_assignment.h" -#include "src/tint/utils/rtti/switch.h" -#include "src/tint/utils/text/string.h" -#include "src/tint/utils/text/string_stream.h" - -using namespace tint::core::number_suffixes; // NOLINT -using namespace tint::core::fluent_types; // NOLINT - -namespace tint::glsl::writer { -namespace { - -const char kTempNamePrefix[] = "tint_tmp"; - -bool last_is_break(const ast::BlockStatement* stmts) { - return tint::IsAnyOf(stmts->Last()); -} - -bool IsRelational(tint::core::BinaryOp op) { - return op == tint::core::BinaryOp::kEqual || op == tint::core::BinaryOp::kNotEqual || - op == tint::core::BinaryOp::kLessThan || op == tint::core::BinaryOp::kGreaterThan || - op == tint::core::BinaryOp::kLessThanEqual || - op == tint::core::BinaryOp::kGreaterThanEqual; -} - -bool RequiresOESSampleVariables(tint::core::BuiltinValue builtin) { - switch (builtin) { - case tint::core::BuiltinValue::kSampleIndex: - case tint::core::BuiltinValue::kSampleMask: - return true; - default: - return false; - } -} - -} // namespace - -SanitizedResult::SanitizedResult() = default; -SanitizedResult::~SanitizedResult() = default; -SanitizedResult::SanitizedResult(SanitizedResult&&) = default; - -SanitizedResult Sanitize(const Program& in, - const Options& options, - const std::string& entry_point) { - ast::transform::Manager manager; - ast::transform::DataMap data; - - manager.Add(); - - manager.Add(); - - // ExpandCompoundAssignment must come before BuiltinPolyfill - manager.Add(); - - if (!entry_point.empty()) { - manager.Add(); - data.Add(entry_point); - } - - manager.Add(); // Must come before DirectVariableAccess - - manager.Add(); // Must come before DirectVariableAccess - - manager.Add(); - - if (!options.disable_robustness) { - // Robustness must come after PromoteSideEffectsToDecl - // Robustness must come before BuiltinPolyfill and CanonicalizeEntryPointIO - manager.Add(); - } - - if (!options.disable_workgroup_init) { - // ZeroInitWorkgroupMemory must come before CanonicalizeEntryPointIO as - // ZeroInitWorkgroupMemory may inject new builtin parameters. - manager.Add(); - } - - manager.Add(); - - // TextureBuiltinsFromUniform must come before CombineSamplers to preserve texture binding point - // info, instead of combined sampler binding point. As a result, TextureBuiltinsFromUniform also - // comes before BindingRemapper so the binding point info it reflects is before remapping. - manager.Add(); - data.Add( - options.texture_builtins_from_uniform.ubo_binding, - options.texture_builtins_from_uniform.ubo_bindingpoint_ordering); - - // Note: it is more efficient for MultiplanarExternalTexture to come after Robustness - // Must come before builtin polyfills - data.Add( - options.external_texture_options.bindings_map); - manager.Add(); - - // Must be after multiplanar and must be before OffsetFirstindex - manager.Add(); - - // This must come before ClampFragDepth as the AddBlockAttribute will change around the struct - // that gets created for the push constants and we end up with the `inner` structure sitting at - // the same offset we want to place the first_instance value. - manager.Add(); - data.Add(options.first_vertex_offset, - options.first_instance_offset); - - // ClampFragDepth must come before CanonicalizeEntryPointIO, or the assignments to FragDepth are - // lost - manager.Add(); - data.Add(options.depth_range_offsets); - - // CanonicalizeEntryPointIO must come after Robustness - manager.Add(); - data.Add( - ast::transform::CanonicalizeEntryPointIO::ShaderStyle::kGlsl); - - // DemoteToHelper must come after PromoteSideEffectsToDecl and ExpandCompoundAssignment. - manager.Add(); - - { // Builtin polyfills - ast::transform::BuiltinPolyfill::Builtins polyfills; - polyfills.acosh = ast::transform::BuiltinPolyfill::Level::kRangeCheck; - polyfills.atanh = ast::transform::BuiltinPolyfill::Level::kRangeCheck; - polyfills.bgra8unorm = true; - polyfills.bitshift_modulo = true; - polyfills.conv_f32_to_iu32 = true; - polyfills.count_leading_zeros = true; - polyfills.count_trailing_zeros = true; - polyfills.extract_bits = ast::transform::BuiltinPolyfill::Level::kClampParameters; - polyfills.first_leading_bit = true; - polyfills.first_trailing_bit = true; - polyfills.insert_bits = ast::transform::BuiltinPolyfill::Level::kClampParameters; - polyfills.int_div_mod = !options.disable_polyfill_integer_div_mod; - polyfills.saturate = true; - polyfills.texture_sample_base_clamp_to_edge_2d_f32 = true; - polyfills.workgroup_uniform_load = true; - polyfills.dot_4x8_packed = true; - polyfills.pack_unpack_4x8 = true; - polyfills.pack_4xu8_clamp = true; - data.Add(polyfills); - manager.Add(); // Must come before DirectVariableAccess - } - - manager.Add(); - - // Must come after builtin polyfills (specifically texture_sample_base_clamp_to_edge_2d_f32) - data.Add(options.combined_samplers_info); - manager.Add(); - - // Must come after CombineSamplers - data.Add( - options.binding_remapper_options.binding_points, - std::unordered_map{}, - /* allow_collisions */ true); - manager.Add(); - - // PadStructs must come after CanonicalizeEntryPointIO and CombineSamplers - manager.Add(); - - // Promote initializers must come after binding polyfill - manager.Add(); - manager.Add(); - manager.Add(); - - // Std140 must come after PromoteSideEffectsToDecl and before SimplifyPointers. - manager.Add(); - - manager.Add(); - - manager.Add(); - - SanitizedResult result; - ast::transform::DataMap outputs; - result.program = manager.Run(in, data, outputs); - return result; -} - -ASTPrinter::ASTPrinter(const Program& program, const Version& version) - : builder_(ProgramBuilder::Wrap(program)), version_(version) {} - -ASTPrinter::~ASTPrinter() = default; - -bool ASTPrinter::Generate() { - if (!tint::wgsl::CheckSupportedExtensions( - "GLSL", builder_.AST(), diagnostics_, - Vector{ - wgsl::Extension::kChromiumDisableUniformityAnalysis, - wgsl::Extension::kChromiumExperimentalPushConstant, - wgsl::Extension::kChromiumInternalDualSourceBlending, - wgsl::Extension::kChromiumInternalGraphite, - wgsl::Extension::kF16, - wgsl::Extension::kDualSourceBlending, - })) { - return false; - } - - { - auto out = Line(); - out << "#version " << version_.major_version << version_.minor_version << "0"; - if (version_.IsES()) { - out << " es"; - } - } - - auto helpers_insertion_point = current_buffer_->lines.size(); - - Line(); - - auto* mod = builder_.Sem().Module(); - for (auto* decl : mod->DependencyOrderedDeclarations()) { - if (decl->IsAnyOf()) { - continue; // These are not emitted. - } - - Switch( - decl, // - [&](const ast::Variable* global) { return EmitGlobalVariable(global); }, - [&](const ast::Struct* str) { - auto* sem = builder_.Sem().Get(str); - bool has_rt_arr = false; - if (auto* arr = sem->Members().Back()->Type()->As()) { - has_rt_arr = arr->Count()->Is(); - } - bool is_block = - ast::HasAttribute( - str->attributes) && - !sem->UsedAs(core::AddressSpace::kPushConstant); - if (!has_rt_arr && !is_block) { - EmitStructType(current_buffer_, sem); - } - }, - [&](const ast::Function* func) { - if (func->IsEntryPoint()) { - EmitEntryPointFunction(func); - } else { - EmitFunction(func); - } - }, - [&](const ast::Enable* enable) { - // Record the required extension for generating extension directive later - RecordExtension(enable); - }, // - [&](const ast::Requires*) { - // Do nothing for requiring language features in GLSL. - }, // - TINT_ICE_ON_NO_MATCH); - } - - TextBuffer extensions; - - if (version_.IsES() && requires_oes_sample_variables_) { - extensions.Append("#extension GL_OES_sample_variables : require"); - } - - if (requires_f16_extension_) { - extensions.Append("#extension GL_AMD_gpu_shader_half_float : require"); - } - - if (requires_dual_source_blending_extension_) { - extensions.Append("#extension GL_EXT_blend_func_extended : require"); - } - - auto indent = current_buffer_->current_indent; - - if (!extensions.lines.empty()) { - current_buffer_->Insert(extensions, helpers_insertion_point, indent); - helpers_insertion_point += extensions.lines.size(); - } - - if (version_.IsES() && requires_default_precision_qualifier_) { - current_buffer_->Insert("precision highp float;", helpers_insertion_point++, indent); - current_buffer_->Insert("precision highp int;", helpers_insertion_point++, indent); - } - - if (!helpers_.lines.empty()) { - current_buffer_->Insert("", helpers_insertion_point++, indent); - current_buffer_->Insert(helpers_, helpers_insertion_point, indent); - helpers_insertion_point += helpers_.lines.size(); - } - - return !diagnostics_.ContainsErrors(); -} - -void ASTPrinter::RecordExtension(const ast::Enable* enable) { - // Deal with extension node here, recording it within the generator for later emition. - - if (enable->HasExtension(wgsl::Extension::kF16)) { - requires_f16_extension_ = true; - } - - if (enable->HasExtension(wgsl::Extension::kChromiumInternalDualSourceBlending) || - enable->HasExtension(wgsl::Extension::kDualSourceBlending)) { - requires_dual_source_blending_extension_ = true; - } -} - -void ASTPrinter::EmitIndexAccessor(StringStream& out, const ast::IndexAccessorExpression* expr) { - EmitExpression(out, expr->object); - out << "["; - EmitExpression(out, expr->index); - out << "]"; -} - -void ASTPrinter::EmitBitcastCall(StringStream& out, const ast::CallExpression* call) { - auto* arg = call->args[0]; - auto* src_type = TypeOf(arg)->UnwrapRef(); - auto* dst_type = TypeOf(call); - - if (!dst_type->is_integer_scalar_or_vector() && !dst_type->is_float_scalar_or_vector()) { - diagnostics_.AddError(Source{}) - << "Unable to do bitcast to type " << dst_type->FriendlyName(); - return; - } - - // Handle identity bitcast. - if (src_type == dst_type) { - return EmitExpression(out, arg); - } - - // Use packFloat2x16 and unpackFloat2x16 for f16 types. - if (src_type->DeepestElement()->Is()) { - // Source type must be vec2 or vec4, since type f16 and vec3 can only have - // identity bitcast. - auto* src_vec = src_type->As(); - TINT_ASSERT(src_vec); - TINT_ASSERT(((src_vec->Width() == 2u) || (src_vec->Width() == 4u))); - std::string fn = - GetOrAdd(bitcast_funcs_, BinaryOperandType{{src_type, dst_type}}, [&]() -> std::string { - TextBuffer b; - TINT_DEFER(helpers_.Append(b)); - - auto fn_name = UniqueIdentifier("tint_bitcast_from_f16"); - { - auto decl = Line(&b); - EmitTypeAndName(decl, dst_type, core::AddressSpace::kUndefined, - core::Access::kUndefined, fn_name); - { - ScopedParen sp(decl); - EmitTypeAndName(decl, src_type, core::AddressSpace::kUndefined, - core::Access::kUndefined, "src"); - } - decl << " {"; - } - { - ScopedIndent si(&b); - switch (src_vec->Width()) { - case 2: { - Line(&b) << "uint r = packFloat2x16(src);"; - break; - } - case 4: { - Line(&b) - << "uvec2 r = uvec2(packFloat2x16(src.xy), packFloat2x16(src.zw));"; - break; - } - } - auto s = Line(&b); - s << "return "; - if (dst_type->is_float_scalar_or_vector()) { - s << "uintBitsToFloat"; - } else { - EmitType(s, dst_type, core::AddressSpace::kUndefined, - core::Access::kReadWrite, ""); - } - s << "(r);"; - } - Line(&b) << "}"; - return fn_name; - }); - // Call the helper - out << fn; - { - ScopedParen sp(out); - EmitExpression(out, arg); - } - } else if (dst_type->DeepestElement()->Is()) { - // Destination type must be vec2 or vec4. - auto* dst_vec = dst_type->As(); - TINT_ASSERT(dst_vec); - TINT_ASSERT(((dst_vec->Width() == 2u) || (dst_vec->Width() == 4u))); - std::string fn = - GetOrAdd(bitcast_funcs_, BinaryOperandType{{src_type, dst_type}}, [&]() -> std::string { - TextBuffer b; - TINT_DEFER(helpers_.Append(b)); - - auto fn_name = UniqueIdentifier("tint_bitcast_to_f16"); - { - auto decl = Line(&b); - EmitTypeAndName(decl, dst_type, core::AddressSpace::kUndefined, - core::Access::kUndefined, fn_name); - { - ScopedParen sp(decl); - EmitTypeAndName(decl, src_type, core::AddressSpace::kUndefined, - core::Access::kUndefined, "src"); - } - decl << " {"; - } - { - ScopedIndent si(&b); - if (auto src_vec = src_type->As()) { - // Source vector type must be vec2, destination type vec4. - TINT_ASSERT( - (src_vec->DeepestElement() - ->IsAnyOf())); - TINT_ASSERT((src_vec->Width() == 2u)); - { - auto s = Line(&b); - s << "uvec2 r = "; - if (src_type->is_float_scalar_or_vector()) { - s << "floatBitsToUint"; - } else { - s << "uvec2"; - } - s << "(src);"; - } - Line(&b) << "f16vec2 v_xy = unpackFloat2x16(r.x);"; - Line(&b) << "f16vec2 v_zw = unpackFloat2x16(r.y);"; - Line(&b) << "return f16vec4(v_xy.x, v_xy.y, v_zw.x, v_zw.y);"; - } else { - // Source scalar type must be f32/i32/u32, destination type vec2. - TINT_ASSERT( - (src_type - ->IsAnyOf())); - { - auto s = Line(&b); - s << "uint r = "; - if (src_type->is_float_scalar_or_vector()) { - s << "floatBitsToUint"; - } else { - s << "uint"; - } - s << "(src);"; - } - Line(&b) << "return unpackFloat2x16(r);"; - } - } - Line(&b) << "}"; - return fn_name; - }); - // Call the helper - out << fn; - { - ScopedParen sp(out); - EmitExpression(out, arg); - } - } else { - if (src_type->is_float_scalar_or_vector() && - dst_type->is_signed_integer_scalar_or_vector()) { - out << "floatBitsToInt"; - } else if (src_type->is_float_scalar_or_vector() && - dst_type->is_unsigned_integer_scalar_or_vector()) { - out << "floatBitsToUint"; - } else if (src_type->is_signed_integer_scalar_or_vector() && - dst_type->is_float_scalar_or_vector()) { - out << "intBitsToFloat"; - } else if (src_type->is_unsigned_integer_scalar_or_vector() && - dst_type->is_float_scalar_or_vector()) { - out << "uintBitsToFloat"; - } else { - EmitType(out, dst_type, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); - } - ScopedParen sp(out); - EmitExpression(out, arg); - } -} - -void ASTPrinter::EmitAssign(const ast::AssignmentStatement* stmt) { - auto out = Line(); - EmitExpression(out, stmt->lhs); - out << " = "; - EmitExpression(out, stmt->rhs); - out << ";"; -} - -void ASTPrinter::EmitVectorRelational(StringStream& out, const ast::BinaryExpression* expr) { - switch (expr->op) { - case core::BinaryOp::kEqual: - out << "equal"; - break; - case core::BinaryOp::kNotEqual: - out << "notEqual"; - break; - case core::BinaryOp::kLessThan: - out << "lessThan"; - break; - case core::BinaryOp::kGreaterThan: - out << "greaterThan"; - break; - case core::BinaryOp::kLessThanEqual: - out << "lessThanEqual"; - break; - case core::BinaryOp::kGreaterThanEqual: - out << "greaterThanEqual"; - break; - default: - break; - } - ScopedParen sp(out); - EmitExpression(out, expr->lhs); - out << ", "; - EmitExpression(out, expr->rhs); -} - -void ASTPrinter::EmitBitwiseBoolOp(StringStream& out, const ast::BinaryExpression* expr) { - auto* bool_type = TypeOf(expr->lhs)->UnwrapRef(); - auto* uint_type = BoolTypeToUint(bool_type); - - // Cast result to bool scalar or vector type. - EmitType(out, bool_type, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); - ScopedParen outerCastParen(out); - // Cast LHS to uint scalar or vector type. - EmitType(out, uint_type, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); - { - ScopedParen innerCastParen(out); - // Emit LHS. - EmitExpression(out, expr->lhs); - } - // Emit operator. - if (expr->op == core::BinaryOp::kAnd) { - out << " & "; - } else if (TINT_LIKELY(expr->op == core::BinaryOp::kOr)) { - out << " | "; - } else { - TINT_ICE() << "unexpected binary op: " << expr->op; - } - - // Cast RHS to uint scalar or vector type. - EmitType(out, uint_type, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); - { - ScopedParen innerCastParen(out); - // Emit RHS. - EmitExpression(out, expr->rhs); - } -} - -void ASTPrinter::EmitFloatModulo(StringStream& out, const ast::BinaryExpression* expr) { - std::string fn; - auto* ret_ty = TypeOf(expr)->UnwrapRef(); - auto* lhs_ty = TypeOf(expr->lhs)->UnwrapRef(); - auto* rhs_ty = TypeOf(expr->rhs)->UnwrapRef(); - fn = tint::GetOrAdd(float_modulo_funcs_, BinaryOperandType{{lhs_ty, rhs_ty}}, - [&]() -> std::string { - TextBuffer b; - TINT_DEFER(helpers_.Append(b)); - - auto fn_name = UniqueIdentifier("tint_float_modulo"); - std::vector parameter_names; - { - auto decl = Line(&b); - EmitTypeAndName(decl, ret_ty, core::AddressSpace::kUndefined, - core::Access::kUndefined, fn_name); - { - ScopedParen sp(decl); - const auto* ty = TypeOf(expr->lhs)->UnwrapRef(); - EmitTypeAndName(decl, ty, core::AddressSpace::kUndefined, - core::Access::kUndefined, "lhs"); - decl << ", "; - ty = TypeOf(expr->rhs)->UnwrapRef(); - EmitTypeAndName(decl, ty, core::AddressSpace::kUndefined, - core::Access::kUndefined, "rhs"); - } - decl << " {"; - } - { - ScopedIndent si(&b); - Line(&b) << "return (lhs - rhs * trunc(lhs / rhs));"; - } - Line(&b) << "}"; - Line(&b); - return fn_name; - }); - - // Call the helper - out << fn; - { - ScopedParen sp(out); - EmitExpression(out, expr->lhs); - out << ", "; - EmitExpression(out, expr->rhs); - } -} - -void ASTPrinter::EmitBinary(StringStream& out, const ast::BinaryExpression* expr) { - if (IsRelational(expr->op) && !TypeOf(expr->lhs)->UnwrapRef()->Is()) { - EmitVectorRelational(out, expr); - return; - } - - if (expr->op == core::BinaryOp::kLogicalAnd || expr->op == core::BinaryOp::kLogicalOr) { - auto name = UniqueIdentifier(kTempNamePrefix); - - { - auto pre = Line(); - pre << "bool " << name << " = "; - EmitExpression(pre, expr->lhs); - pre << ";"; - } - - if (expr->op == core::BinaryOp::kLogicalOr) { - Line() << "if (!" << name << ") {"; - } else { - Line() << "if (" << name << ") {"; - } - - { - ScopedIndent si(this); - auto pre = Line(); - pre << name << " = "; - EmitExpression(pre, expr->rhs); - pre << ";"; - } - - Line() << "}"; - - out << "(" << name << ")"; - return; - } - - if ((expr->op == core::BinaryOp::kAnd || expr->op == core::BinaryOp::kOr) && - TypeOf(expr->lhs)->UnwrapRef()->is_bool_scalar_or_vector()) { - EmitBitwiseBoolOp(out, expr); - return; - } - - if (expr->op == core::BinaryOp::kModulo && - (TypeOf(expr->lhs)->UnwrapRef()->is_float_scalar_or_vector() || - TypeOf(expr->rhs)->UnwrapRef()->is_float_scalar_or_vector())) { - EmitFloatModulo(out, expr); - return; - } - - ScopedParen sp(out); - EmitExpression(out, expr->lhs); - out << " "; - - switch (expr->op) { - case core::BinaryOp::kAnd: - out << "&"; - break; - case core::BinaryOp::kOr: - out << "|"; - break; - case core::BinaryOp::kXor: - out << "^"; - break; - case core::BinaryOp::kLogicalAnd: - case core::BinaryOp::kLogicalOr: { - // These are both handled above. - TINT_UNREACHABLE(); - } - case core::BinaryOp::kEqual: - out << "=="; - break; - case core::BinaryOp::kNotEqual: - out << "!="; - break; - case core::BinaryOp::kLessThan: - out << "<"; - break; - case core::BinaryOp::kGreaterThan: - out << ">"; - break; - case core::BinaryOp::kLessThanEqual: - out << "<="; - break; - case core::BinaryOp::kGreaterThanEqual: - out << ">="; - break; - case core::BinaryOp::kShiftLeft: - out << "<<"; - break; - case core::BinaryOp::kShiftRight: - out << R"(>>)"; - break; - - case core::BinaryOp::kAdd: - out << "+"; - break; - case core::BinaryOp::kSubtract: - out << "-"; - break; - case core::BinaryOp::kMultiply: - out << "*"; - break; - case core::BinaryOp::kDivide: - out << "/"; - break; - case core::BinaryOp::kModulo: - out << "%"; - break; - } - out << " "; - EmitExpression(out, expr->rhs); -} - -void ASTPrinter::EmitStatements(VectorRef stmts) { - for (auto* s : stmts) { - EmitStatement(s); - } -} - -void ASTPrinter::EmitStatementsWithIndent(VectorRef stmts) { - ScopedIndent si(this); - EmitStatements(stmts); -} - -void ASTPrinter::EmitBlock(const ast::BlockStatement* stmt) { - Line() << "{"; - EmitStatementsWithIndent(stmt->statements); - Line() << "}"; -} - -void ASTPrinter::EmitBreak(const ast::BreakStatement*) { - Line() << "break;"; -} - -void ASTPrinter::EmitBreakIf(const ast::BreakIfStatement* b) { - auto out = Line(); - out << "if ("; - EmitExpression(out, b->condition); - out << ") { break; }"; -} - -void ASTPrinter::EmitCall(StringStream& out, const ast::CallExpression* expr) { - auto* call = builder_.Sem().Get(expr); - Switch( - call->Target(), // - [&](const sem::Function* fn) { EmitFunctionCall(out, call, fn); }, - [&](const sem::BuiltinFn* builtin) { EmitBuiltinCall(out, call, builtin); }, - [&](const sem::ValueConversion* conv) { EmitValueConversion(out, call, conv); }, - [&](const sem::ValueConstructor* ctor) { EmitValueConstructor(out, call, ctor); }, - TINT_ICE_ON_NO_MATCH); -} - -void ASTPrinter::EmitFunctionCall(StringStream& out, - const sem::Call* call, - const sem::Function* fn) { - const auto& args = call->Arguments(); - auto* ident = fn->Declaration()->name; - - out << ident->symbol.Name(); - ScopedParen sp(out); - - bool first = true; - for (auto* arg : args) { - if (!first) { - out << ", "; - } - first = false; - - EmitExpression(out, arg->Declaration()); - } -} - -void ASTPrinter::EmitBuiltinCall(StringStream& out, - const sem::Call* call, - const sem::BuiltinFn* builtin) { - auto* expr = call->Declaration(); - if (builtin->IsTexture()) { - EmitTextureCall(out, call, builtin); - } else if (builtin->Fn() == wgsl::BuiltinFn::kCountOneBits) { - EmitCountOneBitsCall(out, expr); - } else if (builtin->Fn() == wgsl::BuiltinFn::kSelect) { - EmitSelectCall(out, expr, builtin); - } else if (builtin->Fn() == wgsl::BuiltinFn::kBitcast) { - EmitBitcastCall(out, expr); - } else if (builtin->Fn() == wgsl::BuiltinFn::kDot) { - EmitDotCall(out, expr, builtin); - } else if (builtin->Fn() == wgsl::BuiltinFn::kModf) { - EmitModfCall(out, expr, builtin); - } else if (builtin->Fn() == wgsl::BuiltinFn::kFrexp) { - EmitFrexpCall(out, expr, builtin); - } else if (builtin->Fn() == wgsl::BuiltinFn::kDegrees) { - EmitDegreesCall(out, expr, builtin); - } else if (builtin->Fn() == wgsl::BuiltinFn::kRadians) { - EmitRadiansCall(out, expr, builtin); - } else if (builtin->Fn() == wgsl::BuiltinFn::kQuantizeToF16) { - EmitQuantizeToF16Call(out, expr, builtin); - } else if (builtin->Fn() == wgsl::BuiltinFn::kArrayLength) { - EmitArrayLength(out, expr); - } else if (builtin->Fn() == wgsl::BuiltinFn::kExtractBits) { - EmitExtractBits(out, expr); - } else if (builtin->Fn() == wgsl::BuiltinFn::kInsertBits) { - EmitInsertBits(out, expr); - } else if (builtin->Fn() == wgsl::BuiltinFn::kFma && version_.IsES()) { - EmitEmulatedFMA(out, expr); - } else if (builtin->Fn() == wgsl::BuiltinFn::kAbs && - TypeOf(expr->args[0])->UnwrapRef()->is_unsigned_integer_scalar_or_vector()) { - // GLSL does not support abs() on unsigned arguments. However, it's a no-op. - EmitExpression(out, expr->args[0]); - } else if ((builtin->Fn() == wgsl::BuiltinFn::kAny || builtin->Fn() == wgsl::BuiltinFn::kAll) && - TypeOf(expr->args[0])->UnwrapRef()->Is()) { - // GLSL does not support any() or all() on scalar arguments. It's a no-op. - EmitExpression(out, expr->args[0]); - } else if (builtin->IsBarrier()) { - EmitBarrierCall(out, builtin); - } else if (builtin->IsAtomic()) { - EmitWorkgroupAtomicCall(out, expr, builtin); - } else { - auto name = generate_builtin_name(builtin); - if (name.empty()) { - return; - } - - out << name; - ScopedParen sp(out); - - bool first = true; - for (auto* arg : call->Arguments()) { - if (!first) { - out << ", "; - } - first = false; - - EmitExpression(out, arg->Declaration()); - } - } -} - -void ASTPrinter::EmitValueConversion(StringStream& out, - const sem::Call* call, - const sem::ValueConversion* conv) { - EmitType(out, conv->Target(), core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); - ScopedParen sp(out); - EmitExpression(out, call->Arguments()[0]->Declaration()); -} - -void ASTPrinter::EmitValueConstructor(StringStream& out, - const sem::Call* call, - const sem::ValueConstructor* ctor) { - auto* type = ctor->ReturnType(); - - // If the value constructor is empty then we need to construct with the zero value for all - // components. - if (call->Arguments().IsEmpty()) { - EmitZeroValue(out, type); - return; - } - - EmitType(out, type, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); - ScopedParen sp(out); - - bool first = true; - for (auto* arg : call->Arguments()) { - if (!first) { - out << ", "; - } - first = false; - - EmitExpression(out, arg->Declaration()); - } -} - -void ASTPrinter::EmitWorkgroupAtomicCall(StringStream& out, - const ast::CallExpression* expr, - const sem::BuiltinFn* builtin) { - auto call = [&](const char* name) { - out << name; - { - ScopedParen sp(out); - for (size_t i = 0; i < expr->args.Length(); i++) { - auto* arg = expr->args[i]; - if (i > 0) { - out << ", "; - } - EmitExpression(out, arg); - } - } - return; - }; - - switch (builtin->Fn()) { - case wgsl::BuiltinFn::kAtomicLoad: { - // GLSL does not have an atomicLoad, so we emulate it with - // atomicOr using 0 as the OR value - out << "atomicOr"; - { - ScopedParen sp(out); - EmitExpression(out, expr->args[0]); - out << ", 0"; - if (builtin->ReturnType()->Is()) { - out << "u"; - } - } - return; - } - case wgsl::BuiltinFn::kAtomicCompareExchangeWeak: { - EmitStructType(&helpers_, builtin->ReturnType()->As()); - - auto* dest = expr->args[0]; - auto* compare_value = expr->args[1]; - auto* value = expr->args[2]; - - std::string result = UniqueIdentifier("atomic_compare_result"); - - { - auto pre = Line(); - EmitTypeAndName(pre, builtin->ReturnType(), core::AddressSpace::kUndefined, - core::Access::kUndefined, result); - pre << ";"; - } - { - auto pre = Line(); - pre << result << ".old_value = atomicCompSwap"; - { - ScopedParen sp(pre); - EmitExpression(pre, dest); - pre << ", "; - EmitExpression(pre, compare_value); - pre << ", "; - EmitExpression(pre, value); - } - pre << ";"; - } - { - auto pre = Line(); - pre << result << ".exchanged = " << result << ".old_value == "; - EmitExpression(pre, compare_value); - pre << ";"; - } - - out << result; - return; - } - - case wgsl::BuiltinFn::kAtomicAdd: - call("atomicAdd"); - return; - - case wgsl::BuiltinFn::kAtomicSub: - out << "atomicAdd"; - { - ScopedParen sp(out); - TINT_ASSERT(expr->args.Length() == 2); - EmitExpression(out, expr->args[0]); - out << ", -"; - { - ScopedParen argSP(out); - EmitExpression(out, expr->args[1]); - } - } - return; - - case wgsl::BuiltinFn::kAtomicMax: - call("atomicMax"); - return; - - case wgsl::BuiltinFn::kAtomicMin: - call("atomicMin"); - return; - - case wgsl::BuiltinFn::kAtomicAnd: - call("atomicAnd"); - return; - - case wgsl::BuiltinFn::kAtomicOr: - call("atomicOr"); - return; - - case wgsl::BuiltinFn::kAtomicXor: - call("atomicXor"); - return; - - case wgsl::BuiltinFn::kAtomicExchange: - case wgsl::BuiltinFn::kAtomicStore: - // GLSL does not have an atomicStore, so we emulate it with - // atomicExchange. - call("atomicExchange"); - return; - - default: - break; - } - - TINT_UNREACHABLE() << "unsupported atomic builtin: " << builtin->Fn(); -} - -void ASTPrinter::EmitArrayLength(StringStream& out, const ast::CallExpression* expr) { - out << "uint("; - EmitExpression(out, expr->args[0]); - out << ".length())"; -} - -void ASTPrinter::EmitExtractBits(StringStream& out, const ast::CallExpression* expr) { - out << "bitfieldExtract("; - EmitExpression(out, expr->args[0]); - out << ", int("; - EmitExpression(out, expr->args[1]); - out << "), int("; - EmitExpression(out, expr->args[2]); - out << "))"; -} - -void ASTPrinter::EmitInsertBits(StringStream& out, const ast::CallExpression* expr) { - out << "bitfieldInsert("; - EmitExpression(out, expr->args[0]); - out << ", "; - EmitExpression(out, expr->args[1]); - out << ", int("; - EmitExpression(out, expr->args[2]); - out << "), int("; - EmitExpression(out, expr->args[3]); - out << "))"; -} - -void ASTPrinter::EmitEmulatedFMA(StringStream& out, const ast::CallExpression* expr) { - out << "(("; - EmitExpression(out, expr->args[0]); - out << ") * ("; - EmitExpression(out, expr->args[1]); - out << ") + ("; - EmitExpression(out, expr->args[2]); - out << "))"; -} - -void ASTPrinter::EmitCountOneBitsCall(StringStream& out, const ast::CallExpression* expr) { - // GLSL's bitCount returns an integer type, so cast it to the appropriate - // unsigned type. - EmitType(out, TypeOf(expr)->UnwrapRef(), core::AddressSpace::kUndefined, - core::Access::kReadWrite, ""); - out << "(bitCount("; - EmitExpression(out, expr->args[0]); - out << "))"; -} - -void ASTPrinter::EmitSelectCall(StringStream& out, - const ast::CallExpression* expr, - const sem::BuiltinFn* builtin) { - // GLSL does not support ternary expressions with a bool vector conditional, - // so polyfill with a helper. - if (auto* vec = builtin->Parameters()[2]->Type()->As()) { - CallBuiltinHelper(out, expr, builtin, - [&](TextBuffer* b, const std::vector& params) { - auto l = Line(b); - l << " return "; - EmitType(l, builtin->ReturnType(), core::AddressSpace::kUndefined, - core::Access::kUndefined, ""); - { - ScopedParen sp(l); - for (uint32_t i = 0; i < vec->Width(); i++) { - if (i > 0) { - l << ", "; - } - l << params[2] << "[" << i << "] ? " << params[1] << "[" << i - << "] : " << params[0] << "[" << i << "]"; - } - } - l << ";"; - }); - return; - } - - auto* expr_false = expr->args[0]; - auto* expr_true = expr->args[1]; - auto* expr_cond = expr->args[2]; - - ScopedParen paren(out); - EmitExpression(out, expr_cond); - - out << " ? "; - EmitExpression(out, expr_true); - out << " : "; - EmitExpression(out, expr_false); -} - -void ASTPrinter::EmitDotCall(StringStream& out, - const ast::CallExpression* expr, - const sem::BuiltinFn* builtin) { - auto* vec_ty = builtin->Parameters()[0]->Type()->As(); - std::string fn = "dot"; - if (vec_ty->type()->is_integer_scalar()) { - // GLSL does not have a builtin for dot() with integer vector types. - // Generate the helper function if it hasn't been created already - fn = tint::GetOrAdd(int_dot_funcs_, vec_ty, [&]() -> std::string { - TextBuffer b; - TINT_DEFER(helpers_.Append(b)); - - auto fn_name = UniqueIdentifier("tint_int_dot"); - - std::string v; - { - StringStream s; - EmitType(s, vec_ty->type(), core::AddressSpace::kUndefined, core::Access::kRead, - ""); - v = s.str(); - } - { // (u)int tint_int_dot([i|u]vecN a, [i|u]vecN b) { - auto l = Line(&b); - EmitType(l, vec_ty->type(), core::AddressSpace::kUndefined, core::Access::kRead, - ""); - l << " " << fn_name << "("; - EmitType(l, vec_ty, core::AddressSpace::kUndefined, core::Access::kRead, ""); - l << " a, "; - EmitType(l, vec_ty, core::AddressSpace::kUndefined, core::Access::kRead, ""); - l << " b) {"; - } - { - auto l = Line(&b); - l << " return "; - for (uint32_t i = 0; i < vec_ty->Width(); i++) { - if (i > 0) { - l << " + "; - } - l << "a[" << i << "]*b[" << i << "]"; - } - l << ";"; - } - Line(&b) << "}"; - return fn_name; - }); - } - - out << fn; - ScopedParen sp(out); - - EmitExpression(out, expr->args[0]); - out << ", "; - EmitExpression(out, expr->args[1]); -} - -void ASTPrinter::EmitModfCall(StringStream& out, - const ast::CallExpression* expr, - const sem::BuiltinFn* builtin) { - TINT_ASSERT(expr->args.Length() == 1); - CallBuiltinHelper( - out, expr, builtin, [&](TextBuffer* b, const std::vector& params) { - // Emit the builtin return type unique to this overload. This does not - // exist in the AST, so it will not be generated in Generate(). - EmitStructType(&helpers_, builtin->ReturnType()->As()); - - { - auto l = Line(b); - EmitType(l, builtin->ReturnType(), core::AddressSpace::kUndefined, - core::Access::kUndefined, ""); - l << " result;"; - } - Line(b) << "result.fract = modf(" << params[0] << ", result.whole);"; - Line(b) << "return result;"; - }); -} - -void ASTPrinter::EmitFrexpCall(StringStream& out, - const ast::CallExpression* expr, - const sem::BuiltinFn* builtin) { - TINT_ASSERT(expr->args.Length() == 1); - CallBuiltinHelper( - out, expr, builtin, [&](TextBuffer* b, const std::vector& params) { - // Emit the builtin return type unique to this overload. This does not - // exist in the AST, so it will not be generated in Generate(). - EmitStructType(&helpers_, builtin->ReturnType()->As()); - - { - auto l = Line(b); - EmitType(l, builtin->ReturnType(), core::AddressSpace::kUndefined, - core::Access::kUndefined, ""); - l << " result;"; - } - Line(b) << "result.fract = frexp(" << params[0] << ", result.exp);"; - Line(b) << "return result;"; - }); -} - -void ASTPrinter::EmitDegreesCall(StringStream& out, - const ast::CallExpression* expr, - const sem::BuiltinFn* builtin) { - auto* return_elem_type = builtin->ReturnType()->DeepestElement(); - const std::string suffix = Is(return_elem_type) ? "hf" : "f"; - CallBuiltinHelper(out, expr, builtin, - [&](TextBuffer* b, const std::vector& params) { - Line(b) << "return " << params[0] << " * " << std::setprecision(20) - << sem::kRadToDeg << suffix << ";"; - }); -} - -void ASTPrinter::EmitRadiansCall(StringStream& out, - const ast::CallExpression* expr, - const sem::BuiltinFn* builtin) { - auto* return_elem_type = builtin->ReturnType()->DeepestElement(); - const std::string suffix = Is(return_elem_type) ? "hf" : "f"; - CallBuiltinHelper(out, expr, builtin, - [&](TextBuffer* b, const std::vector& params) { - Line(b) << "return " << params[0] << " * " << std::setprecision(20) - << sem::kDegToRad << suffix << ";"; - }); -} - -void ASTPrinter::EmitQuantizeToF16Call(StringStream& out, - const ast::CallExpression* expr, - const sem::BuiltinFn* builtin) { - // Emulate by casting to f16 and back again. - CallBuiltinHelper( - out, expr, builtin, [&](TextBuffer* b, const std::vector& params) { - const auto v = params[0]; - if (auto* vec = builtin->ReturnType()->As()) { - switch (vec->Width()) { - case 2: { - Line(b) << "return unpackHalf2x16(packHalf2x16(" << v << "));"; - return; - } - case 3: { - Line(b) << "return vec3("; - Line(b) << " unpackHalf2x16(packHalf2x16(" << v << ".xy)),"; - Line(b) << " unpackHalf2x16(packHalf2x16(" << v << ".zz)).x);"; - return; - } - default: { - Line(b) << "return vec4("; - Line(b) << " unpackHalf2x16(packHalf2x16(" << v << ".xy)),"; - Line(b) << " unpackHalf2x16(packHalf2x16(" << v << ".zw)));"; - return; - } - } - } - Line(b) << "return unpackHalf2x16(packHalf2x16(vec2(" << v << "))).x;"; - }); -} - -void ASTPrinter::EmitBarrierCall(StringStream& out, const sem::BuiltinFn* builtin) { - // TODO(crbug.com/tint/661): Combine sequential barriers to a single - // instruction. - if (builtin->Fn() == wgsl::BuiltinFn::kWorkgroupBarrier) { - out << "barrier()"; - } else if (builtin->Fn() == wgsl::BuiltinFn::kStorageBarrier) { - out << "{ barrier(); memoryBarrierBuffer(); }"; - } else if (builtin->Fn() == wgsl::BuiltinFn::kTextureBarrier) { - out << "{ barrier(); memoryBarrierImage(); }"; - } else { - TINT_UNREACHABLE() << "unexpected barrier builtin type " << builtin->Fn(); - } -} - -const ast::Expression* ASTPrinter::CreateF32Zero(const sem::Statement* stmt) { - auto* zero = builder_.Expr(0_f); - auto* f32 = builder_.create(); - auto* sem_zero = builder_.create( - zero, f32, core::EvaluationStage::kRuntime, stmt, /* constant_value */ nullptr, - /* has_side_effects */ false); - builder_.Sem().Add(zero, sem_zero); - return zero; -} - -void ASTPrinter::EmitTextureCall(StringStream& out, - const sem::Call* call, - const sem::BuiltinFn* builtin) { - using Usage = core::ParameterUsage; - - auto& signature = builtin->Signature(); - auto* expr = call->Declaration(); - auto arguments = expr->args; - - // Returns the argument with the given usage - auto arg = [&](Usage usage) { - auto idx = signature.IndexOf(usage); - return (idx >= 0) ? arguments[static_cast(idx)] : nullptr; - }; - - auto* texture = arg(Usage::kTexture); - if (TINT_UNLIKELY(!texture)) { - TINT_ICE() << "missing texture argument"; - } - - auto* texture_type = TypeOf(texture)->UnwrapRef()->As(); - - auto emit_signed_int_type = [&](const core::type::Type* ty) { - uint32_t width = ty->Elements().count; - if (width > 1) { - out << "ivec" << width; - } else { - out << "int"; - } - }; - - auto emit_unsigned_int_type = [&](const core::type::Type* ty) { - uint32_t width = ty->Elements().count; - if (width > 1) { - out << "uvec" << width; - } else { - out << "uint"; - } - }; - - auto emit_expr_as_signed = [&](const ast::Expression* e) { - auto* ty = TypeOf(e)->UnwrapRef(); - if (!ty->is_unsigned_integer_scalar_or_vector()) { - EmitExpression(out, e); - return; - } - emit_signed_int_type(ty); - ScopedParen sp(out); - EmitExpression(out, e); - return; - }; - - switch (builtin->Fn()) { - case wgsl::BuiltinFn::kTextureDimensions: { - // textureDimensions() returns an unsigned scalar / vector in WGSL. - // textureSize() / imageSize() returns a signed scalar / vector in GLSL. - // Cast. - emit_unsigned_int_type(call->Type()); - ScopedParen sp(out); - - if (texture_type->Is()) { - out << "imageSize("; - } else { - out << "textureSize("; - } - EmitExpression(out, texture); - - // The LOD parameter is mandatory on textureSize() for non-multisampled - // textures. - if (!texture_type->Is() && - !texture_type->Is() && - !texture_type->Is()) { - out << ", "; - if (auto* level_arg = arg(Usage::kLevel)) { - emit_expr_as_signed(level_arg); - } else { - out << "0"; - } - } - out << ")"; - // textureSize() on array samplers returns the array size in the - // final component, so strip it out. - if (texture_type->dim() == core::type::TextureDimension::k2dArray || - texture_type->dim() == core::type::TextureDimension::kCubeArray) { - out << ".xy"; - } - return; - } - case wgsl::BuiltinFn::kTextureNumLayers: { - // textureNumLayers() returns an unsigned scalar in WGSL. - // textureSize() / imageSize() returns a signed scalar / vector in GLSL. - // Cast. - out << "uint"; - ScopedParen sp(out); - - if (texture_type->Is()) { - out << "imageSize("; - } else { - out << "textureSize("; - } - // textureSize() on sampler2dArray returns the array size in the - // final component, so return it - EmitExpression(out, texture); - - // The LOD parameter is mandatory on textureSize() for non-multisampled - // textures. - if (!texture_type->Is() && - !texture_type->Is() && - !texture_type->Is()) { - out << ", "; - if (auto* level_arg = arg(Usage::kLevel)) { - emit_expr_as_signed(level_arg); - } else { - out << "0"; - } - } - out << ").z"; - return; - } - case wgsl::BuiltinFn::kTextureNumLevels: { - // textureNumLevels() returns an unsigned scalar in WGSL. - // textureQueryLevels() returns a signed scalar in GLSL. - // Cast. - out << "uint"; - ScopedParen sp(out); - - out << "textureQueryLevels("; - EmitExpression(out, texture); - out << ")"; - return; - } - case wgsl::BuiltinFn::kTextureNumSamples: { - // textureNumSamples() returns an unsigned scalar in WGSL. - // textureSamples() returns a signed scalar in GLSL. - // Cast. - out << "uint"; - ScopedParen sp(out); - - out << "textureSamples("; - EmitExpression(out, texture); - out << ")"; - return; - } - default: - break; - } - - uint32_t glsl_ret_width = 4u; - bool append_depth_ref_to_coords = true; - bool is_depth = texture_type->Is(); - - switch (builtin->Fn()) { - case wgsl::BuiltinFn::kTextureSample: - case wgsl::BuiltinFn::kTextureSampleBias: - out << "texture"; - if (is_depth) { - glsl_ret_width = 1u; - } - break; - case wgsl::BuiltinFn::kTextureSampleLevel: - out << "textureLod"; - if (is_depth) { - glsl_ret_width = 1u; - } - break; - case wgsl::BuiltinFn::kTextureGather: - case wgsl::BuiltinFn::kTextureGatherCompare: - out << "textureGather"; - append_depth_ref_to_coords = false; - break; - case wgsl::BuiltinFn::kTextureSampleGrad: - out << "textureGrad"; - break; - case wgsl::BuiltinFn::kTextureSampleCompare: - case wgsl::BuiltinFn::kTextureSampleCompareLevel: - out << "texture"; - glsl_ret_width = 1; - break; - case wgsl::BuiltinFn::kTextureLoad: - if (texture_type->Is()) { - out << "imageLoad"; - } else { - out << "texelFetch"; - } - break; - case wgsl::BuiltinFn::kTextureStore: - out << "imageStore"; - break; - default: - TINT_ICE() << "Unhandled texture builtin '" << std::string(builtin->str()) << "'"; - } - - if (builtin->Signature().IndexOf(core::ParameterUsage::kOffset) >= 0) { - out << "Offset"; - } - - out << "("; - EmitExpression(out, texture); - out << ", "; - - auto* param_coords = arg(Usage::kCoords); - if (TINT_UNLIKELY(!param_coords)) { - TINT_ICE() << "missing coords argument"; - } - - if (auto* array_index = arg(Usage::kArrayIndex)) { - // Array index needs to be appended to the coordinates. - param_coords = - tint::wgsl::AppendVector(&builder_, param_coords, array_index)->Declaration(); - } - - // GLSL requires Dref to be appended to the coordinates, *unless* it's - // samplerCubeArrayShadow, in which case it will be handled as a separate - // parameter. - if (texture_type->dim() == core::type::TextureDimension::kCubeArray) { - append_depth_ref_to_coords = false; - } - - if (is_depth && append_depth_ref_to_coords) { - auto* depth_ref = arg(Usage::kDepthRef); - if (!depth_ref) { - // Sampling a depth texture in GLSL always requires a depth reference, so - // append zero here. - depth_ref = CreateF32Zero(builder_.Sem().Get(param_coords)->Stmt()); - } - param_coords = tint::wgsl::AppendVector(&builder_, param_coords, depth_ref)->Declaration(); - } - - emit_expr_as_signed(param_coords); - - for (auto usage : {Usage::kLevel, Usage::kDdx, Usage::kDdy, Usage::kSampleIndex}) { - if (auto* e = arg(usage)) { - out << ", "; - if (usage == Usage::kLevel && is_depth) { - // WGSL's textureSampleLevel() "level" param is i32 for depth textures, - // whereas GLSL's textureLod() "lod" param is always float, so cast it. - out << "float("; - EmitExpression(out, e); - out << ")"; - } else { - emit_expr_as_signed(e); - } - } - } - - if (auto* e = arg(Usage::kValue)) { - out << ", "; - EmitExpression(out, e); - } - - // GLSL's textureGather always requires a refZ parameter. - if (is_depth && builtin->Fn() == wgsl::BuiltinFn::kTextureGather) { - out << ", 0.0"; - } - - // [1] samplerCubeArrayShadow requires a separate depthRef parameter - if (is_depth && !append_depth_ref_to_coords) { - if (auto* e = arg(Usage::kDepthRef)) { - out << ", "; - EmitExpression(out, e); - } else if (builtin->Fn() == wgsl::BuiltinFn::kTextureSample) { - out << ", 0.0f"; - } - } - - for (auto usage : {Usage::kOffset, Usage::kComponent, Usage::kBias}) { - if (auto* e = arg(usage)) { - out << ", "; - emit_expr_as_signed(e); - } - } - - out << ")"; - - if (builtin->ReturnType()->Is()) { - return; - } - // If the builtin return type does not match the number of elements of the - // GLSL builtin, we need to swizzle the expression to generate the correct - // number of components. - uint32_t wgsl_ret_width = 1; - if (auto* vec = builtin->ReturnType()->As()) { - wgsl_ret_width = vec->Width(); - } - if (wgsl_ret_width < glsl_ret_width) { - out << "."; - for (uint32_t i = 0; i < wgsl_ret_width; i++) { - out << "xyz"[i]; - } - } - if (TINT_UNLIKELY(wgsl_ret_width > glsl_ret_width)) { - TINT_ICE() << "WGSL return width (" << wgsl_ret_width - << ") is wider than GLSL return width (" << glsl_ret_width << ") for " - << builtin->Fn(); - } -} - -std::string ASTPrinter::generate_builtin_name(const sem::BuiltinFn* builtin) { - switch (builtin->Fn()) { - case wgsl::BuiltinFn::kAbs: - case wgsl::BuiltinFn::kAcos: - case wgsl::BuiltinFn::kAcosh: - case wgsl::BuiltinFn::kAll: - case wgsl::BuiltinFn::kAny: - case wgsl::BuiltinFn::kAsin: - case wgsl::BuiltinFn::kAsinh: - case wgsl::BuiltinFn::kAtan: - case wgsl::BuiltinFn::kAtanh: - case wgsl::BuiltinFn::kCeil: - case wgsl::BuiltinFn::kClamp: - case wgsl::BuiltinFn::kCos: - case wgsl::BuiltinFn::kCosh: - case wgsl::BuiltinFn::kCross: - case wgsl::BuiltinFn::kDeterminant: - case wgsl::BuiltinFn::kDistance: - case wgsl::BuiltinFn::kDot: - case wgsl::BuiltinFn::kExp: - case wgsl::BuiltinFn::kExp2: - case wgsl::BuiltinFn::kFloor: - case wgsl::BuiltinFn::kFrexp: - case wgsl::BuiltinFn::kLdexp: - case wgsl::BuiltinFn::kLength: - case wgsl::BuiltinFn::kLog: - case wgsl::BuiltinFn::kLog2: - case wgsl::BuiltinFn::kMax: - case wgsl::BuiltinFn::kMin: - case wgsl::BuiltinFn::kModf: - case wgsl::BuiltinFn::kNormalize: - case wgsl::BuiltinFn::kPow: - case wgsl::BuiltinFn::kReflect: - case wgsl::BuiltinFn::kRefract: - case wgsl::BuiltinFn::kRound: - case wgsl::BuiltinFn::kSign: - case wgsl::BuiltinFn::kSin: - case wgsl::BuiltinFn::kSinh: - case wgsl::BuiltinFn::kSqrt: - case wgsl::BuiltinFn::kStep: - case wgsl::BuiltinFn::kTan: - case wgsl::BuiltinFn::kTanh: - case wgsl::BuiltinFn::kTranspose: - case wgsl::BuiltinFn::kTrunc: - return builtin->str(); - case wgsl::BuiltinFn::kAtan2: - return "atan"; - case wgsl::BuiltinFn::kCountOneBits: - return "bitCount"; - case wgsl::BuiltinFn::kDpdx: - return "dFdx"; - case wgsl::BuiltinFn::kDpdxCoarse: - if (version_.IsES()) { - return "dFdx"; - } - return "dFdxCoarse"; - case wgsl::BuiltinFn::kDpdxFine: - if (version_.IsES()) { - return "dFdx"; - } - return "dFdxFine"; - case wgsl::BuiltinFn::kDpdy: - return "dFdy"; - case wgsl::BuiltinFn::kDpdyCoarse: - if (version_.IsES()) { - return "dFdy"; - } - return "dFdyCoarse"; - case wgsl::BuiltinFn::kDpdyFine: - if (version_.IsES()) { - return "dFdy"; - } - return "dFdyFine"; - case wgsl::BuiltinFn::kFaceForward: - return "faceforward"; - case wgsl::BuiltinFn::kFract: - return "fract"; - case wgsl::BuiltinFn::kFma: - return "fma"; - case wgsl::BuiltinFn::kFwidth: - case wgsl::BuiltinFn::kFwidthCoarse: - case wgsl::BuiltinFn::kFwidthFine: - return "fwidth"; - case wgsl::BuiltinFn::kInverseSqrt: - return "inversesqrt"; - case wgsl::BuiltinFn::kMix: - return "mix"; - case wgsl::BuiltinFn::kPack2X16Float: - return "packHalf2x16"; - case wgsl::BuiltinFn::kPack2X16Snorm: - return "packSnorm2x16"; - case wgsl::BuiltinFn::kPack2X16Unorm: - return "packUnorm2x16"; - case wgsl::BuiltinFn::kPack4X8Snorm: - return "packSnorm4x8"; - case wgsl::BuiltinFn::kPack4X8Unorm: - return "packUnorm4x8"; - case wgsl::BuiltinFn::kReverseBits: - return "bitfieldReverse"; - case wgsl::BuiltinFn::kSmoothstep: - return "smoothstep"; - case wgsl::BuiltinFn::kUnpack2X16Float: - return "unpackHalf2x16"; - case wgsl::BuiltinFn::kUnpack2X16Snorm: - return "unpackSnorm2x16"; - case wgsl::BuiltinFn::kUnpack2X16Unorm: - return "unpackUnorm2x16"; - case wgsl::BuiltinFn::kUnpack4X8Snorm: - return "unpackSnorm4x8"; - case wgsl::BuiltinFn::kUnpack4X8Unorm: - return "unpackUnorm4x8"; - default: - diagnostics_.AddError(Source{}) << "Unknown builtin method: " << builtin; - } - - return ""; -} - -void ASTPrinter::EmitCase(const ast::CaseStatement* stmt) { - auto* sem = builder_.Sem().Get(stmt); - for (auto* selector : sem->Selectors()) { - auto out = Line(); - - if (selector->IsDefault()) { - out << "default"; - } else { - out << "case "; - EmitConstant(out, selector->Value()); - } - out << ":"; - if (selector == sem->Selectors().back()) { - out << " {"; - } - } - - { - ScopedIndent si(this); - EmitStatements(stmt->body->statements); - if (!last_is_break(stmt->body)) { - Line() << "break;"; - } - } - - Line() << "}"; -} - -void ASTPrinter::EmitContinue(const ast::ContinueStatement*) { - if (emit_continuing_) { - emit_continuing_(); - } - Line() << "continue;"; -} - -void ASTPrinter::EmitDiscard(const ast::DiscardStatement*) { - // TODO(dsinclair): Verify this is correct when the discard semantics are - // defined for WGSL (https://github.com/gpuweb/gpuweb/issues/361) - Line() << "discard;"; -} - -void ASTPrinter::EmitExpression(StringStream& out, const ast::Expression* expr) { - if (auto* sem = builder_.Sem().GetVal(expr)) { - if (auto* constant = sem->ConstantValue()) { - EmitConstant(out, constant); - return; - } - } - Switch( - expr, // - [&](const ast::IndexAccessorExpression* a) { EmitIndexAccessor(out, a); }, - [&](const ast::BinaryExpression* b) { EmitBinary(out, b); }, - [&](const ast::CallExpression* c) { EmitCall(out, c); }, - [&](const ast::IdentifierExpression* i) { EmitIdentifier(out, i); }, - [&](const ast::LiteralExpression* l) { EmitLiteral(out, l); }, - [&](const ast::MemberAccessorExpression* m) { EmitMemberAccessor(out, m); }, - [&](const ast::UnaryOpExpression* u) { EmitUnaryOp(out, u); }, // - TINT_ICE_ON_NO_MATCH); -} - -void ASTPrinter::EmitIdentifier(StringStream& out, const ast::IdentifierExpression* expr) { - out << expr->identifier->symbol.Name(); -} - -void ASTPrinter::EmitIf(const ast::IfStatement* stmt) { - { - auto out = Line(); - out << "if ("; - EmitExpression(out, stmt->condition); - out << ") {"; - } - EmitStatementsWithIndent(stmt->body->statements); - - if (stmt->else_statement) { - Line() << "} else {"; - if (auto* block = stmt->else_statement->As()) { - EmitStatementsWithIndent(block->statements); - } else { - EmitStatementsWithIndent(Vector{stmt->else_statement}); - } - } - Line() << "}"; -} - -void ASTPrinter::EmitFunction(const ast::Function* func) { - auto* sem = builder_.Sem().Get(func); - - if (ast::HasAttribute(func->attributes)) { - // An internal function. Do not emit. - return; - } - - { - auto out = Line(); - auto name = func->name->symbol.Name(); - EmitType(out, sem->ReturnType(), core::AddressSpace::kUndefined, core::Access::kReadWrite, - ""); - out << " " << name << "("; - - bool first = true; - for (auto* v : sem->Parameters()) { - if (!first) { - out << ", "; - } - first = false; - - auto const* type = v->Type(); - - if (auto* ptr = type->As()) { - // 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 AddressSpace::kUndefined on parameters, however - // the sanitizer transforms generates load / store functions for storage - // or uniform buffers. These functions have a buffer parameter with - // AddressSpace::kStorage or AddressSpace::kUniform. This is required to - // correctly translate the parameter to a [RW]ByteAddressBuffer for - // storage buffers and a uint4[N] for uniform buffers. - EmitTypeAndName(out, type, v->AddressSpace(), v->Access(), - v->Declaration()->name->symbol.Name()); - } - out << ") {"; - } - - EmitStatementsWithIndent(func->body->statements); - - Line() << "}"; - Line(); -} - -void ASTPrinter::EmitGlobalVariable(const ast::Variable* global) { - Switch( - global, // - [&](const ast::Var* var) { - auto* sem = builder_.Sem().Get(global); - switch (sem->AddressSpace()) { - case core::AddressSpace::kUniform: - EmitUniformVariable(var, sem); - return; - case core::AddressSpace::kStorage: - EmitStorageVariable(var, sem); - return; - case core::AddressSpace::kHandle: - EmitHandleVariable(var, sem); - return; - case core::AddressSpace::kPrivate: - EmitPrivateVariable(sem); - return; - case core::AddressSpace::kWorkgroup: - EmitWorkgroupVariable(sem); - return; - case core::AddressSpace::kIn: - case core::AddressSpace::kOut: - EmitIOVariable(sem); - return; - case core::AddressSpace::kPushConstant: - EmitPushConstant(sem); - return; - default: { - TINT_ICE() << "unhandled address space " << sem->AddressSpace(); - } - } - }, - [&](const ast::Let* let) { EmitProgramConstVariable(let); }, - [&](const ast::Override*) { - // Override is removed with SubstituteOverride - diagnostics_.AddError(Source{}) - << "override-expressions should have been removed with the " - "SubstituteOverride transform"; - }, - [&](const ast::Const*) { - // Constants are embedded at their use - }, // - TINT_ICE_ON_NO_MATCH); -} - -void ASTPrinter::EmitUniformVariable(const ast::Var* var, const sem::Variable* sem) { - auto* type = sem->Type()->UnwrapRef(); - auto* str = type->As(); - if (TINT_UNLIKELY(!str)) { - TINT_ICE() << "storage variable must be of struct type"; - } - auto bp = *sem->As()->Attributes().binding_point; - { - auto out = Line(); - out << "layout(binding = " << bp.binding << ", std140"; - out << ") uniform " << UniqueIdentifier(StructName(str) + "_ubo") << " {"; - } - EmitStructMembers(current_buffer_, str); - auto name = var->name->symbol.Name(); - Line() << "} " << name << ";"; - Line(); -} - -void ASTPrinter::EmitStorageVariable(const ast::Var* var, const sem::Variable* sem) { - auto* type = sem->Type()->UnwrapRef(); - auto* str = type->As(); - if (TINT_UNLIKELY(!str)) { - TINT_ICE() << "storage variable must be of struct type"; - } - auto bp = *sem->As()->Attributes().binding_point; - Line() << "layout(binding = " << bp.binding << ", std430) buffer " - << UniqueIdentifier(StructName(str) + "_ssbo") << " {"; - EmitStructMembers(current_buffer_, str); - auto name = var->name->symbol.Name(); - Line() << "} " << name << ";"; - Line(); -} - -void ASTPrinter::EmitHandleVariable(const ast::Var* var, const sem::Variable* sem) { - auto out = Line(); - - auto name = var->name->symbol.Name(); - auto* type = sem->Type()->UnwrapRef(); - if (type->Is()) { - // GLSL ignores Sampler variables. - return; - } - - if (auto* storage = type->As()) { - out << "layout("; - switch (storage->texel_format()) { - case core::TexelFormat::kBgra8Unorm: - TINT_ICE() << "bgra8unorm should have been polyfilled to rgba8unorm"; - case core::TexelFormat::kR32Uint: - out << "r32ui"; - break; - case core::TexelFormat::kR32Sint: - out << "r32i"; - break; - case core::TexelFormat::kR32Float: - out << "r32f"; - break; - case core::TexelFormat::kRgba8Unorm: - out << "rgba8"; - break; - case core::TexelFormat::kRgba8Snorm: - out << "rgba8_snorm"; - break; - case core::TexelFormat::kRgba8Uint: - out << "rgba8ui"; - break; - case core::TexelFormat::kRgba8Sint: - out << "rgba8i"; - break; - case core::TexelFormat::kRg32Uint: - out << "rg32ui"; - break; - case core::TexelFormat::kRg32Sint: - out << "rg32i"; - break; - case core::TexelFormat::kRg32Float: - out << "rg32f"; - break; - case core::TexelFormat::kRgba16Uint: - out << "rgba16ui"; - break; - case core::TexelFormat::kRgba16Sint: - out << "rgba16i"; - break; - case core::TexelFormat::kRgba16Float: - out << "rgba16f"; - break; - case core::TexelFormat::kRgba32Uint: - out << "rgba32ui"; - break; - case core::TexelFormat::kRgba32Sint: - out << "rgba32i"; - break; - case core::TexelFormat::kRgba32Float: - out << "rgba32f"; - break; - case core::TexelFormat::kR8Unorm: - out << "r8"; - break; - case core::TexelFormat::kUndefined: - TINT_ICE() << "invalid texel format"; - } - out << ") "; - } - EmitTypeAndName(out, type, sem->AddressSpace(), sem->Access(), name); - out << ";"; -} - -void ASTPrinter::EmitPrivateVariable(const sem::Variable* var) { - auto* decl = var->Declaration(); - auto out = Line(); - - auto name = decl->name->symbol.Name(); - auto* type = var->Type()->UnwrapRef(); - EmitTypeAndName(out, type, var->AddressSpace(), var->Access(), name); - - out << " = "; - if (auto* initializer = decl->initializer) { - EmitExpression(out, initializer); - } else { - EmitZeroValue(out, var->Type()->UnwrapRef()); - } - out << ";"; -} - -void ASTPrinter::EmitWorkgroupVariable(const sem::Variable* var) { - auto* decl = var->Declaration(); - auto out = Line(); - - out << "shared "; - - auto name = decl->name->symbol.Name(); - auto* type = var->Type()->UnwrapRef(); - EmitTypeAndName(out, type, var->AddressSpace(), var->Access(), name); - - if (auto* initializer = decl->initializer) { - out << " = "; - EmitExpression(out, initializer); - } - - out << ";"; -} - -void ASTPrinter::EmitIOVariable(const sem::GlobalVariable* var) { - auto* decl = var->Declaration(); - - if (auto* attr = ast::GetAttribute(decl->attributes)) { - auto builtin = builder_.Sem().Get(attr)->Value(); - // Use of gl_SampleID requires the GL_OES_sample_variables extension - if (RequiresOESSampleVariables(builtin)) { - requires_oes_sample_variables_ = true; - } - // Do not emit builtin (gl_) variables. - return; - } - - auto out = Line(); - EmitAttributes(out, var); - EmitInterpolationQualifiers(out, decl->attributes); - - auto name = decl->name->symbol.Name(); - auto* type = var->Type()->UnwrapRef(); - EmitTypeAndName(out, type, var->AddressSpace(), var->Access(), name); - - if (auto* initializer = decl->initializer) { - out << " = "; - EmitExpression(out, initializer); - } - out << ";"; -} - -void ASTPrinter::EmitPushConstant(const sem::GlobalVariable* var) { - auto* decl = var->Declaration(); - - auto out = Line(); - - auto name = decl->name->symbol.Name(); - auto* type = var->Type()->UnwrapRef(); - out << "layout(location=0) "; - EmitTypeAndName(out, type, var->AddressSpace(), var->Access(), name); - out << ";"; -} - -void ASTPrinter::EmitInterpolationQualifiers(StringStream& out, - VectorRef attributes) { - for (auto* attr : attributes) { - if (auto* interpolate = attr->As()) { - auto& sem = builder_.Sem(); - auto i_type = - sem.Get>(interpolate->type) - ->Value(); - switch (i_type) { - case core::InterpolationType::kPerspective: - case core::InterpolationType::kLinear: - case core::InterpolationType::kUndefined: - break; - case core::InterpolationType::kFlat: - out << "flat "; - break; - } - - if (interpolate->sampling) { - auto i_smpl = sem.Get>( - interpolate->sampling) - ->Value(); - switch (i_smpl) { - case core::InterpolationSampling::kCentroid: - out << "centroid "; - break; - case core::InterpolationSampling::kSample: - case core::InterpolationSampling::kCenter: - case core::InterpolationSampling::kUndefined: - break; - } - } - } - } -} - -void ASTPrinter::EmitAttributes(StringStream& out, const sem::GlobalVariable* var) { - auto& attrs = var->Attributes(); - - bool first = true; - if (attrs.location.has_value()) { - out << (first ? "layout(" : ", "); - out << "location = " << std::to_string(attrs.location.value()); - first = false; - } - if (attrs.index.has_value()) { - out << ", index = " << std::to_string(attrs.index.value()); - } - if (!first) { - out << ") "; - } -} - -void ASTPrinter::EmitEntryPointFunction(const ast::Function* func) { - auto* func_sem = builder_.Sem().Get(func); - - if (func->PipelineStage() == ast::PipelineStage::kFragment) { - requires_default_precision_qualifier_ = true; - } - - if (func->PipelineStage() == ast::PipelineStage::kCompute) { - auto out = Line(); - // Emit the layout(local_size) attributes. - auto wgsize = func_sem->WorkgroupSize(); - out << "layout("; - for (size_t i = 0; i < 3; i++) { - if (i > 0) { - out << ", "; - } - out << "local_size_" << (i == 0 ? "x" : i == 1 ? "y" : "z") << " = "; - - if (!wgsize[i].has_value()) { - diagnostics_.AddError(Source{}) - << "override-expressions should have been removed with the SubstituteOverride " - "transform"; - return; - } - out << std::to_string(wgsize[i].value()); - } - out << ") in;"; - } - - // Emit original entry point signature - { - auto out = Line(); - EmitTypeAndName(out, func_sem->ReturnType(), core::AddressSpace::kUndefined, - core::Access::kUndefined, func->name->symbol.Name()); - out << "("; - - bool first = true; - - // Emit entry point parameters. - for (auto* var : func->params) { - auto* sem = builder_.Sem().Get(var); - auto* type = sem->Type(); - if (TINT_UNLIKELY(!type->Is())) { - // ICE likely indicates that the CanonicalizeEntryPointIO transform was - // not run, or a builtin parameter was added after it was run. - TINT_ICE() << "Unsupported non-struct entry point parameter"; - } - - if (!first) { - out << ", "; - } - first = false; - - EmitTypeAndName(out, type, sem->AddressSpace(), sem->Access(), - var->name->symbol.Name()); - } - - out << ") {"; - } - - // Emit original entry point function body - { - ScopedIndent si(this); - if (func->PipelineStage() == ast::PipelineStage::kVertex) { - Line() << "gl_PointSize = 1.0;"; - } - - EmitStatements(func->body->statements); - - if (!Is(func->body->Last())) { - ast::ReturnStatement ret(GenerationID{}, ast::NodeID{}, Source{}); - EmitStatement(&ret); - } - } - - Line() << "}"; -} - -void ASTPrinter::EmitConstant(StringStream& out, const core::constant::Value* constant) { - Switch( - constant->Type(), // - [&](const core::type::Bool*) { out << (constant->ValueAs() ? "true" : "false"); }, - [&](const core::type::F32*) { PrintF32(out, constant->ValueAs()); }, - [&](const core::type::F16*) { PrintF16(out, constant->ValueAs()); }, - [&](const core::type::I32*) { PrintI32(out, constant->ValueAs()); }, - [&](const core::type::U32*) { out << constant->ValueAs() << "u"; }, - [&](const core::type::Vector* v) { - EmitType(out, v, core::AddressSpace::kUndefined, core::Access::kUndefined, ""); - - ScopedParen sp(out); - - if (auto* splat = constant->As()) { - EmitConstant(out, splat->el); - return; - } - - for (size_t i = 0; i < v->Width(); i++) { - if (i > 0) { - out << ", "; - } - EmitConstant(out, constant->Index(i)); - } - }, - [&](const core::type::Matrix* m) { - EmitType(out, m, core::AddressSpace::kUndefined, core::Access::kUndefined, ""); - - ScopedParen sp(out); - - for (size_t column_idx = 0; column_idx < m->columns(); column_idx++) { - if (column_idx > 0) { - out << ", "; - } - EmitConstant(out, constant->Index(column_idx)); - } - }, - [&](const core::type::Array* a) { - EmitType(out, a, core::AddressSpace::kUndefined, core::Access::kUndefined, ""); - - ScopedParen sp(out); - - auto count = a->ConstantCount(); - if (!count) { - diagnostics_.AddError(Source{}) << core::type::Array::kErrExpectedConstantCount; - return; - } - - for (size_t i = 0; i < count; i++) { - if (i > 0) { - out << ", "; - } - EmitConstant(out, constant->Index(i)); - } - }, - [&](const core::type::Struct* s) { - EmitStructType(&helpers_, s); - - out << StructName(s); - - ScopedParen sp(out); - - for (size_t i = 0; i < s->Members().Length(); i++) { - if (i > 0) { - out << ", "; - } - EmitConstant(out, constant->Index(i)); - } - }, // - TINT_ICE_ON_NO_MATCH); -} - -void ASTPrinter::EmitLiteral(StringStream& out, const ast::LiteralExpression* lit) { - Switch( - lit, // - [&](const ast::BoolLiteralExpression* l) { out << (l->value ? "true" : "false"); }, - [&](const ast::FloatLiteralExpression* l) { - if (l->suffix == ast::FloatLiteralExpression::Suffix::kH) { - PrintF16(out, static_cast(l->value)); - } else { - PrintF32(out, static_cast(l->value)); - } - }, - [&](const ast::IntLiteralExpression* i) { - switch (i->suffix) { - case ast::IntLiteralExpression::Suffix::kNone: - case ast::IntLiteralExpression::Suffix::kI: { - PrintI32(out, static_cast(i->value)); - return; - } - case ast::IntLiteralExpression::Suffix::kU: { - out << i->value << "u"; - return; - } - } - diagnostics_.AddError(Source{}) << "unknown integer literal suffix type"; - }, // - TINT_ICE_ON_NO_MATCH); -} - -void ASTPrinter::EmitZeroValue(StringStream& out, const core::type::Type* type) { - if (type->Is()) { - out << "false"; - } else if (type->Is()) { - out << "0.0f"; - } else if (type->Is()) { - out << "0.0hf"; - } else if (type->Is()) { - out << "0"; - } else if (type->Is()) { - out << "0u"; - } else if (auto* vec = type->As()) { - EmitType(out, type, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); - ScopedParen sp(out); - for (uint32_t i = 0; i < vec->Width(); i++) { - if (i != 0) { - out << ", "; - } - EmitZeroValue(out, vec->type()); - } - } else if (auto* mat = type->As()) { - EmitType(out, type, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); - ScopedParen sp(out); - for (uint32_t i = 0; i < (mat->rows() * mat->columns()); i++) { - if (i != 0) { - out << ", "; - } - EmitZeroValue(out, mat->type()); - } - } else if (auto* str = type->As()) { - EmitType(out, type, core::AddressSpace::kUndefined, core::Access::kUndefined, ""); - bool first = true; - ScopedParen sp(out); - for (auto* member : str->Members()) { - if (!first) { - out << ", "; - } else { - first = false; - } - EmitZeroValue(out, member->Type()); - } - } else if (auto* arr = type->As()) { - EmitType(out, type, core::AddressSpace::kUndefined, core::Access::kUndefined, ""); - ScopedParen sp(out); - - auto count = arr->ConstantCount(); - if (!count) { - diagnostics_.AddError(Source{}) << core::type::Array::kErrExpectedConstantCount; - return; - } - - for (uint32_t i = 0; i < count; i++) { - if (i != 0) { - out << ", "; - } - EmitZeroValue(out, arr->ElemType()); - } - } else { - diagnostics_.AddError(Source{}) - << "Invalid type for zero emission: " << type->FriendlyName(); - } -} - -void ASTPrinter::EmitLoop(const ast::LoopStatement* stmt) { - auto emit_continuing = [this, stmt] { - if (stmt->continuing && !stmt->continuing->Empty()) { - EmitBlock(stmt->continuing); - } - }; - - TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing); - Line() << "while (true) {"; - { - ScopedIndent si(this); - EmitStatements(stmt->body->statements); - emit_continuing_(); - } - Line() << "}"; -} - -void ASTPrinter::EmitForLoop(const ast::ForLoopStatement* stmt) { - // Nest a for loop with a new block. In HLSL the initializer scope is not - // nested by the for-loop, so we may get variable redefinitions. - Line() << "{"; - IncrementIndent(); - TINT_DEFER({ - DecrementIndent(); - Line() << "}"; - }); - - TextBuffer init_buf; - if (auto* init = stmt->initializer) { - TINT_SCOPED_ASSIGNMENT(current_buffer_, &init_buf); - EmitStatement(init); - } - - TextBuffer cond_pre; - StringStream cond_buf; - if (auto* cond = stmt->condition) { - TINT_SCOPED_ASSIGNMENT(current_buffer_, &cond_pre); - EmitExpression(cond_buf, cond); - } - - TextBuffer cont_buf; - if (auto* cont = stmt->continuing) { - TINT_SCOPED_ASSIGNMENT(current_buffer_, &cont_buf); - EmitStatement(cont); - } - - // If the for-loop has a multi-statement conditional and / or continuing, then - // we cannot emit this as a regular for-loop in HLSL. Instead we need to - // generate a `while(true)` loop. - bool emit_as_loop = cond_pre.lines.size() > 0 || cont_buf.lines.size() > 1; - - // If the for-loop has multi-statement initializer, or is going to be emitted - // as a `while(true)` loop, then declare the initializer statement(s) before - // the loop. - if (init_buf.lines.size() > 1 || (stmt->initializer && emit_as_loop)) { - current_buffer_->Append(init_buf); - init_buf.lines.clear(); // Don't emit the initializer again in the 'for' - } - - if (emit_as_loop) { - auto emit_continuing = [&] { current_buffer_->Append(cont_buf); }; - - TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing); - Line() << "while (true) {"; - IncrementIndent(); - TINT_DEFER({ - DecrementIndent(); - Line() << "}"; - }); - - if (stmt->condition) { - current_buffer_->Append(cond_pre); - Line() << "if (!(" << cond_buf.str() << ")) { break; }"; - } - - EmitStatements(stmt->body->statements); - emit_continuing_(); - } else { - // For-loop can be generated. - { - auto out = Line(); - out << "for"; - { - ScopedParen sp(out); - - if (!init_buf.lines.empty()) { - out << init_buf.lines[0].content << " "; - } else { - out << "; "; - } - - out << cond_buf.str() << "; "; - - if (!cont_buf.lines.empty()) { - out << tint::TrimSuffix(cont_buf.lines[0].content, ";"); - } - } - out << " {"; - } - { - auto emit_continuing = [] { return true; }; - TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing); - EmitStatementsWithIndent(stmt->body->statements); - } - Line() << "}"; - } -} - -void ASTPrinter::EmitWhile(const ast::WhileStatement* stmt) { - TextBuffer cond_pre; - StringStream cond_buf; - { - auto* cond = stmt->condition; - TINT_SCOPED_ASSIGNMENT(current_buffer_, &cond_pre); - EmitExpression(cond_buf, cond); - } - - auto emit_continuing = [&] {}; - TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing); - - // If the whilehas a multi-statement conditional, then we cannot emit this - // as a regular while in GLSL. Instead we need to generate a `while(true)` loop. - bool emit_as_loop = cond_pre.lines.size() > 0; - if (emit_as_loop) { - Line() << "while (true) {"; - IncrementIndent(); - TINT_DEFER({ - DecrementIndent(); - Line() << "}"; - }); - - current_buffer_->Append(cond_pre); - Line() << "if (!(" << cond_buf.str() << ")) { break; }"; - - EmitStatements(stmt->body->statements); - } else { - // While can be generated. - { - auto out = Line(); - out << "while"; - { - ScopedParen sp(out); - out << cond_buf.str(); - } - out << " {"; - } - EmitStatementsWithIndent(stmt->body->statements); - Line() << "}"; - } -} - -void ASTPrinter::EmitMemberAccessor(StringStream& out, const ast::MemberAccessorExpression* expr) { - EmitExpression(out, expr->object); - out << "."; - - auto* sem = builder_.Sem().Get(expr)->UnwrapLoad(); - - Switch( - sem, - [&](const sem::Swizzle*) { - // Swizzles output the name directly - out << expr->member->symbol.Name(); - }, - [&](const sem::StructMemberAccess* member_access) { - out << member_access->Member()->Name().Name(); - }, // - TINT_ICE_ON_NO_MATCH); -} - -void ASTPrinter::EmitReturn(const ast::ReturnStatement* stmt) { - if (stmt->value) { - auto out = Line(); - out << "return "; - EmitExpression(out, stmt->value); - out << ";"; - } else { - Line() << "return;"; - } -} - -void ASTPrinter::EmitStatement(const ast::Statement* stmt) { - Switch( - stmt, // - [&](const ast::AssignmentStatement* a) { EmitAssign(a); }, - [&](const ast::BlockStatement* b) { EmitBlock(b); }, - [&](const ast::BreakStatement* b) { EmitBreak(b); }, - [&](const ast::BreakIfStatement* b) { EmitBreakIf(b); }, - [&](const ast::CallStatement* c) { - auto out = Line(); - EmitCall(out, c->expr); - out << ";"; - }, - [&](const ast::ContinueStatement* c) { EmitContinue(c); }, - [&](const ast::DiscardStatement* d) { EmitDiscard(d); }, - [&](const ast::IfStatement* i) { EmitIf(i); }, - [&](const ast::LoopStatement* l) { EmitLoop(l); }, - [&](const ast::ForLoopStatement* l) { EmitForLoop(l); }, - [&](const ast::WhileStatement* l) { EmitWhile(l); }, - [&](const ast::ReturnStatement* r) { EmitReturn(r); }, - [&](const ast::SwitchStatement* s) { EmitSwitch(s); }, - [&](const ast::VariableDeclStatement* v) { - Switch( - v->variable, // - [&](const ast::Var* var) { EmitVar(var); }, - [&](const ast::Let* let) { EmitLet(let); }, - [&](const ast::Const*) { - // Constants are embedded at their use - }, // - TINT_ICE_ON_NO_MATCH); - }, - [&](const ast::ConstAssert*) { - // Not emitted - }, // - TINT_ICE_ON_NO_MATCH); -} - -void ASTPrinter::EmitSwitch(const ast::SwitchStatement* stmt) { - { // switch(expr) { - auto out = Line(); - out << "switch("; - EmitExpression(out, stmt->condition); - out << ") {"; - } - - { - ScopedIndent si(this); - for (auto* s : stmt->body) { - EmitCase(s); - } - } - - Line() << "}"; -} - -void ASTPrinter::EmitType(StringStream& out, - const core::type::Type* type, - core::AddressSpace address_space, - core::Access access, - const std::string& name, - bool* name_printed /* = nullptr */) { - if (name_printed) { - *name_printed = false; - } - switch (address_space) { - case core::AddressSpace::kIn: { - out << "in "; - break; - } - case core::AddressSpace::kOut: { - out << "out "; - break; - } - case core::AddressSpace::kUniform: - case core::AddressSpace::kPushConstant: - case core::AddressSpace::kHandle: { - out << "uniform "; - break; - } - default: - break; - } - - if (auto* ary = type->As()) { - const core::type::Type* base_type = ary; - std::vector sizes; - while (auto* arr = base_type->As()) { - if (arr->Count()->Is()) { - sizes.push_back(0); - } else { - auto count = arr->ConstantCount(); - if (!count) { - diagnostics_.AddError(Source{}) << core::type::Array::kErrExpectedConstantCount; - return; - } - sizes.push_back(count.value()); - } - - base_type = arr->ElemType(); - } - EmitType(out, base_type, address_space, access, ""); - if (!name.empty()) { - out << " " << name; - if (name_printed) { - *name_printed = true; - } - } - for (uint32_t size : sizes) { - if (size > 0) { - out << "[" << size << "]"; - } else { - out << "[]"; - } - } - } else if (type->Is()) { - out << "bool"; - } else if (type->Is()) { - out << "float"; - } else if (type->Is()) { - out << "float16_t"; - } else if (type->Is()) { - out << "int"; - } else if (auto* mat = type->As()) { - TINT_ASSERT((mat->type()->IsAnyOf())); - if (mat->type()->Is()) { - out << "f16"; - } - out << "mat" << mat->columns(); - if (mat->rows() != mat->columns()) { - out << "x" << mat->rows(); - } - } else if (TINT_UNLIKELY(type->Is())) { - TINT_ICE() << "Attempting to emit pointer type. These should have been removed with the " - "SimplifyPointers transform"; - } else if (type->Is()) { - } else if (auto* str = type->As()) { - out << StructName(str); - } else if (auto* tex = type->As()) { - if (TINT_UNLIKELY(tex->Is())) { - TINT_ICE() << "Multiplanar external texture transform was not run."; - } - - auto* storage = tex->As(); - auto* ms = tex->As(); - auto* depth_ms = tex->As(); - auto* sampled = tex->As(); - - out << "highp "; - - if (storage) { - switch (storage->access()) { - case core::Access::kRead: - out << "readonly "; - break; - case core::Access::kWrite: - out << "writeonly "; - break; - case core::Access::kReadWrite: { - // ESSL 3.1 SPEC (chapter 4.9, Memory Access Qualifiers): - // Except for image variables qualified with the format qualifiers r32f, r32i, - // and r32ui, image variables must specify either memory qualifier readonly or - // the memory qualifier writeonly. - switch (storage->texel_format()) { - case core::TexelFormat::kR32Float: - case core::TexelFormat::kR32Sint: - case core::TexelFormat::kR32Uint: - break; - default: { - // TODO(dawn:1972): Fix the tests that contain read-write storage - // textures with illegal formats. - out << "writeonly "; - break; - } - } - } break; - default: - TINT_UNREACHABLE() << "unexpected storage texture access " << storage->access(); - } - } - auto* subtype = sampled ? sampled->type() - : storage ? storage->type() - : ms ? ms->type() - : nullptr; - if (!subtype || subtype->Is()) { - } else if (subtype->Is()) { - out << "i"; - } else if (TINT_LIKELY(subtype->Is())) { - out << "u"; - } else { - TINT_ICE() << "Unsupported texture type"; - } - - out << (storage ? "image" : "sampler"); - - switch (tex->dim()) { - case core::type::TextureDimension::k1d: - out << "1D"; - break; - case core::type::TextureDimension::k2d: - out << ((ms || depth_ms) ? "2DMS" : "2D"); - break; - case core::type::TextureDimension::k2dArray: - out << ((ms || depth_ms) ? "2DMSArray" : "2DArray"); - break; - case core::type::TextureDimension::k3d: - out << "3D"; - break; - case core::type::TextureDimension::kCube: - out << "Cube"; - break; - case core::type::TextureDimension::kCubeArray: - out << "CubeArray"; - break; - default: - TINT_UNREACHABLE() << "unexpected TextureDimension " << tex->dim(); - } - if (tex->Is()) { - out << "Shadow"; - } - } else if (type->Is()) { - out << "uint"; - } else if (auto* vec = type->As()) { - auto width = vec->Width(); - if (vec->type()->Is() && width >= 1 && width <= 4) { - out << "vec" << width; - } else if (vec->type()->Is() && width >= 1 && width <= 4) { - out << "f16vec" << width; - } else if (vec->type()->Is() && width >= 1 && width <= 4) { - out << "ivec" << width; - } else if (vec->type()->Is() && width >= 1 && width <= 4) { - out << "uvec" << width; - } else if (vec->type()->Is() && width >= 1 && width <= 4) { - out << "bvec" << width; - } else { - out << "vector<"; - EmitType(out, vec->type(), address_space, access, ""); - out << ", " << width << ">"; - } - } else if (auto* atomic = type->As()) { - EmitType(out, atomic->Type(), address_space, access, name); - } else if (type->Is()) { - out << "void"; - } else { - diagnostics_.AddError(Source{}) << "unknown type in EmitType"; - } -} - -void ASTPrinter::EmitTypeAndName(StringStream& out, - const core::type::Type* type, - core::AddressSpace address_space, - core::Access access, - const std::string& name) { - bool printed_name = false; - EmitType(out, type, address_space, access, name, &printed_name); - if (!name.empty() && !printed_name) { - out << " " << name; - } -} - -void ASTPrinter::EmitStructType(TextBuffer* b, const core::type::Struct* str) { - auto it = emitted_structs_.emplace(str); - if (!it.second) { - return; - } - - auto address_space_uses = str->AddressSpaceUsage(); - Line(b) << "struct " << StructName(str) << " {"; - EmitStructMembers(b, str); - Line(b) << "};"; - Line(b); -} - -void ASTPrinter::EmitStructMembers(TextBuffer* b, const core::type::Struct* str) { - ScopedIndent si(b); - for (auto* mem : str->Members()) { - auto name = mem->Name().Name(); - auto* ty = mem->Type(); - - auto out = Line(b); - EmitTypeAndName(out, ty, core::AddressSpace::kUndefined, core::Access::kReadWrite, name); - out << ";"; - } -} - -void ASTPrinter::EmitUnaryOp(StringStream& out, const ast::UnaryOpExpression* expr) { - switch (expr->op) { - case core::UnaryOp::kIndirection: - case core::UnaryOp::kAddressOf: - EmitExpression(out, expr->expr); - return; - case core::UnaryOp::kComplement: - out << "~"; - break; - case core::UnaryOp::kNot: - if (TypeOf(expr)->UnwrapRef()->Is()) { - out << "!"; - } else { - out << "not"; - } - break; - case core::UnaryOp::kNegation: - out << "-"; - break; - } - - ScopedParen sp(out); - EmitExpression(out, expr->expr); -} - -void ASTPrinter::EmitVar(const ast::Var* var) { - auto* sem = builder_.Sem().Get(var); - auto* type = sem->Type()->UnwrapRef(); - - auto out = Line(); - EmitTypeAndName(out, type, sem->AddressSpace(), sem->Access(), var->name->symbol.Name()); - - out << " = "; - - if (var->initializer) { - EmitExpression(out, var->initializer); - } else { - EmitZeroValue(out, type); - } - out << ";"; -} - -void ASTPrinter::EmitLet(const ast::Let* let) { - auto* sem = builder_.Sem().Get(let); - auto* type = sem->Type()->UnwrapRef(); - - auto out = Line(); - // TODO(senorblanco): handle const - EmitTypeAndName(out, type, core::AddressSpace::kUndefined, core::Access::kUndefined, - let->name->symbol.Name()); - - out << " = "; - EmitExpression(out, let->initializer); - out << ";"; -} - -void ASTPrinter::EmitProgramConstVariable(const ast::Variable* var) { - auto* sem = builder_.Sem().Get(var); - auto* type = sem->Type(); - - auto out = Line(); - out << "const "; - EmitTypeAndName(out, type, core::AddressSpace::kUndefined, core::Access::kUndefined, - var->name->symbol.Name()); - out << " = "; - EmitExpression(out, var->initializer); - out << ";"; -} - -template -void ASTPrinter::CallBuiltinHelper(StringStream& out, - const ast::CallExpression* call, - const sem::BuiltinFn* builtin, - F&& build) { - // Generate the helper function if it hasn't been created already - auto fn = tint::GetOrAdd(builtins_, builtin, [&]() -> std::string { - TextBuffer b; - TINT_DEFER(helpers_.Append(b)); - - auto fn_name = UniqueIdentifier(std::string("tint_") + wgsl::str(builtin->Fn())); - std::vector parameter_names; - { - auto decl = Line(&b); - EmitTypeAndName(decl, builtin->ReturnType(), core::AddressSpace::kUndefined, - core::Access::kUndefined, fn_name); - { - ScopedParen sp(decl); - for (auto* param : builtin->Parameters()) { - if (!parameter_names.empty()) { - decl << ", "; - } - auto param_name = "param_" + std::to_string(parameter_names.size()); - const auto* ty = param->Type(); - if (auto* ptr = ty->As()) { - decl << "inout "; - ty = ptr->StoreType(); - } - EmitTypeAndName(decl, ty, core::AddressSpace::kUndefined, - core::Access::kUndefined, param_name); - parameter_names.emplace_back(std::move(param_name)); - } - } - decl << " {"; - } - { - ScopedIndent si(&b); - build(&b, parameter_names); - } - Line(&b) << "}"; - Line(&b); - return fn_name; - }); - - // Call the helper - out << fn; - { - ScopedParen sp(out); - bool first = true; - for (auto* arg : call->args) { - if (!first) { - out << ", "; - } - first = false; - EmitExpression(out, arg); - } - } -} - -core::type::Type* ASTPrinter::BoolTypeToUint(const core::type::Type* type) { - auto* u32 = builder_.create(); - if (type->Is()) { - return u32; - } else if (auto* vec = type->As()) { - return builder_.create(u32, vec->Width()); - } else { - return nullptr; - } -} - -std::string ASTPrinter::StructName(const core::type::Struct* s) { - auto name = s->Name().Name(); - if (HasPrefix(name, "__")) { - name = tint::GetOrAdd(builtin_struct_names_, s, - [&] { return UniqueIdentifier(name.substr(2)); }); - } - return name; -} - -std::string ASTPrinter::UniqueIdentifier(const std::string& prefix /* = "" */) { - return builder_.Symbols().New(prefix).Name(); -} - -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/ast_printer.h b/references/writer/ast_printer/ast_printer.h deleted file mode 100644 index a1c41aea..00000000 --- a/references/writer/ast_printer/ast_printer.h +++ /dev/null @@ -1,500 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef SRC_TINT_LANG_GLSL_WRITER_AST_PRINTER_AST_PRINTER_H_ -#define SRC_TINT_LANG_GLSL_WRITER_AST_PRINTER_AST_PRINTER_H_ - -#include -#include -#include -#include -#include - -#include "src/tint/api/options/texture_builtins_from_uniform.h" -#include "src/tint/lang/core/builtin_value.h" -#include "src/tint/lang/glsl/writer/common/version.h" -#include "src/tint/lang/wgsl/program/program_builder.h" -#include "src/tint/utils/containers/scope_stack.h" -#include "src/tint/utils/generator/text_generator.h" -#include "src/tint/utils/math/hash.h" -#include "src/tint/utils/text/string_stream.h" - -// Forward declarations -namespace tint::sem { -class BuiltinFn; -class Call; -class ValueConstructor; -class ValueConversion; -} // namespace tint::sem -namespace tint::glsl::writer { -struct Options; -} - -namespace tint::glsl::writer { - -/// The result of sanitizing a program for generation. -struct SanitizedResult { - /// Constructor - SanitizedResult(); - /// Destructor - ~SanitizedResult(); - /// Move constructor - SanitizedResult(SanitizedResult&&); - - /// The sanitized program. - Program program; -}; - -/// Sanitize a program in preparation for generating GLSL. -/// @param program The program to sanitize -/// @param options The HLSL generator options. -/// @param entry_point the entry point to generate GLSL for -/// @returns the sanitized program and any supplementary information -SanitizedResult Sanitize(const Program& program, - const Options& options, - const std::string& entry_point); - -/// Implementation class for GLSL generator -class ASTPrinter : public tint::TextGenerator { - public: - /// Constructor - /// @param program the program to generate - /// @param version the GLSL version to use - ASTPrinter(const Program& program, const Version& version); - ~ASTPrinter() override; - - /// Generates the GLSL shader - /// @returns true on successful generation, false otherwise - bool Generate(); - - /// Record an extension directive within the generator - /// @param ext the extension to record - void RecordExtension(const ast::Enable* ext); - /// Handles an index accessor expression - /// @param out the output of the expression stream - /// @param expr the expression to emit - void EmitIndexAccessor(StringStream& out, const ast::IndexAccessorExpression* expr); - /// Handles an assignment statement - /// @param stmt the statement to emit - void EmitAssign(const ast::AssignmentStatement* stmt); - /// Handles emission of bitwise operators (&|) on bool scalars and vectors - /// @param out the output of the expression stream - /// @param expr the binary expression - void EmitBitwiseBoolOp(StringStream& out, const ast::BinaryExpression* expr); - /// Handles generating a binary expression - /// @param out the output of the expression stream - /// @param expr the binary expression - void EmitFloatModulo(StringStream& out, const ast::BinaryExpression* expr); - /// Handles generating the modulo operator on float vector operands - /// @param out the output of the expression stream - /// @param expr the binary expression - void EmitBinary(StringStream& out, const ast::BinaryExpression* expr); - /// Handles generating a bitcast expression - /// @param out the output of the expression stream - /// @param expr the expression - void EmitVectorRelational(StringStream& out, const ast::BinaryExpression* expr); - /// Handles a bitcast call expression - /// @param out the output of the expression stream - /// @param expr the expression - void EmitBitcastCall(StringStream& out, const ast::CallExpression* expr); - /// Emits a list of statements - /// @param stmts the statement list - void EmitStatements(VectorRef stmts); - /// Emits a list of statements with an indentation - /// @param stmts the statement list - void EmitStatementsWithIndent(VectorRef stmts); - /// Handles a block statement - /// @param stmt the statement to emit - void EmitBlock(const ast::BlockStatement* stmt); - /// Handles a break statement - /// @param stmt the statement to emit - void EmitBreak(const ast::BreakStatement* stmt); - /// Handles a break-if statement - /// @param stmt the statement to emit - void EmitBreakIf(const ast::BreakIfStatement* stmt); - /// Handles generating a call expression - /// @param out the output of the expression stream - /// @param expr the call expression - void EmitCall(StringStream& out, const ast::CallExpression* expr); - /// Handles generating a function call expression - /// @param out the output of the expression stream - /// @param call the call expression - /// @param fn the function being called - void EmitFunctionCall(StringStream& out, const sem::Call* call, const sem::Function* fn); - /// Handles generating a builtin call expression - /// @param out the output of the expression stream - /// @param call the call expression - /// @param builtin the builtin being called - void EmitBuiltinCall(StringStream& out, const sem::Call* call, const sem::BuiltinFn* builtin); - /// Handles generating a value conversion expression - /// @param out the output of the expression stream - /// @param call the call expression - /// @param conv the value conversion - void EmitValueConversion(StringStream& out, - const sem::Call* call, - const sem::ValueConversion* conv); - /// Handles generating a value constructor expression - /// @param out the output of the expression stream - /// @param call the call expression - /// @param ctor the value constructor - void EmitValueConstructor(StringStream& out, - const sem::Call* call, - const sem::ValueConstructor* ctor); - /// Handles generating a barrier builtin call - /// @param out the output of the expression stream - /// @param builtin the semantic information for the barrier builtin - void EmitBarrierCall(StringStream& out, const sem::BuiltinFn* builtin); - /// Handles generating an atomic builtin call for a workgroup variable - /// @param out the output of the expression stream - /// @param expr the call expression - /// @param builtin the semantic information for the atomic builtin - void EmitWorkgroupAtomicCall(StringStream& out, - const ast::CallExpression* expr, - const sem::BuiltinFn* builtin); - /// Handles generating an array.length() call - /// @param out the output of the expression stream - /// @param expr the call expression - void EmitArrayLength(StringStream& out, const ast::CallExpression* expr); - /// Handles generating a call to `bitfieldExtract` - /// @param out the output of the expression stream - /// @param expr the call expression - void EmitExtractBits(StringStream& out, const ast::CallExpression* expr); - /// Handles generating a call to `bitfieldInsert` - /// @param out the output of the expression stream - /// @param expr the call expression - void EmitInsertBits(StringStream& out, const ast::CallExpression* expr); - /// Emulates 'fma' on GLSL ES, where it is unsupported. - /// @param out the output of the expression stream - /// @param expr the fma() expression - void EmitEmulatedFMA(StringStream& out, const ast::CallExpression* expr); - /// Create a float literal zero AST node, and associated semantic nodes. - /// @param stmt the statement which will own the semantic expression node - /// @returns an AST expression representing 0.0f - const ast::Expression* CreateF32Zero(const sem::Statement* stmt); - - /// Handles generating a call to a texture function (`textureSample`, - /// `textureSampleGrad`, etc) - /// @param out the output of the expression stream - /// @param call the call expression - /// @param builtin the semantic information for the texture builtin - void EmitTextureCall(StringStream& out, const sem::Call* call, const sem::BuiltinFn* builtin); - /// Handles generating a call to the `select()` builtin - /// @param out the output of the expression stream - /// @param expr the call expression - void EmitCountOneBitsCall(StringStream& out, const ast::CallExpression* expr); - /// Handles generating a call to the `countOneBits()` builtin - /// @param out the output of the expression stream - /// @param expr the call expression - /// @param builtin the semantic information for the builtin - void EmitSelectCall(StringStream& out, - const ast::CallExpression* expr, - const sem::BuiltinFn* builtin); - /// Handles generating a call to the `dot()` builtin - /// @param out the output of the expression stream - /// @param expr the call expression - /// @param builtin the semantic information for the builtin - void EmitDotCall(StringStream& out, - const ast::CallExpression* expr, - const sem::BuiltinFn* builtin); - /// Handles generating a call to the `modf()` builtin - /// @param out the output of the expression stream - /// @param expr the call expression - /// @param builtin the semantic information for the builtin - void EmitModfCall(StringStream& out, - const ast::CallExpression* expr, - const sem::BuiltinFn* builtin); - /// Handles generating a call to the `frexp()` builtin - /// @param out the output of the expression stream - /// @param expr the call expression - /// @param builtin the semantic information for the builtin - void EmitFrexpCall(StringStream& out, - const ast::CallExpression* expr, - const sem::BuiltinFn* builtin); - /// Handles generating a call to the `degrees()` builtin - /// @param out the output of the expression stream - /// @param expr the call expression - /// @param builtin the semantic information for the builtin - void EmitDegreesCall(StringStream& out, - const ast::CallExpression* expr, - const sem::BuiltinFn* builtin); - /// Handles generating a call to the `radians()` builtin - /// @param out the output of the expression stream - /// @param expr the call expression - /// @param builtin the semantic information for the builtin - void EmitRadiansCall(StringStream& out, - const ast::CallExpression* expr, - const sem::BuiltinFn* builtin); - /// Handles generating a call to the `quantizeToF16()` intrinsic - /// @param out the output of the expression stream - /// @param expr the call expression - /// @param builtin the semantic information for the builtin - void EmitQuantizeToF16Call(StringStream& out, - const ast::CallExpression* expr, - const sem::BuiltinFn* builtin); - /// Handles a case statement - /// @param stmt the statement - void EmitCase(const ast::CaseStatement* stmt); - /// Handles generating a discard statement - /// @param stmt the discard statement - void EmitDiscard(const ast::DiscardStatement* stmt); - /// Handles a continue statement - /// @param stmt the statement to emit - void EmitContinue(const ast::ContinueStatement* stmt); - /// Handles generate an Expression - /// @param out the output of the expression stream - /// @param expr the expression - void EmitExpression(StringStream& out, const ast::Expression* expr); - /// Handles generating a function - /// @param func the function to generate - void EmitFunction(const ast::Function* func); - - /// Handles emitting a global variable - /// @param global the global variable - void EmitGlobalVariable(const ast::Variable* global); - - /// Handles emitting a global variable with the uniform address space - /// @param var the AST node for the 'var' - /// @param sem the semantic node for the 'var' - void EmitUniformVariable(const ast::Var* var, const sem::Variable* sem); - - /// Handles emitting a global variable with the storage address space - /// @param var the AST node for the 'var' - /// @param sem the semantic node for the 'var' - void EmitStorageVariable(const ast::Var* var, const sem::Variable* sem); - - /// Handles emitting a global variable with the handle address space - /// @param var the AST node for the 'var' - /// @param sem the semantic node for the 'var' - void EmitHandleVariable(const ast::Var* var, const sem::Variable* sem); - - /// Handles emitting a global variable with the private address space - /// @param var the global variable - void EmitPrivateVariable(const sem::Variable* var); - - /// Handles emitting a global variable with the workgroup address space - /// @param var the global variable - void EmitWorkgroupVariable(const sem::Variable* var); - - /// Handles emitting a global variable with the input or output address space - /// @param var the global variable - void EmitIOVariable(const sem::GlobalVariable* var); - - /// Handles emitting a global variable with the push_constant address space - /// @param var the global variable - void EmitPushConstant(const sem::GlobalVariable* var); - - /// Handles emitting interpolation qualifiers - /// @param out the output of the expression stream - /// @param attrs the attributes - void EmitInterpolationQualifiers(StringStream& out, VectorRef attrs); - /// Handles emitting attributes - /// @param out the output of the expression stream - /// @param var the global variable semantics - void EmitAttributes(StringStream& out, const sem::GlobalVariable* var); - /// Handles emitting the entry point function - /// @param func the entry point - void EmitEntryPointFunction(const ast::Function* func); - /// Handles an if statement - /// @param stmt the statement to emit - void EmitIf(const ast::IfStatement* stmt); - /// Handles a constant value - /// @param out the output stream - /// @param constant the constant value to emit - void EmitConstant(StringStream& out, const core::constant::Value* constant); - /// Handles a literal - /// @param out the output stream - /// @param lit the literal to emit - void EmitLiteral(StringStream& out, const ast::LiteralExpression* lit); - /// Handles a loop statement - /// @param stmt the statement to emit - void EmitLoop(const ast::LoopStatement* stmt); - /// Handles a for loop statement - /// @param stmt the statement to emit - void EmitForLoop(const ast::ForLoopStatement* stmt); - /// Handles a while statement - /// @param stmt the statement to emit - void EmitWhile(const ast::WhileStatement* stmt); - /// Handles generating an identifier expression - /// @param out the output of the expression stream - /// @param expr the identifier expression - void EmitIdentifier(StringStream& out, const ast::IdentifierExpression* expr); - /// Handles a member accessor expression - /// @param out the output of the expression stream - /// @param expr the member accessor expression - void EmitMemberAccessor(StringStream& out, const ast::MemberAccessorExpression* expr); - /// Handles return statements - /// @param stmt the statement to emit - void EmitReturn(const ast::ReturnStatement* stmt); - /// Handles statement - /// @param stmt the statement to emit - void EmitStatement(const ast::Statement* stmt); - /// Handles generating a switch statement - /// @param stmt the statement to emit - void EmitSwitch(const ast::SwitchStatement* stmt); - /// Handles generating type - /// @param out the output stream - /// @param type the type to generate - /// @param address_space the address space of the variable - /// @param access the access control type of the variable - /// @param name the name of the variable, used for array emission. - /// @param name_printed (optional) if not nullptr and an array was printed - /// then the boolean is set to true. - void EmitType(StringStream& out, - const core::type::Type* type, - core::AddressSpace address_space, - core::Access access, - const std::string& name, - bool* name_printed = nullptr); - /// Handles generating type and name - /// @param out the output stream - /// @param type the type to generate - /// @param address_space the address space of the variable - /// @param access the access control type of the variable - /// @param name the name to emit - void EmitTypeAndName(StringStream& out, - const core::type::Type* type, - core::AddressSpace address_space, - core::Access access, - const std::string& name); - /// Handles generating a structure declaration. If the structure has already been emitted, then - /// this function will simply return `true` without emitting anything. - /// @param buffer the text buffer that the type declaration will be written to - /// @param ty the struct to generate - void EmitStructType(TextBuffer* buffer, const core::type::Struct* ty); - /// Handles generating the members of a structure - /// @param buffer the text buffer that the struct members will be written to - /// @param ty the struct to generate - void EmitStructMembers(TextBuffer* buffer, const core::type::Struct* ty); - /// Handles a unary op expression - /// @param out the output of the expression stream - /// @param expr the expression to emit - void EmitUnaryOp(StringStream& out, const ast::UnaryOpExpression* expr); - /// Emits the zero value for the given type - /// @param out the output stream - /// @param type the type to emit the value for - void EmitZeroValue(StringStream& out, const core::type::Type* type); - /// Handles generating a 'var' declaration - /// @param var the variable to generate - void EmitVar(const ast::Var* var); - /// Handles generating a 'let' declaration - /// @param let the variable to generate - void EmitLet(const ast::Let* let); - /// Handles generating a module-scope 'let' declaration - /// @param let the 'let' to emit - void EmitProgramConstVariable(const ast::Variable* let); - /// Handles generating a builtin method name - /// @param builtin the semantic info for the builtin - /// @returns the name or "" if not valid - std::string generate_builtin_name(const sem::BuiltinFn* builtin); - /// Converts a builtin to a gl_ string - /// @param builtin the builtin to convert - /// @param stage pipeline stage in which this builtin is used - /// @returns the string name of the builtin or blank on error - const char* builtin_to_string(core::BuiltinValue builtin, ast::PipelineStage stage); - /// Converts a builtin to a core::type::Type appropriate for GLSL. - /// @param builtin the builtin to convert - /// @returns the appropriate semantic type or null on error. - core::type::Type* builtin_type(core::BuiltinValue builtin); - - private: - enum class VarType { kIn, kOut }; - - struct EntryPointData { - std::string struct_name; - std::string var_name; - }; - - /// The map key for two semantic types. - using BinaryOperandType = - tint::UnorderedKeyWrapper>; - - /// CallBuiltinHelper will call the builtin helper function, creating it - /// if it hasn't been built already. If the builtin needs to be built then - /// CallBuiltinHelper will generate the function signature and will call - /// `build` to emit the body of the function. - /// @param out the output of the expression stream - /// @param call the call expression - /// @param builtin the semantic information for the builtin - /// @param build a function with the signature: - /// `bool(TextBuffer* buffer, const std::vector& params)` - /// Where: - /// `buffer` is the body of the generated function - /// `params` is the name of all the generated function parameters - template - void CallBuiltinHelper(StringStream& out, - const ast::CallExpression* call, - const sem::BuiltinFn* builtin, - F&& build); - - /// Create a uint type corresponding to the given bool or bool vector type. - /// @param type the bool or bool vector type to convert - /// @returns the corresponding uint type - core::type::Type* BoolTypeToUint(const core::type::Type* type); - - /// @param s the structure - /// @returns the name of the structure, taking special care of builtin structures that start - /// with double underscores. If the structure is a builtin, then the returned name will be a - /// unique name without the leading underscores. - std::string StructName(const core::type::Struct* s); - - /// @return a new, unique identifier with the given prefix. - /// @param prefix optional prefix to apply to the generated identifier. If empty "tint_symbol" - /// will be used. - std::string UniqueIdentifier(const std::string& prefix = ""); - - /// Alias for builder_.TypeOf(ptr) - template - auto TypeOf(T* ptr) { - return builder_.TypeOf(ptr); - } - - ProgramBuilder builder_; - - /// Helper functions emitted at the top of the output - TextBuffer helpers_; - - /// Map of builtin structure to unique generated name - std::unordered_map builtin_struct_names_; - std::function emit_continuing_; - std::unordered_map builtins_; - std::unordered_map dynamic_vector_write_; - std::unordered_map int_dot_funcs_; - std::unordered_map float_modulo_funcs_; - // Polyfill functions for bitcast expression, BinaryOperandType indicates the source type and - // the destination type - std::unordered_map bitcast_funcs_; - std::unordered_set emitted_structs_; - bool requires_oes_sample_variables_ = false; - bool requires_default_precision_qualifier_ = false; - bool requires_f16_extension_ = false; - bool requires_dual_source_blending_extension_ = false; - Version version_; -}; - -} // namespace tint::glsl::writer - -#endif // SRC_TINT_LANG_GLSL_WRITER_AST_PRINTER_AST_PRINTER_H_ diff --git a/references/writer/ast_printer/ast_printer_test.cc b/references/writer/ast_printer/ast_printer_test.cc deleted file mode 100644 index 125afe8b..00000000 --- a/references/writer/ast_printer/ast_printer_test.cc +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" - -#include "gmock/gmock.h" - -namespace tint::glsl::writer { -namespace { - -using GlslASTPrinterTest = TestHelper; - -TEST_F(GlslASTPrinterTest, InvalidProgram) { - Diagnostics().AddError(Source{}) << "make the program invalid"; - ASSERT_FALSE(IsValid()); - auto program = resolver::Resolve(*this); - ASSERT_FALSE(program.IsValid()); - auto result = Generate(program, Options{}, ""); - EXPECT_NE(result, Success); - EXPECT_EQ(result.Failure().reason.Str(), "error: make the program invalid"); -} - -TEST_F(GlslASTPrinterTest, Generate) { - Func("my_func", tint::Empty, ty.void_(), tint::Empty); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void my_func() { -} - -)"); -} - -TEST_F(GlslASTPrinterTest, GenerateDesktop) { - Func("my_func", tint::Empty, ty.void_(), tint::Empty); - - ASTPrinter& gen = Build(Version(Version::Standard::kDesktop, 4, 4)); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 440 - -void my_func() { -} - -)"); -} - -TEST_F(GlslASTPrinterTest, GenerateSampleIndexES) { - GlobalVar("gl_SampleID", ty.i32(), - Vector{ - Builtin(core::BuiltinValue::kSampleIndex), - Disable(ast::DisabledValidation::kIgnoreAddressSpace), - }, - core::AddressSpace::kIn); - Func("my_func", tint::Empty, ty.i32(), - Vector{ - Return(Expr("gl_SampleID")), - }); - - ASTPrinter& gen = Build(Version(Version::Standard::kES, 3, 1)); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -#extension GL_OES_sample_variables : require - -int my_func() { - return gl_SampleID; -} - -)"); -} - -TEST_F(GlslASTPrinterTest, GenerateSampleIndexDesktop) { - GlobalVar("gl_SampleID", ty.i32(), - Vector{ - Builtin(core::BuiltinValue::kSampleIndex), - Disable(ast::DisabledValidation::kIgnoreAddressSpace), - }, - core::AddressSpace::kIn); - Func("my_func", tint::Empty, ty.i32(), - Vector{ - Return(Expr("gl_SampleID")), - }); - - ASTPrinter& gen = Build(Version(Version::Standard::kDesktop, 4, 4)); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 440 - -int my_func() { - return gl_SampleID; -} - -)"); -} - -TEST_F(GlslASTPrinterTest, UnsupportedExtension) { - Enable(Source{{12, 34}}, wgsl::Extension::kChromiumInternalRelaxedUniformLayout); - - ASTPrinter& gen = Build(); - - ASSERT_FALSE(gen.Generate()); - EXPECT_EQ( - gen.Diagnostics().Str(), - R"(12:34 error: GLSL backend does not support extension 'chromium_internal_relaxed_uniform_layout')"); -} - -TEST_F(GlslASTPrinterTest, RequiresDirective) { - Require(wgsl::LanguageFeature::kReadonlyAndReadwriteStorageTextures); - - ASTPrinter& gen = Build(); - - ASSERT_TRUE(gen.Generate()) << gen.Diagnostics(); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -)"); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/binary_test.cc b/references/writer/ast_printer/binary_test.cc deleted file mode 100644 index 6895b48d..00000000 --- a/references/writer/ast_printer/binary_test.cc +++ /dev/null @@ -1,901 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" -#include "src/tint/lang/wgsl/ast/call_statement.h" -#include "src/tint/lang/wgsl/ast/variable_decl_statement.h" -#include "src/tint/utils/text/string_stream.h" - -#include "gmock/gmock.h" - -namespace tint::glsl::writer { -namespace { - -using namespace tint::core::fluent_types; // NOLINT -using namespace tint::core::number_suffixes; // NOLINT - -using GlslASTPrinterTest_Binary = TestHelper; - -struct BinaryData { - const char* result; - core::BinaryOp op; -}; -inline std::ostream& operator<<(std::ostream& out, BinaryData data) { - StringStream str; - str << data.op; - out << str.str(); - return out; -} - -using GlslBinaryTest = TestParamHelper; -TEST_P(GlslBinaryTest, Emit_f32) { - auto params = GetParam(); - - // Skip ops that are illegal for this type - if (params.op == core::BinaryOp::kAnd || params.op == core::BinaryOp::kOr || - params.op == core::BinaryOp::kXor || params.op == core::BinaryOp::kShiftLeft || - params.op == core::BinaryOp::kShiftRight || params.op == core::BinaryOp::kModulo) { - return; - } - - GlobalVar("left", ty.f32(), core::AddressSpace::kPrivate); - GlobalVar("right", ty.f32(), core::AddressSpace::kPrivate); - - auto* left = Expr("left"); - auto* right = Expr("right"); - - auto* expr = create(params.op, left, right); - - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), params.result); -} -TEST_P(GlslBinaryTest, Emit_f16) { - auto params = GetParam(); - - // Skip ops that are illegal for this type - if (params.op == core::BinaryOp::kAnd || params.op == core::BinaryOp::kOr || - params.op == core::BinaryOp::kXor || params.op == core::BinaryOp::kShiftLeft || - params.op == core::BinaryOp::kShiftRight || params.op == core::BinaryOp::kModulo) { - return; - } - - Enable(wgsl::Extension::kF16); - - GlobalVar("left", ty.f16(), core::AddressSpace::kPrivate); - GlobalVar("right", ty.f16(), core::AddressSpace::kPrivate); - - auto* left = Expr("left"); - auto* right = Expr("right"); - - auto* expr = create(params.op, left, right); - - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), params.result); -} -TEST_P(GlslBinaryTest, Emit_u32) { - auto params = GetParam(); - - GlobalVar("left", ty.u32(), core::AddressSpace::kPrivate); - GlobalVar("right", ty.u32(), core::AddressSpace::kPrivate); - - auto* left = Expr("left"); - auto* right = Expr("right"); - - auto* expr = create(params.op, left, right); - - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), params.result); -} -TEST_P(GlslBinaryTest, Emit_i32) { - auto params = GetParam(); - - // Skip ops that are illegal for this type - if (params.op == core::BinaryOp::kShiftLeft || params.op == core::BinaryOp::kShiftRight) { - return; - } - - GlobalVar("left", ty.i32(), core::AddressSpace::kPrivate); - GlobalVar("right", ty.i32(), core::AddressSpace::kPrivate); - - auto* left = Expr("left"); - auto* right = Expr("right"); - - auto* expr = create(params.op, left, right); - - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), params.result); -} -INSTANTIATE_TEST_SUITE_P( - GlslASTPrinterTest, - GlslBinaryTest, - testing::Values(BinaryData{"(left & right)", core::BinaryOp::kAnd}, - BinaryData{"(left | right)", core::BinaryOp::kOr}, - BinaryData{"(left ^ right)", core::BinaryOp::kXor}, - BinaryData{"(left == right)", core::BinaryOp::kEqual}, - BinaryData{"(left != right)", core::BinaryOp::kNotEqual}, - BinaryData{"(left < right)", core::BinaryOp::kLessThan}, - BinaryData{"(left > right)", core::BinaryOp::kGreaterThan}, - BinaryData{"(left <= right)", core::BinaryOp::kLessThanEqual}, - BinaryData{"(left >= right)", core::BinaryOp::kGreaterThanEqual}, - BinaryData{"(left << right)", core::BinaryOp::kShiftLeft}, - BinaryData{"(left >> right)", core::BinaryOp::kShiftRight}, - BinaryData{"(left + right)", core::BinaryOp::kAdd}, - BinaryData{"(left - right)", core::BinaryOp::kSubtract}, - BinaryData{"(left * right)", core::BinaryOp::kMultiply}, - BinaryData{"(left / right)", core::BinaryOp::kDivide}, - BinaryData{"(left % right)", core::BinaryOp::kModulo})); - -TEST_F(GlslASTPrinterTest_Binary, Multiply_VectorScalar_f32) { - GlobalVar("a", Call>(1_f, 1_f, 1_f), core::AddressSpace::kPrivate); - auto* lhs = Expr("a"); - auto* rhs = Expr(1_f); - - auto* expr = create(core::BinaryOp::kMultiply, lhs, rhs); - - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "(a * 1.0f)"); -} - -TEST_F(GlslASTPrinterTest_Binary, Multiply_VectorScalar_f16) { - Enable(wgsl::Extension::kF16); - - GlobalVar("a", Call>(1_h, 1_h, 1_h), core::AddressSpace::kPrivate); - auto* lhs = Expr("a"); - auto* rhs = Expr(1_h); - - auto* expr = create(core::BinaryOp::kMultiply, lhs, rhs); - - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "(a * 1.0hf)"); -} - -TEST_F(GlslASTPrinterTest_Binary, Multiply_ScalarVector_f32) { - GlobalVar("a", Call>(1_f, 1_f, 1_f), core::AddressSpace::kPrivate); - auto* lhs = Expr(1_f); - auto* rhs = Expr("a"); - - auto* expr = create(core::BinaryOp::kMultiply, lhs, rhs); - - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "(1.0f * a)"); -} - -TEST_F(GlslASTPrinterTest_Binary, Multiply_ScalarVector_f16) { - Enable(wgsl::Extension::kF16); - - GlobalVar("a", Call>(1_h, 1_h, 1_h), core::AddressSpace::kPrivate); - auto* lhs = Expr(1_h); - auto* rhs = Expr("a"); - - auto* expr = create(core::BinaryOp::kMultiply, lhs, rhs); - - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "(1.0hf * a)"); -} - -TEST_F(GlslASTPrinterTest_Binary, Multiply_MatrixScalar_f32) { - GlobalVar("mat", ty.mat3x3(), core::AddressSpace::kPrivate); - auto* lhs = Expr("mat"); - auto* rhs = Expr(1_f); - - auto* expr = create(core::BinaryOp::kMultiply, lhs, rhs); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "(mat * 1.0f)"); -} - -TEST_F(GlslASTPrinterTest_Binary, Multiply_MatrixScalar_f16) { - Enable(wgsl::Extension::kF16); - - GlobalVar("mat", ty.mat3x3(), core::AddressSpace::kPrivate); - auto* lhs = Expr("mat"); - auto* rhs = Expr(1_h); - - auto* expr = create(core::BinaryOp::kMultiply, lhs, rhs); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "(mat * 1.0hf)"); -} - -TEST_F(GlslASTPrinterTest_Binary, Multiply_ScalarMatrix_f32) { - GlobalVar("mat", ty.mat3x3(), core::AddressSpace::kPrivate); - auto* lhs = Expr(1_f); - auto* rhs = Expr("mat"); - - auto* expr = create(core::BinaryOp::kMultiply, lhs, rhs); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "(1.0f * mat)"); -} - -TEST_F(GlslASTPrinterTest_Binary, Multiply_ScalarMatrix_f16) { - Enable(wgsl::Extension::kF16); - - GlobalVar("mat", ty.mat3x3(), core::AddressSpace::kPrivate); - auto* lhs = Expr(1_h); - auto* rhs = Expr("mat"); - - auto* expr = create(core::BinaryOp::kMultiply, lhs, rhs); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "(1.0hf * mat)"); -} - -TEST_F(GlslASTPrinterTest_Binary, Multiply_MatrixVector_f32) { - GlobalVar("mat", ty.mat3x3(), core::AddressSpace::kPrivate); - auto* lhs = Expr("mat"); - auto* rhs = Call>(1_f, 1_f, 1_f); - - auto* expr = create(core::BinaryOp::kMultiply, lhs, rhs); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "(mat * vec3(1.0f))"); -} - -TEST_F(GlslASTPrinterTest_Binary, Multiply_MatrixVector_f16) { - Enable(wgsl::Extension::kF16); - - GlobalVar("mat", ty.mat3x3(), core::AddressSpace::kPrivate); - auto* lhs = Expr("mat"); - auto* rhs = Call>(1_h, 1_h, 1_h); - - auto* expr = create(core::BinaryOp::kMultiply, lhs, rhs); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "(mat * f16vec3(1.0hf))"); -} - -TEST_F(GlslASTPrinterTest_Binary, Multiply_VectorMatrix_f32) { - GlobalVar("mat", ty.mat3x3(), core::AddressSpace::kPrivate); - auto* lhs = Call>(1_f, 1_f, 1_f); - auto* rhs = Expr("mat"); - - auto* expr = create(core::BinaryOp::kMultiply, lhs, rhs); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "(vec3(1.0f) * mat)"); -} - -TEST_F(GlslASTPrinterTest_Binary, Multiply_VectorMatrix_f16) { - Enable(wgsl::Extension::kF16); - - GlobalVar("mat", ty.mat3x3(), core::AddressSpace::kPrivate); - auto* lhs = Call>(1_h, 1_h, 1_h); - auto* rhs = Expr("mat"); - - auto* expr = create(core::BinaryOp::kMultiply, lhs, rhs); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "(f16vec3(1.0hf) * mat)"); -} - -TEST_F(GlslASTPrinterTest_Binary, Multiply_MatrixMatrix_f32) { - GlobalVar("lhs", ty.mat3x3(), core::AddressSpace::kPrivate); - GlobalVar("rhs", ty.mat3x3(), core::AddressSpace::kPrivate); - - auto* expr = create(core::BinaryOp::kMultiply, Expr("lhs"), Expr("rhs")); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "(lhs * rhs)"); -} - -TEST_F(GlslASTPrinterTest_Binary, Multiply_MatrixMatrix_f16) { - Enable(wgsl::Extension::kF16); - - GlobalVar("lhs", ty.mat3x3(), core::AddressSpace::kPrivate); - GlobalVar("rhs", ty.mat3x3(), core::AddressSpace::kPrivate); - - auto* expr = create(core::BinaryOp::kMultiply, Expr("lhs"), Expr("rhs")); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "(lhs * rhs)"); -} - -TEST_F(GlslASTPrinterTest_Binary, ModF32) { - GlobalVar("a", ty.f32(), core::AddressSpace::kPrivate); - GlobalVar("b", ty.f32(), core::AddressSpace::kPrivate); - - auto* expr = create(core::BinaryOp::kModulo, Expr("a"), Expr("b")); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "tint_float_modulo(a, b)"); -} - -TEST_F(GlslASTPrinterTest_Binary, ModF16) { - Enable(wgsl::Extension::kF16); - - GlobalVar("a", ty.f16(), core::AddressSpace::kPrivate); - GlobalVar("b", ty.f16(), core::AddressSpace::kPrivate); - - auto* expr = create(core::BinaryOp::kModulo, Expr("a"), Expr("b")); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "tint_float_modulo(a, b)"); -} - -TEST_F(GlslASTPrinterTest_Binary, ModVec3F32) { - GlobalVar("a", ty.vec3(), core::AddressSpace::kPrivate); - GlobalVar("b", ty.vec3(), core::AddressSpace::kPrivate); - - auto* expr = create(core::BinaryOp::kModulo, Expr("a"), Expr("b")); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "tint_float_modulo(a, b)"); -} - -TEST_F(GlslASTPrinterTest_Binary, ModVec3F16) { - Enable(wgsl::Extension::kF16); - - GlobalVar("a", ty.vec3(), core::AddressSpace::kPrivate); - GlobalVar("b", ty.vec3(), core::AddressSpace::kPrivate); - - auto* expr = create(core::BinaryOp::kModulo, Expr("a"), Expr("b")); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "tint_float_modulo(a, b)"); -} - -TEST_F(GlslASTPrinterTest_Binary, ModVec3F32ScalarF32) { - GlobalVar("a", ty.vec3(), core::AddressSpace::kPrivate); - GlobalVar("b", ty.f32(), core::AddressSpace::kPrivate); - - auto* expr = create(core::BinaryOp::kModulo, Expr("a"), Expr("b")); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "tint_float_modulo(a, b)"); -} - -TEST_F(GlslASTPrinterTest_Binary, ModVec3F16ScalarF16) { - Enable(wgsl::Extension::kF16); - - GlobalVar("a", ty.vec3(), core::AddressSpace::kPrivate); - GlobalVar("b", ty.f16(), core::AddressSpace::kPrivate); - - auto* expr = create(core::BinaryOp::kModulo, Expr("a"), Expr("b")); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "tint_float_modulo(a, b)"); -} - -TEST_F(GlslASTPrinterTest_Binary, ModScalarF32Vec3F32) { - GlobalVar("a", ty.f32(), core::AddressSpace::kPrivate); - GlobalVar("b", ty.vec3(), core::AddressSpace::kPrivate); - - auto* expr = create(core::BinaryOp::kModulo, Expr("a"), Expr("b")); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "tint_float_modulo(a, b)"); -} - -TEST_F(GlslASTPrinterTest_Binary, ModScalarF16Vec3F16) { - Enable(wgsl::Extension::kF16); - - GlobalVar("a", ty.f16(), core::AddressSpace::kPrivate); - GlobalVar("b", ty.vec3(), core::AddressSpace::kPrivate); - - auto* expr = create(core::BinaryOp::kModulo, Expr("a"), Expr("b")); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "tint_float_modulo(a, b)"); -} - -TEST_F(GlslASTPrinterTest_Binary, ModMixedVec3ScalarF32) { - GlobalVar("a", ty.vec3(), core::AddressSpace::kPrivate); - GlobalVar("b", ty.f32(), core::AddressSpace::kPrivate); - - auto* expr_vec_mod_vec = - create(core::BinaryOp::kModulo, Expr("a"), Expr("a")); - auto* expr_vec_mod_scalar = - create(core::BinaryOp::kModulo, Expr("a"), Expr("b")); - auto* expr_scalar_mod_vec = - create(core::BinaryOp::kModulo, Expr("b"), Expr("a")); - WrapInFunction(expr_vec_mod_vec, expr_vec_mod_scalar, expr_scalar_mod_vec); - - ASTPrinter& gen = Build(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -vec3 tint_float_modulo(vec3 lhs, vec3 rhs) { - return (lhs - rhs * trunc(lhs / rhs)); -} - -vec3 tint_float_modulo_1(vec3 lhs, float rhs) { - return (lhs - rhs * trunc(lhs / rhs)); -} - -vec3 tint_float_modulo_2(float lhs, vec3 rhs) { - return (lhs - rhs * trunc(lhs / rhs)); -} - - -vec3 a = vec3(0.0f, 0.0f, 0.0f); -float b = 0.0f; -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void test_function() { - vec3 tint_symbol = tint_float_modulo(a, a); - vec3 tint_symbol_1 = tint_float_modulo_1(a, b); - vec3 tint_symbol_2 = tint_float_modulo_2(b, a); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Binary, ModMixedVec3ScalarF16) { - Enable(wgsl::Extension::kF16); - - GlobalVar("a", ty.vec3(), core::AddressSpace::kPrivate); - GlobalVar("b", ty.f16(), core::AddressSpace::kPrivate); - - auto* expr_vec_mod_vec = - create(core::BinaryOp::kModulo, Expr("a"), Expr("a")); - auto* expr_vec_mod_scalar = - create(core::BinaryOp::kModulo, Expr("a"), Expr("b")); - auto* expr_scalar_mod_vec = - create(core::BinaryOp::kModulo, Expr("b"), Expr("a")); - WrapInFunction(expr_vec_mod_vec, expr_vec_mod_scalar, expr_scalar_mod_vec); - - ASTPrinter& gen = Build(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -#extension GL_AMD_gpu_shader_half_float : require - -f16vec3 tint_float_modulo(f16vec3 lhs, f16vec3 rhs) { - return (lhs - rhs * trunc(lhs / rhs)); -} - -f16vec3 tint_float_modulo_1(f16vec3 lhs, float16_t rhs) { - return (lhs - rhs * trunc(lhs / rhs)); -} - -f16vec3 tint_float_modulo_2(float16_t lhs, f16vec3 rhs) { - return (lhs - rhs * trunc(lhs / rhs)); -} - - -f16vec3 a = f16vec3(0.0hf, 0.0hf, 0.0hf); -float16_t b = 0.0hf; -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void test_function() { - f16vec3 tint_symbol = tint_float_modulo(a, a); - f16vec3 tint_symbol_1 = tint_float_modulo_1(a, b); - f16vec3 tint_symbol_2 = tint_float_modulo_2(b, a); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Binary, Logical_And) { - GlobalVar("a", ty.bool_(), core::AddressSpace::kPrivate); - GlobalVar("b", ty.bool_(), core::AddressSpace::kPrivate); - - auto* expr = create(core::BinaryOp::kLogicalAnd, Expr("a"), Expr("b")); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "(tint_tmp)"); - EXPECT_EQ(gen.Result(), R"(bool tint_tmp = a; -if (tint_tmp) { - tint_tmp = b; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Binary, Logical_Multi) { - // (a && b) || (c || d) - GlobalVar("a", ty.bool_(), core::AddressSpace::kPrivate); - GlobalVar("b", ty.bool_(), core::AddressSpace::kPrivate); - GlobalVar("c", ty.bool_(), core::AddressSpace::kPrivate); - GlobalVar("d", ty.bool_(), core::AddressSpace::kPrivate); - - auto* expr = create( - core::BinaryOp::kLogicalOr, - create(core::BinaryOp::kLogicalAnd, Expr("a"), Expr("b")), - create(core::BinaryOp::kLogicalOr, Expr("c"), Expr("d"))); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "(tint_tmp)"); - EXPECT_EQ(gen.Result(), R"(bool tint_tmp_1 = a; -if (tint_tmp_1) { - tint_tmp_1 = b; -} -bool tint_tmp = (tint_tmp_1); -if (!tint_tmp) { - bool tint_tmp_2 = c; - if (!tint_tmp_2) { - tint_tmp_2 = d; - } - tint_tmp = (tint_tmp_2); -} -)"); -} - -TEST_F(GlslASTPrinterTest_Binary, Logical_Or) { - GlobalVar("a", ty.bool_(), core::AddressSpace::kPrivate); - GlobalVar("b", ty.bool_(), core::AddressSpace::kPrivate); - - auto* expr = create(core::BinaryOp::kLogicalOr, Expr("a"), Expr("b")); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "(tint_tmp)"); - EXPECT_EQ(gen.Result(), R"(bool tint_tmp = a; -if (!tint_tmp) { - tint_tmp = b; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Binary, If_WithLogical) { - // if (a && b) { - // return 1i; - // } else if (b || c) { - // return 2i; - // } else { - // return 3i; - // } - - GlobalVar("a", ty.bool_(), core::AddressSpace::kPrivate); - GlobalVar("b", ty.bool_(), core::AddressSpace::kPrivate); - GlobalVar("c", ty.bool_(), core::AddressSpace::kPrivate); - - auto* expr = - If(create(core::BinaryOp::kLogicalAnd, Expr("a"), Expr("b")), - Block(Return(1_i)), - Else(If(create(core::BinaryOp::kLogicalOr, Expr("b"), Expr("c")), - Block(Return(2_i)), Else(Block(Return(3_i)))))); - Func("func", tint::Empty, ty.i32(), Vector{WrapInStatement(expr)}); - - ASTPrinter& gen = Build(); - - gen.EmitStatement(expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(bool tint_tmp = a; -if (tint_tmp) { - tint_tmp = b; -} -if ((tint_tmp)) { - return 1; -} else { - bool tint_tmp_1 = b; - if (!tint_tmp_1) { - tint_tmp_1 = c; - } - if ((tint_tmp_1)) { - return 2; - } else { - return 3; - } -} -)"); -} - -TEST_F(GlslASTPrinterTest_Binary, Return_WithLogical) { - // return (a && b) || c; - - GlobalVar("a", ty.bool_(), core::AddressSpace::kPrivate); - GlobalVar("b", ty.bool_(), core::AddressSpace::kPrivate); - GlobalVar("c", ty.bool_(), core::AddressSpace::kPrivate); - - auto* expr = Return(create( - core::BinaryOp::kLogicalOr, - create(core::BinaryOp::kLogicalAnd, Expr("a"), Expr("b")), - Expr("c"))); - Func("func", tint::Empty, ty.bool_(), Vector{WrapInStatement(expr)}); - - ASTPrinter& gen = Build(); - - gen.EmitStatement(expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(bool tint_tmp_1 = a; -if (tint_tmp_1) { - tint_tmp_1 = b; -} -bool tint_tmp = (tint_tmp_1); -if (!tint_tmp) { - tint_tmp = c; -} -return (tint_tmp); -)"); -} - -TEST_F(GlslASTPrinterTest_Binary, Assign_WithLogical) { - // a = (b || c) && d; - - GlobalVar("a", ty.bool_(), core::AddressSpace::kPrivate); - GlobalVar("b", ty.bool_(), core::AddressSpace::kPrivate); - GlobalVar("c", ty.bool_(), core::AddressSpace::kPrivate); - GlobalVar("d", ty.bool_(), core::AddressSpace::kPrivate); - - auto* expr = - Assign(Expr("a"), - create( - core::BinaryOp::kLogicalAnd, - create(core::BinaryOp::kLogicalOr, Expr("b"), Expr("c")), - Expr("d"))); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - gen.EmitStatement(expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(bool tint_tmp_1 = b; -if (!tint_tmp_1) { - tint_tmp_1 = c; -} -bool tint_tmp = (tint_tmp_1); -if (tint_tmp) { - tint_tmp = d; -} -a = (tint_tmp); -)"); -} - -TEST_F(GlslASTPrinterTest_Binary, Decl_WithLogical) { - // var a : bool = (b && c) || d; - - GlobalVar("b", ty.bool_(), core::AddressSpace::kPrivate); - GlobalVar("c", ty.bool_(), core::AddressSpace::kPrivate); - GlobalVar("d", ty.bool_(), core::AddressSpace::kPrivate); - - auto* var = - Var("a", ty.bool_(), - create( - core::BinaryOp::kLogicalOr, - create(core::BinaryOp::kLogicalAnd, Expr("b"), Expr("c")), - Expr("d"))); - - auto* decl = Decl(var); - WrapInFunction(decl); - - ASTPrinter& gen = Build(); - - gen.EmitStatement(decl); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(bool tint_tmp_1 = b; -if (tint_tmp_1) { - tint_tmp_1 = c; -} -bool tint_tmp = (tint_tmp_1); -if (!tint_tmp) { - tint_tmp = d; -} -bool a = (tint_tmp); -)"); -} - -TEST_F(GlslASTPrinterTest_Binary, Call_WithLogical) { - // foo(a && b, c || d, (a || c) && (b || d)) - - Func("foo", - Vector{ - Param(Sym(), ty.bool_()), - Param(Sym(), ty.bool_()), - Param(Sym(), ty.bool_()), - }, - ty.void_(), tint::Empty, tint::Empty); - GlobalVar("a", ty.bool_(), core::AddressSpace::kPrivate); - GlobalVar("b", ty.bool_(), core::AddressSpace::kPrivate); - GlobalVar("c", ty.bool_(), core::AddressSpace::kPrivate); - GlobalVar("d", ty.bool_(), core::AddressSpace::kPrivate); - - Vector params{ - create(core::BinaryOp::kLogicalAnd, Expr("a"), Expr("b")), - create(core::BinaryOp::kLogicalOr, Expr("c"), Expr("d")), - create( - core::BinaryOp::kLogicalAnd, - create(core::BinaryOp::kLogicalOr, Expr("a"), Expr("c")), - create(core::BinaryOp::kLogicalOr, Expr("b"), Expr("d"))), - }; - - auto* expr = CallStmt(Call("foo", params)); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - gen.EmitStatement(expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(bool tint_tmp = a; -if (tint_tmp) { - tint_tmp = b; -} -bool tint_tmp_1 = c; -if (!tint_tmp_1) { - tint_tmp_1 = d; -} -bool tint_tmp_3 = a; -if (!tint_tmp_3) { - tint_tmp_3 = c; -} -bool tint_tmp_2 = (tint_tmp_3); -if (tint_tmp_2) { - bool tint_tmp_4 = b; - if (!tint_tmp_4) { - tint_tmp_4 = d; - } - tint_tmp_2 = (tint_tmp_4); -} -foo((tint_tmp), (tint_tmp_1), (tint_tmp_2)); -)"); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/bitcast_test.cc b/references/writer/ast_printer/bitcast_test.cc deleted file mode 100644 index 76570451..00000000 --- a/references/writer/ast_printer/bitcast_test.cc +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" - -#include "src/tint/utils/text/string_stream.h" - -#include "gmock/gmock.h" - -using namespace tint::core::fluent_types; // NOLINT -using namespace tint::core::number_suffixes; // NOLINT - -namespace tint::glsl::writer { -namespace { - -using GlslASTPrinterTest_Bitcast = TestHelper; - -TEST_F(GlslASTPrinterTest_Bitcast, EmitExpression_Bitcast_Float) { - auto* a = Let("a", Expr(1_i)); - auto* bitcast = Bitcast(Expr("a")); - WrapInFunction(a, bitcast); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, bitcast); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "intBitsToFloat(a)"); -} - -TEST_F(GlslASTPrinterTest_Bitcast, EmitExpression_Bitcast_Int) { - auto* a = Let("a", Expr(1_u)); - auto* bitcast = Bitcast(Expr("a")); - WrapInFunction(a, bitcast); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, bitcast); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "int(a)"); -} - -TEST_F(GlslASTPrinterTest_Bitcast, EmitExpression_Bitcast_Uint) { - auto* a = Let("a", Expr(1_i)); - auto* bitcast = Bitcast(Expr("a")); - WrapInFunction(a, bitcast); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, bitcast); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "uint(a)"); -} - -TEST_F(GlslASTPrinterTest_Bitcast, EmitExpression_Bitcast_F16_Vec2) { - Enable(wgsl::Extension::kF16); - - auto* a = Let("a", Call>(1_h, 2_h)); - auto* b = Let("b", Bitcast(Expr("a"))); - auto* c = Let("c", Bitcast>(Expr("b"))); - auto* d = Let("d", Bitcast(Expr("c"))); - auto* e = Let("e", Bitcast>(Expr("d"))); - auto* f = Let("f", Bitcast(Expr("e"))); - auto* g = Let("g", Bitcast>(Expr("f"))); - WrapInFunction(a, b, c, d, e, f, g); - - ASTPrinter& gen = Build(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -#extension GL_AMD_gpu_shader_half_float : require - -int tint_bitcast_from_f16(f16vec2 src) { - uint r = packFloat2x16(src); - return int(r); -} -f16vec2 tint_bitcast_to_f16(int src) { - uint r = uint(src); - return unpackFloat2x16(r); -} -float tint_bitcast_from_f16_1(f16vec2 src) { - uint r = packFloat2x16(src); - return uintBitsToFloat(r); -} -f16vec2 tint_bitcast_to_f16_1(float src) { - uint r = floatBitsToUint(src); - return unpackFloat2x16(r); -} -uint tint_bitcast_from_f16_2(f16vec2 src) { - uint r = packFloat2x16(src); - return uint(r); -} -f16vec2 tint_bitcast_to_f16_2(uint src) { - uint r = uint(src); - return unpackFloat2x16(r); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void test_function() { - f16vec2 a = f16vec2(1.0hf, 2.0hf); - int b = tint_bitcast_from_f16(a); - f16vec2 c = tint_bitcast_to_f16(b); - float d = tint_bitcast_from_f16_1(c); - f16vec2 e = tint_bitcast_to_f16_1(d); - uint f = tint_bitcast_from_f16_2(e); - f16vec2 g = tint_bitcast_to_f16_2(f); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Bitcast, EmitExpression_Bitcast_F16_Vec4) { - Enable(wgsl::Extension::kF16); - - auto* a = Let("a", Call>(1_h, 2_h, 3_h, 4_h)); - auto* b = Let("b", Bitcast>(Expr("a"))); - auto* c = Let("c", Bitcast>(Expr("b"))); - auto* d = Let("d", Bitcast>(Expr("c"))); - auto* e = Let("e", Bitcast>(Expr("d"))); - auto* f = Let("f", Bitcast>(Expr("e"))); - auto* g = Let("g", Bitcast>(Expr("f"))); - WrapInFunction(a, b, c, d, e, f, g); - - ASTPrinter& gen = Build(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -#extension GL_AMD_gpu_shader_half_float : require - -ivec2 tint_bitcast_from_f16(f16vec4 src) { - uvec2 r = uvec2(packFloat2x16(src.xy), packFloat2x16(src.zw)); - return ivec2(r); -} -f16vec4 tint_bitcast_to_f16(ivec2 src) { - uvec2 r = uvec2(src); - f16vec2 v_xy = unpackFloat2x16(r.x); - f16vec2 v_zw = unpackFloat2x16(r.y); - return f16vec4(v_xy.x, v_xy.y, v_zw.x, v_zw.y); -} -vec2 tint_bitcast_from_f16_1(f16vec4 src) { - uvec2 r = uvec2(packFloat2x16(src.xy), packFloat2x16(src.zw)); - return uintBitsToFloat(r); -} -f16vec4 tint_bitcast_to_f16_1(vec2 src) { - uvec2 r = floatBitsToUint(src); - f16vec2 v_xy = unpackFloat2x16(r.x); - f16vec2 v_zw = unpackFloat2x16(r.y); - return f16vec4(v_xy.x, v_xy.y, v_zw.x, v_zw.y); -} -uvec2 tint_bitcast_from_f16_2(f16vec4 src) { - uvec2 r = uvec2(packFloat2x16(src.xy), packFloat2x16(src.zw)); - return uvec2(r); -} -f16vec4 tint_bitcast_to_f16_2(uvec2 src) { - uvec2 r = uvec2(src); - f16vec2 v_xy = unpackFloat2x16(r.x); - f16vec2 v_zw = unpackFloat2x16(r.y); - return f16vec4(v_xy.x, v_xy.y, v_zw.x, v_zw.y); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void test_function() { - f16vec4 a = f16vec4(1.0hf, 2.0hf, 3.0hf, 4.0hf); - ivec2 b = tint_bitcast_from_f16(a); - f16vec4 c = tint_bitcast_to_f16(b); - vec2 d = tint_bitcast_from_f16_1(c); - f16vec4 e = tint_bitcast_to_f16_1(d); - uvec2 f = tint_bitcast_from_f16_2(e); - f16vec4 g = tint_bitcast_to_f16_2(f); - return; -} -)"); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/block_test.cc b/references/writer/ast_printer/block_test.cc deleted file mode 100644 index 7257ffc8..00000000 --- a/references/writer/ast_printer/block_test.cc +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" - -#include "gmock/gmock.h" - -namespace tint::glsl::writer { -namespace { - -using GlslASTPrinterTest_Block = TestHelper; - -TEST_F(GlslASTPrinterTest_Block, Emit_Block) { - auto* b = Block(Return()); - WrapInFunction(b); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitStatement(b); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( { - return; - } -)"); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/break_test.cc b/references/writer/ast_printer/break_test.cc deleted file mode 100644 index fd54c195..00000000 --- a/references/writer/ast_printer/break_test.cc +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" - -#include "gmock/gmock.h" - -namespace tint::glsl::writer { -namespace { - -using GlslASTPrinterTest_Break = TestHelper; - -TEST_F(GlslASTPrinterTest_Break, Emit_Break) { - auto* b = create(); - WrapInFunction(Loop(Block(b))); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitStatement(b); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), " break;\n"); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/builtin_test.cc b/references/writer/ast_printer/builtin_test.cc deleted file mode 100644 index b923ec6a..00000000 --- a/references/writer/ast_printer/builtin_test.cc +++ /dev/null @@ -1,1680 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "gmock/gmock.h" -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" -#include "src/tint/lang/wgsl/ast/call_statement.h" -#include "src/tint/lang/wgsl/ast/stage_attribute.h" -#include "src/tint/lang/wgsl/sem/call.h" -#include "src/tint/utils/text/string_stream.h" - -namespace tint::glsl::writer { -namespace { - -using ::testing::HasSubstr; -using namespace tint::core::fluent_types; // NOLINT -using namespace tint::core::number_suffixes; // NOLINT - -using GlslASTPrinterTest_Builtin = TestHelper; - -enum class CallParamType { - kF32, - kU32, - kBool, - kF16, -}; - -struct BuiltinData { - wgsl::BuiltinFn builtin; - CallParamType type; - const char* glsl_name; -}; -inline std::ostream& operator<<(std::ostream& out, BuiltinData data) { - out << data.glsl_name << "<"; - switch (data.type) { - case CallParamType::kF32: - out << "f32"; - break; - case CallParamType::kU32: - out << "u32"; - break; - case CallParamType::kBool: - out << "bool"; - break; - case CallParamType::kF16: - out << "f16"; - break; - } - out << ">"; - return out; -} - -const ast::CallExpression* GenerateCall(wgsl::BuiltinFn builtin, - CallParamType type, - ProgramBuilder* builder) { - std::string name; - StringStream str; - str << name << builtin; - switch (builtin) { - case wgsl::BuiltinFn::kAcos: - case wgsl::BuiltinFn::kAsin: - case wgsl::BuiltinFn::kAtan: - case wgsl::BuiltinFn::kCeil: - case wgsl::BuiltinFn::kCos: - case wgsl::BuiltinFn::kCosh: - case wgsl::BuiltinFn::kDpdx: - case wgsl::BuiltinFn::kDpdxCoarse: - case wgsl::BuiltinFn::kDpdxFine: - case wgsl::BuiltinFn::kDpdy: - case wgsl::BuiltinFn::kDpdyCoarse: - case wgsl::BuiltinFn::kDpdyFine: - case wgsl::BuiltinFn::kExp: - case wgsl::BuiltinFn::kExp2: - case wgsl::BuiltinFn::kFloor: - case wgsl::BuiltinFn::kFract: - case wgsl::BuiltinFn::kFwidth: - case wgsl::BuiltinFn::kFwidthCoarse: - case wgsl::BuiltinFn::kFwidthFine: - case wgsl::BuiltinFn::kInverseSqrt: - case wgsl::BuiltinFn::kLength: - case wgsl::BuiltinFn::kLog: - case wgsl::BuiltinFn::kLog2: - case wgsl::BuiltinFn::kNormalize: - case wgsl::BuiltinFn::kRound: - case wgsl::BuiltinFn::kSin: - case wgsl::BuiltinFn::kSinh: - case wgsl::BuiltinFn::kSqrt: - case wgsl::BuiltinFn::kTan: - case wgsl::BuiltinFn::kTanh: - case wgsl::BuiltinFn::kTrunc: - case wgsl::BuiltinFn::kSign: - if (type == CallParamType::kF16) { - return builder->Call(str.str(), "h2"); - } else { - return builder->Call(str.str(), "f2"); - } - case wgsl::BuiltinFn::kLdexp: - if (type == CallParamType::kF16) { - return builder->Call(str.str(), "h2", "i2"); - } else { - return builder->Call(str.str(), "f2", "i2"); - } - case wgsl::BuiltinFn::kAtan2: - case wgsl::BuiltinFn::kDot: - case wgsl::BuiltinFn::kDistance: - case wgsl::BuiltinFn::kPow: - case wgsl::BuiltinFn::kReflect: - case wgsl::BuiltinFn::kStep: - if (type == CallParamType::kF16) { - return builder->Call(str.str(), "h2", "h2"); - } else { - return builder->Call(str.str(), "f2", "f2"); - } - case wgsl::BuiltinFn::kCross: - if (type == CallParamType::kF16) { - return builder->Call(str.str(), "h3", "h3"); - } else { - return builder->Call(str.str(), "f3", "f3"); - } - case wgsl::BuiltinFn::kFma: - case wgsl::BuiltinFn::kMix: - case wgsl::BuiltinFn::kFaceForward: - case wgsl::BuiltinFn::kSmoothstep: - if (type == CallParamType::kF16) { - return builder->Call(str.str(), "h2", "h2", "h2"); - } else { - return builder->Call(str.str(), "f2", "f2", "f2"); - } - case wgsl::BuiltinFn::kAll: - case wgsl::BuiltinFn::kAny: - return builder->Call(str.str(), "b2"); - case wgsl::BuiltinFn::kAbs: - if (type == CallParamType::kF32) { - return builder->Call(str.str(), "f2"); - } else if (type == CallParamType::kF16) { - return builder->Call(str.str(), "h2"); - } else { - return builder->Call(str.str(), "u2"); - } - case wgsl::BuiltinFn::kCountOneBits: - case wgsl::BuiltinFn::kReverseBits: - return builder->Call(str.str(), "u2"); - case wgsl::BuiltinFn::kMax: - case wgsl::BuiltinFn::kMin: - if (type == CallParamType::kF32) { - return builder->Call(str.str(), "f2", "f2"); - } else if (type == CallParamType::kF16) { - return builder->Call(str.str(), "h2", "h2"); - } else { - return builder->Call(str.str(), "u2", "u2"); - } - case wgsl::BuiltinFn::kClamp: - if (type == CallParamType::kF32) { - return builder->Call(str.str(), "f2", "f2", "f2"); - } else if (type == CallParamType::kF16) { - return builder->Call(str.str(), "h2", "h2", "h2"); - } else { - return builder->Call(str.str(), "u2", "u2", "u2"); - } - case wgsl::BuiltinFn::kSelect: - if (type == CallParamType::kF16) { - return builder->Call(str.str(), "h2", "h2", "b2"); - } else { - return builder->Call(str.str(), "f2", "f2", "b2"); - } - case wgsl::BuiltinFn::kDeterminant: - if (type == CallParamType::kF16) { - return builder->Call(str.str(), "hm2x2"); - } else { - return builder->Call(str.str(), "m2x2"); - } - case wgsl::BuiltinFn::kTranspose: - if (type == CallParamType::kF16) { - return builder->Call(str.str(), "hm3x2"); - } else { - return builder->Call(str.str(), "m3x2"); - } - default: - break; - } - return nullptr; -} -using GlslBuiltinTest = TestParamHelper; -TEST_P(GlslBuiltinTest, Emit) { - auto param = GetParam(); - - if (param.type == CallParamType::kF16) { - Enable(wgsl::Extension::kF16); - - GlobalVar("h2", ty.vec2(), core::AddressSpace::kPrivate); - GlobalVar("h3", ty.vec3(), core::AddressSpace::kPrivate); - GlobalVar("hm2x2", ty.mat2x2(), core::AddressSpace::kPrivate); - GlobalVar("hm3x2", ty.mat3x2(), core::AddressSpace::kPrivate); - } - - GlobalVar("f2", ty.vec2(), core::AddressSpace::kPrivate); - GlobalVar("f3", ty.vec3(), core::AddressSpace::kPrivate); - GlobalVar("u2", ty.vec2(), core::AddressSpace::kPrivate); - GlobalVar("i2", ty.vec2(), core::AddressSpace::kPrivate); - GlobalVar("b2", ty.vec2(), core::AddressSpace::kPrivate); - GlobalVar("m2x2", ty.mat2x2(), core::AddressSpace::kPrivate); - GlobalVar("m3x2", ty.mat3x2(), core::AddressSpace::kPrivate); - - auto* call = GenerateCall(param.builtin, param.type, this); - ASSERT_NE(nullptr, call) << "Unhandled builtin"; - Func("func", tint::Empty, ty.void_(), - Vector{ - Assign(Phony(), call), - }, - Vector{create(ast::PipelineStage::kFragment)}); - - ASTPrinter& gen = Build(); - - auto* sem = program->Sem().Get(call); - ASSERT_NE(sem, nullptr); - auto* target = sem->Target(); - ASSERT_NE(target, nullptr); - auto* builtin = target->As(); - ASSERT_NE(builtin, nullptr); - - EXPECT_EQ(gen.generate_builtin_name(builtin), param.glsl_name); -} -INSTANTIATE_TEST_SUITE_P( - GlslASTPrinterTest_Builtin, - GlslBuiltinTest, - testing::Values(/* Logical built-in */ - BuiltinData{wgsl::BuiltinFn::kAll, CallParamType::kBool, "all"}, - BuiltinData{wgsl::BuiltinFn::kAny, CallParamType::kBool, "any"}, - /* Float built-in */ - BuiltinData{wgsl::BuiltinFn::kAbs, CallParamType::kF32, "abs"}, - BuiltinData{wgsl::BuiltinFn::kAbs, CallParamType::kF16, "abs"}, - BuiltinData{wgsl::BuiltinFn::kAcos, CallParamType::kF32, "acos"}, - BuiltinData{wgsl::BuiltinFn::kAcos, CallParamType::kF16, "acos"}, - BuiltinData{wgsl::BuiltinFn::kAsin, CallParamType::kF32, "asin"}, - BuiltinData{wgsl::BuiltinFn::kAsin, CallParamType::kF16, "asin"}, - BuiltinData{wgsl::BuiltinFn::kAtan, CallParamType::kF32, "atan"}, - BuiltinData{wgsl::BuiltinFn::kAtan, CallParamType::kF16, "atan"}, - BuiltinData{wgsl::BuiltinFn::kAtan2, CallParamType::kF32, "atan"}, - BuiltinData{wgsl::BuiltinFn::kAtan2, CallParamType::kF16, "atan"}, - BuiltinData{wgsl::BuiltinFn::kCeil, CallParamType::kF32, "ceil"}, - BuiltinData{wgsl::BuiltinFn::kCeil, CallParamType::kF16, "ceil"}, - BuiltinData{wgsl::BuiltinFn::kClamp, CallParamType::kF32, "clamp"}, - BuiltinData{wgsl::BuiltinFn::kClamp, CallParamType::kF16, "clamp"}, - BuiltinData{wgsl::BuiltinFn::kCos, CallParamType::kF32, "cos"}, - BuiltinData{wgsl::BuiltinFn::kCos, CallParamType::kF16, "cos"}, - BuiltinData{wgsl::BuiltinFn::kCosh, CallParamType::kF32, "cosh"}, - BuiltinData{wgsl::BuiltinFn::kCosh, CallParamType::kF16, "cosh"}, - BuiltinData{wgsl::BuiltinFn::kCross, CallParamType::kF32, "cross"}, - BuiltinData{wgsl::BuiltinFn::kCross, CallParamType::kF16, "cross"}, - BuiltinData{wgsl::BuiltinFn::kDistance, CallParamType::kF32, "distance"}, - BuiltinData{wgsl::BuiltinFn::kDistance, CallParamType::kF16, "distance"}, - BuiltinData{wgsl::BuiltinFn::kExp, CallParamType::kF32, "exp"}, - BuiltinData{wgsl::BuiltinFn::kExp, CallParamType::kF16, "exp"}, - BuiltinData{wgsl::BuiltinFn::kExp2, CallParamType::kF32, "exp2"}, - BuiltinData{wgsl::BuiltinFn::kExp2, CallParamType::kF16, "exp2"}, - BuiltinData{wgsl::BuiltinFn::kFaceForward, CallParamType::kF32, "faceforward"}, - BuiltinData{wgsl::BuiltinFn::kFaceForward, CallParamType::kF16, "faceforward"}, - BuiltinData{wgsl::BuiltinFn::kFloor, CallParamType::kF32, "floor"}, - BuiltinData{wgsl::BuiltinFn::kFloor, CallParamType::kF16, "floor"}, - BuiltinData{wgsl::BuiltinFn::kFma, CallParamType::kF32, "fma"}, - BuiltinData{wgsl::BuiltinFn::kFma, CallParamType::kF16, "fma"}, - BuiltinData{wgsl::BuiltinFn::kFract, CallParamType::kF32, "fract"}, - BuiltinData{wgsl::BuiltinFn::kFract, CallParamType::kF16, "fract"}, - BuiltinData{wgsl::BuiltinFn::kInverseSqrt, CallParamType::kF32, "inversesqrt"}, - BuiltinData{wgsl::BuiltinFn::kInverseSqrt, CallParamType::kF16, "inversesqrt"}, - BuiltinData{wgsl::BuiltinFn::kLdexp, CallParamType::kF32, "ldexp"}, - BuiltinData{wgsl::BuiltinFn::kLdexp, CallParamType::kF16, "ldexp"}, - BuiltinData{wgsl::BuiltinFn::kLength, CallParamType::kF32, "length"}, - BuiltinData{wgsl::BuiltinFn::kLength, CallParamType::kF16, "length"}, - BuiltinData{wgsl::BuiltinFn::kLog, CallParamType::kF32, "log"}, - BuiltinData{wgsl::BuiltinFn::kLog, CallParamType::kF16, "log"}, - BuiltinData{wgsl::BuiltinFn::kLog2, CallParamType::kF32, "log2"}, - BuiltinData{wgsl::BuiltinFn::kLog2, CallParamType::kF16, "log2"}, - BuiltinData{wgsl::BuiltinFn::kMax, CallParamType::kF32, "max"}, - BuiltinData{wgsl::BuiltinFn::kMax, CallParamType::kF16, "max"}, - BuiltinData{wgsl::BuiltinFn::kMin, CallParamType::kF32, "min"}, - BuiltinData{wgsl::BuiltinFn::kMin, CallParamType::kF16, "min"}, - BuiltinData{wgsl::BuiltinFn::kMix, CallParamType::kF32, "mix"}, - BuiltinData{wgsl::BuiltinFn::kMix, CallParamType::kF16, "mix"}, - BuiltinData{wgsl::BuiltinFn::kNormalize, CallParamType::kF32, "normalize"}, - BuiltinData{wgsl::BuiltinFn::kNormalize, CallParamType::kF16, "normalize"}, - BuiltinData{wgsl::BuiltinFn::kPow, CallParamType::kF32, "pow"}, - BuiltinData{wgsl::BuiltinFn::kPow, CallParamType::kF16, "pow"}, - BuiltinData{wgsl::BuiltinFn::kReflect, CallParamType::kF32, "reflect"}, - BuiltinData{wgsl::BuiltinFn::kReflect, CallParamType::kF16, "reflect"}, - BuiltinData{wgsl::BuiltinFn::kSign, CallParamType::kF32, "sign"}, - BuiltinData{wgsl::BuiltinFn::kSign, CallParamType::kF16, "sign"}, - BuiltinData{wgsl::BuiltinFn::kSin, CallParamType::kF32, "sin"}, - BuiltinData{wgsl::BuiltinFn::kSin, CallParamType::kF16, "sin"}, - BuiltinData{wgsl::BuiltinFn::kSinh, CallParamType::kF32, "sinh"}, - BuiltinData{wgsl::BuiltinFn::kSinh, CallParamType::kF16, "sinh"}, - BuiltinData{wgsl::BuiltinFn::kSmoothstep, CallParamType::kF32, "smoothstep"}, - BuiltinData{wgsl::BuiltinFn::kSmoothstep, CallParamType::kF16, "smoothstep"}, - BuiltinData{wgsl::BuiltinFn::kSqrt, CallParamType::kF32, "sqrt"}, - BuiltinData{wgsl::BuiltinFn::kSqrt, CallParamType::kF16, "sqrt"}, - BuiltinData{wgsl::BuiltinFn::kStep, CallParamType::kF32, "step"}, - BuiltinData{wgsl::BuiltinFn::kStep, CallParamType::kF16, "step"}, - BuiltinData{wgsl::BuiltinFn::kTan, CallParamType::kF32, "tan"}, - BuiltinData{wgsl::BuiltinFn::kTan, CallParamType::kF16, "tan"}, - BuiltinData{wgsl::BuiltinFn::kTanh, CallParamType::kF32, "tanh"}, - BuiltinData{wgsl::BuiltinFn::kTanh, CallParamType::kF16, "tanh"}, - BuiltinData{wgsl::BuiltinFn::kTrunc, CallParamType::kF32, "trunc"}, - BuiltinData{wgsl::BuiltinFn::kTrunc, CallParamType::kF16, "trunc"}, - /* Integer built-in */ - BuiltinData{wgsl::BuiltinFn::kAbs, CallParamType::kU32, "abs"}, - BuiltinData{wgsl::BuiltinFn::kClamp, CallParamType::kU32, "clamp"}, - BuiltinData{wgsl::BuiltinFn::kCountOneBits, CallParamType::kU32, "bitCount"}, - BuiltinData{wgsl::BuiltinFn::kMax, CallParamType::kU32, "max"}, - BuiltinData{wgsl::BuiltinFn::kMin, CallParamType::kU32, "min"}, - BuiltinData{wgsl::BuiltinFn::kReverseBits, CallParamType::kU32, - "bitfieldReverse"}, - BuiltinData{wgsl::BuiltinFn::kRound, CallParamType::kU32, "round"}, - /* Matrix built-in */ - BuiltinData{wgsl::BuiltinFn::kDeterminant, CallParamType::kF32, "determinant"}, - BuiltinData{wgsl::BuiltinFn::kDeterminant, CallParamType::kF16, "determinant"}, - BuiltinData{wgsl::BuiltinFn::kTranspose, CallParamType::kF32, "transpose"}, - BuiltinData{wgsl::BuiltinFn::kTranspose, CallParamType::kF16, "transpose"}, - /* Vector built-in */ - BuiltinData{wgsl::BuiltinFn::kDot, CallParamType::kF32, "dot"}, - BuiltinData{wgsl::BuiltinFn::kDot, CallParamType::kF16, "dot"}, - /* Derivate built-in */ - BuiltinData{wgsl::BuiltinFn::kDpdx, CallParamType::kF32, "dFdx"}, - BuiltinData{wgsl::BuiltinFn::kDpdxCoarse, CallParamType::kF32, "dFdx"}, - BuiltinData{wgsl::BuiltinFn::kDpdxFine, CallParamType::kF32, "dFdx"}, - BuiltinData{wgsl::BuiltinFn::kDpdy, CallParamType::kF32, "dFdy"}, - BuiltinData{wgsl::BuiltinFn::kDpdyCoarse, CallParamType::kF32, "dFdy"}, - BuiltinData{wgsl::BuiltinFn::kDpdyFine, CallParamType::kF32, "dFdy"}, - BuiltinData{wgsl::BuiltinFn::kFwidth, CallParamType::kF32, "fwidth"}, - BuiltinData{wgsl::BuiltinFn::kFwidthCoarse, CallParamType::kF32, "fwidth"}, - BuiltinData{wgsl::BuiltinFn::kFwidthFine, CallParamType::kF32, "fwidth"})); - -TEST_F(GlslASTPrinterTest_Builtin, Builtin_Call) { - auto* call = Call("dot", "param1", "param2"); - - GlobalVar("param1", ty.vec3(), core::AddressSpace::kPrivate); - GlobalVar("param2", ty.vec3(), core::AddressSpace::kPrivate); - - WrapInFunction(Decl(Var("r", call))); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - StringStream out; - gen.EmitExpression(out, call); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "dot(param1, param2)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Select_Scalar) { - GlobalVar("a", Expr(1_f), core::AddressSpace::kPrivate); - GlobalVar("b", Expr(2_f), core::AddressSpace::kPrivate); - auto* call = Call("select", "a", "b", true); - WrapInFunction(Decl(Var("r", call))); - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - StringStream out; - gen.EmitExpression(out, call); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "(true ? b : a)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Select_Vector) { - GlobalVar("a", Call>(1_i, 2_i), core::AddressSpace::kPrivate); - GlobalVar("b", Call>(3_i, 4_i), core::AddressSpace::kPrivate); - auto* call = Call("select", "a", "b", Call>(true, false)); - WrapInFunction(Decl(Var("r", call))); - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - StringStream out; - gen.EmitExpression(out, call); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "tint_select(a, b, bvec2(true, false))"); -} - -TEST_F(GlslASTPrinterTest_Builtin, FMA_f32) { - auto* call = Call("fma", "a", "b", "c"); - - GlobalVar("a", ty.vec3(), core::AddressSpace::kPrivate); - GlobalVar("b", ty.vec3(), core::AddressSpace::kPrivate); - GlobalVar("c", ty.vec3(), core::AddressSpace::kPrivate); - - WrapInFunction(Decl(Var("r", call))); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - StringStream out; - gen.EmitExpression(out, call); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "((a) * (b) + (c))"); -} - -TEST_F(GlslASTPrinterTest_Builtin, FMA_f16) { - Enable(wgsl::Extension::kF16); - - GlobalVar("a", ty.vec3(), core::AddressSpace::kPrivate); - GlobalVar("b", ty.vec3(), core::AddressSpace::kPrivate); - GlobalVar("c", ty.vec3(), core::AddressSpace::kPrivate); - - auto* call = Call("fma", "a", "b", "c"); - WrapInFunction(Decl(Var("r", call))); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - StringStream out; - gen.EmitExpression(out, call); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "((a) * (b) + (c))"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Runtime_Modf_Scalar_f32) { - WrapInFunction(Decl(Let("f", Expr(1.5_f))), // - Decl(Let("v", Call("modf", "f")))); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -struct modf_result_f32 { - float fract; - float whole; -}; - -modf_result_f32 tint_modf(float param_0) { - modf_result_f32 result; - result.fract = modf(param_0, result.whole); - return result; -} - - -void test_function() { - float f = 1.5f; - modf_result_f32 v = tint_modf(f); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Runtime_Modf_Scalar_f16) { - Enable(wgsl::Extension::kF16); - - WrapInFunction(Decl(Let("f", Expr(1.5_h))), // - Decl(Let("v", Call("modf", "f")))); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -#extension GL_AMD_gpu_shader_half_float : require - -struct modf_result_f16 { - float16_t fract; - float16_t whole; -}; - -modf_result_f16 tint_modf(float16_t param_0) { - modf_result_f16 result; - result.fract = modf(param_0, result.whole); - return result; -} - - -void test_function() { - float16_t f = 1.5hf; - modf_result_f16 v = tint_modf(f); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Runtime_Modf_Vector_f32) { - WrapInFunction(Decl(Let("f", Call>(1.5_f, 2.5_f, 3.5_f))), // - Decl(Let("v", Call("modf", "f")))); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -struct modf_result_vec3_f32 { - vec3 fract; - vec3 whole; -}; - -modf_result_vec3_f32 tint_modf(vec3 param_0) { - modf_result_vec3_f32 result; - result.fract = modf(param_0, result.whole); - return result; -} - - -void test_function() { - vec3 f = vec3(1.5f, 2.5f, 3.5f); - modf_result_vec3_f32 v = tint_modf(f); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Runtime_Modf_Vector_f16) { - Enable(wgsl::Extension::kF16); - - WrapInFunction(Decl(Let("f", Call>(1.5_h, 2.5_h, 3.5_h))), // - Decl(Let("v", Call("modf", "f")))); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -#extension GL_AMD_gpu_shader_half_float : require - -struct modf_result_vec3_f16 { - f16vec3 fract; - f16vec3 whole; -}; - -modf_result_vec3_f16 tint_modf(f16vec3 param_0) { - modf_result_vec3_f16 result; - result.fract = modf(param_0, result.whole); - return result; -} - - -void test_function() { - f16vec3 f = f16vec3(1.5hf, 2.5hf, 3.5hf); - modf_result_vec3_f16 v = tint_modf(f); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Const_Modf_Scalar_f32) { - WrapInFunction(Decl(Let("v", Call("modf", 1.5_f)))); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -struct modf_result_f32 { - float fract; - float whole; -}; - - -void test_function() { - modf_result_f32 v = modf_result_f32(0.5f, 1.0f); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Const_Modf_Scalar_f16) { - Enable(wgsl::Extension::kF16); - - WrapInFunction(Decl(Let("v", Call("modf", 1.5_h)))); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -#extension GL_AMD_gpu_shader_half_float : require - -struct modf_result_f16 { - float16_t fract; - float16_t whole; -}; - - -void test_function() { - modf_result_f16 v = modf_result_f16(0.5hf, 1.0hf); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Const_Modf_Vector_f32) { - WrapInFunction(Decl(Let("v", Call("modf", Call>(1.5_f, 2.5_f, 3.5_f))))); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -struct modf_result_vec3_f32 { - vec3 fract; - vec3 whole; -}; - - -void test_function() { - modf_result_vec3_f32 v = modf_result_vec3_f32(vec3(0.5f), vec3(1.0f, 2.0f, 3.0f)); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Const_Modf_Vector_f16) { - Enable(wgsl::Extension::kF16); - - WrapInFunction(Decl(Let("v", Call("modf", Call>(1.5_h, 2.5_h, 3.5_h))))); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -#extension GL_AMD_gpu_shader_half_float : require - -struct modf_result_vec3_f16 { - f16vec3 fract; - f16vec3 whole; -}; - - -void test_function() { - modf_result_vec3_f16 v = modf_result_vec3_f16(f16vec3(0.5hf), f16vec3(1.0hf, 2.0hf, 3.0hf)); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Runtime_Frexp_Scalar_f32) { - WrapInFunction(Var("f", Expr(1_f)), // - Var("v", Call("frexp", "f"))); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -struct frexp_result_f32 { - float fract; - int exp; -}; - -frexp_result_f32 tint_frexp(float param_0) { - frexp_result_f32 result; - result.fract = frexp(param_0, result.exp); - return result; -} - - -void test_function() { - float f = 1.0f; - frexp_result_f32 v = tint_frexp(f); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Runtime_Frexp_Scalar_f16) { - Enable(wgsl::Extension::kF16); - - WrapInFunction(Var("f", Expr(1_h)), // - Var("v", Call("frexp", "f"))); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -#extension GL_AMD_gpu_shader_half_float : require - -struct frexp_result_f16 { - float16_t fract; - int exp; -}; - -frexp_result_f16 tint_frexp(float16_t param_0) { - frexp_result_f16 result; - result.fract = frexp(param_0, result.exp); - return result; -} - - -void test_function() { - float16_t f = 1.0hf; - frexp_result_f16 v = tint_frexp(f); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Runtime_Frexp_Vector_f32) { - WrapInFunction(Var("f", Call>()), // - Var("v", Call("frexp", "f"))); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -struct frexp_result_vec3_f32 { - vec3 fract; - ivec3 exp; -}; - -frexp_result_vec3_f32 tint_frexp(vec3 param_0) { - frexp_result_vec3_f32 result; - result.fract = frexp(param_0, result.exp); - return result; -} - - -void test_function() { - vec3 f = vec3(0.0f); - frexp_result_vec3_f32 v = tint_frexp(f); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Runtime_Frexp_Vector_f16) { - Enable(wgsl::Extension::kF16); - - WrapInFunction(Var("f", Call>()), // - Var("v", Call("frexp", "f"))); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -#extension GL_AMD_gpu_shader_half_float : require - -struct frexp_result_vec3_f16 { - f16vec3 fract; - ivec3 exp; -}; - -frexp_result_vec3_f16 tint_frexp(f16vec3 param_0) { - frexp_result_vec3_f16 result; - result.fract = frexp(param_0, result.exp); - return result; -} - - -void test_function() { - f16vec3 f = f16vec3(0.0hf); - frexp_result_vec3_f16 v = tint_frexp(f); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Const_Frexp_Scalar_f32) { - WrapInFunction(Decl(Let("v", Call("frexp", 1_f)))); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -struct frexp_result_f32 { - float fract; - int exp; -}; - - -void test_function() { - frexp_result_f32 v = frexp_result_f32(0.5f, 1); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Const_Frexp_Scalar_f16) { - Enable(wgsl::Extension::kF16); - - WrapInFunction(Decl(Let("v", Call("frexp", 1_h)))); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -#extension GL_AMD_gpu_shader_half_float : require - -struct frexp_result_f16 { - float16_t fract; - int exp; -}; - - -void test_function() { - frexp_result_f16 v = frexp_result_f16(0.5hf, 1); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Const_Frexp_Vector_f32) { - WrapInFunction(Decl(Let("v", Call("frexp", Call>())))); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -struct frexp_result_vec3_f32 { - vec3 fract; - ivec3 exp; -}; - - -void test_function() { - frexp_result_vec3_f32 v = frexp_result_vec3_f32(vec3(0.0f), ivec3(0)); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Const_Frexp_Vector_f16) { - Enable(wgsl::Extension::kF16); - - WrapInFunction(Decl(Let("v", Call("frexp", Call>())))); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -#extension GL_AMD_gpu_shader_half_float : require - -struct frexp_result_vec3_f16 { - f16vec3 fract; - ivec3 exp; -}; - - -void test_function() { - frexp_result_vec3_f16 v = frexp_result_vec3_f16(f16vec3(0.0hf), ivec3(0)); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Degrees_Scalar_f32) { - auto* val = Var("val", ty.f32()); - auto* call = Call("degrees", val); - WrapInFunction(val, call); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -float tint_degrees(float param_0) { - return param_0 * 57.29577951308232286465f; -} - - -void test_function() { - float val = 0.0f; - float tint_symbol = tint_degrees(val); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Degrees_Vector_f32) { - auto* val = Var("val", ty.vec3()); - auto* call = Call("degrees", val); - WrapInFunction(val, call); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -vec3 tint_degrees(vec3 param_0) { - return param_0 * 57.29577951308232286465f; -} - - -void test_function() { - vec3 val = vec3(0.0f, 0.0f, 0.0f); - vec3 tint_symbol = tint_degrees(val); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Degrees_Scalar_f16) { - Enable(wgsl::Extension::kF16); - - auto* val = Var("val", ty.f16()); - auto* call = Call("degrees", val); - WrapInFunction(val, call); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -#extension GL_AMD_gpu_shader_half_float : require - -float16_t tint_degrees(float16_t param_0) { - return param_0 * 57.29577951308232286465hf; -} - - -void test_function() { - float16_t val = 0.0hf; - float16_t tint_symbol = tint_degrees(val); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Degrees_Vector_f16) { - Enable(wgsl::Extension::kF16); - - auto* val = Var("val", ty.vec3()); - auto* call = Call("degrees", val); - WrapInFunction(val, call); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -#extension GL_AMD_gpu_shader_half_float : require - -f16vec3 tint_degrees(f16vec3 param_0) { - return param_0 * 57.29577951308232286465hf; -} - - -void test_function() { - f16vec3 val = f16vec3(0.0hf, 0.0hf, 0.0hf); - f16vec3 tint_symbol = tint_degrees(val); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Radians_Scalar_f32) { - auto* val = Var("val", ty.f32()); - auto* call = Call("radians", val); - WrapInFunction(val, call); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -float tint_radians(float param_0) { - return param_0 * 0.01745329251994329547f; -} - - -void test_function() { - float val = 0.0f; - float tint_symbol = tint_radians(val); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Radians_Vector_f32) { - auto* val = Var("val", ty.vec3()); - auto* call = Call("radians", val); - WrapInFunction(val, call); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -vec3 tint_radians(vec3 param_0) { - return param_0 * 0.01745329251994329547f; -} - - -void test_function() { - vec3 val = vec3(0.0f, 0.0f, 0.0f); - vec3 tint_symbol = tint_radians(val); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Radians_Scalar_f16) { - Enable(wgsl::Extension::kF16); - - auto* val = Var("val", ty.f16()); - auto* call = Call("radians", val); - WrapInFunction(val, call); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -#extension GL_AMD_gpu_shader_half_float : require - -float16_t tint_radians(float16_t param_0) { - return param_0 * 0.01745329251994329547hf; -} - - -void test_function() { - float16_t val = 0.0hf; - float16_t tint_symbol = tint_radians(val); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Radians_Vector_f16) { - Enable(wgsl::Extension::kF16); - - auto* val = Var("val", ty.vec3()); - auto* call = Call("radians", val); - WrapInFunction(val, call); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -#extension GL_AMD_gpu_shader_half_float : require - -f16vec3 tint_radians(f16vec3 param_0) { - return param_0 * 0.01745329251994329547hf; -} - - -void test_function() { - f16vec3 val = f16vec3(0.0hf, 0.0hf, 0.0hf); - f16vec3 tint_symbol = tint_radians(val); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, ExtractBits) { - auto* v = Var("v", ty.vec3()); - auto* offset = Var("offset", ty.u32()); - auto* count = Var("count", ty.u32()); - auto* call = Call("extractBits", v, offset, count); - WrapInFunction(v, offset, count, call); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -uvec3 tint_extract_bits(uvec3 v, uint offset, uint count) { - uint s = min(offset, 32u); - uint e = min(32u, (s + count)); - return bitfieldExtract(v, int(s), int((e - s))); -} - -void test_function() { - uvec3 v = uvec3(0u, 0u, 0u); - uint offset = 0u; - uint count = 0u; - uvec3 tint_symbol = tint_extract_bits(v, offset, count); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, InsertBits) { - auto* v = Var("v", ty.vec3()); - auto* n = Var("n", ty.vec3()); - auto* offset = Var("offset", ty.u32()); - auto* count = Var("count", ty.u32()); - auto* call = Call("insertBits", v, n, offset, count); - WrapInFunction(v, n, offset, count, call); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -uvec3 tint_insert_bits(uvec3 v, uvec3 n, uint offset, uint count) { - uint s = min(offset, 32u); - uint e = min(32u, (s + count)); - return bitfieldInsert(v, n, int(s), int((e - s))); -} - -void test_function() { - uvec3 v = uvec3(0u, 0u, 0u); - uvec3 n = uvec3(0u, 0u, 0u); - uint offset = 0u; - uint count = 0u; - uvec3 tint_symbol = tint_insert_bits(v, n, offset, count); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Pack4x8Snorm) { - auto* call = Call("pack4x8snorm", "p1"); - GlobalVar("p1", ty.vec4(), core::AddressSpace::kPrivate); - WrapInFunction(Decl(Var("r", call))); - ASTPrinter& gen = Build(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -vec4 p1 = vec4(0.0f, 0.0f, 0.0f, 0.0f); -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void test_function() { - uint r = packSnorm4x8(p1); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Pack4x8Unorm) { - auto* call = Call("pack4x8unorm", "p1"); - GlobalVar("p1", ty.vec4(), core::AddressSpace::kPrivate); - WrapInFunction(Decl(Var("r", call))); - ASTPrinter& gen = Build(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -vec4 p1 = vec4(0.0f, 0.0f, 0.0f, 0.0f); -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void test_function() { - uint r = packUnorm4x8(p1); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Pack2x16Snorm) { - auto* call = Call("pack2x16snorm", "p1"); - GlobalVar("p1", ty.vec2(), core::AddressSpace::kPrivate); - WrapInFunction(Decl(Var("r", call))); - ASTPrinter& gen = Build(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -vec2 p1 = vec2(0.0f, 0.0f); -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void test_function() { - uint r = packSnorm2x16(p1); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Pack2x16Unorm) { - auto* call = Call("pack2x16unorm", "p1"); - GlobalVar("p1", ty.vec2(), core::AddressSpace::kPrivate); - WrapInFunction(Decl(Var("r", call))); - ASTPrinter& gen = Build(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -vec2 p1 = vec2(0.0f, 0.0f); -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void test_function() { - uint r = packUnorm2x16(p1); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Pack2x16Float) { - auto* call = Call("pack2x16float", "p1"); - GlobalVar("p1", ty.vec2(), core::AddressSpace::kPrivate); - WrapInFunction(Decl(Var("r", call))); - ASTPrinter& gen = Build(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -vec2 p1 = vec2(0.0f, 0.0f); -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void test_function() { - uint r = packHalf2x16(p1); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Unpack4x8Snorm) { - auto* call = Call("unpack4x8snorm", "p1"); - GlobalVar("p1", ty.u32(), core::AddressSpace::kPrivate); - WrapInFunction(Decl(Var("r", call))); - ASTPrinter& gen = Build(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -uint p1 = 0u; -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void test_function() { - vec4 r = unpackSnorm4x8(p1); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Unpack4x8Unorm) { - auto* call = Call("unpack4x8unorm", "p1"); - GlobalVar("p1", ty.u32(), core::AddressSpace::kPrivate); - WrapInFunction(Decl(Var("r", call))); - ASTPrinter& gen = Build(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -uint p1 = 0u; -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void test_function() { - vec4 r = unpackUnorm4x8(p1); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Unpack2x16Snorm) { - auto* call = Call("unpack2x16snorm", "p1"); - GlobalVar("p1", ty.u32(), core::AddressSpace::kPrivate); - WrapInFunction(Decl(Var("r", call))); - ASTPrinter& gen = Build(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -uint p1 = 0u; -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void test_function() { - vec2 r = unpackSnorm2x16(p1); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Unpack2x16Unorm) { - auto* call = Call("unpack2x16unorm", "p1"); - GlobalVar("p1", ty.u32(), core::AddressSpace::kPrivate); - WrapInFunction(Decl(Var("r", call))); - ASTPrinter& gen = Build(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -uint p1 = 0u; -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void test_function() { - vec2 r = unpackUnorm2x16(p1); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, Unpack2x16Float) { - auto* call = Call("unpack2x16float", "p1"); - GlobalVar("p1", ty.u32(), core::AddressSpace::kPrivate); - WrapInFunction(Decl(Var("r", call))); - ASTPrinter& gen = Build(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -uint p1 = 0u; -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void test_function() { - vec2 r = unpackHalf2x16(p1); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, StorageBarrier) { - Func("main", tint::Empty, ty.void_(), - Vector{ - CallStmt(Call("storageBarrier")), - }, - Vector{ - Stage(ast::PipelineStage::kCompute), - WorkgroupSize(1_i), - }); - - ASTPrinter& gen = Build(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - { barrier(); memoryBarrierBuffer(); }; - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, WorkgroupBarrier) { - Func("main", tint::Empty, ty.void_(), - Vector{ - CallStmt(Call("workgroupBarrier")), - }, - Vector{ - Stage(ast::PipelineStage::kCompute), - WorkgroupSize(1_i), - }); - - ASTPrinter& gen = Build(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - barrier(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, DotI32) { - GlobalVar("v", ty.vec3(), core::AddressSpace::kPrivate); - WrapInFunction(Decl(Var("r", Call("dot", "v", "v")))); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -int tint_int_dot(ivec3 a, ivec3 b) { - return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; -} - -ivec3 v = ivec3(0, 0, 0); -void test_function() { - int r = tint_int_dot(v, v); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, DotU32) { - GlobalVar("v", ty.vec3(), core::AddressSpace::kPrivate); - WrapInFunction(Decl(Var("r", Call("dot", "v", "v")))); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -uint tint_int_dot(uvec3 a, uvec3 b) { - return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; -} - -uvec3 v = uvec3(0u, 0u, 0u); -void test_function() { - uint r = tint_int_dot(v, v); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, QuantizeToF16_Scalar) { - GlobalVar("v", Expr(2_f), core::AddressSpace::kPrivate); - WrapInFunction(Call("quantizeToF16", "v")); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -float tint_quantizeToF16(float param_0) { - return unpackHalf2x16(packHalf2x16(vec2(param_0))).x; -} - - -float v = 2.0f; -void test_function() { - float tint_symbol = tint_quantizeToF16(v); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, QuantizeToF16_Vec2) { - GlobalVar("v", Call>(2_f), core::AddressSpace::kPrivate); - WrapInFunction(Call("quantizeToF16", "v")); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -vec2 tint_quantizeToF16(vec2 param_0) { - return unpackHalf2x16(packHalf2x16(param_0)); -} - - -vec2 v = vec2(2.0f); -void test_function() { - vec2 tint_symbol = tint_quantizeToF16(v); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, QuantizeToF16_Vec3) { - GlobalVar("v", Call>(2_f), core::AddressSpace::kPrivate); - WrapInFunction(Call("quantizeToF16", "v")); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -vec3 tint_quantizeToF16(vec3 param_0) { - return vec3( - unpackHalf2x16(packHalf2x16(param_0.xy)), - unpackHalf2x16(packHalf2x16(param_0.zz)).x); -} - - -vec3 v = vec3(2.0f); -void test_function() { - vec3 tint_symbol = tint_quantizeToF16(v); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Builtin, QuantizeToF16_Vec4) { - GlobalVar("v", Call>(2_f), core::AddressSpace::kPrivate); - WrapInFunction(Call("quantizeToF16", "v")); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -vec4 tint_quantizeToF16(vec4 param_0) { - return vec4( - unpackHalf2x16(packHalf2x16(param_0.xy)), - unpackHalf2x16(packHalf2x16(param_0.zw))); -} - - -vec4 v = vec4(2.0f); -void test_function() { - vec4 tint_symbol = tint_quantizeToF16(v); -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/builtin_texture_test.cc b/references/writer/ast_printer/builtin_texture_test.cc deleted file mode 100644 index b384c75c..00000000 --- a/references/writer/ast_printer/builtin_texture_test.cc +++ /dev/null @@ -1,318 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "gmock/gmock.h" -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" -#include "src/tint/lang/wgsl/ast/builtin_texture_helper_test.h" -#include "src/tint/lang/wgsl/ast/call_statement.h" -#include "src/tint/lang/wgsl/ast/stage_attribute.h" - -namespace tint::glsl::writer { -namespace { - -using ::testing::HasSubstr; - -struct ExpectedResult { - ExpectedResult(const char* o) : out(o) {} // NOLINT - - std::string pre; - std::string out; -}; - -ExpectedResult expected_texture_overload(ast::test::ValidTextureOverload overload) { - using ValidTextureOverload = ast::test::ValidTextureOverload; - switch (overload) { - case ValidTextureOverload::kDimensions1d: - case ValidTextureOverload::kDimensions2d: - case ValidTextureOverload::kDimensionsDepth2d: - case ValidTextureOverload::kDimensionsDepthMultisampled2d: - case ValidTextureOverload::kDimensionsMultisampled2d: - case ValidTextureOverload::kDimensions2dArray: - case ValidTextureOverload::kDimensionsDepth2dArray: - case ValidTextureOverload::kDimensions3d: - case ValidTextureOverload::kDimensionsCube: - case ValidTextureOverload::kDimensionsDepthCube: - case ValidTextureOverload::kDimensionsCubeArray: - case ValidTextureOverload::kDimensionsDepthCubeArray: - case ValidTextureOverload::kDimensions2dLevel: - case ValidTextureOverload::kDimensionsDepth2dLevel: - case ValidTextureOverload::kDimensions2dArrayLevel: - case ValidTextureOverload::kDimensionsDepth2dArrayLevel: - case ValidTextureOverload::kDimensions3dLevel: - case ValidTextureOverload::kDimensionsCubeLevel: - case ValidTextureOverload::kDimensionsDepthCubeLevel: - case ValidTextureOverload::kDimensionsCubeArrayLevel: - case ValidTextureOverload::kDimensionsDepthCubeArrayLevel: - return {"textureSize"}; - case ValidTextureOverload::kDimensionsStorageWO1d: - case ValidTextureOverload::kDimensionsStorageWO2d: - case ValidTextureOverload::kDimensionsStorageWO2dArray: - case ValidTextureOverload::kDimensionsStorageWO3d: - return {"imageSize"}; - case ValidTextureOverload::kGather2dF32: - return R"(textureGather(Texture_Sampler, vec2(1.0f, 2.0f), 0))"; - case ValidTextureOverload::kGather2dOffsetF32: - return R"(textureGatherOffset(Texture_Sampler, vec2(1.0f, 2.0f), ivec2(3, 4), int(0u)))"; - case ValidTextureOverload::kGather2dArrayF32: - return R"(textureGather(Texture_Sampler, vec3(1.0f, 2.0f, float(3)), 0))"; - case ValidTextureOverload::kGather2dArrayOffsetF32: - return R"(textureGatherOffset(Texture_Sampler, vec3(1.0f, 2.0f, float(3u)), ivec2(4, 5), int(0u)))"; - case ValidTextureOverload::kGatherCubeF32: - return R"(textureGather(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), 0))"; - case ValidTextureOverload::kGatherCubeArrayF32: - return R"(textureGather(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, float(4u)), int(0u))"; - case ValidTextureOverload::kGatherDepth2dF32: - return R"(textureGather(Texture_Sampler, vec2(1.0f, 2.0f), 0.0))"; - case ValidTextureOverload::kGatherDepth2dOffsetF32: - return R"(textureGatherOffset(Texture_Sampler, vec2(1.0f, 2.0f), 0.0, ivec2(3, 4))"; - case ValidTextureOverload::kGatherDepth2dArrayF32: - return R"(textureGather(Texture_Sampler, vec3(1.0f, 2.0f, float(3u)), 0.0))"; - case ValidTextureOverload::kGatherDepth2dArrayOffsetF32: - return R"(textureGatherOffset(Texture_Sampler, vec3(1.0f, 2.0f, float(3)), 0.0, ivec2(4, 5)))"; - case ValidTextureOverload::kGatherDepthCubeF32: - return R"(textureGather(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), 0.0))"; - case ValidTextureOverload::kGatherDepthCubeArrayF32: - return R"(textureGather(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, float(4u)), 0.0))"; - case ValidTextureOverload::kGatherCompareDepth2dF32: - return R"(textureGather(Texture_Sampler, vec2(1.0f, 2.0f), 3.0f))"; - case ValidTextureOverload::kGatherCompareDepth2dOffsetF32: - return R"(textureGatherOffset(Texture_Sampler, vec2(1.0f, 2.0f), 3.0f, ivec2(4, 5)))"; - case ValidTextureOverload::kGatherCompareDepth2dArrayF32: - return R"(textureGather(Texture_Sampler, vec3(1.0f, 2.0f, float(3)), 4.0f))"; - case ValidTextureOverload::kGatherCompareDepth2dArrayOffsetF32: - return R"(textureGatherOffset(Texture_Sampler, vec3(1.0f, 2.0f, float(3)), 4.0f, ivec2(5, 6)))"; - case ValidTextureOverload::kGatherCompareDepthCubeF32: - return R"(textureGather(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f))"; - case ValidTextureOverload::kGatherCompareDepthCubeArrayF32: - return R"(textureGather(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, float(4u)), 5.0f))"; - case ValidTextureOverload::kNumLayers2dArray: - case ValidTextureOverload::kNumLayersDepth2dArray: - case ValidTextureOverload::kNumLayersCubeArray: - case ValidTextureOverload::kNumLayersDepthCubeArray: - return {"textureSize"}; - case ValidTextureOverload::kNumLayersStorageWO2dArray: - return {"imageSize"}; - case ValidTextureOverload::kNumLevels2d: - case ValidTextureOverload::kNumLevelsCube: - case ValidTextureOverload::kNumLevelsDepth2d: - case ValidTextureOverload::kNumLevelsDepthCube: - case ValidTextureOverload::kNumLevels2dArray: - case ValidTextureOverload::kNumLevels3d: - case ValidTextureOverload::kNumLevelsCubeArray: - case ValidTextureOverload::kNumLevelsDepth2dArray: - case ValidTextureOverload::kNumLevelsDepthCubeArray: - return {"textureQueryLevels"}; - case ValidTextureOverload::kNumSamplesDepthMultisampled2d: - case ValidTextureOverload::kNumSamplesMultisampled2d: - return {"textureSamples"}; - case ValidTextureOverload::kSample1dF32: - return R"(texture(Texture_Sampler, vec2(1.0f, 0.5f));)"; - case ValidTextureOverload::kSample2dF32: - return R"(texture(Texture_Sampler, vec2(1.0f, 2.0f));)"; - case ValidTextureOverload::kSample2dOffsetF32: - return R"(textureOffset(Texture_Sampler, vec2(1.0f, 2.0f), ivec2(3, 4));)"; - case ValidTextureOverload::kSample2dArrayF32: - return R"(texture(Texture_Sampler, vec3(1.0f, 2.0f, float(3)));)"; - case ValidTextureOverload::kSample2dArrayOffsetF32: - return R"(textureOffset(Texture_Sampler, vec3(1.0f, 2.0f, float(3u)), ivec2(4, 5));)"; - case ValidTextureOverload::kSample3dF32: - return R"(texture(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f));)"; - case ValidTextureOverload::kSample3dOffsetF32: - return R"(textureOffset(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), ivec3(4, 5, 6));)"; - case ValidTextureOverload::kSampleCubeF32: - return R"(texture(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f));)"; - case ValidTextureOverload::kSampleCubeArrayF32: - return R"(texture(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, float(4)));)"; - case ValidTextureOverload::kSampleDepth2dF32: - return R"(texture(Texture_Sampler, vec3(1.0f, 2.0f, 0.0f));)"; - case ValidTextureOverload::kSampleDepth2dOffsetF32: - return R"(textureOffset(Texture_Sampler, vec3(1.0f, 2.0f, 0.0f), ivec2(3, 4));)"; - case ValidTextureOverload::kSampleDepth2dArrayF32: - return R"(texture(Texture_Sampler, vec4(1.0f, 2.0f, float(3), 0.0f));)"; - case ValidTextureOverload::kSampleDepth2dArrayOffsetF32: - return R"(textureOffset(Texture_Sampler, vec4(1.0f, 2.0f, float(3), 0.0f), ivec2(4, 5));)"; - case ValidTextureOverload::kSampleDepthCubeF32: - return R"(texture(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, 0.0f));)"; - case ValidTextureOverload::kSampleDepthCubeArrayF32: - return R"(texture(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, float(4u)), 0.0f);)"; - case ValidTextureOverload::kSampleBias2dF32: - return R"(texture(Texture_Sampler, vec2(1.0f, 2.0f), 3.0f);)"; - case ValidTextureOverload::kSampleBias2dOffsetF32: - return R"(textureOffset(Texture_Sampler, vec2(1.0f, 2.0f), ivec2(4, 5), 3.0f);)"; - case ValidTextureOverload::kSampleBias2dArrayF32: - return R"(texture(Texture_Sampler, vec3(1.0f, 2.0f, float(4u)), 3.0f);)"; - case ValidTextureOverload::kSampleBias2dArrayOffsetF32: - return R"(textureOffset(Texture_Sampler, vec3(1.0f, 2.0f, float(3)), ivec2(5, 6), 4.0f);)"; - case ValidTextureOverload::kSampleBias3dF32: - return R"(texture(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f);)"; - case ValidTextureOverload::kSampleBias3dOffsetF32: - return R"(textureOffset(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), ivec3(5, 6, 7), 4.0f);)"; - case ValidTextureOverload::kSampleBiasCubeF32: - return R"(texture(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f);)"; - case ValidTextureOverload::kSampleBiasCubeArrayF32: - return R"(texture(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, float(3)), 4.0f);)"; - case ValidTextureOverload::kSampleLevel2dF32: - return R"(textureLod(Texture_Sampler, vec2(1.0f, 2.0f), 3.0f);)"; - case ValidTextureOverload::kSampleLevel2dOffsetF32: - return R"(textureLodOffset(Texture_Sampler, vec2(1.0f, 2.0f), 3.0f, ivec2(4, 5));)"; - case ValidTextureOverload::kSampleLevel2dArrayF32: - return R"(textureLod(Texture_Sampler, vec3(1.0f, 2.0f, float(3)), 4.0f);)"; - case ValidTextureOverload::kSampleLevel2dArrayOffsetF32: - return R"(textureLodOffset(Texture_Sampler, vec3(1.0f, 2.0f, float(3)), 4.0f, ivec2(5, 6));)"; - case ValidTextureOverload::kSampleLevel3dF32: - return R"(textureLod(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f);)"; - case ValidTextureOverload::kSampleLevel3dOffsetF32: - return R"(textureLodOffset(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f, ivec3(5, 6, 7));)"; - case ValidTextureOverload::kSampleLevelCubeF32: - return R"(textureLod(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), 4.0f);)"; - case ValidTextureOverload::kSampleLevelCubeArrayF32: - return R"(textureLod(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), 5.0f);)"; - case ValidTextureOverload::kSampleLevelDepth2dF32: - return R"(textureLod(Texture_Sampler, vec3(1.0f, 2.0f, 0.0f), float(3u));)"; - case ValidTextureOverload::kSampleLevelDepth2dOffsetF32: - return R"(textureLodOffset(Texture_Sampler, vec3(1.0f, 2.0f, 0.0f), float(3), ivec2(4, 5));)"; - case ValidTextureOverload::kSampleLevelDepth2dArrayF32: - return R"(textureLod(Texture_Sampler, vec4(1.0f, 2.0f, float(3u), 0.0f), float(4u));)"; - case ValidTextureOverload::kSampleLevelDepth2dArrayOffsetF32: - return R"(textureLodOffset(Texture_Sampler, vec4(1.0f, 2.0f, float(3u), 0.0f), float(4u), ivec2(5, 6));)"; - case ValidTextureOverload::kSampleLevelDepthCubeF32: - return R"(textureLod(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, 0.0f), float(4)))"; - case ValidTextureOverload::kSampleLevelDepthCubeArrayF32: - return R"(textureLod(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), float(5));)"; - case ValidTextureOverload::kSampleGrad2dF32: - return R"(textureGrad(Texture_Sampler, vec2(1.0f, 2.0f), vec2(3.0f, 4.0f), vec2(5.0f, 6.0f));)"; - case ValidTextureOverload::kSampleGrad2dOffsetF32: - return R"(textureGradOffset(Texture_Sampler, vec2(1.0f, 2.0f), vec2(3.0f, 4.0f), vec2(5.0f, 6.0f), ivec2(7));)"; - case ValidTextureOverload::kSampleGrad2dArrayF32: - return R"(textureGrad(Texture_Sampler, vec3(1.0f, 2.0f, float(3)), vec2(4.0f, 5.0f), vec2(6.0f, 7.0f));)"; - case ValidTextureOverload::kSampleGrad2dArrayOffsetF32: - return R"(textureGradOffset(Texture_Sampler, vec3(1.0f, 2.0f, float(3u)), vec2(4.0f, 5.0f), vec2(6.0f, 7.0f), ivec2(6, 7));)"; - case ValidTextureOverload::kSampleGrad3dF32: - return R"(textureGrad(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f), vec3(7.0f, 8.0f, 9.0f));)"; - case ValidTextureOverload::kSampleGrad3dOffsetF32: - return R"(textureGradOffset(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f), vec3(7.0f, 8.0f, 9.0f), ivec3(0, 1, 2));)"; - case ValidTextureOverload::kSampleGradCubeF32: - return R"(textureGrad(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f), vec3(7.0f, 8.0f, 9.0f));)"; - case ValidTextureOverload::kSampleGradCubeArrayF32: - return R"(textureGrad(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, float(4u)), vec3(5.0f, 6.0f, 7.0f), vec3(8.0f, 9.0f, 10.0f));)"; - case ValidTextureOverload::kSampleCompareDepth2dF32: - return R"(texture(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f));)"; - case ValidTextureOverload::kSampleCompareDepth2dOffsetF32: - return R"(textureOffset(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), ivec2(4, 5));)"; - case ValidTextureOverload::kSampleCompareDepth2dArrayF32: - return R"(texture(Texture_Sampler, vec4(1.0f, 2.0f, float(4), 3.0f));)"; - case ValidTextureOverload::kSampleCompareDepth2dArrayOffsetF32: - return R"(textureOffset(Texture_Sampler, vec4(1.0f, 2.0f, float(4u), 3.0f), ivec2(5, 6));)"; - case ValidTextureOverload::kSampleCompareDepthCubeF32: - return R"(texture(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, 4.0f));)"; - case ValidTextureOverload::kSampleCompareDepthCubeArrayF32: - return R"(texture(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), 5.0f);)"; - case ValidTextureOverload::kSampleCompareLevelDepth2dF32: - return R"(texture(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f));)"; - case ValidTextureOverload::kSampleCompareLevelDepth2dOffsetF32: - return R"(textureOffset(Texture_Sampler, vec3(1.0f, 2.0f, 3.0f), ivec2(4, 5));)"; - case ValidTextureOverload::kSampleCompareLevelDepth2dArrayF32: - return R"(texture(Texture_Sampler, vec4(1.0f, 2.0f, float(3), 4.0f));)"; - case ValidTextureOverload::kSampleCompareLevelDepth2dArrayOffsetF32: - return R"(textureOffset(Texture_Sampler, vec4(1.0f, 2.0f, float(3), 4.0f), ivec2(5, 6));)"; - case ValidTextureOverload::kSampleCompareLevelDepthCubeF32: - return R"(texture(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, 4.0f));)"; - case ValidTextureOverload::kSampleCompareLevelDepthCubeArrayF32: - return R"(texture(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, float(4)), 5.0f);)"; - case ValidTextureOverload::kLoad1dLevelF32: - return R"(texelFetch(Texture_1, ivec2(uvec2(1u, 0u)), int(3u));)"; - case ValidTextureOverload::kLoad1dLevelU32: - case ValidTextureOverload::kLoad1dLevelI32: - return R"(texelFetch(Texture_1, ivec2(1, 0), 3);)"; - case ValidTextureOverload::kLoad2dLevelU32: - return R"(texelFetch(Texture_1, ivec2(1, 2), 3);)"; - case ValidTextureOverload::kLoad2dLevelF32: - case ValidTextureOverload::kLoad2dLevelI32: - return R"(texelFetch(Texture_1, ivec2(uvec2(1u, 2u)), int(3u));)"; - case ValidTextureOverload::kLoad2dArrayLevelF32: - case ValidTextureOverload::kLoad2dArrayLevelU32: - case ValidTextureOverload::kLoad3dLevelF32: - case ValidTextureOverload::kLoad3dLevelU32: - return R"(texelFetch(Texture_1, ivec3(1, 2, 3), 4);)"; - case ValidTextureOverload::kLoad2dArrayLevelI32: - case ValidTextureOverload::kLoad3dLevelI32: - return R"(texelFetch(Texture_1, ivec3(uvec3(1u, 2u, 3u)), int(4u));)"; - case ValidTextureOverload::kLoadMultisampled2dF32: - case ValidTextureOverload::kLoadMultisampled2dU32: - return R"(texelFetch(Texture_1, ivec2(1, 2), 3);)"; - case ValidTextureOverload::kLoadMultisampled2dI32: - return R"(texelFetch(Texture_1, ivec2(uvec2(1u, 2u)), int(3u));)"; - case ValidTextureOverload::kLoadDepth2dLevelF32: - return R"(texelFetch(Texture_1, ivec2(1, 2), 3).x;)"; - case ValidTextureOverload::kLoadDepth2dArrayLevelF32: - return R"(texelFetch(Texture_1, ivec3(uvec3(1u, 2u, 3u)), int(4u)).x;)"; - case ValidTextureOverload::kLoadDepthMultisampled2dF32: - return R"(texelFetch(Texture_1, ivec2(uvec2(1u, 2u)), int(3u)).x;)"; - case ValidTextureOverload::kStoreWO1dRgba32float: - return R"(imageStore(Texture, ivec2(1, 0), vec4(2.0f, 3.0f, 4.0f, 5.0f));)"; - case ValidTextureOverload::kStoreWO2dRgba32float: - return R"(imageStore(Texture, ivec2(1, 2), vec4(3.0f, 4.0f, 5.0f, 6.0f));)"; - case ValidTextureOverload::kStoreWO2dArrayRgba32float: - return R"(imageStore(Texture, ivec3(uvec3(1u, 2u, 3u)), vec4(4.0f, 5.0f, 6.0f, 7.0f));)"; - case ValidTextureOverload::kStoreWO3dRgba32float: - return R"(imageStore(Texture, ivec3(uvec3(1u, 2u, 3u)), vec4(4.0f, 5.0f, 6.0f, 7.0f));)"; - } - return ""; -} // NOLINT - Ignore the length of this function - -class GlslGeneratorBuiltinTextureTest : public TestParamHelper {}; - -TEST_P(GlslGeneratorBuiltinTextureTest, Call) { - auto param = GetParam(); - - param.BuildTextureVariable(this); - param.BuildSamplerVariable(this); - - auto* call = Call(param.function, param.args(this)); - auto* stmt = param.returns_value ? static_cast(Decl(Var("v", call))) - : static_cast(CallStmt(call)); - - Func("main", tint::Empty, ty.void_(), Vector{stmt}, - Vector{Stage(ast::PipelineStage::kFragment)}); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - - auto expected = expected_texture_overload(param.overload); - - EXPECT_THAT(gen.Result(), HasSubstr(expected.pre)); - EXPECT_THAT(gen.Result(), HasSubstr(expected.out)); -} - -INSTANTIATE_TEST_SUITE_P(GlslGeneratorBuiltinTextureTest, - GlslGeneratorBuiltinTextureTest, - testing::ValuesIn(ast::test::TextureOverloadCase::ValidCases())); - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/call_test.cc b/references/writer/ast_printer/call_test.cc deleted file mode 100644 index ee6addd8..00000000 --- a/references/writer/ast_printer/call_test.cc +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" -#include "src/tint/lang/wgsl/ast/call_statement.h" -#include "src/tint/utils/text/string_stream.h" - -#include "gmock/gmock.h" - -using namespace tint::core::number_suffixes; // NOLINT - -namespace tint::glsl::writer { -namespace { - -using GlslASTPrinterTest_Call = TestHelper; - -TEST_F(GlslASTPrinterTest_Call, EmitExpression_Call_WithoutParams) { - Func("my_func", tint::Empty, ty.f32(), Vector{Return(1.23_f)}); - - auto* call = Call("my_func"); - WrapInFunction(call); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, call); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "my_func()"); -} - -TEST_F(GlslASTPrinterTest_Call, EmitExpression_Call_WithParams) { - Func("my_func", - Vector{ - Param(Sym(), ty.f32()), - Param(Sym(), ty.f32()), - }, - ty.f32(), Vector{Return(1.23_f)}); - GlobalVar("param1", ty.f32(), core::AddressSpace::kPrivate); - GlobalVar("param2", ty.f32(), core::AddressSpace::kPrivate); - - auto* call = Call("my_func", "param1", "param2"); - WrapInFunction(call); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, call); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "my_func(param1, param2)"); -} - -TEST_F(GlslASTPrinterTest_Call, EmitStatement_Call) { - Func("my_func", - Vector{ - Param(Sym(), ty.f32()), - Param(Sym(), ty.f32()), - }, - ty.void_(), tint::Empty, tint::Empty); - GlobalVar("param1", ty.f32(), core::AddressSpace::kPrivate); - GlobalVar("param2", ty.f32(), core::AddressSpace::kPrivate); - - auto* call = CallStmt(Call("my_func", "param1", "param2")); - WrapInFunction(call); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitStatement(call); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), " my_func(param1, param2);\n"); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/case_test.cc b/references/writer/ast_printer/case_test.cc deleted file mode 100644 index f6707d7d..00000000 --- a/references/writer/ast_printer/case_test.cc +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" - -#include "gmock/gmock.h" - -using namespace tint::core::number_suffixes; // NOLINT - -namespace tint::glsl::writer { -namespace { - -using GlslASTPrinterTest_Case = TestHelper; - -TEST_F(GlslASTPrinterTest_Case, Emit_Case) { - auto* s = - Switch(1_i, Case(CaseSelector(5_i), Block(create())), DefaultCase()); - WrapInFunction(s); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitCase(s->body[0]); - EXPECT_EQ(gen.Result(), R"( case 5: { - break; - } -)"); -} - -TEST_F(GlslASTPrinterTest_Case, Emit_Case_BreaksByDefault) { - auto* s = Switch(1_i, Case(CaseSelector(5_i), Block()), DefaultCase()); - WrapInFunction(s); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitCase(s->body[0]); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( case 5: { - break; - } -)"); -} - -TEST_F(GlslASTPrinterTest_Case, Emit_Case_MultipleSelectors) { - auto* s = Switch(1_i, - Case( - Vector{ - CaseSelector(5_i), - CaseSelector(6_i), - }, - Block(create())), - DefaultCase()); - WrapInFunction(s); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitCase(s->body[0]); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( case 5: - case 6: { - break; - } -)"); -} - -TEST_F(GlslASTPrinterTest_Case, Emit_Case_Default) { - auto* s = Switch(1_i, DefaultCase(Block(create()))); - WrapInFunction(s); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitCase(s->body[0]); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( default: { - break; - } -)"); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/cast_test.cc b/references/writer/ast_printer/cast_test.cc deleted file mode 100644 index c2926b66..00000000 --- a/references/writer/ast_printer/cast_test.cc +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" -#include "src/tint/utils/text/string_stream.h" - -#include "gmock/gmock.h" - -namespace tint::glsl::writer { -namespace { - -using namespace tint::core::fluent_types; // NOLINT -using namespace tint::core::number_suffixes; // NOLINT - -using GlslASTPrinterTest_Cast = TestHelper; - -TEST_F(GlslASTPrinterTest_Cast, EmitExpression_Cast_Scalar) { - auto* cast = Call(1_i); - WrapInFunction(cast); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, cast); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "1.0f"); -} - -TEST_F(GlslASTPrinterTest_Cast, EmitExpression_Cast_Vector) { - auto* cast = Call>(Call>(1_i, 2_i, 3_i)); - WrapInFunction(cast); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, cast); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "vec3(1.0f, 2.0f, 3.0f)"); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/constructor_test.cc b/references/writer/ast_printer/constructor_test.cc deleted file mode 100644 index fd694823..00000000 --- a/references/writer/ast_printer/constructor_test.cc +++ /dev/null @@ -1,437 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "gmock/gmock.h" -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" - -namespace tint::glsl::writer { -namespace { - -using ::testing::HasSubstr; -using namespace tint::core::fluent_types; // NOLINT -using namespace tint::core::number_suffixes; // NOLINT - -using GlslASTPrinterTest_Constructor = TestHelper; - -TEST_F(GlslASTPrinterTest_Constructor, Bool) { - WrapInFunction(Expr(false)); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("false")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Int) { - WrapInFunction(Expr(-12345_i)); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("-12345")); -} - -TEST_F(GlslASTPrinterTest_Constructor, UInt) { - WrapInFunction(Expr(56779_u)); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("56779u")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Float) { - // Use a number close to 1<<30 but whose decimal representation ends in 0. - WrapInFunction(Expr(f32((1 << 30) - 4))); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("1073741824.0f")); -} - -TEST_F(GlslASTPrinterTest_Constructor, F16) { - Enable(wgsl::Extension::kF16); - - // Use a number close to 1<<16 but whose decimal representation ends in 0. - WrapInFunction(Expr(f16((1 << 15) - 8))); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("32752.0hf")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_Float) { - WrapInFunction(Call(-1.2e-5_f)); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("-0.00001200000042445026f")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_F16) { - Enable(wgsl::Extension::kF16); - - WrapInFunction(Call(-1.2e-3_h)); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("-0.0011997222900390625hf")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_Bool) { - WrapInFunction(Call(true)); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("true")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_Int) { - WrapInFunction(Call(-12345_i)); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("-12345")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_Uint) { - WrapInFunction(Call(12345_u)); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("12345u")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_Vec_F32) { - WrapInFunction(Call>(1_f, 2_f, 3_f)); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("vec3(1.0f, 2.0f, 3.0f)")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_Vec_F16) { - Enable(wgsl::Extension::kF16); - - WrapInFunction(Call>(1_h, 2_h, 3_h)); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("f16vec3(1.0hf, 2.0hf, 3.0hf)")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_Vec_Empty_F32) { - WrapInFunction(Call>()); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("vec3(0.0f)")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_Vec_Empty_F16) { - Enable(wgsl::Extension::kF16); - - WrapInFunction(Call>()); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("f16vec3(0.0hf)")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_Vec_SingleScalar_F32_Literal) { - WrapInFunction(Call>(2_f)); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("vec3(2.0f)")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_Vec_SingleScalar_F16_Literal) { - Enable(wgsl::Extension::kF16); - - WrapInFunction(Call>(2_h)); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("f16vec3(2.0hf)")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_Vec_SingleScalar_F32_Var) { - auto* var = Var("v", Expr(2_f)); - auto* cast = Call>(var); - WrapInFunction(var, cast); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr(R"(float v = 2.0f; - vec3 tint_symbol = vec3(v);)")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_Vec_SingleScalar_F16_Var) { - Enable(wgsl::Extension::kF16); - - auto* var = Var("v", Expr(2_h)); - auto* cast = Call>(var); - WrapInFunction(var, cast); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr(R"(float16_t v = 2.0hf; - f16vec3 tint_symbol = f16vec3(v);)")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_Vec_SingleScalar_Bool) { - WrapInFunction(Call>(true)); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("bvec3(true)")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_Vec_SingleScalar_Int) { - WrapInFunction(Call>(2_i)); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("ivec3(2)")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_Vec_SingleScalar_UInt) { - WrapInFunction(Call>(2_u)); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("uvec3(2u)")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_Mat_F32) { - WrapInFunction( - Call>(Call>(1_f, 2_f, 3_f), Call>(3_f, 4_f, 5_f))); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("mat2x3(vec3(1.0f, 2.0f, 3.0f), vec3(3.0f, 4.0f, 5.0f))")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_Mat_F16) { - Enable(wgsl::Extension::kF16); - - WrapInFunction( - Call>(Call>(1_h, 2_h, 3_h), Call>(3_h, 4_h, 5_h))); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), - HasSubstr("f16mat2x3(f16vec3(1.0hf, 2.0hf, 3.0hf), f16vec3(3.0hf, 4.0hf, 5.0hf))")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_Mat_Complex_F32) { - // mat4x4( - // vec4(2.0f, 3.0f, 4.0f, 8.0f), - // vec4(), - // vec4(7.0f), - // vec4(vec4(42.0f, 21.0f, 6.0f, -5.0f)), - // ); - auto* vector_literal = - Call>(Expr(f32(2.0)), Expr(f32(3.0)), Expr(f32(4.0)), Expr(f32(8.0))); - auto* vector_zero_init = Call>(); - auto* vector_single_scalar_init = Call>(Expr(f32(7.0))); - auto* vector_identical_init = Call>( - Call>(Expr(f32(42.0)), Expr(f32(21.0)), Expr(f32(6.0)), Expr(f32(-5.0)))); - - auto* ctor = Call>(vector_literal, vector_zero_init, vector_single_scalar_init, - vector_identical_init); - - WrapInFunction(ctor); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("mat4(vec4(2.0f, 3.0f, 4.0f, 8.0f), vec4(0.0f), " - "vec4(7.0f), vec4(42.0f, 21.0f, 6.0f, -5.0f))")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_Mat_Complex_F16) { - // mat4x4( - // vec4(2.0h, 3.0h, 4.0h, 8.0h), - // vec4(), - // vec4(7.0h), - // vec4(vec4(42.0h, 21.0h, 6.0h, -5.0h)), - // ); - Enable(wgsl::Extension::kF16); - - auto* vector_literal = - Call>(Expr(f16(2.0)), Expr(f16(3.0)), Expr(f16(4.0)), Expr(f16(8.0))); - auto* vector_zero_init = Call>(); - auto* vector_single_scalar_init = Call>(Expr(f16(7.0))); - auto* vector_identical_init = Call>( - Call>(Expr(f16(42.0)), Expr(f16(21.0)), Expr(f16(6.0)), Expr(f16(-5.0)))); - - auto* ctor = Call>(vector_literal, vector_zero_init, vector_single_scalar_init, - vector_identical_init); - - WrapInFunction(ctor); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), - HasSubstr("f16mat4(f16vec4(2.0hf, 3.0hf, 4.0hf, 8.0hf), f16vec4(0.0hf), " - "f16vec4(7.0hf), f16vec4(42.0hf, 21.0hf, 6.0hf, -5.0hf))")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_Mat_Empty_F32) { - WrapInFunction(Call>()); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("mat2x3 tint_symbol = mat2x3(vec3(0.0f), vec3(0.0f))")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_Mat_Empty_F16) { - Enable(wgsl::Extension::kF16); - - WrapInFunction(Call>()); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), - HasSubstr("f16mat2x3 tint_symbol = f16mat2x3(f16vec3(0.0hf), f16vec3(0.0hf))")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_Mat_Identity_F32) { - // fn f() { - // var m_1: mat4x4 = mat4x4(); - // var m_2: mat4x4 = mat4x4(m_1); - // } - - auto* m_1 = Var("m_1", ty.mat4x4(ty.f32()), Call>()); - auto* m_2 = Var("m_2", ty.mat4x4(ty.f32()), Call>(m_1)); - - WrapInFunction(m_1, m_2); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("mat4 m_2 = mat4(m_1);")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_Mat_Identity_F16) { - // fn f() { - // var m_1: mat4x4 = mat4x4(); - // var m_2: mat4x4 = mat4x4(m_1); - // } - - Enable(wgsl::Extension::kF16); - - auto* m_1 = Var("m_1", ty.mat4x4(ty.f16()), Call>()); - auto* m_2 = Var("m_2", ty.mat4x4(ty.f16()), Call>(m_1)); - - WrapInFunction(m_1, m_2); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("f16mat4 m_2 = f16mat4(m_1);")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_Array) { - WrapInFunction(Call, 3>>(Call>(1_f, 2_f, 3_f), - Call>(4_f, 5_f, 6_f), - Call>(7_f, 8_f, 9_f))); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("vec3[3](vec3(1.0f, 2.0f, 3.0f), " - "vec3(4.0f, 5.0f, 6.0f), " - "vec3(7.0f, 8.0f, 9.0f))")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_Array_Empty) { - WrapInFunction(Call, 3>>()); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("vec3[3](vec3(0.0f), vec3(0.0f), vec3(0.0f))")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_Struct) { - auto* str = Structure("S", Vector{ - Member("a", ty.i32()), - Member("b", ty.f32()), - Member("c", ty.vec3()), - }); - - WrapInFunction(Call(ty.Of(str), 1_i, 2_f, Call>(3_i, 4_i, 5_i))); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("S(1, 2.0f, ivec3(3, 4, 5))")); -} - -TEST_F(GlslASTPrinterTest_Constructor, Type_Struct_Empty) { - auto* str = Structure("S", Vector{ - Member("a", ty.i32()), - Member("b", ty.f32()), - Member("c", ty.vec3()), - }); - - WrapInFunction(Call(ty.Of(str))); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("S(0")); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/continue_test.cc b/references/writer/ast_printer/continue_test.cc deleted file mode 100644 index fd52fc31..00000000 --- a/references/writer/ast_printer/continue_test.cc +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" - -#include "gmock/gmock.h" - -namespace tint::glsl::writer { -namespace { - -using GlslASTPrinterTest_Continue = TestHelper; - -TEST_F(GlslASTPrinterTest_Continue, Emit_Continue) { - auto* loop = Loop(Block(If(false, Block(Break())), Continue())); - WrapInFunction(loop); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - - gen.EmitStatement(loop); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( while (true) { - if (false) { - break; - } - continue; - } -)"); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/discard_test.cc b/references/writer/ast_printer/discard_test.cc deleted file mode 100644 index f41a07a6..00000000 --- a/references/writer/ast_printer/discard_test.cc +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" - -#include "gmock/gmock.h" - -namespace tint::glsl::writer { -namespace { - -using GlslASTPrinterTest_Discard = TestHelper; - -TEST_F(GlslASTPrinterTest_Discard, Emit_Discard) { - auto* stmt = Discard(); - - Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)}); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitStatement(stmt); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), " discard;\n"); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/function_test.cc b/references/writer/ast_printer/function_test.cc deleted file mode 100644 index a04b329a..00000000 --- a/references/writer/ast_printer/function_test.cc +++ /dev/null @@ -1,977 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "gmock/gmock.h" -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" -#include "src/tint/lang/wgsl/ast/stage_attribute.h" -#include "src/tint/lang/wgsl/ast/variable_decl_statement.h" -#include "src/tint/lang/wgsl/ast/workgroup_attribute.h" - -using ::testing::HasSubstr; - -namespace tint::glsl::writer { -namespace { - -using namespace tint::core::fluent_types; // NOLINT -using namespace tint::core::number_suffixes; // NOLINT - -using GlslASTPrinterTest_Function = TestHelper; - -TEST_F(GlslASTPrinterTest_Function, Emit_Function) { - Func("my_func", tint::Empty, ty.void_(), - Vector{ - Return(), - }); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( #version 310 es - - void my_func() { - return; - } - -)"); -} - -TEST_F(GlslASTPrinterTest_Function, Emit_Function_Name_Collision) { - Func("centroid", tint::Empty, ty.void_(), - Vector{ - Return(), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.IncrementIndent(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr(R"( void tint_symbol() { - return; - })")); -} - -TEST_F(GlslASTPrinterTest_Function, Emit_Function_WithParams) { - Func("my_func", - Vector{ - Param("a", ty.f32()), - Param("b", ty.i32()), - }, - ty.void_(), - Vector{ - Return(), - }); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( #version 310 es - - void my_func(float a, int b) { - return; - } - -)"); -} - -TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_NoReturn_Void) { - Func("func", tint::Empty, ty.void_(), tint::Empty /* no explicit return */, - Vector{ - Stage(ast::PipelineStage::kFragment), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -precision highp float; -precision highp int; - -void func() { - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Function, PtrParameter) { - // fn f(foo : ptr) -> f32 { - // return *foo; - // } - Func("f", Vector{Param("foo", ty.ptr())}, ty.f32(), - Vector{Return(Deref("foo"))}); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr(R"(float f(inout float foo) { - return foo; -} -)")); -} - -TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_WithInOutVars) { - // fn frag_main(@location(0) foo : f32) -> @location(1) f32 { - // return foo; - // } - Func("frag_main", - Vector{ - Param("foo", ty.f32(), Vector{Location(0_a)}), - }, - ty.f32(), - Vector{ - Return("foo"), - }, - Vector{ - Stage(ast::PipelineStage::kFragment), - }, - Vector{ - Location(1_a), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -precision highp float; -precision highp int; - -layout(location = 0) in float foo_1; -layout(location = 1) out float value; -float frag_main(float foo) { - return foo; -} - -void main() { - float inner_result = frag_main(foo_1); - value = inner_result; - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_WithInOut_Builtins) { - // fn frag_main(@position(0) coord : vec4) -> @frag_depth f32 { - // return coord.x; - // } - auto* coord_in = Param("coord", ty.vec4(), Vector{Builtin(core::BuiltinValue::kPosition)}); - Func("frag_main", - Vector{ - coord_in, - }, - ty.f32(), - Vector{ - Return(MemberAccessor("coord", "x")), - }, - Vector{ - Stage(ast::PipelineStage::kFragment), - }, - Vector{ - Builtin(core::BuiltinValue::kFragDepth), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -precision highp float; -precision highp int; - -float frag_main(vec4 coord) { - return coord.x; -} - -void main() { - float inner_result = frag_main(gl_FragCoord); - gl_FragDepth = inner_result; - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_SharedStruct_DifferentStages) { - // struct Interface { - // @builtin(position) pos : vec4; - // @location(1) col1 : f32; - // @location(2) col2 : f32; - // }; - // fn vert_main() -> Interface { - // return Interface(vec4(), 0.5, 0.25); - // } - // fn frag_main(inputs : Interface) { - // const r = inputs.col1; - // const g = inputs.col2; - // const p = inputs.pos; - // } - auto* interface_struct = - Structure("Interface", - Vector{ - Member("pos", ty.vec4(), Vector{Builtin(core::BuiltinValue::kPosition)}), - Member("col1", ty.f32(), Vector{Location(1_a)}), - Member("col2", ty.f32(), Vector{Location(2_a)}), - }); - - Func("vert_main", tint::Empty, ty.Of(interface_struct), - Vector{Return(Call(ty.Of(interface_struct), Call>(), 0.5_f, 0.25_f))}, - Vector{Stage(ast::PipelineStage::kVertex)}); - - Func("frag_main", Vector{Param("inputs", ty.Of(interface_struct))}, ty.void_(), - Vector{ - Decl(Let("r", ty.f32(), MemberAccessor("inputs", "col1"))), - Decl(Let("g", ty.f32(), MemberAccessor("inputs", "col2"))), - Decl(Let("p", ty.vec4(), MemberAccessor("inputs", "pos"))), - }, - Vector{Stage(ast::PipelineStage::kFragment)}); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -precision highp float; -precision highp int; - -layout(location = 1) out float col1_1; -layout(location = 2) out float col2_1; -layout(location = 1) in float col1_2; -layout(location = 2) in float col2_2; -struct Interface { - vec4 pos; - float col1; - float col2; -}; - -Interface vert_main() { - Interface tint_symbol = Interface(vec4(0.0f), 0.5f, 0.25f); - return tint_symbol; -} - -void main() { - gl_PointSize = 1.0; - Interface inner_result = vert_main(); - gl_Position = inner_result.pos; - col1_1 = inner_result.col1; - col2_1 = inner_result.col2; - gl_Position.y = -(gl_Position.y); - gl_Position.z = ((2.0f * gl_Position.z) - gl_Position.w); - return; -} -void frag_main(Interface inputs) { - float r = inputs.col1; - float g = inputs.col2; - vec4 p = inputs.pos; -} - -void main_1() { - Interface tint_symbol_1 = Interface(gl_FragCoord, col1_2, col2_2); - frag_main(tint_symbol_1); - return; -} -)"); -} - -#if 0 -TEST_F(GlslASTPrinterTest_Function, - Emit_Attribute_EntryPoint_SharedStruct_HelperFunction) { - // struct VertexOutput { - // @builtin(position) pos : vec4; - // }; - // fn foo(x : f32) -> VertexOutput { - // return VertexOutput(vec4(x, x, x, 1.0)); - // } - // fn vert_main1() -> VertexOutput { - // return foo(0.5); - // } - // fn vert_main2() -> VertexOutput { - // return foo(0.25); - // } - auto* vertex_output_struct = Structure( - "VertexOutput", - {Member("pos", ty.vec4(), {Builtin(core::BuiltinValue::kPosition)})}); - - Func("foo", Vector{Param("x", ty.f32())}, ty.Of(vertex_output_struct), - {Return(Call(ty.Of(vertex_output_struct), - Call>( "x", "x", "x", 1_f)))}, - {}); - - Func("vert_main1", tint::Empty, ty.Of(vertex_output_struct), - {Return(Call(ty.Of(vertex_output_struct), - Expr(Call("foo", Expr(0.5_f)))))}, - {Stage(ast::PipelineStage::kVertex)}); - - Func("vert_main2", tint::Empty, ty.Of(vertex_output_struct), - {Return(Call(ty.Of(vertex_output_struct), - Expr(Call("foo", Expr(0.25_f)))))}, - {Stage(ast::PipelineStage::kVertex)}); - - ASTPrinter& gen = SanitizeAndBuild(); - - ASSERT_TRUE(gen.Generate()) << gen.Diagnostics(); - EXPECT_EQ(gen.Result(), R"(struct VertexOutput { - float4 pos; -}; - -VertexOutput foo(float x) { - const VertexOutput tint_symbol_4 = {float4(x, x, x, 1.0f)}; - return tint_symbol_4; -} - -struct tint_symbol { - float4 pos : SV_Position; -}; - -tint_symbol vert_main1() { - const VertexOutput tint_symbol_1 = {foo(0.5f)}; - const tint_symbol tint_symbol_5 = {tint_symbol_1.pos}; - return tint_symbol_5; -} - -struct tint_symbol_2 { - float4 pos : SV_Position; -}; - -tint_symbol_2 vert_main2() { - const VertexOutput tint_symbol_3 = {foo(0.25f)}; - const tint_symbol_2 tint_symbol_6 = {tint_symbol_3.pos}; - return tint_symbol_6; -} -)"); -} -#endif - -TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_With_Uniform) { - auto* ubo_ty = Structure("UBO", Vector{Member("coord", ty.vec4())}); - auto* ubo = - GlobalVar("ubo", ty.Of(ubo_ty), core::AddressSpace::kUniform, Binding(0_a), Group(1_a)); - - Func("sub_func", - Vector{ - Param("param", ty.f32()), - }, - ty.f32(), - Vector{ - Return(MemberAccessor(MemberAccessor(ubo, "coord"), "x")), - }); - - auto* var = Var("v", ty.f32(), Call("sub_func", 1_f)); - - Func("frag_main", tint::Empty, ty.void_(), - Vector{ - Decl(var), - Return(), - }, - Vector{ - Stage(ast::PipelineStage::kFragment), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -precision highp float; -precision highp int; - -struct UBO { - vec4 coord; -}; - -layout(binding = 0, std140) uniform UBO_ubo { - vec4 coord; -} ubo; - -float sub_func(float param) { - return ubo.coord.x; -} - -void frag_main() { - float v = sub_func(1.0f); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_With_UniformStruct) { - auto* s = Structure("Uniforms", Vector{Member("coord", ty.vec4())}); - - GlobalVar("uniforms", ty.Of(s), core::AddressSpace::kUniform, Binding(0_a), Group(1_a)); - - auto* var = Var("v", ty.f32(), MemberAccessor(MemberAccessor("uniforms", "coord"), "x")); - - Func("frag_main", tint::Empty, ty.void_(), - Vector{ - Decl(var), - Return(), - }, - Vector{ - Stage(ast::PipelineStage::kFragment), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -precision highp float; -precision highp int; - -struct Uniforms { - vec4 coord; -}; - -layout(binding = 0, std140) uniform Uniforms_ubo { - vec4 coord; -} uniforms; - -void frag_main() { - float v = uniforms.coord.x; - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_With_RW_StorageBuffer_Read) { - auto* s = Structure("Data", Vector{ - Member("a", ty.i32()), - Member("b", ty.f32()), - }); - - GlobalVar("coord", ty.Of(s), core::AddressSpace::kStorage, core::Access::kReadWrite, - Binding(0_a), Group(1_a)); - - auto* var = Var("v", ty.f32(), MemberAccessor("coord", "b")); - - Func("frag_main", tint::Empty, ty.void_(), - Vector{ - Decl(var), - Return(), - }, - Vector{ - Stage(ast::PipelineStage::kFragment), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -precision highp float; -precision highp int; - -struct Data { - int a; - float b; -}; - -layout(binding = 0, std430) buffer coord_block_ssbo { - Data inner; -} coord; - -void frag_main() { - float v = coord.inner.b; - return; -} - -void main() { - frag_main(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_With_RO_StorageBuffer_Read) { - auto* s = Structure("Data", Vector{ - Member("a", ty.i32()), - Member("b", ty.f32()), - }); - - GlobalVar("coord", ty.Of(s), core::AddressSpace::kStorage, core::Access::kRead, Binding(0_a), - Group(1_a)); - - auto* var = Var("v", ty.f32(), MemberAccessor("coord", "b")); - - Func("frag_main", tint::Empty, ty.void_(), - Vector{ - Decl(var), - Return(), - }, - Vector{ - Stage(ast::PipelineStage::kFragment), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), - R"(#version 310 es -precision highp float; -precision highp int; - -struct Data { - int a; - float b; -}; - -layout(binding = 0, std430) buffer coord_block_ssbo { - Data inner; -} coord; - -void frag_main() { - float v = coord.inner.b; - return; -} - -void main() { - frag_main(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_With_WO_StorageBuffer_Store) { - auto* s = Structure("Data", Vector{ - Member("a", ty.i32()), - Member("b", ty.f32()), - }); - - GlobalVar("coord", ty.Of(s), core::AddressSpace::kStorage, core::Access::kReadWrite, - Binding(0_a), Group(1_a)); - - Func("frag_main", tint::Empty, ty.void_(), - Vector{ - Assign(MemberAccessor("coord", "b"), Expr(2_f)), - Return(), - }, - Vector{ - Stage(ast::PipelineStage::kFragment), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -precision highp float; -precision highp int; - -struct Data { - int a; - float b; -}; - -layout(binding = 0, std430) buffer coord_block_ssbo { - Data inner; -} coord; - -void frag_main() { - coord.inner.b = 2.0f; - return; -} - -void main() { - frag_main(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_With_StorageBuffer_Store) { - auto* s = Structure("Data", Vector{ - Member("a", ty.i32()), - Member("b", ty.f32()), - }); - - GlobalVar("coord", ty.Of(s), core::AddressSpace::kStorage, core::Access::kReadWrite, - Binding(0_a), Group(1_a)); - - Func("frag_main", tint::Empty, ty.void_(), - Vector{ - Assign(MemberAccessor("coord", "b"), Expr(2_f)), - Return(), - }, - Vector{ - Stage(ast::PipelineStage::kFragment), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -precision highp float; -precision highp int; - -struct Data { - int a; - float b; -}; - -layout(binding = 0, std430) buffer coord_block_ssbo { - Data inner; -} coord; - -void frag_main() { - coord.inner.b = 2.0f; - return; -} - -void main() { - frag_main(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_Called_By_EntryPoint_With_Uniform) { - auto* s = Structure("S", Vector{Member("x", ty.f32())}); - GlobalVar("coord", ty.Of(s), core::AddressSpace::kUniform, Binding(0_a), Group(1_a)); - - Func("sub_func", Vector{Param("param", ty.f32())}, ty.f32(), - Vector{ - Return(MemberAccessor("coord", "x")), - }); - - auto* var = Var("v", ty.f32(), Call("sub_func", 1_f)); - - Func("frag_main", tint::Empty, ty.void_(), - Vector{ - Decl(var), - Return(), - }, - Vector{ - Stage(ast::PipelineStage::kFragment), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -precision highp float; -precision highp int; - -struct S { - float x; -}; - -layout(binding = 0, std140) uniform S_ubo { - float x; -} coord; - -float sub_func(float param) { - return coord.x; -} - -void frag_main() { - float v = sub_func(1.0f); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_Called_By_EntryPoint_With_StorageBuffer) { - auto* s = Structure("S", Vector{Member("x", ty.f32())}); - GlobalVar("coord", ty.Of(s), core::AddressSpace::kStorage, core::Access::kReadWrite, - Binding(0_a), Group(1_a)); - - Func("sub_func", Vector{Param("param", ty.f32())}, ty.f32(), - Vector{ - Return(MemberAccessor("coord", "x")), - }); - - auto* var = Var("v", ty.f32(), Call("sub_func", 1_f)); - - Func("frag_main", tint::Empty, ty.void_(), - Vector{ - Decl(var), - Return(), - }, - Vector{ - Stage(ast::PipelineStage::kFragment), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), - R"(#version 310 es -precision highp float; -precision highp int; - -struct S { - float x; -}; - -layout(binding = 0, std430) buffer coord_block_ssbo { - S inner; -} coord; - -float sub_func(float param) { - return coord.inner.x; -} - -void frag_main() { - float v = sub_func(1.0f); - return; -} - -void main() { - frag_main(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_WithNameCollision) { - Func("centroid", tint::Empty, ty.void_(), {}, - Vector{ - Stage(ast::PipelineStage::kFragment), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -precision highp float; -precision highp int; - -void tint_symbol() { -} - -void main() { - tint_symbol(); - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_Compute) { - Func("main", tint::Empty, ty.void_(), - Vector{ - Return(), - }, - Vector{ - Stage(ast::PipelineStage::kCompute), - WorkgroupSize(1_i), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_Compute_WithWorkgroup_Literal) { - Func("main", tint::Empty, ty.void_(), {}, - Vector{ - Stage(ast::PipelineStage::kCompute), - WorkgroupSize(2_i, 4_i, 6_i), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -layout(local_size_x = 2, local_size_y = 4, local_size_z = 6) in; -void main() { - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Function, Emit_Attribute_EntryPoint_Compute_WithWorkgroup_Const) { - GlobalConst("width", ty.i32(), Call(2_i)); - GlobalConst("height", ty.i32(), Call(3_i)); - GlobalConst("depth", ty.i32(), Call(4_i)); - Func("main", tint::Empty, ty.void_(), {}, - Vector{ - Stage(ast::PipelineStage::kCompute), - WorkgroupSize("width", "height", "depth"), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -layout(local_size_x = 2, local_size_y = 3, local_size_z = 4) in; -void main() { - return; -} -)"); -} - -TEST_F(GlslASTPrinterTest_Function, - Emit_Attribute_EntryPoint_Compute_WithWorkgroup_OverridableConst) { - Override("width", ty.i32(), Call(2_i), Id(7_u)); - Override("height", ty.i32(), Call(3_i), Id(8_u)); - Override("depth", ty.i32(), Call(4_i), Id(9_u)); - Func("main", tint::Empty, ty.void_(), {}, - Vector{ - Stage(ast::PipelineStage::kCompute), - WorkgroupSize("width", "height", "depth"), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_EQ( - gen.Diagnostics().Str(), - R"(error: override-expressions should have been removed with the SubstituteOverride transform -error: override-expressions should have been removed with the SubstituteOverride transform -error: override-expressions should have been removed with the SubstituteOverride transform -error: override-expressions should have been removed with the SubstituteOverride transform)"); -} - -TEST_F(GlslASTPrinterTest_Function, Emit_Function_WithArrayParams) { - Func("my_func", Vector{Param("a", ty.array())}, ty.void_(), - Vector{ - Return(), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void my_func(float a[5]) { - return; -} - -)"); -} - -TEST_F(GlslASTPrinterTest_Function, Emit_Function_WithArrayReturn) { - Func("my_func", tint::Empty, ty.array(), - Vector{ - Return(Call>()), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -float[5] my_func() { - return float[5](0.0f, 0.0f, 0.0f, 0.0f, 0.0f); -} - -)"); -} - -// https://crbug.com/tint/297 -TEST_F(GlslASTPrinterTest_Function, Emit_Multiple_EntryPoint_With_Same_ModuleVar) { - // struct Data { - // d : f32; - // }; - // @binding(0) @group(0) var data : Data; - // - // @compute @workgroup_size(1) - // fn a() { - // var v = data.d; - // return; - // } - // - // @compute @workgroup_size(1) - // fn b() { - // var v = data.d; - // return; - // } - - auto* s = Structure("Data", Vector{Member("d", ty.f32())}); - - GlobalVar("data", ty.Of(s), core::AddressSpace::kStorage, core::Access::kReadWrite, - Binding(0_a), Group(0_a)); - - { - auto* var = Var("v", ty.f32(), MemberAccessor("data", "d")); - - Func("a", tint::Empty, ty.void_(), - Vector{ - Decl(var), - Return(), - }, - Vector{ - Stage(ast::PipelineStage::kCompute), - WorkgroupSize(1_i), - }); - } - - { - auto* var = Var("v", ty.f32(), MemberAccessor("data", "d")); - - Func("b", tint::Empty, ty.void_(), - Vector{ - Decl(var), - Return(), - }, - Vector{ - Stage(ast::PipelineStage::kCompute), - WorkgroupSize(1_i), - }); - } - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -struct Data { - float d; -}; - -layout(binding = 0, std430) buffer data_block_ssbo { - Data inner; -} data; - -void a() { - float v = data.inner.d; - return; -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - a(); - return; -} -void b() { - float v = data.inner.d; - return; -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main_1() { - b(); - return; -} -)"); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/helper_test.h b/references/writer/ast_printer/helper_test.h deleted file mode 100644 index dc1972db..00000000 --- a/references/writer/ast_printer/helper_test.h +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef SRC_TINT_LANG_GLSL_WRITER_AST_PRINTER_HELPER_TEST_H_ -#define SRC_TINT_LANG_GLSL_WRITER_AST_PRINTER_HELPER_TEST_H_ - -#include -#include -#include - -#include "gtest/gtest.h" -#include "src/tint/lang/glsl/writer/ast_printer/ast_printer.h" -#include "src/tint/lang/glsl/writer/common/version.h" -#include "src/tint/lang/glsl/writer/writer.h" -#include "src/tint/lang/wgsl/ast/transform/manager.h" -#include "src/tint/lang/wgsl/ast/transform/renamer.h" -#include "src/tint/lang/wgsl/resolver/resolve.h" - -namespace tint::glsl::writer { - -/// Helper class for testing -template -class TestHelperBase : public BODY, public ProgramBuilder { - public: - TestHelperBase() = default; - ~TestHelperBase() override = default; - - /// @returns the default generator options for SanitizeAndBuild(), if no explicit options are - /// provided. - static Options DefaultOptions() { - Options opts; - opts.disable_robustness = true; - return opts; - } - - /// Builds the program and returns a ASTPrinter from the program. - /// @note The generator is only built once. Multiple calls to Build() will - /// return the same ASTPrinter without rebuilding. - /// @param version the GLSL version - /// @return the built generator - ASTPrinter& Build(Version version = Version()) { - if (gen_) { - return *gen_; - } - if (!IsValid()) { - ADD_FAILURE() << "ProgramBuilder is not valid: " << Diagnostics(); - } - program = std::make_unique(resolver::Resolve(*this)); - if (!program->IsValid()) { - ADD_FAILURE() << program->Diagnostics(); - } - gen_ = std::make_unique(*program, version); - return *gen_; - } - - /// Builds the program, runs the program through the transform::Glsl sanitizer - /// and returns a ASTPrinter from the sanitized program. - /// @note The generator is only built once. Multiple calls to Build() will - /// return the same ASTPrinter without rebuilding. - /// @param version the GLSL version - /// @param options the GLSL backend options - /// @return the built generator - ASTPrinter& SanitizeAndBuild(Version version = Version(), - const Options& options = DefaultOptions()) { - if (gen_) { - return *gen_; - } - if (!IsValid()) { - ADD_FAILURE() << "ProgramBuilder is not valid: " << Diagnostics(); - } - program = std::make_unique(resolver::Resolve(*this)); - if (!program->IsValid()) { - ADD_FAILURE() << program->Diagnostics(); - } - - // Run the glsl keyword renamer before all other transforms. - ast::transform::Manager transform_manager; - ast::transform::DataMap transform_data; - ast::transform::DataMap outputs; - transform_data.Add( - ast::transform::Renamer::Target::kGlslKeywords, - /* preserve_unicode */ false); - transform_manager.Add(); - auto result = transform_manager.Run(*program, transform_data, outputs); - if (!result.IsValid()) { - ADD_FAILURE() << result.Diagnostics(); - } - *program = std::move(result); - - auto sanitized_result = Sanitize(*program, options, /* entry_point */ ""); - if (!sanitized_result.program.IsValid()) { - ADD_FAILURE() << sanitized_result.program.Diagnostics(); - } - - *program = std::move(sanitized_result.program); - gen_ = std::make_unique(*program, version); - return *gen_; - } - - /// The program built with a call to Build() - std::unique_ptr program; - - private: - std::unique_ptr gen_; -}; - -/// Test class -using TestHelper = TestHelperBase; - -/// Test param class -template -using TestParamHelper = TestHelperBase>; - -} // namespace tint::glsl::writer - -#endif // SRC_TINT_LANG_GLSL_WRITER_AST_PRINTER_HELPER_TEST_H_ diff --git a/references/writer/ast_printer/identifier_test.cc b/references/writer/ast_printer/identifier_test.cc deleted file mode 100644 index 3e041f8b..00000000 --- a/references/writer/ast_printer/identifier_test.cc +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" -#include "src/tint/utils/text/string_stream.h" - -#include "gmock/gmock.h" - -namespace tint::glsl::writer { -namespace { - -using GlslASTPrinterTest_Identifier = TestHelper; - -TEST_F(GlslASTPrinterTest_Identifier, EmitIdentifierExpression) { - GlobalVar("foo", ty.i32(), core::AddressSpace::kPrivate); - - auto* i = Expr("foo"); - WrapInFunction(i); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, i); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "foo"); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/if_test.cc b/references/writer/ast_printer/if_test.cc deleted file mode 100644 index c0f4456d..00000000 --- a/references/writer/ast_printer/if_test.cc +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" - -#include "gmock/gmock.h" - -namespace tint::glsl::writer { -namespace { - -using GlslASTPrinterTest_If = TestHelper; - -TEST_F(GlslASTPrinterTest_If, Emit_If) { - GlobalVar("cond", ty.bool_(), core::AddressSpace::kPrivate); - - auto* cond = Expr("cond"); - auto* body = Block(Return()); - auto* i = If(cond, body); - WrapInFunction(i); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitStatement(i); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( if (cond) { - return; - } -)"); -} - -TEST_F(GlslASTPrinterTest_If, Emit_IfWithElseIf) { - GlobalVar("cond", ty.bool_(), core::AddressSpace::kPrivate); - GlobalVar("else_cond", ty.bool_(), core::AddressSpace::kPrivate); - - auto* else_cond = Expr("else_cond"); - auto* else_body = Block(Return()); - - auto* cond = Expr("cond"); - auto* body = Block(Return()); - auto* i = If(cond, body, Else(If(else_cond, else_body))); - WrapInFunction(i); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitStatement(i); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( if (cond) { - return; - } else { - if (else_cond) { - return; - } - } -)"); -} - -TEST_F(GlslASTPrinterTest_If, Emit_IfWithElse) { - GlobalVar("cond", ty.bool_(), core::AddressSpace::kPrivate); - - auto* else_body = Block(Return()); - - auto* cond = Expr("cond"); - auto* body = Block(Return()); - auto* i = If(cond, body, Else(else_body)); - WrapInFunction(i); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitStatement(i); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( if (cond) { - return; - } else { - return; - } -)"); -} - -TEST_F(GlslASTPrinterTest_If, Emit_IfWithMultiple) { - GlobalVar("cond", ty.bool_(), core::AddressSpace::kPrivate); - GlobalVar("else_cond", ty.bool_(), core::AddressSpace::kPrivate); - - auto* else_cond = Expr("else_cond"); - auto* else_body = Block(Return()); - auto* else_body_2 = Block(Return()); - - auto* cond = Expr("cond"); - auto* body = Block(Return()); - auto* i = If(cond, body, Else(If(else_cond, else_body, Else(else_body_2)))); - WrapInFunction(i); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitStatement(i); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( if (cond) { - return; - } else { - if (else_cond) { - return; - } else { - return; - } - } -)"); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/import_test.cc b/references/writer/ast_printer/import_test.cc deleted file mode 100644 index 5c0db0af..00000000 --- a/references/writer/ast_printer/import_test.cc +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" -#include "src/tint/utils/text/string_stream.h" - -#include "gmock/gmock.h" - -namespace tint::glsl::writer { -namespace { - -using namespace tint::core::fluent_types; // NOLINT -using namespace tint::core::number_suffixes; // NOLINT - -using GlslASTPrinterTest_Import = TestHelper; - -struct GlslImportData { - const char* name; - const char* glsl_name; -}; -inline std::ostream& operator<<(std::ostream& out, GlslImportData data) { - out << data.name; - return out; -} - -using GlslImportData_SingleParamTest = TestParamHelper; -TEST_P(GlslImportData_SingleParamTest, FloatScalar) { - auto param = GetParam(); - - auto* expr = Call(param.name, 1_f); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitCall(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1.0f)"); -} -INSTANTIATE_TEST_SUITE_P(GlslASTPrinterTest_Import, - GlslImportData_SingleParamTest, - testing::Values(GlslImportData{"abs", "abs"}, - GlslImportData{"acos", "acos"}, - GlslImportData{"asin", "asin"}, - GlslImportData{"atan", "atan"}, - GlslImportData{"cos", "cos"}, - GlslImportData{"cosh", "cosh"}, - GlslImportData{"ceil", "ceil"}, - GlslImportData{"exp", "exp"}, - GlslImportData{"exp2", "exp2"}, - GlslImportData{"floor", "floor"}, - GlslImportData{"fract", "fract"}, - GlslImportData{"inverseSqrt", "inversesqrt"}, - GlslImportData{"length", "length"}, - GlslImportData{"log", "log"}, - GlslImportData{"log2", "log2"}, - GlslImportData{"round", "round"}, - GlslImportData{"sign", "sign"}, - GlslImportData{"sin", "sin"}, - GlslImportData{"sinh", "sinh"}, - GlslImportData{"sqrt", "sqrt"}, - GlslImportData{"tan", "tan"}, - GlslImportData{"tanh", "tanh"}, - GlslImportData{"trunc", "trunc"})); - -using GlslImportData_SingleIntParamTest = TestParamHelper; -TEST_P(GlslImportData_SingleIntParamTest, IntScalar) { - auto param = GetParam(); - - auto* expr = Call(param.name, Expr(1_i)); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitCall(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1)"); -} -INSTANTIATE_TEST_SUITE_P(GlslASTPrinterTest_Import, - GlslImportData_SingleIntParamTest, - testing::Values(GlslImportData{"abs", "abs"})); - -using GlslImportData_SingleVectorParamTest = TestParamHelper; -TEST_P(GlslImportData_SingleVectorParamTest, FloatVector) { - auto param = GetParam(); - - auto* expr = Call(param.name, Call>(0.1_f, 0.2_f, 0.3_f)); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitCall(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ( - out.str(), - std::string(param.glsl_name) + - "(vec3(0.10000000149011611938f, 0.20000000298023223877f, 0.30000001192092895508f))"); -} -INSTANTIATE_TEST_SUITE_P(GlslASTPrinterTest_Import, - GlslImportData_SingleVectorParamTest, - testing::Values(GlslImportData{"abs", "abs"}, - GlslImportData{"acos", "acos"}, - GlslImportData{"asin", "asin"}, - GlslImportData{"atan", "atan"}, - GlslImportData{"cos", "cos"}, - GlslImportData{"cosh", "cosh"}, - GlslImportData{"ceil", "ceil"}, - GlslImportData{"exp", "exp"}, - GlslImportData{"exp2", "exp2"}, - GlslImportData{"floor", "floor"}, - GlslImportData{"fract", "fract"}, - GlslImportData{"inverseSqrt", "inversesqrt"}, - GlslImportData{"length", "length"}, - GlslImportData{"log", "log"}, - GlslImportData{"log2", "log2"}, - GlslImportData{"normalize", "normalize"}, - GlslImportData{"round", "round"}, - GlslImportData{"sign", "sign"}, - GlslImportData{"sin", "sin"}, - GlslImportData{"sinh", "sinh"}, - GlslImportData{"sqrt", "sqrt"}, - GlslImportData{"tan", "tan"}, - GlslImportData{"tanh", "tanh"}, - GlslImportData{"trunc", "trunc"})); - -using GlslImportData_DualParam_ScalarTest = TestParamHelper; -TEST_P(GlslImportData_DualParam_ScalarTest, Float) { - auto param = GetParam(); - - auto* expr = Call(param.name, 1_f, 2_f); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitCall(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1.0f, 2.0f)"); -} -INSTANTIATE_TEST_SUITE_P(GlslASTPrinterTest_Import, - GlslImportData_DualParam_ScalarTest, - testing::Values(GlslImportData{"atan2", "atan"}, - GlslImportData{"distance", "distance"}, - GlslImportData{"max", "max"}, - GlslImportData{"min", "min"}, - GlslImportData{"pow", "pow"}, - GlslImportData{"step", "step"})); - -using GlslImportData_DualParam_VectorTest = TestParamHelper; -TEST_P(GlslImportData_DualParam_VectorTest, Float) { - auto param = GetParam(); - - auto* expr = Call(param.name, Call>(1_f, 2_f, 3_f), Call>(4_f, 5_f, 6_f)); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitCall(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), - std::string(param.glsl_name) + "(vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f))"); -} -INSTANTIATE_TEST_SUITE_P(GlslASTPrinterTest_Import, - GlslImportData_DualParam_VectorTest, - testing::Values(GlslImportData{"atan2", "atan"}, - GlslImportData{"cross", "cross"}, - GlslImportData{"distance", "distance"}, - GlslImportData{"max", "max"}, - GlslImportData{"min", "min"}, - GlslImportData{"pow", "pow"}, - GlslImportData{"reflect", "reflect"}, - GlslImportData{"step", "step"})); - -using GlslImportData_DualParam_Int_Test = TestParamHelper; -TEST_P(GlslImportData_DualParam_Int_Test, IntScalar) { - auto param = GetParam(); - - auto* expr = Call(param.name, 1_i, 2_i); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitCall(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1, 2)"); -} -INSTANTIATE_TEST_SUITE_P(GlslASTPrinterTest_Import, - GlslImportData_DualParam_Int_Test, - testing::Values(GlslImportData{"max", "max"}, - GlslImportData{"min", "min"})); - -using GlslImportData_TripleParam_ScalarTest = TestParamHelper; -TEST_P(GlslImportData_TripleParam_ScalarTest, Float) { - auto param = GetParam(); - - auto* expr = Call(param.name, 1_f, 2_f, 3_f); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitCall(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1.0f, 2.0f, 3.0f)"); -} -INSTANTIATE_TEST_SUITE_P(GlslASTPrinterTest_Import, - GlslImportData_TripleParam_ScalarTest, - testing::Values(GlslImportData{"mix", "mix"}, - GlslImportData{"clamp", "clamp"}, - GlslImportData{"smoothstep", "smoothstep"})); - -using GlslImportData_TripleParam_VectorTest = TestParamHelper; -TEST_P(GlslImportData_TripleParam_VectorTest, Float) { - auto param = GetParam(); - - auto* expr = Call(param.name, Call>(1_f, 2_f, 3_f), Call>(4_f, 5_f, 6_f), - Call>(7_f, 8_f, 9_f)); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitCall(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), - std::string(param.glsl_name) + - R"((vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f), vec3(7.0f, 8.0f, 9.0f)))"); -} -INSTANTIATE_TEST_SUITE_P(GlslASTPrinterTest_Import, - GlslImportData_TripleParam_VectorTest, - testing::Values(GlslImportData{"faceForward", "faceforward"}, - GlslImportData{"clamp", "clamp"}, - GlslImportData{"smoothstep", "smoothstep"})); - -using GlslImportData_TripleParam_Int_Test = TestParamHelper; -TEST_P(GlslImportData_TripleParam_Int_Test, IntScalar) { - auto param = GetParam(); - - auto* expr = Call(param.name, 1_i, 2_i, 3_i); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitCall(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), std::string(param.glsl_name) + "(1, 2, 3)"); -} -INSTANTIATE_TEST_SUITE_P(GlslASTPrinterTest_Import, - GlslImportData_TripleParam_Int_Test, - testing::Values(GlslImportData{"clamp", "clamp"})); - -TEST_F(GlslASTPrinterTest_Import, GlslImportData_Determinant) { - GlobalVar("var", ty.mat3x3(), core::AddressSpace::kPrivate); - - auto* expr = Call("determinant", "var"); - WrapInFunction(expr); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitCall(out, expr); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), std::string("determinant(var)")); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/loop_test.cc b/references/writer/ast_printer/loop_test.cc deleted file mode 100644 index 617acb53..00000000 --- a/references/writer/ast_printer/loop_test.cc +++ /dev/null @@ -1,493 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" -#include "src/tint/lang/wgsl/ast/variable_decl_statement.h" - -#include "gmock/gmock.h" - -using namespace tint::core::number_suffixes; // NOLINT - -namespace tint::glsl::writer { -namespace { - -using GlslASTPrinterTest_Loop = TestHelper; - -TEST_F(GlslASTPrinterTest_Loop, Emit_Loop) { - auto* body = Block(Break()); - auto* continuing = Block(); - auto* l = Loop(body, continuing); - - Func("F", tint::Empty, ty.void_(), Vector{l}, Vector{Stage(ast::PipelineStage::kFragment)}); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitStatement(l); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( while (true) { - break; - } -)"); -} - -TEST_F(GlslASTPrinterTest_Loop, Emit_LoopWithContinuing) { - Func("a_statement", {}, ty.void_(), {}); - - auto* body = Block(Break()); - auto* continuing = Block(CallStmt(Call("a_statement"))); - auto* l = Loop(body, continuing); - - Func("F", tint::Empty, ty.void_(), Vector{l}, Vector{Stage(ast::PipelineStage::kFragment)}); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitStatement(l); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( while (true) { - break; - { - a_statement(); - } - } -)"); -} - -TEST_F(GlslASTPrinterTest_Loop, Emit_LoopWithContinuing_BreakIf) { - Func("a_statement", {}, ty.void_(), {}); - - auto* body = Block(Break()); - auto* continuing = Block(CallStmt(Call("a_statement")), BreakIf(true)); - auto* l = Loop(body, continuing); - - Func("F", tint::Empty, ty.void_(), Vector{l}, Vector{Stage(ast::PipelineStage::kFragment)}); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitStatement(l); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( while (true) { - break; - { - a_statement(); - if (true) { break; } - } - } -)"); -} - -TEST_F(GlslASTPrinterTest_Loop, Emit_LoopNestedWithContinuing) { - Func("a_statement", {}, ty.void_(), {}); - - GlobalVar("lhs", ty.f32(), core::AddressSpace::kPrivate); - GlobalVar("rhs", ty.f32(), core::AddressSpace::kPrivate); - - auto* body = Block(Break()); - auto* continuing = Block(CallStmt(Call("a_statement"))); - auto* inner = Loop(body, continuing); - - body = Block(inner); - - auto* lhs = Expr("lhs"); - auto* rhs = Expr("rhs"); - - continuing = Block(Assign(lhs, rhs), BreakIf(true)); - - auto* outer = Loop(body, continuing); - - Func("F", tint::Empty, ty.void_(), Vector{outer}, Vector{Stage(ast::PipelineStage::kFragment)}); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitStatement(outer); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( while (true) { - while (true) { - break; - { - a_statement(); - } - } - { - lhs = rhs; - if (true) { break; } - } - } -)"); -} - -TEST_F(GlslASTPrinterTest_Loop, Emit_LoopWithVarUsedInContinuing) { - // loop { - // var lhs : f32 = 2.5; - // var other : f32; - // break; - // continuing { - // lhs = rhs - // } - // } - - GlobalVar("rhs", ty.f32(), core::AddressSpace::kPrivate); - - auto* body = Block(Decl(Var("lhs", ty.f32(), Expr(2.5_f))), // - Decl(Var("other", ty.f32())), // - Break()); - auto* continuing = Block(Assign("lhs", "rhs")); - auto* outer = Loop(body, continuing); - WrapInFunction(outer); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitStatement(outer); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( while (true) { - float lhs = 2.5f; - float other = 0.0f; - break; - { - lhs = rhs; - } - } -)"); -} - -TEST_F(GlslASTPrinterTest_Loop, Emit_ForLoop) { - // for(; ; ) { - // return; - // } - - auto* f = For(nullptr, nullptr, nullptr, // - Block(Return())); - WrapInFunction(f); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitStatement(f); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( { - for(; ; ) { - return; - } - } -)"); -} - -TEST_F(GlslASTPrinterTest_Loop, Emit_ForLoopWithSimpleInit) { - // for(var i : i32; ; ) { - // return; - // } - - auto* f = For(Decl(Var("i", ty.i32())), nullptr, nullptr, // - Block(Return())); - WrapInFunction(f); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitStatement(f); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( { - for(int i = 0; ; ) { - return; - } - } -)"); -} - -TEST_F(GlslASTPrinterTest_Loop, Emit_ForLoopWithMultiStmtInit) { - // let t = true; - // for(var b = t && false; ; ) { - // return; - // } - - auto* t = Let("t", Expr(true)); - auto* multi_stmt = LogicalAnd(t, false); - auto* f = For(Decl(Var("b", multi_stmt)), nullptr, nullptr, Block(Return())); - WrapInFunction(t, f); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitStatement(f); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( { - bool tint_tmp = t; - if (tint_tmp) { - tint_tmp = false; - } - bool b = (tint_tmp); - for(; ; ) { - return; - } - } -)"); -} - -TEST_F(GlslASTPrinterTest_Loop, Emit_ForLoopWithSimpleCond) { - // for(; true; ) { - // return; - // } - - Func("a_statement", {}, ty.void_(), {}); - - auto* f = For(nullptr, true, nullptr, Block(CallStmt(Call("a_statement")))); - WrapInFunction(f); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitStatement(f); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( { - for(; true; ) { - a_statement(); - } - } -)"); -} - -TEST_F(GlslASTPrinterTest_Loop, Emit_ForLoopWithMultiStmtCond) { - // let t = true; - // for(; t && false; ) { - // return; - // } - - Func("a_statement", {}, ty.void_(), {}); - auto* t = Let("t", Expr(true)); - auto* multi_stmt = LogicalAnd(t, false); - auto* f = For(nullptr, multi_stmt, nullptr, Block(CallStmt(Call("a_statement")))); - WrapInFunction(t, f); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitStatement(f); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( { - while (true) { - bool tint_tmp = t; - if (tint_tmp) { - tint_tmp = false; - } - if (!((tint_tmp))) { break; } - a_statement(); - } - } -)"); -} - -TEST_F(GlslASTPrinterTest_Loop, Emit_ForLoopWithSimpleCont) { - // for(; ; i = i + 1i) { - // return; - // } - - auto* v = Decl(Var("i", ty.i32())); - auto* f = For(nullptr, nullptr, Assign("i", Add("i", 1_i)), // - Block(Return())); - WrapInFunction(v, f); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitStatement(f); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( { - for(; ; i = (i + 1)) { - return; - } - } -)"); -} - -TEST_F(GlslASTPrinterTest_Loop, Emit_ForLoopWithMultiStmtCont) { - // let t = true; - // for(; ; i = t && false) { - // return; - // } - - auto* t = Let("t", Expr(true)); - auto* multi_stmt = LogicalAnd(t, false); - auto* v = Decl(Var("i", ty.bool_())); - auto* f = For(nullptr, nullptr, Assign("i", multi_stmt), // - Block(Return())); - WrapInFunction(t, v, f); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitStatement(f); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( { - while (true) { - return; - bool tint_tmp = t; - if (tint_tmp) { - tint_tmp = false; - } - i = (tint_tmp); - } - } -)"); -} - -TEST_F(GlslASTPrinterTest_Loop, Emit_ForLoopWithSimpleInitCondCont) { - // for(var i : i32; true; i = ii + 1) { - // return; - // } - - auto* f = For(Decl(Var("i", ty.i32())), true, Assign("i", Add("i", 1_i)), Block(Return())); - WrapInFunction(f); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitStatement(f); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( { - for(int i = 0; true; i = (i + 1)) { - return; - } - } -)"); -} - -TEST_F(GlslASTPrinterTest_Loop, Emit_ForLoopWithMultiStmtInitCondCont) { - // let t = true; - // for(var i = t && false; t && false; i = t && false) { - // return; - // } - - auto* t = Let("t", Expr(true)); - auto* multi_stmt_a = LogicalAnd(t, false); - auto* multi_stmt_b = LogicalAnd(t, false); - auto* multi_stmt_c = LogicalAnd(t, false); - - auto* f = For(Decl(Var("i", multi_stmt_a)), multi_stmt_b, Assign("i", multi_stmt_c), // - Block(Return())); - WrapInFunction(t, f); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitStatement(f); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( { - bool tint_tmp = t; - if (tint_tmp) { - tint_tmp = false; - } - bool i = (tint_tmp); - while (true) { - bool tint_tmp_1 = t; - if (tint_tmp_1) { - tint_tmp_1 = false; - } - if (!((tint_tmp_1))) { break; } - return; - bool tint_tmp_2 = t; - if (tint_tmp_2) { - tint_tmp_2 = false; - } - i = (tint_tmp_2); - } - } -)"); -} - -TEST_F(GlslASTPrinterTest_Loop, Emit_While) { - // while(true) { - // return; - // } - - auto* f = While(Expr(true), Block(Return())); - WrapInFunction(f); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitStatement(f); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( while(true) { - return; - } -)"); -} - -TEST_F(GlslASTPrinterTest_Loop, Emit_While_WithContinue) { - // while(true) { - // continue; - // } - - auto* f = While(Expr(true), Block(Continue())); - WrapInFunction(f); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitStatement(f); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( while(true) { - continue; - } -)"); -} - -TEST_F(GlslASTPrinterTest_Loop, Emit_WhileWithMultiStmtCond) { - // let t = true; - // while(t && false) { - // return; - // } - - Func("a_statement", {}, ty.void_(), {}); - - auto* t = Let("t", Expr(true)); - auto* multi_stmt = LogicalAnd(t, false); - auto* f = While(multi_stmt, Block(CallStmt(Call("a_statement")))); - WrapInFunction(t, f); - - ASTPrinter& gen = Build(); - - gen.IncrementIndent(); - gen.EmitStatement(f); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( while (true) { - bool tint_tmp = t; - if (tint_tmp) { - tint_tmp = false; - } - if (!((tint_tmp))) { break; } - a_statement(); - } -)"); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/member_accessor_test.cc b/references/writer/ast_printer/member_accessor_test.cc deleted file mode 100644 index 1e4ee0c9..00000000 --- a/references/writer/ast_printer/member_accessor_test.cc +++ /dev/null @@ -1,917 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" -#include "src/tint/lang/wgsl/ast/stage_attribute.h" - -#include "gmock/gmock.h" - -namespace tint::glsl::writer { -namespace { - -using ::testing::HasSubstr; -using namespace tint::core::fluent_types; // NOLINT -using namespace tint::core::number_suffixes; // NOLINT - -using create_type_func_ptr = ast::Type (*)(const ProgramBuilder::TypesBuilder& ty); - -inline ast::Type ty_i32(const ProgramBuilder::TypesBuilder& ty) { - return ty.i32(); -} -inline ast::Type ty_u32(const ProgramBuilder::TypesBuilder& ty) { - return ty.u32(); -} -inline ast::Type ty_f32(const ProgramBuilder::TypesBuilder& ty) { - return ty.f32(); -} -template -inline ast::Type ty_vec2(const ProgramBuilder::TypesBuilder& ty) { - return ty.vec2(); -} -template -inline ast::Type ty_vec3(const ProgramBuilder::TypesBuilder& ty) { - return ty.vec3(); -} -template -inline ast::Type ty_vec4(const ProgramBuilder::TypesBuilder& ty) { - return ty.vec4(); -} -template -inline ast::Type ty_mat2x2(const ProgramBuilder::TypesBuilder& ty) { - return ty.mat2x2(); -} -template -inline ast::Type ty_mat2x3(const ProgramBuilder::TypesBuilder& ty) { - return ty.mat2x3(); -} -template -inline ast::Type ty_mat2x4(const ProgramBuilder::TypesBuilder& ty) { - return ty.mat2x4(); -} -template -inline ast::Type ty_mat3x2(const ProgramBuilder::TypesBuilder& ty) { - return ty.mat3x2(); -} -template -inline ast::Type ty_mat3x3(const ProgramBuilder::TypesBuilder& ty) { - return ty.mat3x3(); -} -template -inline ast::Type ty_mat3x4(const ProgramBuilder::TypesBuilder& ty) { - return ty.mat3x4(); -} -template -inline ast::Type ty_mat4x2(const ProgramBuilder::TypesBuilder& ty) { - return ty.mat4x2(); -} -template -inline ast::Type ty_mat4x3(const ProgramBuilder::TypesBuilder& ty) { - return ty.mat4x3(); -} -template -inline ast::Type ty_mat4x4(const ProgramBuilder::TypesBuilder& ty) { - return ty.mat4x4(); -} - -template -class GlslASTPrinterTest_MemberAccessorBase : public BASE { - public: - void SetupStorageBuffer(VectorRef members) { - ProgramBuilder& b = *this; - - auto* s = b.Structure("Data", members); - - b.GlobalVar("data", b.ty.Of(s), core::AddressSpace::kStorage, core::Access::kReadWrite, - b.Group(1_a), b.Binding(0_a)); - } - - void SetupFunction(VectorRef statements) { - ProgramBuilder& b = *this; - b.Func("main", tint::Empty, b.ty.void_(), statements, - Vector{ - b.Stage(ast::PipelineStage::kFragment), - }); - } -}; - -using GlslASTPrinterTest_MemberAccessor = GlslASTPrinterTest_MemberAccessorBase; - -template -using GlslASTPrinterTest_MemberAccessorWithParam = - GlslASTPrinterTest_MemberAccessorBase>; - -TEST_F(GlslASTPrinterTest_MemberAccessor, EmitExpression_MemberAccessor) { - auto* s = Structure("Data", Vector{Member("mem", ty.f32())}); - GlobalVar("str", ty.Of(s), core::AddressSpace::kPrivate); - - auto* expr = MemberAccessor("str", "mem"); - WrapInFunction(Var("expr", ty.f32(), expr)); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -struct Data { - float mem; -}; - -Data str = Data(0.0f); -void test_function() { - float expr = str.mem; -} - -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() { - test_function(); - return; -} -)"); -} - -struct TypeCase { - create_type_func_ptr member_type; - std::string expected; -}; -inline std::ostream& operator<<(std::ostream& out, TypeCase c) { - return out << c.expected; -} - -using GlslASTPrinterTest_MemberAccessor_StorageBufferLoad = - GlslASTPrinterTest_MemberAccessorWithParam; -TEST_P(GlslASTPrinterTest_MemberAccessor_StorageBufferLoad, Test) { - // struct Data { - // a : i32; - // b : ; - // }; - // var data : Data; - // data.b; - - auto p = GetParam(); - - SetupStorageBuffer(Vector{ - Member("a", ty.i32()), - Member("b", p.member_type(ty)), - }); - - SetupFunction(Vector{ - Decl(Var("x", MemberAccessor("data", "b"))), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr(p.expected)); -} - -INSTANTIATE_TEST_SUITE_P(GlslASTPrinterTest_MemberAccessor, - GlslASTPrinterTest_MemberAccessor_StorageBufferLoad, - testing::Values(TypeCase{ty_u32, "data.inner.b"}, - TypeCase{ty_f32, "data.inner.b"}, - TypeCase{ty_i32, "data.inner.b"}, - TypeCase{ty_vec2, "data.inner.b"}, - TypeCase{ty_vec2, "data.inner.b"}, - TypeCase{ty_vec2, "data.inner.b"}, - TypeCase{ty_vec3, "data.inner.b"}, - TypeCase{ty_vec3, "data.inner.b"}, - TypeCase{ty_vec3, "data.inner.b"}, - TypeCase{ty_vec4, "data.inner.b"}, - TypeCase{ty_vec4, "data.inner.b"}, - TypeCase{ty_vec4, "data.inner.b"}, - TypeCase{ty_mat2x2, "data.inner.b"}, - TypeCase{ty_mat2x3, "data.inner.b"}, - TypeCase{ty_mat2x4, "data.inner.b"}, - TypeCase{ty_mat3x2, "data.inner.b"}, - TypeCase{ty_mat3x3, "data.inner.b"}, - TypeCase{ty_mat3x4, "data.inner.b"}, - TypeCase{ty_mat4x2, "data.inner.b"}, - TypeCase{ty_mat4x3, "data.inner.b"}, - TypeCase{ty_mat4x4, "data.inner.b"})); - -using GlslASTPrinterTest_MemberAccessor_StorageBufferStore = - GlslASTPrinterTest_MemberAccessorWithParam; -TEST_P(GlslASTPrinterTest_MemberAccessor_StorageBufferStore, Test) { - // struct Data { - // a : i32; - // b : ; - // }; - // var data : Data; - // data.b = (); - - auto p = GetParam(); - - SetupStorageBuffer(Vector{ - Member("a", ty.i32()), - Member("b", p.member_type(ty)), - }); - - SetupFunction(Vector{ - Decl(Var("value", p.member_type(ty), Call(p.member_type(ty)))), - Assign(MemberAccessor("data", "b"), Expr("value")), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr(p.expected)); -} - -INSTANTIATE_TEST_SUITE_P(GlslASTPrinterTest_MemberAccessor, - GlslASTPrinterTest_MemberAccessor_StorageBufferStore, - testing::Values(TypeCase{ty_u32, "data.inner.b = value"}, - TypeCase{ty_f32, "data.inner.b = value"}, - TypeCase{ty_i32, "data.inner.b = value"}, - TypeCase{ty_vec2, "data.inner.b = value"}, - TypeCase{ty_vec2, "data.inner.b = value"}, - TypeCase{ty_vec2, "data.inner.b = value"}, - TypeCase{ty_vec3, "data.inner.b = value"}, - TypeCase{ty_vec3, "data.inner.b = value"}, - TypeCase{ty_vec3, "data.inner.b = value"}, - TypeCase{ty_vec4, "data.inner.b = value"}, - TypeCase{ty_vec4, "data.inner.b = value"}, - TypeCase{ty_vec4, "data.inner.b = value"}, - TypeCase{ty_mat2x2, "data.inner.b = value"}, - TypeCase{ty_mat2x3, R"( - data.inner.b[0] = value[0u]; - data.inner.b[1] = value[1u];)"}, - TypeCase{ty_mat2x4, "data.inner.b = value"}, - TypeCase{ty_mat3x2, "data.inner.b = value"}, - TypeCase{ty_mat3x3, R"( - data.inner.b[0] = value[0u]; - data.inner.b[1] = value[1u]; - data.inner.b[2] = value[2u];)"}, - TypeCase{ty_mat3x4, "data.inner.b = value"}, - TypeCase{ty_mat4x2, "data.inner.b = value"}, - TypeCase{ty_mat4x3, R"( - data.inner.b[0] = value[0u]; - data.inner.b[1] = value[1u]; - data.inner.b[2] = value[2u]; - data.inner.b[3] = value[3u];)"}, - TypeCase{ty_mat4x4, "data.inner.b = value"})); - -TEST_F(GlslASTPrinterTest_MemberAccessor, StorageBuffer_Store_Matrix_Empty) { - // struct Data { - // z : f32; - // a : mat2x3; - // }; - // var data : Data; - // data.a = mat2x3(); - - SetupStorageBuffer(Vector{ - Member("a", ty.i32()), - Member("b", ty.mat2x3()), - }); - - SetupFunction(Vector{ - Assign(MemberAccessor("data", "b"), Call>()), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - - auto* expected = - R"(#version 310 es -precision highp float; -precision highp int; - -struct Data { - int a; - uint pad; - uint pad_1; - uint pad_2; - mat2x3 b; -}; - -layout(binding = 0, std430) buffer data_block_ssbo { - Data inner; -} data; - -void assign_and_preserve_padding_data_inner_b(mat2x3 value) { - data.inner.b[0] = value[0u]; - data.inner.b[1] = value[1u]; -} - -void tint_symbol() { - assign_and_preserve_padding_data_inner_b(mat2x3(vec3(0.0f), vec3(0.0f))); -} - -void main() { - tint_symbol(); - return; -} -)"; - EXPECT_EQ(gen.Result(), expected); -} - -TEST_F(GlslASTPrinterTest_MemberAccessor, StorageBuffer_Load_Matrix_Single_Element) { - // struct Data { - // z : f32; - // a : mat4x3; - // }; - // var data : Data; - // data.a[2i][1i]; - - SetupStorageBuffer(Vector{ - Member("z", ty.f32()), - Member("a", ty.mat4x3()), - }); - - SetupFunction(Vector{ - Decl(Var("x", IndexAccessor(IndexAccessor(MemberAccessor("data", "a"), 2_i), 1_i))), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - auto* expected = - R"(#version 310 es -precision highp float; -precision highp int; - -struct Data { - float z; - uint pad; - uint pad_1; - uint pad_2; - mat4x3 a; -}; - -layout(binding = 0, std430) buffer data_block_ssbo { - Data inner; -} data; - -void tint_symbol() { - float x = data.inner.a[2][1]; -} - -void main() { - tint_symbol(); - return; -} -)"; - EXPECT_EQ(gen.Result(), expected); -} - -TEST_F(GlslASTPrinterTest_MemberAccessor, - EmitExpression_IndexAccessor_StorageBuffer_Load_Int_FromArray) { - // struct Data { - // a : array; - // }; - // var data : Data; - // data.a[2]; - - SetupStorageBuffer(Vector{ - Member("z", ty.f32()), - Member("a", ty.array()), - }); - - SetupFunction(Vector{ - Decl(Var("x", IndexAccessor(MemberAccessor("data", "a"), 2_i))), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - auto* expected = - R"(#version 310 es -precision highp float; -precision highp int; - -struct Data { - float z; - int a[5]; -}; - -layout(binding = 0, std430) buffer data_block_ssbo { - Data inner; -} data; - -void tint_symbol() { - int x = data.inner.a[2]; -} - -void main() { - tint_symbol(); - return; -} -)"; - EXPECT_EQ(gen.Result(), expected); -} - -TEST_F(GlslASTPrinterTest_MemberAccessor, - EmitExpression_IndexAccessor_StorageBuffer_Load_Int_FromArray_ExprIdx) { - // struct Data { - // a : array; - // }; - // var data : Data; - // data.a[(2i + 4i) - 3i]; - - SetupStorageBuffer(Vector{ - Member("z", ty.f32()), - Member("a", ty.array()), - }); - - SetupFunction(Vector{ - Decl(Var("a", Expr(2_i))), - Decl(Var("b", Expr(4_i))), - Decl(Var("c", Expr(3_i))), - Decl(Var("x", IndexAccessor(MemberAccessor("data", "a"), Sub(Add("a", "b"), "c")))), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - auto* expected = - R"(#version 310 es -precision highp float; -precision highp int; - -struct Data { - float z; - int a[5]; -}; - -layout(binding = 0, std430) buffer data_block_ssbo { - Data inner; -} data; - -void tint_symbol() { - int a = 2; - int b = 4; - int c = 3; - int x = data.inner.a[((a + b) - c)]; -} - -void main() { - tint_symbol(); - return; -} -)"; - EXPECT_EQ(gen.Result(), expected); -} - -TEST_F(GlslASTPrinterTest_MemberAccessor, StorageBuffer_Store_ToArray) { - // struct Data { - // a : array; - // }; - // var data : Data; - // data.a[2i] = 2i; - - SetupStorageBuffer(Vector{ - Member("z", ty.f32()), - Member("a", ty.array()), - }); - - SetupFunction(Vector{ - Assign(IndexAccessor(MemberAccessor("data", "a"), 2_i), 2_i), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - auto* expected = - R"(#version 310 es -precision highp float; -precision highp int; - -struct Data { - float z; - int a[5]; -}; - -layout(binding = 0, std430) buffer data_block_ssbo { - Data inner; -} data; - -void tint_symbol() { - data.inner.a[2] = 2; -} - -void main() { - tint_symbol(); - return; -} -)"; - EXPECT_EQ(gen.Result(), expected); -} - -TEST_F(GlslASTPrinterTest_MemberAccessor, StorageBuffer_Load_MultiLevel) { - // struct Inner { - // a : vec3; - // b : vec3; - // }; - // struct Data { - // var c : array; - // }; - // - // var data : Pre; - // data.c[2i].b - - auto* inner = Structure("Inner", Vector{ - Member("a", ty.vec3()), - Member("b", ty.vec3()), - }); - - SetupStorageBuffer(Vector{ - Member("c", ty.array(ty.Of(inner), 4_u)), - }); - - SetupFunction(Vector{ - Decl(Var("x", MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"))), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - auto* expected = - R"(#version 310 es -precision highp float; -precision highp int; - -struct Inner { - vec3 a; - uint pad; - vec3 b; - uint pad_1; -}; - -struct Data { - Inner c[4]; -}; - -layout(binding = 0, std430) buffer data_block_ssbo { - Data inner; -} data; - -void tint_symbol() { - vec3 x = data.inner.c[2].b; -} - -void main() { - tint_symbol(); - return; -} -)"; - EXPECT_EQ(gen.Result(), expected); -} - -TEST_F(GlslASTPrinterTest_MemberAccessor, StorageBuffer_Load_MultiLevel_Swizzle) { - // struct Inner { - // a : vec3; - // b : vec3; - // }; - // struct Data { - // var c : array; - // }; - // - // var data : Pre; - // data.c[2i].b.xy - - auto* inner = Structure("Inner", Vector{ - Member("a", ty.vec3()), - Member("b", ty.vec3()), - }); - - SetupStorageBuffer(Vector{ - Member("c", ty.array(ty.Of(inner), 4_u)), - }); - - SetupFunction(Vector{ - Decl(Var("x", - MemberAccessor( - MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"), "xy"))), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - auto* expected = - R"(#version 310 es -precision highp float; -precision highp int; - -struct Inner { - vec3 a; - uint pad; - vec3 b; - uint pad_1; -}; - -struct Data { - Inner c[4]; -}; - -layout(binding = 0, std430) buffer data_block_ssbo { - Data inner; -} data; - -void tint_symbol() { - vec2 x = data.inner.c[2].b.xy; -} - -void main() { - tint_symbol(); - return; -} -)"; - EXPECT_EQ(gen.Result(), expected); -} - -TEST_F(GlslASTPrinterTest_MemberAccessor, - StorageBuffer_Load_MultiLevel_Swizzle_SingleLetter) { // NOLINT - // struct Inner { - // a : vec3; - // b : vec3; - // }; - // struct Data { - // var c : array; - // }; - // - // var data : Pre; - // data.c[2i].b.g - - auto* inner = Structure("Inner", Vector{ - Member("a", ty.vec3()), - Member("b", ty.vec3()), - }); - - SetupStorageBuffer(Vector{ - Member("c", ty.array(ty.Of(inner), 4_u)), - }); - - SetupFunction(Vector{ - Decl(Var("x", - MemberAccessor( - MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"), "g"))), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - auto* expected = - R"(#version 310 es -precision highp float; -precision highp int; - -struct Inner { - vec3 a; - uint pad; - vec3 b; - uint pad_1; -}; - -struct Data { - Inner c[4]; -}; - -layout(binding = 0, std430) buffer data_block_ssbo { - Data inner; -} data; - -void tint_symbol() { - float x = data.inner.c[2].b.g; -} - -void main() { - tint_symbol(); - return; -} -)"; - EXPECT_EQ(gen.Result(), expected); -} - -TEST_F(GlslASTPrinterTest_MemberAccessor, StorageBuffer_Load_MultiLevel_Index) { - // struct Inner { - // a : vec3; - // b : vec3; - // }; - // struct Data { - // var c : array; - // }; - // - // var data : Pre; - // data.c[2i].b[1i] - - auto* inner = Structure("Inner", Vector{ - Member("a", ty.vec3()), - Member("b", ty.vec3()), - }); - - SetupStorageBuffer(Vector{ - Member("c", ty.array(ty.Of(inner), 4_u)), - }); - - SetupFunction(Vector{ - Decl(Var("x", - IndexAccessor(MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"), - 1_i))), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - auto* expected = - R"(#version 310 es -precision highp float; -precision highp int; - -struct Inner { - vec3 a; - uint pad; - vec3 b; - uint pad_1; -}; - -struct Data { - Inner c[4]; -}; - -layout(binding = 0, std430) buffer data_block_ssbo { - Data inner; -} data; - -void tint_symbol() { - float x = data.inner.c[2].b[1]; -} - -void main() { - tint_symbol(); - return; -} -)"; - EXPECT_EQ(gen.Result(), expected); -} - -TEST_F(GlslASTPrinterTest_MemberAccessor, StorageBuffer_Store_MultiLevel) { - // struct Inner { - // a : vec3; - // b : vec3; - // }; - // struct Data { - // var c : array; - // }; - // - // var data : Pre; - // data.c[2i].b = vec3(1.f, 2.f, 3.f); - - auto* inner = Structure("Inner", Vector{ - Member("a", ty.vec3()), - Member("b", ty.vec3()), - }); - - SetupStorageBuffer(Vector{ - Member("c", ty.array(ty.Of(inner), 4_u)), - }); - - SetupFunction(Vector{ - Assign(MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"), - Call>(1_f, 2_f, 3_f)), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - auto* expected = - R"(#version 310 es -precision highp float; -precision highp int; - -struct Inner { - vec3 a; - uint pad; - vec3 b; - uint pad_1; -}; - -struct Data { - Inner c[4]; -}; - -layout(binding = 0, std430) buffer data_block_ssbo { - Data inner; -} data; - -void tint_symbol() { - data.inner.c[2].b = vec3(1.0f, 2.0f, 3.0f); -} - -void main() { - tint_symbol(); - return; -} -)"; - EXPECT_EQ(gen.Result(), expected); -} - -TEST_F(GlslASTPrinterTest_MemberAccessor, StorageBuffer_Store_Swizzle_SingleLetter) { - // struct Inner { - // a : vec3; - // b : vec3; - // }; - // struct Data { - // var c : array; - // }; - // - // var data : Pre; - // data.c[2i].b.y = 1.f; - - auto* inner = Structure("Inner", Vector{ - Member("a", ty.vec3()), - Member("b", ty.vec3()), - }); - - SetupStorageBuffer(Vector{ - Member("c", ty.array(ty.Of(inner), 4_u)), - }); - - SetupFunction(Vector{ - Assign(MemberAccessor(MemberAccessor(IndexAccessor(MemberAccessor("data", "c"), 2_i), "b"), - "y"), - Expr(1_f)), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - auto* expected = - R"(#version 310 es -precision highp float; -precision highp int; - -struct Inner { - ivec3 a; - uint pad; - vec3 b; - uint pad_1; -}; - -struct Data { - Inner c[4]; -}; - -layout(binding = 0, std430) buffer data_block_ssbo { - Data inner; -} data; - -void tint_symbol() { - data.inner.c[2].b.y = 1.0f; -} - -void main() { - tint_symbol(); - return; -} -)"; - EXPECT_EQ(gen.Result(), expected); -} - -TEST_F(GlslASTPrinterTest_MemberAccessor, Swizzle_xyz) { - auto* var = Var("my_vec", ty.vec4(), Call>(1_f, 2_f, 3_f, 4_f)); - auto* expr = MemberAccessor("my_vec", "xyz"); - WrapInFunction(var, expr); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("my_vec.xyz")); -} - -TEST_F(GlslASTPrinterTest_MemberAccessor, Swizzle_gbr) { - auto* var = Var("my_vec", ty.vec4(), Call>(1_f, 2_f, 3_f, 4_f)); - auto* expr = MemberAccessor("my_vec", "gbr"); - WrapInFunction(var, expr); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("my_vec.gbr")); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/module_constant_test.cc b/references/writer/ast_printer/module_constant_test.cc deleted file mode 100644 index 9dfb9c3b..00000000 --- a/references/writer/ast_printer/module_constant_test.cc +++ /dev/null @@ -1,349 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" -#include "src/tint/lang/wgsl/ast/id_attribute.h" - -#include "gmock/gmock.h" - -namespace tint::glsl::writer { -namespace { - -using namespace tint::core::fluent_types; // NOLINT -using namespace tint::core::number_suffixes; // NOLINT - -using GlslASTPrinterTest_ModuleConstant = TestHelper; - -TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalLet) { - auto* var = Let("pos", ty.array(), Call>(1_f, 2_f, 3_f)); - WrapInFunction(Decl(var)); - - ASTPrinter& gen = Build(); - gen.EmitProgramConstVariable(var); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), "const float pos[3] = float[3](1.0f, 2.0f, 3.0f);\n"); -} - -TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_AInt) { - auto* var = GlobalConst("G", Expr(1_a)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(Let("l", Expr(var))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void f() { - int l = 1; -} - -)"); -} - -TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_AFloat) { - auto* var = GlobalConst("G", Expr(1._a)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(Let("l", Expr(var))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void f() { - float l = 1.0f; -} - -)"); -} - -TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_i32) { - auto* var = GlobalConst("G", Expr(1_i)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(Let("l", Expr(var))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void f() { - int l = 1; -} - -)"); -} - -TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_u32) { - auto* var = GlobalConst("G", Expr(1_u)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(Let("l", Expr(var))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void f() { - uint l = 1u; -} - -)"); -} - -TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_f32) { - auto* var = GlobalConst("G", Expr(1_f)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(Let("l", Expr(var))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void f() { - float l = 1.0f; -} - -)"); -} - -TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_f16) { - Enable(wgsl::Extension::kF16); - - auto* var = GlobalConst("G", Expr(1_h)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(Let("l", Expr(var))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -#extension GL_AMD_gpu_shader_half_float : require - -void f() { - float16_t l = 1.0hf; -} - -)"); -} - -TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_vec3_AInt) { - auto* var = GlobalConst("G", Call>(1_a, 2_a, 3_a)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(Let("l", Expr(var))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void f() { - ivec3 l = ivec3(1, 2, 3); -} - -)"); -} - -TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_vec3_AFloat) { - auto* var = GlobalConst("G", Call>(1._a, 2._a, 3._a)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(Let("l", Expr(var))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void f() { - vec3 l = vec3(1.0f, 2.0f, 3.0f); -} - -)"); -} - -TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_vec3_f32) { - auto* var = GlobalConst("G", Call>(1_f, 2_f, 3_f)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(Let("l", Expr(var))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void f() { - vec3 l = vec3(1.0f, 2.0f, 3.0f); -} - -)"); -} - -TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_vec3_f16) { - Enable(wgsl::Extension::kF16); - - auto* var = GlobalConst("G", Call>(1_h, 2_h, 3_h)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(Let("l", Expr(var))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -#extension GL_AMD_gpu_shader_half_float : require - -void f() { - f16vec3 l = f16vec3(1.0hf, 2.0hf, 3.0hf); -} - -)"); -} - -TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_mat2x3_AFloat) { - auto* var = GlobalConst("G", Call>(1._a, 2._a, 3._a, 4._a, 5._a, 6._a)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(Let("l", Expr(var))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void f() { - mat2x3 l = mat2x3(vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f)); -} - -)"); -} - -TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_mat2x3_f32) { - auto* var = GlobalConst("G", Call>(1_f, 2_f, 3_f, 4_f, 5_f, 6_f)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(Let("l", Expr(var))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void f() { - mat2x3 l = mat2x3(vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f)); -} - -)"); -} - -TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_mat2x3_f16) { - Enable(wgsl::Extension::kF16); - - auto* var = GlobalConst("G", Call>(1_h, 2_h, 3_h, 4_h, 5_h, 6_h)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(Let("l", Expr(var))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -#extension GL_AMD_gpu_shader_half_float : require - -void f() { - f16mat2x3 l = f16mat2x3(f16vec3(1.0hf, 2.0hf, 3.0hf), f16vec3(4.0hf, 5.0hf, 6.0hf)); -} - -)"); -} - -TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_arr_f32) { - auto* var = GlobalConst("G", Call>(1_f, 2_f, 3_f)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(Let("l", Expr(var))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void f() { - float l[3] = float[3](1.0f, 2.0f, 3.0f); -} - -)"); -} - -TEST_F(GlslASTPrinterTest_ModuleConstant, Emit_GlobalConst_arr_vec2_bool) { - auto* var = GlobalConst("G", Call, 3>>( // - Call>(true, false), // - Call>(false, true), // - Call>(true, true))); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(Let("l", Expr(var))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void f() { - bvec2 l[3] = bvec2[3](bvec2(true, false), bvec2(false, true), bvec2(true)); -} - -)"); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/return_test.cc b/references/writer/ast_printer/return_test.cc deleted file mode 100644 index 45ae9bb3..00000000 --- a/references/writer/ast_printer/return_test.cc +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" - -#include "gmock/gmock.h" - -using namespace tint::core::number_suffixes; // NOLINT - -namespace tint::glsl::writer { -namespace { - -using GlslASTPrinterTest_Return = TestHelper; - -TEST_F(GlslASTPrinterTest_Return, Emit_Return) { - auto* r = Return(); - WrapInFunction(r); - - ASTPrinter& gen = Build(); - gen.IncrementIndent(); - gen.EmitStatement(r); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), " return;\n"); -} - -TEST_F(GlslASTPrinterTest_Return, Emit_ReturnWithValue) { - auto* r = Return(123_i); - Func("f", tint::Empty, ty.i32(), Vector{r}); - - ASTPrinter& gen = Build(); - gen.IncrementIndent(); - gen.EmitStatement(r); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), " return 123;\n"); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/sanitizer_test.cc b/references/writer/ast_printer/sanitizer_test.cc deleted file mode 100644 index 0cd03ab6..00000000 --- a/references/writer/ast_printer/sanitizer_test.cc +++ /dev/null @@ -1,339 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" -#include "src/tint/lang/wgsl/ast/call_statement.h" -#include "src/tint/lang/wgsl/ast/stage_attribute.h" -#include "src/tint/lang/wgsl/ast/variable_decl_statement.h" - -#include "gmock/gmock.h" - -namespace tint::glsl::writer { -namespace { - -using namespace tint::core::fluent_types; // NOLINT -using namespace tint::core::number_suffixes; // NOLINT - -using GlslSanitizerTest = TestHelper; - -TEST_F(GlslSanitizerTest, Call_ArrayLength) { - auto* s = Structure("my_struct", Vector{Member(0, "a", ty.array())}); - GlobalVar("b", ty.Of(s), core::AddressSpace::kStorage, core::Access::kRead, Binding(1_a), - Group(2_a)); - - Func("a_func", tint::Empty, ty.void_(), - Vector{ - Decl(Var("len", ty.u32(), Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))), - }, - Vector{ - Stage(ast::PipelineStage::kFragment), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - - auto got = gen.Result(); - auto* expect = R"(#version 310 es -precision highp float; -precision highp int; - -layout(binding = 1, std430) buffer my_struct_ssbo { - float a[]; -} b; - -void a_func() { - uint len = uint(b.a.length()); -} - -void main() { - a_func(); - return; -} -)"; - EXPECT_EQ(expect, got); -} - -TEST_F(GlslSanitizerTest, Call_ArrayLength_OtherMembersInStruct) { - auto* s = Structure("my_struct", Vector{ - Member(0, "z", ty.f32()), - Member(4, "a", ty.array()), - }); - GlobalVar("b", ty.Of(s), core::AddressSpace::kStorage, core::Access::kRead, Binding(1_a), - Group(2_a)); - - Func("a_func", tint::Empty, ty.void_(), - Vector{ - Decl(Var("len", ty.u32(), Call("arrayLength", AddressOf(MemberAccessor("b", "a"))))), - }, - Vector{ - Stage(ast::PipelineStage::kFragment), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - - auto got = gen.Result(); - auto* expect = R"(#version 310 es -precision highp float; -precision highp int; - -layout(binding = 1, std430) buffer my_struct_ssbo { - float z; - float a[]; -} b; - -void a_func() { - uint len = uint(b.a.length()); -} - -void main() { - a_func(); - return; -} -)"; - - EXPECT_EQ(expect, got); -} - -TEST_F(GlslSanitizerTest, Call_ArrayLength_ViaLets) { - auto* s = Structure("my_struct", Vector{Member(0, "a", ty.array())}); - GlobalVar("b", ty.Of(s), core::AddressSpace::kStorage, core::Access::kRead, Binding(1_a), - Group(2_a)); - - auto* p = Let("p", AddressOf("b")); - auto* p2 = Let("p2", AddressOf(MemberAccessor(Deref(p), "a"))); - - Func("a_func", tint::Empty, ty.void_(), - Vector{ - Decl(p), - Decl(p2), - Decl(Var("len", ty.u32(), Call("arrayLength", p2))), - }, - Vector{ - Stage(ast::PipelineStage::kFragment), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - - auto got = gen.Result(); - auto* expect = R"(#version 310 es -precision highp float; -precision highp int; - -layout(binding = 1, std430) buffer my_struct_ssbo { - float a[]; -} b; - -void a_func() { - uint len = uint(b.a.length()); -} - -void main() { - a_func(); - return; -} -)"; - - EXPECT_EQ(expect, got); -} - -TEST_F(GlslSanitizerTest, PromoteArrayInitializerToConstVar) { - auto* array_init = Call>(1_i, 2_i, 3_i, 4_i); - - Func("main", tint::Empty, ty.void_(), - Vector{ - Decl(Var("idx", Expr(3_i))), - Decl(Var("pos", ty.i32(), IndexAccessor(array_init, "idx"))), - }, - Vector{ - Stage(ast::PipelineStage::kFragment), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - - auto got = gen.Result(); - auto* expect = R"(#version 310 es -precision highp float; -precision highp int; - -void tint_symbol() { - int idx = 3; - int tint_symbol_1[4] = int[4](1, 2, 3, 4); - int pos = tint_symbol_1[idx]; -} - -void main() { - tint_symbol(); - return; -} -)"; - EXPECT_EQ(expect, got); -} - -TEST_F(GlslSanitizerTest, PromoteStructInitializerToConstVar) { - auto* str = Structure("S", Vector{ - Member("a", ty.i32()), - Member("b", ty.vec3()), - Member("c", ty.i32()), - }); - auto* runtime_value = Var("runtime_value", Expr(3_f)); - auto* struct_init = Call(ty.Of(str), 1_i, Call>(2_f, runtime_value, 4_f), 4_i); - auto* struct_access = MemberAccessor(struct_init, "b"); - auto* pos = Var("pos", ty.vec3(), struct_access); - - Func("main", tint::Empty, ty.void_(), - Vector{ - Decl(runtime_value), - Decl(pos), - }, - Vector{ - Stage(ast::PipelineStage::kFragment), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - - auto got = gen.Result(); - auto* expect = R"(#version 310 es -precision highp float; -precision highp int; - -struct S { - int a; - vec3 b; - int c; -}; - -void tint_symbol() { - float runtime_value = 3.0f; - S tint_symbol_1 = S(1, vec3(2.0f, runtime_value, 4.0f), 4); - vec3 pos = tint_symbol_1.b; -} - -void main() { - tint_symbol(); - return; -} -)"; - EXPECT_EQ(expect, got); -} - -TEST_F(GlslSanitizerTest, SimplifyPointersBasic) { - // var v : i32; - // let p : ptr = &v; - // let x : i32 = *p; - auto* v = Var("v", ty.i32()); - auto* p = Let("p", ty.ptr(), AddressOf(v)); - auto* x = Var("x", ty.i32(), Deref(p)); - - Func("main", tint::Empty, ty.void_(), - Vector{ - Decl(v), - Decl(p), - Decl(x), - }, - Vector{ - Stage(ast::PipelineStage::kFragment), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - - auto got = gen.Result(); - auto* expect = R"(#version 310 es -precision highp float; -precision highp int; - -void tint_symbol() { - int v = 0; - int x = v; -} - -void main() { - tint_symbol(); - return; -} -)"; - EXPECT_EQ(expect, got); -} - -TEST_F(GlslSanitizerTest, SimplifyPointersComplexChain) { - // var a : array, 4u>; - // let ap : ptr, 4u>> = &a; - // let mp : ptr> = &(*ap)[3i]; - // let vp : ptr> = &(*mp)[2i]; - // let v : vec4 = *vp; - auto* a = Var("a", ty.array(ty.mat4x4(), 4_u)); - auto* ap = Let("ap", ty.ptr, 4>>(), AddressOf(a)); - auto* mp = Let("mp", ty.ptr>(), AddressOf(IndexAccessor(Deref(ap), 3_i))); - auto* vp = Let("vp", ty.ptr>(), AddressOf(IndexAccessor(Deref(mp), 2_i))); - auto* v = Var("v", ty.vec4(), Deref(vp)); - - Func("main", tint::Empty, ty.void_(), - Vector{ - Decl(a), - Decl(ap), - Decl(mp), - Decl(vp), - Decl(v), - }, - Vector{ - Stage(ast::PipelineStage::kFragment), - }); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - - auto got = gen.Result(); - auto* expect = R"(#version 310 es -precision highp float; -precision highp int; - -void tint_symbol() { - mat4 a[4] = mat4[4](mat4(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), mat4(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), mat4(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), mat4(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)); - vec4 v = a[3][2]; -} - -void main() { - tint_symbol(); - return; -} -)"; - EXPECT_EQ(expect, got); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/storage_buffer_test.cc b/references/writer/ast_printer/storage_buffer_test.cc deleted file mode 100644 index b016ef11..00000000 --- a/references/writer/ast_printer/storage_buffer_test.cc +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2022 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/core/number.h" -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" - -#include "gmock/gmock.h" - -using ::testing::HasSubstr; -using namespace tint::core::number_suffixes; // NOLINT - -namespace tint::glsl::writer { -namespace { - -using GlslASTPrinterTest_StorageBuffer = TestHelper; - -void TestAlign(ProgramBuilder* ctx) { - // struct Nephews { - // @align(256) huey : f32; - // @align(256) dewey : f32; - // @align(256) louie : f32; - // }; - // @group(0) @binding(0) var nephews : Nephews; - auto* nephews = ctx->Structure( - "Nephews", Vector{ - ctx->Member("huey", ctx->ty.f32(), Vector{ctx->MemberAlign(256_i)}), - ctx->Member("dewey", ctx->ty.f32(), Vector{ctx->MemberAlign(256_i)}), - ctx->Member("louie", ctx->ty.f32(), Vector{ctx->MemberAlign(256_i)}), - }); - ctx->GlobalVar("nephews", ctx->ty.Of(nephews), core::AddressSpace::kStorage, ctx->Binding(0_a), - ctx->Group(0_a)); -} - -TEST_F(GlslASTPrinterTest_StorageBuffer, Align) { - TestAlign(this); - - ASTPrinter& gen = Build(); - - // TODO(crbug.com/tint/1421) offsets do not currently work on GLSL ES. - // They will likely require manual padding. - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -struct Nephews { - float huey; - float dewey; - float louie; -}; - -layout(binding = 0, std430) buffer Nephews_ssbo { - float huey; - float dewey; - float louie; -} nephews; - -)"); -} - -TEST_F(GlslASTPrinterTest_StorageBuffer, Align_Desktop) { - TestAlign(this); - - ASTPrinter& gen = Build(Version(Version::Standard::kDesktop, 4, 4)); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 440 - -struct Nephews { - float huey; - float dewey; - float louie; -}; - -layout(binding = 0, std430) buffer Nephews_ssbo { - float huey; - float dewey; - float louie; -} nephews; - -)"); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/switch_test.cc b/references/writer/ast_printer/switch_test.cc deleted file mode 100644 index 99b0cdc1..00000000 --- a/references/writer/ast_printer/switch_test.cc +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" - -#include "gmock/gmock.h" - -using namespace tint::core::number_suffixes; // NOLINT - -namespace tint::glsl::writer { -namespace { - -using GlslASTPrinterTest_Switch = TestHelper; - -TEST_F(GlslASTPrinterTest_Switch, Emit_Switch) { - GlobalVar("cond", ty.i32(), core::AddressSpace::kPrivate); - - auto* def_body = Block(create()); - auto* def = create(Vector{DefaultCaseSelector()}, def_body); - - auto* case_body = Block(create()); - auto* case_stmt = create(Vector{CaseSelector(5_i)}, case_body); - - auto* cond = Expr("cond"); - auto* s = Switch(cond, Vector{case_stmt, def}); - WrapInFunction(s); - - ASTPrinter& gen = Build(); - gen.IncrementIndent(); - gen.EmitStatement(s); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( switch(cond) { - case 5: { - break; - } - default: { - break; - } - } -)"); -} - -TEST_F(GlslASTPrinterTest_Switch, Emit_Switch_MixedDefault) { - GlobalVar("cond", ty.i32(), core::AddressSpace::kPrivate); - - auto* def_body = Block(create()); - auto* def = - create(Vector{CaseSelector(5_i), DefaultCaseSelector()}, def_body); - - auto* cond = Expr("cond"); - auto* s = Switch(cond, Vector{def}); - WrapInFunction(s); - - ASTPrinter& gen = Build(); - gen.IncrementIndent(); - gen.EmitStatement(s); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"( switch(cond) { - case 5: - default: { - break; - } - } -)"); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/type_test.cc b/references/writer/ast_printer/type_test.cc deleted file mode 100644 index 8c1c09b7..00000000 --- a/references/writer/ast_printer/type_test.cc +++ /dev/null @@ -1,594 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/core/type/depth_texture.h" -#include "src/tint/lang/core/type/multisampled_texture.h" -#include "src/tint/lang/core/type/sampled_texture.h" -#include "src/tint/lang/core/type/sampler.h" -#include "src/tint/lang/core/type/storage_texture.h" -#include "src/tint/lang/core/type/texture_dimension.h" -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" -#include "src/tint/lang/wgsl/ast/call_statement.h" -#include "src/tint/lang/wgsl/ast/stage_attribute.h" -#include "src/tint/utils/text/string_stream.h" - -#include "gmock/gmock.h" - -using ::testing::HasSubstr; - -using namespace tint::core::number_suffixes; // NOLINT - -namespace tint::glsl::writer { -namespace { - -using GlslASTPrinterTest_Type = TestHelper; - -TEST_F(GlslASTPrinterTest_Type, EmitType_Array) { - auto arr = ty.array(); - ast::Type ty = GlobalVar("G", arr, core::AddressSpace::kPrivate)->type; - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitType(out, program->TypeOf(ty), core::AddressSpace::kUndefined, core::Access::kReadWrite, - "ary"); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "bool ary[4]"); -} - -TEST_F(GlslASTPrinterTest_Type, EmitType_ArrayOfArray) { - auto arr = ty.array(ty.array(), 5_u); - ast::Type ty = GlobalVar("G", arr, core::AddressSpace::kPrivate)->type; - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitType(out, program->TypeOf(ty), core::AddressSpace::kUndefined, core::Access::kReadWrite, - "ary"); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "bool ary[5][4]"); -} - -TEST_F(GlslASTPrinterTest_Type, EmitType_ArrayOfArrayOfArray) { - auto arr = ty.array(ty.array(ty.array(), 5_u), 6_u); - ast::Type ty = GlobalVar("G", arr, core::AddressSpace::kPrivate)->type; - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitType(out, program->TypeOf(ty), core::AddressSpace::kUndefined, core::Access::kReadWrite, - "ary"); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "bool ary[6][5][4]"); -} - -TEST_F(GlslASTPrinterTest_Type, EmitType_Array_WithoutName) { - auto arr = ty.array(); - ast::Type ty = GlobalVar("G", arr, core::AddressSpace::kPrivate)->type; - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitType(out, program->TypeOf(ty), core::AddressSpace::kUndefined, core::Access::kReadWrite, - ""); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "bool[4]"); -} - -TEST_F(GlslASTPrinterTest_Type, EmitType_Bool) { - auto* bool_ = create(); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitType(out, bool_, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "bool"); -} - -TEST_F(GlslASTPrinterTest_Type, EmitType_F32) { - auto* f32 = create(); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitType(out, f32, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "float"); -} - -TEST_F(GlslASTPrinterTest_Type, EmitType_F16) { - Enable(wgsl::Extension::kF16); - - auto* f16 = create(); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitType(out, f16, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "float16_t"); -} - -TEST_F(GlslASTPrinterTest_Type, EmitType_I32) { - auto* i32 = create(); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitType(out, i32, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "int"); -} - -TEST_F(GlslASTPrinterTest_Type, EmitType_Matrix_F32) { - auto* f32 = create(); - auto* vec3 = create(f32, 3u); - auto* mat2x3 = create(vec3, 2u); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitType(out, mat2x3, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "mat2x3"); -} - -TEST_F(GlslASTPrinterTest_Type, EmitType_Matrix_F16) { - Enable(wgsl::Extension::kF16); - - auto* f16 = create(); - auto* vec3 = create(f16, 3u); - auto* mat2x3 = create(vec3, 2u); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitType(out, mat2x3, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "f16mat2x3"); -} - -TEST_F(GlslASTPrinterTest_Type, EmitType_StructDecl) { - auto* s = Structure("S", Vector{ - Member("a", ty.i32()), - Member("b", ty.f32()), - }); - GlobalVar("g", ty.Of(s), core::AddressSpace::kPrivate); - - ASTPrinter& gen = Build(); - - tint::TextGenerator::TextBuffer buf; - auto* str = program->TypeOf(s)->As(); - gen.EmitStructType(&buf, str); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(buf.String(), R"(struct S { - int a; - float b; -}; - -)"); -} - -TEST_F(GlslASTPrinterTest_Type, EmitType_Struct) { - auto* s = Structure("S", Vector{ - Member("a", ty.i32()), - Member("b", ty.f32()), - }); - GlobalVar("g", ty.Of(s), core::AddressSpace::kPrivate); - - ASTPrinter& gen = Build(); - - auto* str = program->TypeOf(s)->As(); - StringStream out; - gen.EmitType(out, str, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "S"); -} - -TEST_F(GlslASTPrinterTest_Type, EmitType_Struct_NameCollision) { - auto* s = Structure("S", Vector{ - Member("double", ty.i32()), - Member("float", ty.f32()), - }); - GlobalVar("g", ty.Of(s), core::AddressSpace::kPrivate); - - ASTPrinter& gen = SanitizeAndBuild(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr(R"(struct S { - int tint_symbol; - float tint_symbol_1; -}; -)")); -} - -TEST_F(GlslASTPrinterTest_Type, EmitType_Struct_WithOffsetAttributes) { - auto* s = Structure("S", Vector{ - Member("a", ty.i32(), Vector{MemberOffset(0_a)}), - Member("b", ty.f32(), Vector{MemberOffset(8_a)}), - }); - GlobalVar("g", ty.Of(s), core::AddressSpace::kPrivate); - - ASTPrinter& gen = Build(); - - tint::TextGenerator::TextBuffer buf; - auto* str = program->TypeOf(s)->As(); - gen.EmitStructType(&buf, str); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(buf.String(), R"(struct S { - int a; - float b; -}; - -)"); -} - -TEST_F(GlslASTPrinterTest_Type, EmitType_U32) { - auto* u32 = create(); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitType(out, u32, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "uint"); -} - -TEST_F(GlslASTPrinterTest_Type, EmitType_Vector_F32) { - auto* f32 = create(); - auto* vec3 = create(f32, 3u); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitType(out, vec3, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "vec3"); -} - -TEST_F(GlslASTPrinterTest_Type, EmitType_Vector_F16) { - Enable(wgsl::Extension::kF16); - - auto* f16 = create(); - auto* vec3 = create(f16, 3u); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitType(out, vec3, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "f16vec3"); -} - -TEST_F(GlslASTPrinterTest_Type, EmitType_Void) { - auto* void_ = create(); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitType(out, void_, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "void"); -} - -TEST_F(GlslASTPrinterTest_Type, EmitSampler) { - auto* sampler = create(core::type::SamplerKind::kSampler); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitType(out, sampler, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); -} - -TEST_F(GlslASTPrinterTest_Type, EmitSamplerComparison) { - auto* sampler = create(core::type::SamplerKind::kComparisonSampler); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitType(out, sampler, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); -} - -struct GlslDepthTextureData { - core::type::TextureDimension dim; - std::string result; -}; -inline std::ostream& operator<<(std::ostream& out, GlslDepthTextureData data) { - StringStream s; - s << data.dim; - out << s.str(); - return out; -} -using GlslDepthTexturesTest = TestParamHelper; -TEST_P(GlslDepthTexturesTest, Emit) { - auto params = GetParam(); - - auto t = ty.depth_texture(params.dim); - - GlobalVar("tex", t, Binding(1_a), Group(2_a)); - - Func("main", tint::Empty, ty.void_(), - Vector{ - Decl(Var("v", Call("textureDimensions", "tex"))), - }, - Vector{ - Stage(ast::PipelineStage::kFragment), - }); - - ASTPrinter& gen = Build(); - - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr(params.result)); -} -INSTANTIATE_TEST_SUITE_P( - GlslASTPrinterTest_Type, - GlslDepthTexturesTest, - testing::Values( - GlslDepthTextureData{core::type::TextureDimension::k2d, "sampler2DShadow tex;"}, - GlslDepthTextureData{core::type::TextureDimension::k2dArray, "sampler2DArrayShadow tex;"}, - GlslDepthTextureData{core::type::TextureDimension::kCube, "samplerCubeShadow tex;"}, - GlslDepthTextureData{core::type::TextureDimension::kCubeArray, - "samplerCubeArrayShadow tex;"})); - -using GlslDepthMultisampledTexturesTest = TestHelper; -TEST_F(GlslDepthMultisampledTexturesTest, Emit) { - auto t = ty.depth_multisampled_texture(core::type::TextureDimension::k2d); - - GlobalVar("tex", t, Binding(1_a), Group(2_a)); - - Func("main", tint::Empty, ty.void_(), - Vector{ - Decl(Var("v", Call("textureDimensions", "tex"))), - }, - Vector{ - Stage(ast::PipelineStage::kFragment), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("sampler2DMS tex;")); -} - -enum class TextureDataType { F32, U32, I32 }; -struct GlslSampledTextureData { - core::type::TextureDimension dim; - TextureDataType datatype; - std::string result; -}; -inline std::ostream& operator<<(std::ostream& out, GlslSampledTextureData data) { - StringStream str; - str << data.dim; - out << str.str(); - return out; -} -using GlslSampledTexturesTest = TestParamHelper; -TEST_P(GlslSampledTexturesTest, Emit) { - auto params = GetParam(); - - ast::Type datatype; - switch (params.datatype) { - case TextureDataType::F32: - datatype = ty.f32(); - break; - case TextureDataType::U32: - datatype = ty.u32(); - break; - case TextureDataType::I32: - datatype = ty.i32(); - break; - } - ast::Type t = ty.sampled_texture(params.dim, datatype); - - GlobalVar("tex", t, Binding(1_a), Group(2_a)); - - Func("main", tint::Empty, ty.void_(), - Vector{ - Decl(Var("v", Call("textureDimensions", "tex"))), - }, - Vector{ - Stage(ast::PipelineStage::kFragment), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr(params.result)); -} -INSTANTIATE_TEST_SUITE_P(GlslASTPrinterTest_Type, - GlslSampledTexturesTest, - testing::Values( - GlslSampledTextureData{ - core::type::TextureDimension::k1d, - TextureDataType::F32, - "sampler1D tex;", - }, - GlslSampledTextureData{ - core::type::TextureDimension::k2d, - TextureDataType::F32, - "sampler2D tex;", - }, - GlslSampledTextureData{ - core::type::TextureDimension::k2dArray, - TextureDataType::F32, - "sampler2DArray tex;", - }, - GlslSampledTextureData{ - core::type::TextureDimension::k3d, - TextureDataType::F32, - "sampler3D tex;", - }, - GlslSampledTextureData{ - core::type::TextureDimension::kCube, - TextureDataType::F32, - "samplerCube tex;", - }, - GlslSampledTextureData{ - core::type::TextureDimension::kCubeArray, - TextureDataType::F32, - "samplerCubeArray tex;", - }, - GlslSampledTextureData{ - core::type::TextureDimension::k1d, - TextureDataType::U32, - "usampler1D tex;", - }, - GlslSampledTextureData{ - core::type::TextureDimension::k2d, - TextureDataType::U32, - "usampler2D tex;", - }, - GlslSampledTextureData{ - core::type::TextureDimension::k2dArray, - TextureDataType::U32, - "usampler2DArray tex;", - }, - GlslSampledTextureData{ - core::type::TextureDimension::k3d, - TextureDataType::U32, - "usampler3D tex;", - }, - GlslSampledTextureData{ - core::type::TextureDimension::kCube, - TextureDataType::U32, - "usamplerCube tex;", - }, - GlslSampledTextureData{ - core::type::TextureDimension::kCubeArray, - TextureDataType::U32, - "usamplerCubeArray tex;", - }, - GlslSampledTextureData{ - core::type::TextureDimension::k1d, - TextureDataType::I32, - "isampler1D tex;", - }, - GlslSampledTextureData{ - core::type::TextureDimension::k2d, - TextureDataType::I32, - "isampler2D tex;", - }, - GlslSampledTextureData{ - core::type::TextureDimension::k2dArray, - TextureDataType::I32, - "isampler2DArray tex;", - }, - GlslSampledTextureData{ - core::type::TextureDimension::k3d, - TextureDataType::I32, - "isampler3D tex;", - }, - GlslSampledTextureData{ - core::type::TextureDimension::kCube, - TextureDataType::I32, - "isamplerCube tex;", - }, - GlslSampledTextureData{ - core::type::TextureDimension::kCubeArray, - TextureDataType::I32, - "isamplerCubeArray tex;", - })); - -TEST_F(GlslASTPrinterTest_Type, EmitMultisampledTexture) { - auto* f32 = create(); - auto* s = create(core::type::TextureDimension::k2d, f32); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitType(out, s, core::AddressSpace::kUndefined, core::Access::kReadWrite, ""); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "highp sampler2DMS"); -} - -struct GlslStorageTextureData { - core::type::TextureDimension dim; - core::TexelFormat imgfmt; - std::string result; -}; -inline std::ostream& operator<<(std::ostream& out, GlslStorageTextureData data) { - StringStream str; - str << data.dim; - return out << str.str(); -} -using GlslStorageTexturesTest = TestParamHelper; -TEST_P(GlslStorageTexturesTest, Emit) { - auto params = GetParam(); - - auto t = ty.storage_texture(params.dim, params.imgfmt, core::Access::kWrite); - - GlobalVar("tex", t, Binding(1_a), Group(2_a)); - - Func("main", tint::Empty, ty.void_(), - Vector{ - Decl(Var("v", Call("textureDimensions", "tex"))), - }, - Vector{ - Stage(ast::PipelineStage::kFragment), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr(params.result)); -} -INSTANTIATE_TEST_SUITE_P( - GlslASTPrinterTest_Type, - GlslStorageTexturesTest, - testing::Values(GlslStorageTextureData{core::type::TextureDimension::k1d, - core::TexelFormat::kRgba8Unorm, "image1D tex;"}, - GlslStorageTextureData{core::type::TextureDimension::k2d, - core::TexelFormat::kRgba16Float, "image2D tex;"}, - GlslStorageTextureData{core::type::TextureDimension::k2dArray, - core::TexelFormat::kR32Float, "image2DArray tex;"}, - GlslStorageTextureData{core::type::TextureDimension::k3d, - core::TexelFormat::kRg32Float, "image3D tex;"}, - GlslStorageTextureData{core::type::TextureDimension::k1d, - core::TexelFormat::kRgba32Float, "image1D tex;"}, - GlslStorageTextureData{core::type::TextureDimension::k2d, - core::TexelFormat::kRgba16Uint, "image2D tex;"}, - GlslStorageTextureData{core::type::TextureDimension::k2dArray, - core::TexelFormat::kR32Uint, "image2DArray tex;"}, - GlslStorageTextureData{core::type::TextureDimension::k3d, - core::TexelFormat::kRg32Uint, "image3D tex;"}, - GlslStorageTextureData{core::type::TextureDimension::k1d, - core::TexelFormat::kRgba32Uint, "image1D tex;"}, - GlslStorageTextureData{core::type::TextureDimension::k2d, - core::TexelFormat::kRgba16Sint, "image2D tex;"}, - GlslStorageTextureData{core::type::TextureDimension::k2dArray, - core::TexelFormat::kR32Sint, "image2DArray tex;"}, - GlslStorageTextureData{core::type::TextureDimension::k3d, - core::TexelFormat::kRg32Sint, "image3D tex;"}, - GlslStorageTextureData{core::type::TextureDimension::k1d, - core::TexelFormat::kRgba32Sint, "image1D tex;"})); - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/unary_op_test.cc b/references/writer/ast_printer/unary_op_test.cc deleted file mode 100644 index c2df9cb9..00000000 --- a/references/writer/ast_printer/unary_op_test.cc +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/core/fluent_types.h" -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" -#include "src/tint/utils/text/string_stream.h" - -#include "gmock/gmock.h" - -using namespace tint::core::fluent_types; // NOLINT - -namespace tint::glsl::writer { -namespace { - -using GlslUnaryOpTest = TestHelper; - -TEST_F(GlslUnaryOpTest, AddressOf) { - GlobalVar("expr", ty.f32(), core::AddressSpace::kPrivate); - auto* op = create(core::UnaryOp::kAddressOf, Expr("expr")); - WrapInFunction(op); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, op); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "expr"); -} - -TEST_F(GlslUnaryOpTest, Complement) { - GlobalVar("expr", ty.u32(), core::AddressSpace::kPrivate); - auto* op = create(core::UnaryOp::kComplement, Expr("expr")); - WrapInFunction(op); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, op); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "~(expr)"); -} - -TEST_F(GlslUnaryOpTest, Indirection) { - GlobalVar("G", ty.f32(), core::AddressSpace::kPrivate); - auto* p = Let("expr", create(core::UnaryOp::kAddressOf, Expr("G"))); - auto* op = create(core::UnaryOp::kIndirection, Expr("expr")); - WrapInFunction(p, op); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, op); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "expr"); -} - -TEST_F(GlslUnaryOpTest, Not) { - GlobalVar("expr", ty.bool_(), core::AddressSpace::kPrivate); - auto* op = create(core::UnaryOp::kNot, Expr("expr")); - WrapInFunction(op); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, op); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "!(expr)"); -} - -TEST_F(GlslUnaryOpTest, Negation) { - GlobalVar("expr", ty.i32(), core::AddressSpace::kPrivate); - auto* op = create(core::UnaryOp::kNegation, Expr("expr")); - WrapInFunction(op); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, op); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "-(expr)"); -} - -TEST_F(GlslUnaryOpTest, IntMin) { - auto* op = Expr(i32(std::numeric_limits::min())); - WrapInFunction(op); - - ASTPrinter& gen = Build(); - - StringStream out; - gen.EmitExpression(out, op); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(out.str(), "(-2147483647 - 1)"); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/uniform_buffer_test.cc b/references/writer/ast_printer/uniform_buffer_test.cc deleted file mode 100644 index bcc90091..00000000 --- a/references/writer/ast_printer/uniform_buffer_test.cc +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2022 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" - -#include "gmock/gmock.h" - -using ::testing::HasSubstr; - -namespace tint::glsl::writer { -namespace { - -using namespace tint::core::number_suffixes; // NOLINT - -using GlslASTPrinterTest_UniformBuffer = TestHelper; - -TEST_F(GlslASTPrinterTest_UniformBuffer, Simple) { - auto* simple = Structure("Simple", Vector{Member("member", ty.f32())}); - GlobalVar("simple", ty.Of(simple), core::AddressSpace::kUniform, Group(0_a), Binding(0_a)); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -struct Simple { - float member; -}; - -layout(binding = 0, std140) uniform Simple_ubo { - float member; -} simple; - -)"); -} - -TEST_F(GlslASTPrinterTest_UniformBuffer, Simple_Desktop) { - auto* simple = Structure("Simple", Vector{Member("member", ty.f32())}); - GlobalVar("simple", ty.Of(simple), core::AddressSpace::kUniform, Group(0_a), Binding(0_a)); - - ASTPrinter& gen = Build(Version(Version::Standard::kDesktop, 4, 4)); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 440 - -struct Simple { - float member; -}; - -layout(binding = 0, std140) uniform Simple_ubo { - float member; -} simple; - -)"); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/variable_decl_statement_test.cc b/references/writer/ast_printer/variable_decl_statement_test.cc deleted file mode 100644 index 3c3c8d83..00000000 --- a/references/writer/ast_printer/variable_decl_statement_test.cc +++ /dev/null @@ -1,540 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/wgsl/ast/variable_decl_statement.h" -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" - -#include "gmock/gmock.h" - -namespace tint::glsl::writer { -namespace { - -using ::testing::HasSubstr; -using namespace tint::core::fluent_types; // NOLINT -using namespace tint::core::number_suffixes; // NOLINT - -using GlslASTPrinterTest_VariableDecl = TestHelper; - -TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement) { - auto* var = Var("a", ty.f32()); - auto* stmt = Decl(var); - WrapInFunction(stmt); - - ASTPrinter& gen = Build(); - gen.IncrementIndent(); - gen.EmitStatement(stmt); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), " float a = 0.0f;\n"); -} - -TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Let) { - auto* var = Let("a", ty.f32(), Call()); - auto* stmt = Decl(var); - WrapInFunction(stmt); - - ASTPrinter& gen = Build(); - gen.IncrementIndent(); - gen.EmitStatement(stmt); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), " float a = 0.0f;\n"); -} - -TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const) { - auto* var = Const("a", ty.f32(), Call()); - auto* stmt = Decl(var); - WrapInFunction(stmt); - - ASTPrinter& gen = Build(); - gen.IncrementIndent(); - gen.EmitStatement(stmt); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), ""); // Not a mistake - 'const' is inlined -} - -TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_AInt) { - auto* C = Const("C", Expr(1_a)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(C), - Decl(Let("l", Expr(C))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void f() { - int l = 1; -} - -)"); -} - -TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_AFloat) { - auto* C = Const("C", Expr(1._a)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(C), - Decl(Let("l", Expr(C))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void f() { - float l = 1.0f; -} - -)"); -} - -TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_i32) { - auto* C = Const("C", Expr(1_i)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(C), - Decl(Let("l", Expr(C))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void f() { - int l = 1; -} - -)"); -} - -TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_u32) { - auto* C = Const("C", Expr(1_u)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(C), - Decl(Let("l", Expr(C))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void f() { - uint l = 1u; -} - -)"); -} - -TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_f32) { - auto* C = Const("C", Expr(1_f)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(C), - Decl(Let("l", Expr(C))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void f() { - float l = 1.0f; -} - -)"); -} - -TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_f16) { - Enable(wgsl::Extension::kF16); - - auto* C = Const("C", Expr(1_h)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(C), - Decl(Let("l", Expr(C))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -#extension GL_AMD_gpu_shader_half_float : require - -void f() { - float16_t l = 1.0hf; -} - -)"); -} - -TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_vec3_AInt) { - auto* C = Const("C", Call>(1_a, 2_a, 3_a)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(C), - Decl(Let("l", Expr(C))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void f() { - ivec3 l = ivec3(1, 2, 3); -} - -)"); -} - -TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_vec3_AFloat) { - auto* C = Const("C", Call>(1._a, 2._a, 3._a)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(C), - Decl(Let("l", Expr(C))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void f() { - vec3 l = vec3(1.0f, 2.0f, 3.0f); -} - -)"); -} - -TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_vec3_f32) { - auto* C = Const("C", Call>(1_f, 2_f, 3_f)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(C), - Decl(Let("l", Expr(C))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void f() { - vec3 l = vec3(1.0f, 2.0f, 3.0f); -} - -)"); -} - -TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_vec3_f16) { - Enable(wgsl::Extension::kF16); - - auto* C = Const("C", Call>(1_h, 2_h, 3_h)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(C), - Decl(Let("l", Expr(C))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -#extension GL_AMD_gpu_shader_half_float : require - -void f() { - f16vec3 l = f16vec3(1.0hf, 2.0hf, 3.0hf); -} - -)"); -} - -TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_mat2x3_AFloat) { - auto* C = Const("C", Call>(1._a, 2._a, 3._a, 4._a, 5._a, 6._a)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(C), - Decl(Let("l", Expr(C))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void f() { - mat2x3 l = mat2x3(vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f)); -} - -)"); -} - -TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_mat2x3_f32) { - auto* C = Const("C", Call>(1_f, 2_f, 3_f, 4_f, 5_f, 6_f)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(C), - Decl(Let("l", Expr(C))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void f() { - mat2x3 l = mat2x3(vec3(1.0f, 2.0f, 3.0f), vec3(4.0f, 5.0f, 6.0f)); -} - -)"); -} - -TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_mat2x3_f16) { - Enable(wgsl::Extension::kF16); - - auto* C = Const("C", Call>(1_h, 2_h, 3_h, 4_h, 5_h, 6_h)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(C), - Decl(Let("l", Expr(C))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es -#extension GL_AMD_gpu_shader_half_float : require - -void f() { - f16mat2x3 l = f16mat2x3(f16vec3(1.0hf, 2.0hf, 3.0hf), f16vec3(4.0hf, 5.0hf, 6.0hf)); -} - -)"); -} - -TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_arr_f32) { - auto* C = Const("C", Call>(1_f, 2_f, 3_f)); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(C), - Decl(Let("l", Expr(C))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void f() { - float l[3] = float[3](1.0f, 2.0f, 3.0f); -} - -)"); -} - -TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_arr_f32_zero) { - auto* C = Const("C", Call>()); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(C), - Decl(Let("l", Expr(C))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void f() { - float l[2] = float[2](0.0f, 0.0f); -} - -)"); -} - -TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_arr_arr_f32_zero) { - auto* C = Const("C", Call, 3>>()); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(C), - Decl(Let("l", Expr(C))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void f() { - float l[3][2] = float[3][2](float[2](0.0f, 0.0f), float[2](0.0f, 0.0f), float[2](0.0f, 0.0f)); -} - -)"); -} - -TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_arr_struct_zero) { - Structure("S", Vector{Member("a", ty.i32()), Member("b", ty.f32())}); - auto* C = Const("C", Call(ty.array(ty("S"), 2_i))); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(C), - Decl(Let("l", Expr(C))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -struct S { - int a; - float b; -}; - -void f() { - S l[2] = S[2](S(0, 0.0f), S(0, 0.0f)); -} - -)"); -} - -TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Const_arr_vec2_bool) { - auto* C = Const("C", Call, 3>>( // - Call>(true, false), // - Call>(false, true), // - Call>(true, true))); - Func("f", tint::Empty, ty.void_(), - Vector{ - Decl(C), - Decl(Let("l", Expr(C))), - }); - - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(#version 310 es - -void f() { - bvec2 l[3] = bvec2[3](bvec2(true, false), bvec2(false, true), bvec2(true)); -} - -)"); -} - -TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Array) { - auto* var = Var("a", ty.array()); - - WrapInFunction(var, Expr("a")); - - ASTPrinter& gen = Build(); - gen.IncrementIndent(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), - HasSubstr(" float a[5] = float[5](0.0f, 0.0f, 0.0f, 0.0f, 0.0f);\n")); -} - -TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Private) { - GlobalVar("a", ty.f32(), core::AddressSpace::kPrivate); - - WrapInFunction(Expr("a")); - - ASTPrinter& gen = Build(); - gen.IncrementIndent(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr(" float a = 0.0f;\n")); -} - -TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Initializer_ZeroVec_f32) { - auto* var = Var("a", ty.vec3(), Call>()); - - auto* stmt = Decl(var); - WrapInFunction(stmt); - - ASTPrinter& gen = Build(); - gen.EmitStatement(stmt); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(vec3 a = vec3(0.0f); -)"); -} - -TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Initializer_ZeroVec_f16) { - Enable(wgsl::Extension::kF16); - - auto* var = Var("a", ty.vec3(), Call>()); - - auto* stmt = Decl(var); - WrapInFunction(stmt); - - ASTPrinter& gen = Build(); - gen.EmitStatement(stmt); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), R"(f16vec3 a = f16vec3(0.0hf); -)"); -} - -TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Initializer_ZeroMat_f32) { - auto* var = Var("a", ty.mat2x3(), Call>()); - - auto* stmt = Decl(var); - WrapInFunction(stmt); - - ASTPrinter& gen = Build(); - gen.EmitStatement(stmt); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), - R"(mat2x3 a = mat2x3(vec3(0.0f), vec3(0.0f)); -)"); -} - -TEST_F(GlslASTPrinterTest_VariableDecl, Emit_VariableDeclStatement_Initializer_ZeroMat_f16) { - Enable(wgsl::Extension::kF16); - - auto* var = Var("a", ty.mat2x3(), Call>()); - - auto* stmt = Decl(var); - WrapInFunction(stmt); - - ASTPrinter& gen = Build(); - gen.EmitStatement(stmt); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_EQ(gen.Result(), - R"(f16mat2x3 a = f16mat2x3(f16vec3(0.0hf), f16vec3(0.0hf)); -)"); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_printer/workgroup_var_test.cc b/references/writer/ast_printer/workgroup_var_test.cc deleted file mode 100644 index 227a27e3..00000000 --- a/references/writer/ast_printer/workgroup_var_test.cc +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_printer/helper_test.h" -#include "src/tint/lang/wgsl/ast/id_attribute.h" -#include "src/tint/lang/wgsl/ast/stage_attribute.h" - -#include "gmock/gmock.h" - -using ::testing::HasSubstr; - -using namespace tint::core::number_suffixes; // NOLINT - -namespace tint::glsl::writer { -namespace { - -using GlslASTPrinterTest_WorkgroupVar = TestHelper; - -TEST_F(GlslASTPrinterTest_WorkgroupVar, Basic) { - GlobalVar("wg", ty.f32(), core::AddressSpace::kWorkgroup); - - Func("main", tint::Empty, ty.void_(), Vector{Assign("wg", 1.2_f)}, - Vector{ - Stage(ast::PipelineStage::kCompute), - WorkgroupSize(1_i), - }); - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("shared float wg;\n")); -} - -TEST_F(GlslASTPrinterTest_WorkgroupVar, Aliased) { - auto* alias = Alias("F32", ty.f32()); - - GlobalVar("wg", ty.Of(alias), core::AddressSpace::kWorkgroup); - - Func("main", tint::Empty, ty.void_(), Vector{Assign("wg", 1.2_f)}, - Vector{ - Stage(ast::PipelineStage::kCompute), - WorkgroupSize(1_i), - }); - ASTPrinter& gen = Build(); - gen.Generate(); - EXPECT_THAT(gen.Diagnostics(), testing::IsEmpty()); - EXPECT_THAT(gen.Result(), HasSubstr("shared float wg;\n")); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_raise/BUILD.bazel b/references/writer/ast_raise/BUILD.bazel deleted file mode 100644 index 49e220fc..00000000 --- a/references/writer/ast_raise/BUILD.bazel +++ /dev/null @@ -1,183 +0,0 @@ -# Copyright 2023 The Dawn & Tint Authors -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -################################################################################ -# File generated by 'tools/src/cmd/gen' using the template: -# tools/src/cmd/gen/build/BUILD.bazel.tmpl -# -# To regenerate run: './tools/run gen' -# -# Do not modify this file directly -################################################################################ - -load("//src/tint:flags.bzl", "COPTS") -load("@bazel_skylib//lib:selects.bzl", "selects") -cc_library( - name = "ast_raise", - srcs = [ - "combine_samplers.cc", - "pad_structs.cc", - "texture_1d_to_2d.cc", - "texture_builtins_from_uniform.cc", - ], - hdrs = [ - "combine_samplers.h", - "pad_structs.h", - "texture_1d_to_2d.h", - "texture_builtins_from_uniform.h", - ], - deps = [ - "//src/tint/api/common", - "//src/tint/api/options", - "//src/tint/lang/core", - "//src/tint/lang/core/constant", - "//src/tint/lang/core/type", - "//src/tint/lang/wgsl", - "//src/tint/lang/wgsl/ast", - "//src/tint/lang/wgsl/ast/transform", - "//src/tint/lang/wgsl/common", - "//src/tint/lang/wgsl/features", - "//src/tint/lang/wgsl/program", - "//src/tint/lang/wgsl/resolver", - "//src/tint/lang/wgsl/sem", - "//src/tint/utils/containers", - "//src/tint/utils/diagnostic", - "//src/tint/utils/ice", - "//src/tint/utils/id", - "//src/tint/utils/macros", - "//src/tint/utils/math", - "//src/tint/utils/memory", - "//src/tint/utils/reflection", - "//src/tint/utils/result", - "//src/tint/utils/rtti", - "//src/tint/utils/symbol", - "//src/tint/utils/text", - "//src/tint/utils/traits", - ] + select({ - ":tint_build_glsl_writer": [ - "//src/tint/lang/glsl/writer/common", - ], - "//conditions:default": [], - }), - copts = COPTS, - visibility = ["//visibility:public"], -) -cc_library( - name = "test", - alwayslink = True, - srcs = [ - "combine_samplers_test.cc", - "pad_structs_test.cc", - "texture_1d_to_2d_test.cc", - "texture_builtins_from_uniform_test.cc", - ], - deps = [ - "//src/tint/api/common", - "//src/tint/api/options", - "//src/tint/lang/core", - "//src/tint/lang/core/constant", - "//src/tint/lang/core/ir", - "//src/tint/lang/core/type", - "//src/tint/lang/wgsl", - "//src/tint/lang/wgsl/ast", - "//src/tint/lang/wgsl/ast/transform", - "//src/tint/lang/wgsl/common", - "//src/tint/lang/wgsl/features", - "//src/tint/lang/wgsl/program", - "//src/tint/lang/wgsl/sem", - "//src/tint/lang/wgsl/writer/ir_to_program", - "//src/tint/utils/containers", - "//src/tint/utils/diagnostic", - "//src/tint/utils/ice", - "//src/tint/utils/id", - "//src/tint/utils/macros", - "//src/tint/utils/math", - "//src/tint/utils/memory", - "//src/tint/utils/reflection", - "//src/tint/utils/result", - "//src/tint/utils/rtti", - "//src/tint/utils/symbol", - "//src/tint/utils/text", - "//src/tint/utils/traits", - "@gtest", - ] + select({ - ":tint_build_glsl_writer": [ - "//src/tint/lang/glsl/writer/ast_raise", - "//src/tint/lang/glsl/writer/common", - ], - "//conditions:default": [], - }) + select({ - ":tint_build_wgsl_reader": [ - "//src/tint/lang/wgsl/reader", - ], - "//conditions:default": [], - }) + select({ - ":tint_build_wgsl_reader_and_tint_build_wgsl_writer": [ - "//src/tint/lang/wgsl/ast/transform:test", - ], - "//conditions:default": [], - }) + select({ - ":tint_build_wgsl_writer": [ - "//src/tint/lang/wgsl/writer", - ], - "//conditions:default": [], - }), - copts = COPTS, - visibility = ["//visibility:public"], -) - -alias( - name = "tint_build_glsl_writer", - actual = "//src/tint:tint_build_glsl_writer_true", -) - -alias( - name = "tint_build_wgsl_reader", - actual = "//src/tint:tint_build_wgsl_reader_true", -) - -alias( - name = "tint_build_wgsl_writer", - actual = "//src/tint:tint_build_wgsl_writer_true", -) - -selects.config_setting_group( - name = "tint_build_glsl_writer_and_tint_build_wgsl_reader_and_tint_build_wgsl_writer", - match_all = [ - ":tint_build_glsl_writer", - ":tint_build_wgsl_reader", - ":tint_build_wgsl_writer", - ], -) -selects.config_setting_group( - name = "tint_build_wgsl_reader_and_tint_build_wgsl_writer", - match_all = [ - ":tint_build_wgsl_reader", - ":tint_build_wgsl_writer", - ], -) - diff --git a/references/writer/ast_raise/BUILD.cfg b/references/writer/ast_raise/BUILD.cfg deleted file mode 100644 index 375c295f..00000000 --- a/references/writer/ast_raise/BUILD.cfg +++ /dev/null @@ -1,6 +0,0 @@ -{ - "condition": "tint_build_glsl_writer", - "test": { - "condition": "tint_build_wgsl_reader && tint_build_wgsl_writer", - } -} diff --git a/references/writer/ast_raise/BUILD.cmake b/references/writer/ast_raise/BUILD.cmake deleted file mode 100644 index ab41d85e..00000000 --- a/references/writer/ast_raise/BUILD.cmake +++ /dev/null @@ -1,162 +0,0 @@ -# Copyright 2023 The Dawn & Tint Authors -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -################################################################################ -# File generated by 'tools/src/cmd/gen' using the template: -# tools/src/cmd/gen/build/BUILD.cmake.tmpl -# -# To regenerate run: './tools/run gen' -# -# Do not modify this file directly -################################################################################ - -if(TINT_BUILD_GLSL_WRITER) -################################################################################ -# Target: tint_lang_glsl_writer_ast_raise -# Kind: lib -# Condition: TINT_BUILD_GLSL_WRITER -################################################################################ -tint_add_target(tint_lang_glsl_writer_ast_raise lib - lang/glsl/writer/ast_raise/combine_samplers.cc - lang/glsl/writer/ast_raise/combine_samplers.h - lang/glsl/writer/ast_raise/pad_structs.cc - lang/glsl/writer/ast_raise/pad_structs.h - lang/glsl/writer/ast_raise/texture_1d_to_2d.cc - lang/glsl/writer/ast_raise/texture_1d_to_2d.h - lang/glsl/writer/ast_raise/texture_builtins_from_uniform.cc - lang/glsl/writer/ast_raise/texture_builtins_from_uniform.h -) - -tint_target_add_dependencies(tint_lang_glsl_writer_ast_raise lib - tint_api_common - tint_api_options - tint_lang_core - tint_lang_core_constant - tint_lang_core_type - tint_lang_wgsl - tint_lang_wgsl_ast - tint_lang_wgsl_ast_transform - tint_lang_wgsl_common - tint_lang_wgsl_features - tint_lang_wgsl_program - tint_lang_wgsl_resolver - tint_lang_wgsl_sem - tint_utils_containers - tint_utils_diagnostic - tint_utils_ice - tint_utils_id - tint_utils_macros - tint_utils_math - tint_utils_memory - tint_utils_reflection - tint_utils_result - tint_utils_rtti - tint_utils_symbol - tint_utils_text - tint_utils_traits -) - -if(TINT_BUILD_GLSL_WRITER) - tint_target_add_dependencies(tint_lang_glsl_writer_ast_raise lib - tint_lang_glsl_writer_common - ) -endif(TINT_BUILD_GLSL_WRITER) - -endif(TINT_BUILD_GLSL_WRITER) -if(TINT_BUILD_GLSL_WRITER AND TINT_BUILD_WGSL_READER AND TINT_BUILD_WGSL_WRITER) -################################################################################ -# Target: tint_lang_glsl_writer_ast_raise_test -# Kind: test -# Condition: TINT_BUILD_GLSL_WRITER AND TINT_BUILD_WGSL_READER AND TINT_BUILD_WGSL_WRITER -################################################################################ -tint_add_target(tint_lang_glsl_writer_ast_raise_test test - lang/glsl/writer/ast_raise/combine_samplers_test.cc - lang/glsl/writer/ast_raise/pad_structs_test.cc - lang/glsl/writer/ast_raise/texture_1d_to_2d_test.cc - lang/glsl/writer/ast_raise/texture_builtins_from_uniform_test.cc -) - -tint_target_add_dependencies(tint_lang_glsl_writer_ast_raise_test test - tint_api_common - tint_api_options - tint_lang_core - tint_lang_core_constant - tint_lang_core_ir - tint_lang_core_type - tint_lang_wgsl - tint_lang_wgsl_ast - tint_lang_wgsl_ast_transform - tint_lang_wgsl_common - tint_lang_wgsl_features - tint_lang_wgsl_program - tint_lang_wgsl_sem - tint_lang_wgsl_writer_ir_to_program - tint_utils_containers - tint_utils_diagnostic - tint_utils_ice - tint_utils_id - tint_utils_macros - tint_utils_math - tint_utils_memory - tint_utils_reflection - tint_utils_result - tint_utils_rtti - tint_utils_symbol - tint_utils_text - tint_utils_traits -) - -tint_target_add_external_dependencies(tint_lang_glsl_writer_ast_raise_test test - "gtest" -) - -if(TINT_BUILD_GLSL_WRITER) - tint_target_add_dependencies(tint_lang_glsl_writer_ast_raise_test test - tint_lang_glsl_writer_ast_raise - tint_lang_glsl_writer_common - ) -endif(TINT_BUILD_GLSL_WRITER) - -if(TINT_BUILD_WGSL_READER) - tint_target_add_dependencies(tint_lang_glsl_writer_ast_raise_test test - tint_lang_wgsl_reader - ) -endif(TINT_BUILD_WGSL_READER) - -if(TINT_BUILD_WGSL_READER AND TINT_BUILD_WGSL_WRITER) - tint_target_add_dependencies(tint_lang_glsl_writer_ast_raise_test test - tint_lang_wgsl_ast_transform_test - ) -endif(TINT_BUILD_WGSL_READER AND TINT_BUILD_WGSL_WRITER) - -if(TINT_BUILD_WGSL_WRITER) - tint_target_add_dependencies(tint_lang_glsl_writer_ast_raise_test test - tint_lang_wgsl_writer - ) -endif(TINT_BUILD_WGSL_WRITER) - -endif(TINT_BUILD_GLSL_WRITER AND TINT_BUILD_WGSL_READER AND TINT_BUILD_WGSL_WRITER) \ No newline at end of file diff --git a/references/writer/ast_raise/BUILD.gn b/references/writer/ast_raise/BUILD.gn deleted file mode 100644 index 75216fd4..00000000 --- a/references/writer/ast_raise/BUILD.gn +++ /dev/null @@ -1,151 +0,0 @@ -# Copyright 2023 The Dawn & Tint Authors -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -################################################################################ -# File generated by 'tools/src/cmd/gen' using the template: -# tools/src/cmd/gen/build/BUILD.gn.tmpl -# -# To regenerate run: './tools/run gen' -# -# Do not modify this file directly -################################################################################ - -import("../../../../../../scripts/tint_overrides_with_defaults.gni") - -import("${tint_src_dir}/tint.gni") - -if (tint_build_unittests || tint_build_benchmarks) { - import("//testing/test.gni") -} -if (tint_build_glsl_writer) { - libtint_source_set("ast_raise") { - sources = [ - "combine_samplers.cc", - "combine_samplers.h", - "pad_structs.cc", - "pad_structs.h", - "texture_1d_to_2d.cc", - "texture_1d_to_2d.h", - "texture_builtins_from_uniform.cc", - "texture_builtins_from_uniform.h", - ] - deps = [ - "${tint_src_dir}/api/common", - "${tint_src_dir}/api/options", - "${tint_src_dir}/lang/core", - "${tint_src_dir}/lang/core/constant", - "${tint_src_dir}/lang/core/type", - "${tint_src_dir}/lang/wgsl", - "${tint_src_dir}/lang/wgsl/ast", - "${tint_src_dir}/lang/wgsl/ast/transform", - "${tint_src_dir}/lang/wgsl/common", - "${tint_src_dir}/lang/wgsl/features", - "${tint_src_dir}/lang/wgsl/program", - "${tint_src_dir}/lang/wgsl/resolver", - "${tint_src_dir}/lang/wgsl/sem", - "${tint_src_dir}/utils/containers", - "${tint_src_dir}/utils/diagnostic", - "${tint_src_dir}/utils/ice", - "${tint_src_dir}/utils/id", - "${tint_src_dir}/utils/macros", - "${tint_src_dir}/utils/math", - "${tint_src_dir}/utils/memory", - "${tint_src_dir}/utils/reflection", - "${tint_src_dir}/utils/result", - "${tint_src_dir}/utils/rtti", - "${tint_src_dir}/utils/symbol", - "${tint_src_dir}/utils/text", - "${tint_src_dir}/utils/traits", - ] - - if (tint_build_glsl_writer) { - deps += [ "${tint_src_dir}/lang/glsl/writer/common" ] - } - } -} -if (tint_build_unittests) { - if (tint_build_glsl_writer && tint_build_wgsl_reader && - tint_build_wgsl_writer) { - tint_unittests_source_set("unittests") { - sources = [ - "combine_samplers_test.cc", - "pad_structs_test.cc", - "texture_1d_to_2d_test.cc", - "texture_builtins_from_uniform_test.cc", - ] - deps = [ - "${tint_src_dir}:gmock_and_gtest", - "${tint_src_dir}/api/common", - "${tint_src_dir}/api/options", - "${tint_src_dir}/lang/core", - "${tint_src_dir}/lang/core/constant", - "${tint_src_dir}/lang/core/ir", - "${tint_src_dir}/lang/core/type", - "${tint_src_dir}/lang/wgsl", - "${tint_src_dir}/lang/wgsl/ast", - "${tint_src_dir}/lang/wgsl/ast/transform", - "${tint_src_dir}/lang/wgsl/common", - "${tint_src_dir}/lang/wgsl/features", - "${tint_src_dir}/lang/wgsl/program", - "${tint_src_dir}/lang/wgsl/sem", - "${tint_src_dir}/lang/wgsl/writer/ir_to_program", - "${tint_src_dir}/utils/containers", - "${tint_src_dir}/utils/diagnostic", - "${tint_src_dir}/utils/ice", - "${tint_src_dir}/utils/id", - "${tint_src_dir}/utils/macros", - "${tint_src_dir}/utils/math", - "${tint_src_dir}/utils/memory", - "${tint_src_dir}/utils/reflection", - "${tint_src_dir}/utils/result", - "${tint_src_dir}/utils/rtti", - "${tint_src_dir}/utils/symbol", - "${tint_src_dir}/utils/text", - "${tint_src_dir}/utils/traits", - ] - - if (tint_build_glsl_writer) { - deps += [ - "${tint_src_dir}/lang/glsl/writer/ast_raise", - "${tint_src_dir}/lang/glsl/writer/common", - ] - } - - if (tint_build_wgsl_reader) { - deps += [ "${tint_src_dir}/lang/wgsl/reader" ] - } - - if (tint_build_wgsl_reader && tint_build_wgsl_writer) { - deps += [ "${tint_src_dir}/lang/wgsl/ast/transform:unittests" ] - } - - if (tint_build_wgsl_writer) { - deps += [ "${tint_src_dir}/lang/wgsl/writer" ] - } - } - } -} diff --git a/references/writer/ast_raise/combine_samplers.cc b/references/writer/ast_raise/combine_samplers.cc deleted file mode 100644 index 66f5236f..00000000 --- a/references/writer/ast_raise/combine_samplers.cc +++ /dev/null @@ -1,455 +0,0 @@ -// Copyright 2022 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_raise/combine_samplers.h" - -#include -#include -#include -#include - -#include "src/tint/lang/glsl/writer/common/options.h" -#include "src/tint/lang/wgsl/program/clone_context.h" -#include "src/tint/lang/wgsl/program/program_builder.h" -#include "src/tint/lang/wgsl/resolver/resolve.h" -#include "src/tint/lang/wgsl/sem/function.h" -#include "src/tint/lang/wgsl/sem/statement.h" - -#include "src/tint/utils/containers/map.h" - -TINT_INSTANTIATE_TYPEINFO(tint::glsl::writer::CombineSamplers); -TINT_INSTANTIATE_TYPEINFO(tint::glsl::writer::CombineSamplersInfo); - -namespace { - -bool IsGlobal(const tint::sem::VariablePair& pair) { - return (!pair.first || tint::Is(pair.first)) && - (!pair.second || tint::Is(pair.second)); -} - -} // namespace - -namespace tint::glsl::writer { - -using namespace tint::core::number_suffixes; // NOLINT - // -CombineSamplersInfo::CombineSamplersInfo() = default; - -CombineSamplersInfo::CombineSamplersInfo(CombinedTextureSamplerInfo map, BindingPoint placeholder) - : sampler_texture_to_name(std::move(map)), placeholder_sampler_binding(placeholder) {} - -CombineSamplersInfo::~CombineSamplersInfo() = default; - -/// PIMPL state for the transform -struct CombineSamplers::State { - /// The source program - const Program& src; - /// The target program builder - ProgramBuilder b; - /// The clone context - program::CloneContext ctx = {&b, &src, /* auto_clone_symbols */ true}; - - /// The combined sampler information - const CombineSamplersInfo* combined_samplers_info; - - /// Map from a texture/sampler pair to the corresponding combined sampler - /// variable - using CombinedTextureSamplerMap = std::unordered_map; - - /// A map of all global texture/sampler variable pairs to the global - /// combined sampler variable that will replace it. - CombinedTextureSamplerMap global_combined_texture_samplers_; - - /// A map of all texture/sampler variable pairs that contain a function - /// parameter to the combined sampler function paramter that will replace it. - std::unordered_map - function_combined_texture_samplers_; - - /// Placeholder global samplers used when a function contains texture-only - /// references (one comparison sampler, one regular). These are also used as - /// temporary sampler parameters to the texture builtins to satisfy the WGSL - /// resolver, but are then ignored and removed by the GLSL writer. - const ast::Variable* placeholder_samplers_[2] = {}; - - /// Group and binding attributes used by all combined sampler globals. - /// Group 0 and binding 0 are used, with collisions disabled. - /// @returns the newly-created attribute list - auto Attributes() const { - Vector attributes{ctx.dst->Group(0_a), ctx.dst->Binding(0_a)}; - attributes.Push(ctx.dst->Disable(ast::DisabledValidation::kBindingPointCollision)); - return attributes; - } - - /// Constructor - /// @param program the source program - /// @param info the binding map information - State(const Program& program, const CombineSamplersInfo* info) - : src(program), combined_samplers_info(info) {} - - /// Creates a combined sampler global variables. - /// (Note this is actually a Texture node at the AST level, but it will be - /// written as the corresponding sampler (eg., sampler2D) on GLSL output.) - /// @param texture_var the texture (global) variable - /// @param sampler_var the sampler (global) variable - /// @param name the default name to use (may be overridden by map lookup) - /// @returns the newly-created global variable - const ast::Variable* CreateCombinedGlobal(const sem::Variable* texture_var, - const sem::Variable* sampler_var, - std::string name) { - binding::CombinedTextureSamplerPair st_pair; - st_pair.texture = texture_var - ? *texture_var->As()->Attributes().binding_point - : combined_samplers_info->placeholder_sampler_binding; - st_pair.sampler = sampler_var - ? *sampler_var->As()->Attributes().binding_point - : combined_samplers_info->placeholder_sampler_binding; - auto it = combined_samplers_info->sampler_texture_to_name.find(st_pair); - if (it != combined_samplers_info->sampler_texture_to_name.end()) { - name = it->second; - } - ast::Type type = CreateCombinedASTTypeFor(texture_var, sampler_var); - Symbol symbol = ctx.dst->Symbols().New(name); - return ctx.dst->GlobalVar(symbol, type, Attributes()); - } - - /// Creates placeholder global sampler variables. - /// @param kind the sampler kind to create for - /// @returns the newly-created global variable - const ast::Variable* CreatePlaceholder(core::type::SamplerKind kind) { - ast::Type type = ctx.dst->ty.sampler(kind); - const char* name = kind == core::type::SamplerKind::kComparisonSampler - ? "placeholder_comparison_sampler" - : "placeholder_sampler"; - Symbol symbol = ctx.dst->Symbols().New(name); - return ctx.dst->GlobalVar(symbol, type, Attributes()); - } - - /// Creates Identifier for a given texture and sampler variable pair. - /// Depth textures with no samplers are turned into the corresponding - /// f32 texture (e.g., texture_depth_2d -> texture_2d). - /// Either texture or sampler could be nullptr, but cannot be nullptr at the same time. - /// The texture can only be nullptr, when the sampler is a dangling function parameter. - /// @param texture the texture variable of interest - /// @param sampler the texture variable of interest - /// @returns the newly-created type - ast::Type CreateCombinedASTTypeFor(const sem::Variable* texture, const sem::Variable* sampler) { - if (texture) { - const core::type::Type* texture_type = texture->Type()->UnwrapRef(); - const core::type::DepthTexture* depth = texture_type->As(); - if (depth && !sampler) { - return ctx.dst->ty.sampled_texture(depth->dim(), ctx.dst->ty.f32()); - } else { - return CreateASTTypeFor(ctx, texture_type); - } - } else { - TINT_ASSERT(sampler != nullptr); - const core::type::Type* sampler_type = sampler->Type()->UnwrapRef(); - return CreateASTTypeFor(ctx, sampler_type); - } - } - - /// Insert a new texture/sampler pair into the combined samplers maps (global or local, as - /// appropriate). If local, also add a function parameter to "params". - /// @param pair the texture/sampler pair to insert - /// @param fn the function scope in which to insert (if local) - /// @param params the calling function's parameter list to modify (if local) - void InsertPair(sem::VariablePair pair, - const sem::Function* fn, - tint::Vector* params) { - const sem::Variable* texture_var = pair.first; - const sem::Variable* sampler_var = pair.second; - std::string name = ""; - if (texture_var) { - name = texture_var->Declaration()->name->symbol.Name(); - } - if (sampler_var) { - if (!name.empty()) { - name += "_"; - } - name += sampler_var->Declaration()->name->symbol.Name(); - } - if (IsGlobal(pair)) { - // Both texture and sampler are global; add a new global variable - // to represent the combined sampler (if not already created). - GetOrAdd(global_combined_texture_samplers_, pair, - [&] { return CreateCombinedGlobal(texture_var, sampler_var, name); }); - } else { - // Either texture or sampler (or both) is a function parameter; - // add a new function parameter to represent the combined sampler. - ast::Type type = CreateCombinedASTTypeFor(texture_var, sampler_var); - auto* var = ctx.dst->Param(ctx.dst->Symbols().New(name), type); - params->Push(var); - function_combined_texture_samplers_[fn][pair] = var; - } - } - - /// For a given texture, find any texture/sampler pair with a non-null sampler in the given - /// function scope. - /// @param texture_var the texture variable of interest - /// @param fn the function scope in which to search - /// @returns the full pair, if found - const sem::VariablePair* FindFullTextureSamplerPair(const sem::Variable* texture_var, - const sem::Function* fn) { - for (auto pairIter = fn->TextureSamplerPairs().begin(); - pairIter != fn->TextureSamplerPairs().end(); pairIter++) { - if (pairIter->first == texture_var && pairIter->second) { - return pairIter; - } - } - return nullptr; - } - - /// Runs the transform - /// @returns the new program or SkipTransform if the transform is not required - ApplyResult Run() { - auto& sem = ctx.src->Sem(); - - // Remove all texture and sampler global variables. These will be replaced - // by combined samplers. - for (auto* global : ctx.src->AST().GlobalVariables()) { - auto* global_sem = sem.Get(global)->As(); - auto* type = ctx.src->TypeOf(global->type); - if (tint::IsAnyOf(type) && - !type->Is()) { - ctx.Remove(ctx.src->AST().GlobalDeclarations(), global); - } else if (auto binding_point = global_sem->Attributes().binding_point) { - if (binding_point->group == 0 && binding_point->binding == 0) { - auto* attribute = - ctx.dst->Disable(ast::DisabledValidation::kBindingPointCollision); - ctx.InsertFront(global->attributes, attribute); - } - } - } - - // Rewrite all function signatures to use combined samplers, and remove - // separate textures & samplers. Create new combined globals where found. - ctx.ReplaceAll([&](const ast::Function* ast_fn) -> const ast::Function* { - if (auto* fn = sem.Get(ast_fn)) { - auto pairs = fn->TextureSamplerPairs(); - if (pairs.IsEmpty()) { - return nullptr; - } - Vector params; - for (auto pair : fn->TextureSamplerPairs()) { - if (!pair.second) { - continue; - } - InsertPair(pair, fn, ¶ms); - } - for (auto pair : fn->TextureSamplerPairs()) { - if (pair.second) { - continue; - } - // Look for another pair with a non-null sampler. - // NOTE: this is O(N^2) in the number of pairs, since - // FindFullTextureSamplerPair() also loops over all pairs. If this proves - // problematic, it could be optimized. - if (const sem::VariablePair* fullPair = - FindFullTextureSamplerPair(pair.first, fn)) { - if (IsGlobal(pair)) { - global_combined_texture_samplers_[pair] = - global_combined_texture_samplers_[*fullPair]; - } else { - auto* var = function_combined_texture_samplers_[fn][*fullPair]; - function_combined_texture_samplers_[fn][pair] = var; - } - } else { - InsertPair(pair, fn, ¶ms); - } - } - // Filter out separate textures and samplers from the original - // function signature. - for (auto* param : fn->Parameters()) { - if (!param->Type()->IsAnyOf()) { - params.Push(ctx.Clone(param->Declaration())); - } - } - // Create a new function signature that differs only in the parameter - // list. - auto name = ctx.Clone(ast_fn->name); - auto return_type = ctx.Clone(ast_fn->return_type); - auto* body = ctx.Clone(ast_fn->body); - auto attributes = ctx.Clone(ast_fn->attributes); - auto return_type_attributes = ctx.Clone(ast_fn->return_type_attributes); - return ctx.dst->create(name, params, return_type, body, - std::move(attributes), - std::move(return_type_attributes)); - } - return nullptr; - }); - - // Replace all function call expressions containing texture or - // sampler parameters to use the current function's combined samplers or - // the combined global samplers, as appropriate. - ctx.ReplaceAll([&](const ast::CallExpression* expr) -> const ast::Expression* { - if (auto* call = sem.Get(expr)->UnwrapMaterialize()->As()) { - Vector args; - // Replace all texture builtin calls. - if (auto* builtin = call->Target()->As()) { - const auto& signature = builtin->Signature(); - auto sampler_index = signature.IndexOf(core::ParameterUsage::kSampler); - auto texture_index = signature.IndexOf(core::ParameterUsage::kTexture); - if (texture_index == -1) { - return nullptr; - } - const sem::ValueExpression* texture = - call->Arguments()[static_cast(texture_index)]; - // We don't want to combine storage textures with anything, since - // they never have associated samplers in GLSL. - if (texture->Type()->UnwrapRef()->Is()) { - return nullptr; - } - const sem::ValueExpression* sampler = - sampler_index != -1 ? call->Arguments()[static_cast(sampler_index)] - : nullptr; - auto* texture_var = texture->UnwrapLoad()->As()->Variable(); - auto* sampler_var = - sampler ? sampler->UnwrapLoad()->As()->Variable() - : nullptr; - sem::VariablePair new_pair(texture_var, sampler_var); - for (auto* arg : expr->args) { - auto* type = ctx.src->TypeOf(arg)->UnwrapRef(); - if (type->Is()) { - const ast::Variable* var = - IsGlobal(new_pair) - ? global_combined_texture_samplers_[new_pair] - : function_combined_texture_samplers_[call->Stmt()->Function()] - [new_pair]; - args.Push(ctx.dst->Expr(var->name->symbol)); - } else if (auto* sampler_type = type->As()) { - core::type::SamplerKind kind = sampler_type->kind(); - int index = (kind == core::type::SamplerKind::kSampler) ? 0 : 1; - const ast::Variable*& p = placeholder_samplers_[index]; - if (!p) { - p = CreatePlaceholder(kind); - } - args.Push(ctx.dst->Expr(p->name->symbol)); - } else { - args.Push(ctx.Clone(arg)); - } - } - const ast::Expression* value = ctx.dst->Call(ctx.Clone(expr->target), args); - if (builtin->Fn() == wgsl::BuiltinFn::kTextureLoad && - texture_var->Type()->UnwrapRef()->Is() && - !call->Stmt()->Declaration()->Is()) { - value = ctx.dst->MemberAccessor(value, "x"); - } - return value; - } - // Replace all function calls. - if (auto* callee = call->Target()->As()) { - auto make_arg = [&](const sem::Variable* texture_var, - const sem::Variable* sampler_var) { - if (auto* param = tint::As(texture_var)) { - const sem::ValueExpression* texture = call->Arguments()[param->Index()]; - texture_var = - texture->UnwrapLoad()->As()->Variable(); - } - if (auto* param = tint::As(sampler_var)) { - const sem::ValueExpression* sampler = call->Arguments()[param->Index()]; - sampler_var = - sampler->UnwrapLoad()->As()->Variable(); - } - sem::VariablePair new_pair(texture_var, sampler_var); - // If both texture and sampler are (now) global, pass that - // global variable to the callee. Otherwise use the caller's - // function parameter for this pair. - const ast::Variable* var = - IsGlobal(new_pair) - ? global_combined_texture_samplers_[new_pair] - : function_combined_texture_samplers_[call->Stmt()->Function()] - [new_pair]; - return ctx.dst->Expr(var->name->symbol); - }; - - for (auto pair : callee->TextureSamplerPairs()) { - if (!pair.second) { - continue; - } - // Global pairs used by the callee do not require a function - // parameter at the call site. - if (IsGlobal(pair)) { - continue; - } - - args.Push(make_arg(pair.first, pair.second)); - } - for (auto pair : callee->TextureSamplerPairs()) { - if (pair.second) { - continue; - } - // Global pairs used by the callee do not require a function - // parameter at the call site. - if (IsGlobal(pair)) { - continue; - } - // Texture-only pairs do not require a function parameter if they've been - // replaced by a real pair. - if (FindFullTextureSamplerPair(pair.first, callee)) { - continue; - } - - args.Push(make_arg(pair.first, nullptr)); - } - // Append all of the remaining non-texture and non-sampler - // parameters. - for (auto* arg : expr->args) { - if (!ctx.src->TypeOf(arg) - ->UnwrapRef() - ->IsAnyOf()) { - args.Push(ctx.Clone(arg)); - } - } - return ctx.dst->Call(ctx.Clone(expr->target), args); - } - } - return nullptr; - }); - - ctx.Clone(); - return resolver::Resolve(b); - } -}; - -CombineSamplers::CombineSamplers() = default; - -CombineSamplers::~CombineSamplers() = default; - -ast::transform::Transform::ApplyResult CombineSamplers::Apply(const Program& src, - const ast::transform::DataMap& inputs, - ast::transform::DataMap&) const { - auto* info = inputs.Get(); - if (!info) { - ProgramBuilder b; - b.Diagnostics().AddError(Source{}) << "missing transform data for " << TypeInfo().name; - return resolver::Resolve(b); - } - - return State(src, info).Run(); -} - -} // namespace tint::glsl::writer diff --git a/references/writer/ast_raise/combine_samplers.h b/references/writer/ast_raise/combine_samplers.h deleted file mode 100644 index 24b53547..00000000 --- a/references/writer/ast_raise/combine_samplers.h +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2022 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef SRC_TINT_LANG_GLSL_WRITER_AST_RAISE_COMBINE_SAMPLERS_H_ -#define SRC_TINT_LANG_GLSL_WRITER_AST_RAISE_COMBINE_SAMPLERS_H_ - -#include -#include - -#include "src/tint/lang/wgsl/ast/transform/transform.h" - -namespace tint::glsl::writer { - -/// This transform converts all separate texture/sampler refences in a -/// program into combined texture/samplers. This is required for GLSL, -/// which does not support separate texture/samplers. -/// -/// It utilizes the texture/sampler information collected by the -/// Resolver and stored on each sem::Function. For each function, all -/// separate texture/sampler parameters in the function signature are -/// removed. For each unique pair, if both texture and sampler are -/// global variables, the function passes the corresponding combined -/// global stored in global_combined_texture_samplers_ at the call -/// site. Otherwise, either the texture or sampler must be a function -/// parameter. In this case, a new parameter is added to the function -/// signature. All separate texture/sampler parameters are removed. -/// -/// All texture builtin callsites are modified to pass the combined -/// texture/sampler as the first argument, and separate texture/sampler -/// arguments are removed. -/// -/// Note that the sampler may be null, indicating that only a texture -/// reference was required (e.g., textureLoad). In this case, a -/// placeholder global sampler is used at the AST level. This will be -/// combined with the original texture to give a combined global, and -/// the placeholder removed (ignored) by the GLSL writer. -/// -/// Note that the combined samplers are actually represented by a -/// Texture node at the AST level, since this contains all the -/// information needed to represent a combined sampler in GLSL -/// (dimensionality, component type, etc). The GLSL writer outputs such -/// (Tint) Textures as (GLSL) Samplers. -class CombineSamplers final : public Castable { - public: - /// Constructor - CombineSamplers(); - - /// Destructor - ~CombineSamplers() override; - - /// @copydoc ast::transform::Transform::Apply - ApplyResult Apply(const Program& program, - const ast::transform::DataMap& inputs, - ast::transform::DataMap& outputs) const override; - - private: - struct State; -}; - -} // namespace tint::glsl::writer - -#endif // SRC_TINT_LANG_GLSL_WRITER_AST_RAISE_COMBINE_SAMPLERS_H_ diff --git a/references/writer/ast_raise/combine_samplers_test.cc b/references/writer/ast_raise/combine_samplers_test.cc deleted file mode 100644 index 575f428e..00000000 --- a/references/writer/ast_raise/combine_samplers_test.cc +++ /dev/null @@ -1,1218 +0,0 @@ -// Copyright 2022 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_raise/combine_samplers.h" - -#include -#include - -#include "src/tint/lang/glsl/writer/common/options.h" -#include "src/tint/lang/wgsl/ast/transform/helper_test.h" - -namespace tint::glsl::writer { -namespace { - -using CombineSamplersTest = ast::transform::TransformTest; - -TEST_F(CombineSamplersTest, EmptyModule) { - auto* src = ""; - auto* expect = ""; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, SimplePair) { - auto* src = R"( -@group(0) @binding(0) var t : texture_2d; - -@group(0) @binding(1) var s : sampler; - -fn main() -> vec4 { - return textureSample(t, s, vec2(1.0, 2.0)); -} -)"; - auto* expect = R"( -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s : texture_2d; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; - -fn main() -> vec4 { - return textureSample(t_s, placeholder_sampler, vec2(1.0, 2.0)); -} -)"; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, SimplePair_OutOfOrder) { - auto* src = R"( -fn main() -> vec4 { - return textureSample(t, s, vec2(1.0, 2.0)); -} - -@group(0) @binding(0) var t : texture_2d; - -@group(0) @binding(1) var s : sampler; -)"; - auto* expect = R"( -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s : texture_2d; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; - -fn main() -> vec4 { - return textureSample(t_s, placeholder_sampler, vec2(1.0, 2.0)); -} -)"; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, SimplePairInAFunction) { - auto* src = R"( -@group(0) @binding(0) var t : texture_2d; - -@group(0) @binding(1) var s : sampler; - -fn sample(t : texture_2d, s : sampler, coords : vec2) -> vec4 { - return textureSample(t, s, coords); -} - -fn main() -> vec4 { - return sample(t, s, vec2(1.0, 2.0)); -} -)"; - auto* expect = R"( -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; - -fn sample(t_s : texture_2d, coords : vec2) -> vec4 { - return textureSample(t_s, placeholder_sampler, coords); -} - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s_1 : texture_2d; - -fn main() -> vec4 { - return sample(t_s_1, vec2(1.0, 2.0)); -} -)"; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, SimplePairInAFunction_OutOfOrder) { - auto* src = R"( -fn main() -> vec4 { - return sample(t, s, vec2(1.0, 2.0)); -} - -fn sample(t : texture_2d, s : sampler, coords : vec2) -> vec4 { - return textureSample(t, s, coords); -} - -@group(0) @binding(1) var s : sampler; - -@group(0) @binding(0) var t : texture_2d; -)"; - auto* expect = R"( -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s : texture_2d; - -fn main() -> vec4 { - return sample(t_s, vec2(1.0, 2.0)); -} - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; - -fn sample(t_s_1 : texture_2d, coords : vec2) -> vec4 { - return textureSample(t_s_1, placeholder_sampler, coords); -} -)"; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, SimplePairRename) { - auto* src = R"( -@group(0) @binding(1) var t : texture_2d; - -@group(2) @binding(3) var s : sampler; - -fn main() -> vec4 { - return textureSample(t, s, vec2(1.0, 2.0)); -} -)"; - auto* expect = R"( -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var fuzzy : texture_2d; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; - -fn main() -> vec4 { - return textureSample(fuzzy, placeholder_sampler, vec2(1.0, 2.0)); -} -)"; - - ast::transform::DataMap data; - CombinedTextureSamplerInfo map; - binding::CombinedTextureSamplerPair pair; - pair.texture = {0, 1}; - pair.sampler = {2, 3}; - map[pair] = "fuzzy"; - - BindingPoint placeholder{1024, 0}; - data.Add(map, placeholder); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, SimplePairRenameMiss) { - auto* src = R"( -@group(0) @binding(1) var t : texture_2d; - -@group(2) @binding(3) var s : sampler; - -fn main() -> vec4 { - return textureSample(t, s, vec2(1.0, 2.0)); -} -)"; - auto* expect = R"( -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s : texture_2d; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; - -fn main() -> vec4 { - return textureSample(t_s, placeholder_sampler, vec2(1.0, 2.0)); -} -)"; - - ast::transform::DataMap data; - CombinedTextureSamplerInfo map; - binding::CombinedTextureSamplerPair pair; - pair.texture = {3, 2}; - pair.sampler = {1, 0}; - map[pair] = "fuzzy"; - - BindingPoint placeholder{1024, 0}; - data.Add(map, placeholder); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, AliasedTypes) { - auto* src = R"( - -alias Tex2d = texture_2d; - -@group(0) @binding(0) var t : Tex2d; - -@group(0) @binding(1) var s : sampler; - -fn sample(t : Tex2d, s : sampler, coords : vec2) -> vec4 { - return textureSample(t, s, coords); -} - -fn main() -> vec4 { - return sample(t, s, vec2(1.0, 2.0)); -} -)"; - auto* expect = R"( -alias Tex2d = texture_2d; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; - -fn sample(t_s : texture_2d, coords : vec2) -> vec4 { - return textureSample(t_s, placeholder_sampler, coords); -} - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s_1 : texture_2d; - -fn main() -> vec4 { - return sample(t_s_1, vec2(1.0, 2.0)); -} -)"; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, AliasedTypes_OutOfOrder) { - auto* src = R"( -fn main() -> vec4 { - return sample(t, s, vec2(1.0, 2.0)); -} - -fn sample(t : Tex2d, s : sampler, coords : vec2) -> vec4 { - return textureSample(t, s, coords); -} - -@group(0) @binding(0) var t : Tex2d; -@group(0) @binding(1) var s : sampler; - -alias Tex2d = texture_2d; -)"; - auto* expect = R"( -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s : texture_2d; - -fn main() -> vec4 { - return sample(t_s, vec2(1.0, 2.0)); -} - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; - -fn sample(t_s_1 : texture_2d, coords : vec2) -> vec4 { - return textureSample(t_s_1, placeholder_sampler, coords); -} - -alias Tex2d = texture_2d; -)"; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, SimplePairInTwoFunctions) { - auto* src = R"( -@group(0) @binding(0) var t : texture_2d; - -@group(0) @binding(1) var s : sampler; - -fn g(t : texture_2d, s : sampler, coords : vec2) -> vec4 { - return textureSample(t, s, coords); -} - -fn f(t : texture_2d, s : sampler, coords : vec2) -> vec4 { - return g(t, s, coords); -} - -fn main() -> vec4 { - return f(t, s, vec2(1.0, 2.0)); -} -)"; - auto* expect = R"( -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; - -fn g(t_s : texture_2d, coords : vec2) -> vec4 { - return textureSample(t_s, placeholder_sampler, coords); -} - -fn f(t_s_1 : texture_2d, coords : vec2) -> vec4 { - return g(t_s_1, coords); -} - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s_2 : texture_2d; - -fn main() -> vec4 { - return f(t_s_2, vec2(1.0, 2.0)); -} -)"; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, SimplePairInTwoFunctions_OutOfOrder) { - auto* src = R"( -fn main() -> vec4 { - return f(t, s, vec2(1.0, 2.0)); -} - -fn f(t : texture_2d, s : sampler, coords : vec2) -> vec4 { - return g(t, s, coords); -} - -fn g(t : texture_2d, s : sampler, coords : vec2) -> vec4 { - return textureSample(t, s, coords); -} - -@group(0) @binding(1) var s : sampler; -@group(0) @binding(0) var t : texture_2d; -)"; - auto* expect = R"( -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_s : texture_2d; - -fn main() -> vec4 { - return f(t_s, vec2(1.0, 2.0)); -} - -fn f(t_s_1 : texture_2d, coords : vec2) -> vec4 { - return g(t_s_1, coords); -} - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; - -fn g(t_s_2 : texture_2d, coords : vec2) -> vec4 { - return textureSample(t_s_2, placeholder_sampler, coords); -} -)"; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, TwoFunctionsGenerateSamePair) { - auto* src = R"( -@group(1) @binding(0) var tex : texture_2d; - -@group(1) @binding(1) var samp : sampler; - -fn f() -> vec4 { - return textureSample(tex, samp, vec2(1.0, 2.0)); -} - -fn g() -> vec4 { - return textureSample(tex, samp, vec2(3.0, 4.0)); -} - -fn main() -> vec4 { - return f() + g(); -} -)"; - auto* expect = R"( -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp : texture_2d; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; - -fn f() -> vec4 { - return textureSample(tex_samp, placeholder_sampler, vec2(1.0, 2.0)); -} - -fn g() -> vec4 { - return textureSample(tex_samp, placeholder_sampler, vec2(3.0, 4.0)); -} - -fn main() -> vec4 { - return (f() + g()); -} -)"; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, ThreeTexturesThreeSamplers) { - auto* src = R"( -@group(0) @binding(0) var tex1 : texture_2d; -@group(0) @binding(1) var tex2 : texture_2d; -@group(0) @binding(2) var tex3 : texture_2d; - -@group(1) @binding(0) var samp1 : sampler; -@group(1) @binding(1) var samp2: sampler; -@group(1) @binding(2) var samp3: sampler; - -fn sample(t : texture_2d, s : sampler) -> vec4 { - return textureSample(t, s, vec2(1.0, 2.0)); -} - -fn main() -> vec4 { - return sample(tex1, samp1) - + sample(tex1, samp2) - + sample(tex1, samp3) - + sample(tex2, samp1) - + sample(tex2, samp2) - + sample(tex2, samp3) - + sample(tex3, samp1) - + sample(tex3, samp2) - + sample(tex3, samp3); -} -)"; - auto* expect = R"( -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; - -fn sample(t_s : texture_2d) -> vec4 { - return textureSample(t_s, placeholder_sampler, vec2(1.0, 2.0)); -} - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex1_samp1 : texture_2d; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex1_samp2 : texture_2d; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex1_samp3 : texture_2d; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex2_samp1 : texture_2d; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex2_samp2 : texture_2d; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex2_samp3 : texture_2d; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex3_samp1 : texture_2d; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex3_samp2 : texture_2d; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex3_samp3 : texture_2d; - -fn main() -> vec4 { - return ((((((((sample(tex1_samp1) + sample(tex1_samp2)) + sample(tex1_samp3)) + sample(tex2_samp1)) + sample(tex2_samp2)) + sample(tex2_samp3)) + sample(tex3_samp1)) + sample(tex3_samp2)) + sample(tex3_samp3)); -} -)"; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, TwoFunctionsTwoTexturesDiamond) { - auto* src = R"( -@group(0) @binding(0) var tex1 : texture_2d; - -@group(0) @binding(1) var tex2 : texture_2d; - -@group(0) @binding(2) var samp : sampler; - -fn sample(t : texture_2d, s : sampler, coords : vec2) -> vec4 { - return textureSample(t, s, coords); -} - -fn f(t1 : texture_2d, t2 : texture_2d, s : sampler, coords : vec2) -> vec4 { - return sample(t1, s, coords) + sample(t2, s, coords); -} - -fn main() -> vec4 { - return f(tex1, tex2, samp, vec2(1.0, 2.0)); -} -)"; - auto* expect = R"( -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; - -fn sample(t_s : texture_2d, coords : vec2) -> vec4 { - return textureSample(t_s, placeholder_sampler, coords); -} - -fn f(t1_s : texture_2d, t2_s : texture_2d, coords : vec2) -> vec4 { - return (sample(t1_s, coords) + sample(t2_s, coords)); -} - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex1_samp : texture_2d; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex2_samp : texture_2d; - -fn main() -> vec4 { - return f(tex1_samp, tex2_samp, vec2(1.0, 2.0)); -} -)"; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, TwoFunctionsTwoSamplersDiamond) { - auto* src = R"( -@group(0) @binding(0) var tex : texture_2d; - -@group(0) @binding(1) var samp1 : sampler; - -@group(0) @binding(2) var samp2 : sampler; - -fn sample(t : texture_2d, s : sampler, coords : vec2) -> vec4 { - return textureSample(t, s, coords); -} - -fn f(t : texture_2d, s1 : sampler, s2 : sampler, coords : vec2) -> vec4 { - return sample(t, s1, coords) + sample(t, s2, coords); -} - -fn main() -> vec4 { - return f(tex, samp1, samp2, vec2(1.0, 2.0)); -} -)"; - auto* expect = R"( -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; - -fn sample(t_s : texture_2d, coords : vec2) -> vec4 { - return textureSample(t_s, placeholder_sampler, coords); -} - -fn f(t_s1 : texture_2d, t_s2 : texture_2d, coords : vec2) -> vec4 { - return (sample(t_s1, coords) + sample(t_s2, coords)); -} - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp1 : texture_2d; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp2 : texture_2d; - -fn main() -> vec4 { - return f(tex_samp1, tex_samp2, vec2(1.0, 2.0)); -} -)"; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, GlobalTextureLocalSampler) { - auto* src = R"( -@group(0) @binding(0) var tex : texture_2d; - -@group(0) @binding(1) var samp1 : sampler; - -@group(0) @binding(2) var samp2 : sampler; - -fn f(s1 : sampler, s2 : sampler, coords : vec2) -> vec4 { - return textureSample(tex, s1, coords) + textureSample(tex, s2, coords); -} - -fn main() -> vec4 { - return f(samp1, samp2, vec2(1.0, 2.0)); -} -)"; - auto* expect = R"( -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; - -fn f(tex_s1 : texture_2d, tex_s2 : texture_2d, coords : vec2) -> vec4 { - return (textureSample(tex_s1, placeholder_sampler, coords) + textureSample(tex_s2, placeholder_sampler, coords)); -} - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp1 : texture_2d; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp2 : texture_2d; - -fn main() -> vec4 { - return f(tex_samp1, tex_samp2, vec2(1.0, 2.0)); -} -)"; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, GlobalTextureLocalSampler_OutOfOrder) { - auto* src = R"( -fn main() -> vec4 { - return f(samp1, samp2, vec2(1.0, 2.0)); -} - -fn f(s1 : sampler, s2 : sampler, coords : vec2) -> vec4 { - return textureSample(tex, s1, coords) + textureSample(tex, s2, coords); -} - -@group(0) @binding(1) var samp1 : sampler; -@group(0) @binding(2) var samp2 : sampler; -@group(0) @binding(0) var tex : texture_2d; -)"; - auto* expect = R"( -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp1 : texture_2d; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp2 : texture_2d; - -fn main() -> vec4 { - return f(tex_samp1, tex_samp2, vec2(1.0, 2.0)); -} - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; - -fn f(tex_s1 : texture_2d, tex_s2 : texture_2d, coords : vec2) -> vec4 { - return (textureSample(tex_s1, placeholder_sampler, coords) + textureSample(tex_s2, placeholder_sampler, coords)); -} -)"; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, LocalTextureGlobalSampler) { - auto* src = R"( -@group(0) @binding(0) var tex1 : texture_2d; - -@group(0) @binding(1) var tex2 : texture_2d; - -@group(0) @binding(2) var samp : sampler; - -fn f(t1 : texture_2d, t2 : texture_2d, coords : vec2) -> vec4 { - return textureSample(t1, samp, coords) + textureSample(t2, samp, coords); -} - -fn main() -> vec4 { - return f(tex1, tex2, vec2(1.0, 2.0)); -} -)"; - auto* expect = R"( -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; - -fn f(t1_samp : texture_2d, t2_samp : texture_2d, coords : vec2) -> vec4 { - return (textureSample(t1_samp, placeholder_sampler, coords) + textureSample(t2_samp, placeholder_sampler, coords)); -} - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex1_samp : texture_2d; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex2_samp : texture_2d; - -fn main() -> vec4 { - return f(tex1_samp, tex2_samp, vec2(1.0, 2.0)); -} -)"; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, LocalTextureGlobalSampler_OutOfOrder) { - auto* src = R"( -fn main() -> vec4 { - return f(tex1, tex2, vec2(1.0, 2.0)); -} - -fn f(t1 : texture_2d, t2 : texture_2d, coords : vec2) -> vec4 { - return textureSample(t1, samp, coords) + textureSample(t2, samp, coords); -} - -@group(0) @binding(2) var samp : sampler; -@group(0) @binding(0) var tex1 : texture_2d; -@group(0) @binding(1) var tex2 : texture_2d; -)"; - auto* expect = R"( -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex1_samp : texture_2d; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex2_samp : texture_2d; - -fn main() -> vec4 { - return f(tex1_samp, tex2_samp, vec2(1.0, 2.0)); -} - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; - -fn f(t1_samp : texture_2d, t2_samp : texture_2d, coords : vec2) -> vec4 { - return (textureSample(t1_samp, placeholder_sampler, coords) + textureSample(t2_samp, placeholder_sampler, coords)); -} -)"; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, TextureLoadNoSampler) { - auto* src = R"( -@group(0) @binding(0) var tex : texture_2d; - -fn f(t : texture_2d, coords : vec2) -> vec4 { - return textureLoad(t, coords, 0); -} - -fn main() -> vec4 { - return f(tex, vec2(1, 2)); -} -)"; - auto* expect = R"( -fn f(t_1 : texture_2d, coords : vec2) -> vec4 { - return textureLoad(t_1, coords, 0); -} - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var fred : texture_2d; - -fn main() -> vec4 { - return f(fred, vec2(1, 2)); -} -)"; - - CombinedTextureSamplerInfo map; - binding::CombinedTextureSamplerPair pair; - pair.texture = {0, 0}; - pair.sampler = {1024, 0}; - map[pair] = "fred"; - - BindingPoint placeholder{pair.sampler.group, pair.sampler.binding}; - - ast::transform::DataMap data; - data.Add(map, placeholder); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, TextureWithAndWithoutSampler) { - auto* src = R"( -@group(0) @binding(0) var tex : texture_2d; -@group(0) @binding(1) var samp : sampler; - -fn main() -> vec4 { - return textureLoad(tex, vec2(), 0) + - textureSample(tex, samp, vec2()); -} -)"; - auto* expect = R"( -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var barney : texture_2d; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; - -fn main() -> vec4 { - return (textureLoad(barney, vec2(), 0) + textureSample(barney, placeholder_sampler, vec2())); -} -)"; - - BindingPoint placeholder{1024, 0}; - BindingPoint tex{0, 0}; - BindingPoint samp{0, 1}; - - CombinedTextureSamplerInfo map; - binding::CombinedTextureSamplerPair pair; - pair.texture = {tex.group, tex.binding}; - pair.sampler = {samp.group, samp.binding}; - - binding::CombinedTextureSamplerPair placeholder_pair; - placeholder_pair.texture = {tex.group, tex.binding}; - placeholder_pair.sampler = {placeholder.group, placeholder.binding}; - - map[pair] = "barney"; - map[placeholder_pair] = "fred"; - - ast::transform::DataMap data; - data.Add(map, placeholder); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, TextureSampleCompare) { - auto* src = R"( -@group(0) @binding(0) var tex : texture_depth_2d; - -@group(0) @binding(1) var samp : sampler_comparison; - -fn main() -> vec4 { - return vec4(textureSampleCompare(tex, samp, vec2(1.0, 2.0), 0.5)); -} -)"; - auto* expect = R"( -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp : texture_depth_2d; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_comparison_sampler : sampler_comparison; - -fn main() -> vec4 { - return vec4(textureSampleCompare(tex_samp, placeholder_comparison_sampler, vec2(1.0, 2.0), 0.5)); -} -)"; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, TextureSampleCompareInAFunction) { - auto* src = R"( -@group(0) @binding(0) var tex : texture_depth_2d; - -@group(0) @binding(1) var samp : sampler_comparison; - -fn f(t : texture_depth_2d, s : sampler_comparison, coords : vec2) -> f32 { - return textureSampleCompare(t, s, coords, 5.0f); -} - -fn main() -> vec4 { - return vec4(f(tex, samp, vec2(1.0, 2.0))); -} -)"; - auto* expect = R"( -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_comparison_sampler : sampler_comparison; - -fn f(t_s : texture_depth_2d, coords : vec2) -> f32 { - return textureSampleCompare(t_s, placeholder_comparison_sampler, coords, 5.0f); -} - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp : texture_depth_2d; - -fn main() -> vec4 { - return vec4(f(tex_samp, vec2(1.0, 2.0))); -} -)"; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, TextureSampleCompareInAFunction_OutOfOrder) { - auto* src = R"( -fn main() -> vec4 { - return vec4(f(tex, samp, vec2(1.0, 2.0))); -} - -fn f(t : texture_depth_2d, s : sampler_comparison, coords : vec2) -> f32 { - return textureSampleCompare(t, s, coords, 5.0f); -} - -@group(0) @binding(0) var tex : texture_depth_2d; -@group(0) @binding(1) var samp : sampler_comparison; -)"; - auto* expect = R"( -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp : texture_depth_2d; - -fn main() -> vec4 { - return vec4(f(tex_samp, vec2(1.0, 2.0))); -} - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_comparison_sampler : sampler_comparison; - -fn f(t_s : texture_depth_2d, coords : vec2) -> f32 { - return textureSampleCompare(t_s, placeholder_comparison_sampler, coords, 5.0f); -} -)"; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, BindingPointCollision) { - auto* src = R"( -@group(1) @binding(0) var tex : texture_2d; - -@group(1) @binding(1) var samp : sampler; - -@group(0) @binding(0) var gcoords : vec2; - -fn main() -> vec4 { - return textureSample(tex, samp, gcoords); -} -)"; - auto* expect = R"( -@internal(disable_validation__binding_point_collision) @group(0) @binding(0) var gcoords : vec2; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp : texture_2d; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; - -fn main() -> vec4 { - return textureSample(tex_samp, placeholder_sampler, gcoords); -} -)"; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, BindingPointCollision_OutOfOrder) { - auto* src = R"( -fn main() -> vec4 { - return textureSample(tex, samp, gcoords); -} - -@group(1) @binding(1) var samp : sampler; -@group(0) @binding(0) var gcoords : vec2; -@group(1) @binding(0) var tex : texture_2d; - -)"; - auto* expect = R"( -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp : texture_2d; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_sampler : sampler; - -fn main() -> vec4 { - return textureSample(tex_samp, placeholder_sampler, gcoords); -} - -@internal(disable_validation__binding_point_collision) @group(0) @binding(0) var gcoords : vec2; -)"; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, UnusedTextureFunctionParameter) { - auto* src = R"( -@group(0) @binding(0) var t : texture_2d; - -fn f(tex: texture_2d) -> u32 { - return 1u; -} - -fn main() { - _ = f(t); -} -)"; - auto* expect = R"( -fn f(tex_1 : texture_2d) -> u32 { - return 1u; -} - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_1 : texture_2d; - -fn main() { - _ = f(t_1); -} -)"; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, UnusedSamplerFunctionParameter) { - auto* src = R"( -@group(0) @binding(0) var s : sampler; - -fn f(sampler1: sampler) -> u32 { - return 1u; -} - -fn main() { - _ = f(s); -} -)"; - auto* expect = R"( -fn f(sampler1_1 : sampler) -> u32 { - return 1u; -} - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var s_1 : sampler; - -fn main() { - _ = f(s_1); -} -)"; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, UnusedTextureAndSamplerFunctionParameter) { - auto* src = R"( -@group(0) @binding(0) var t : texture_2d; - -@group(0) @binding(1) var s : sampler; - -fn f(tex: texture_2d, sampler1: sampler) -> u32 { - return 1u; -} - -fn main() { - _ = f(t, s); -} -)"; - auto* expect = R"( -fn f(sampler1_1 : sampler, tex_1 : texture_2d) -> u32 { - return 1u; -} - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var s_1 : sampler; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_1 : texture_2d; - -fn main() { - _ = f(s_1, t_1); -} -)"; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, UnusedTextureFunctionParameter_Multiple) { - auto* src = R"( -@group(0) @binding(0) var t1 : texture_2d; - -@group(0) @binding(1) var t2 : texture_2d_array; - -@group(0) @binding(2) var s : sampler; - -fn f(tex1: texture_2d, tex2: texture_2d, tex3: texture_2d_array, sampler1: sampler) -> u32 { - return 1u + textureNumLayers(tex3); -} - -fn main() { - _ = f(t1, t1, t2, s); -} -)"; - auto* expect = R"( -fn f(sampler1_1 : sampler, tex3_1 : texture_2d_array, tex1_1 : texture_2d) -> u32 { - return (1u + textureNumLayers(tex3_1)); -} - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var s_1 : sampler; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t2_1 : texture_2d_array; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t1_1 : texture_2d; - -fn main() { - _ = f(s_1, t2_1, t1_1); -} -)"; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, UnusedTextureFunctionParameter_Nested) { - auto* src = R"( -@group(0) @binding(0) var t : texture_2d; - -fn f_nested(tex: texture_2d) -> u32 { - return 1u; -} - -fn f(tex: texture_2d) -> u32 { - return f_nested(tex); -} - -fn main() { - _ = f(t); -} -)"; - auto* expect = R"( -fn f_nested(tex_1 : texture_2d) -> u32 { - return 1u; -} - -fn f(tex_2 : texture_2d) -> u32 { - return f_nested(tex_2); -} - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_1 : texture_2d; - -fn main() { - _ = f(t_1); -} -)"; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(CombineSamplersTest, UnusedTextureAndSamplerFunctionParameter_Nested) { - auto* src = R"( -@group(0) @binding(0) var t : texture_2d; - -@group(0) @binding(1) var s : sampler; - -fn f_nested(tex: texture_2d, sampler1: sampler) -> u32 { - return 1u; -} - -fn f(tex: texture_2d, sampler1: sampler) -> u32 { - return f_nested(tex, sampler1); -} - -fn main() { - _ = f(t, s); -} -)"; - auto* expect = - R"( -fn f_nested(sampler1_1 : sampler, tex_1 : texture_2d) -> u32 { - return 1u; -} - -fn f(sampler1_2 : sampler, tex_2 : texture_2d) -> u32 { - return f_nested(sampler1_2, tex_2); -} - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var s_1 : sampler; - -@group(0) @binding(0) @internal(disable_validation__binding_point_collision) var t_1 : texture_2d; - -fn main() { - _ = f(s_1, t_1); -} -)"; - - ast::transform::DataMap data; - data.Add(CombinedTextureSamplerInfo(), BindingPoint()); - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_raise/pad_structs.cc b/references/writer/ast_raise/pad_structs.cc deleted file mode 100644 index 08c83610..00000000 --- a/references/writer/ast_raise/pad_structs.cc +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright 2022 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_raise/pad_structs.h" - -#include -#include -#include - -#include "src/tint/lang/wgsl/ast/disable_validation_attribute.h" -#include "src/tint/lang/wgsl/ast/parameter.h" -#include "src/tint/lang/wgsl/program/clone_context.h" -#include "src/tint/lang/wgsl/program/program_builder.h" -#include "src/tint/lang/wgsl/resolver/resolve.h" -#include "src/tint/lang/wgsl/sem/call.h" -#include "src/tint/lang/wgsl/sem/module.h" -#include "src/tint/lang/wgsl/sem/value_constructor.h" - -using namespace tint::core::number_suffixes; // NOLINT - -TINT_INSTANTIATE_TYPEINFO(tint::glsl::writer::PadStructs); - -namespace tint::glsl::writer { - -namespace { - -void CreatePadding(Vector* new_members, - Hashset* padding_members, - Hashset* member_names, - int& last_padding_index, - ast::Builder* b, - uint32_t bytes) { - const size_t count = bytes / 4u; - padding_members->Reserve(count); - new_members->Reserve(count); - member_names->Reserve(count); - std::string name = "pad"; - for (uint32_t i = 0; i < count; ++i) { - while (member_names->Contains(name)) { - name = "pad_" + std::to_string(++last_padding_index); - } - member_names->Add(name); - auto* member = b->Member(name, b->ty.u32()); - padding_members->Add(member); - new_members->Push(member); - } -} - -} // namespace - -PadStructs::PadStructs() = default; - -PadStructs::~PadStructs() = default; - -ast::transform::Transform::ApplyResult PadStructs::Apply(const Program& src, - const ast::transform::DataMap&, - ast::transform::DataMap&) const { - ProgramBuilder b; - program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true}; - auto& sem = src.Sem(); - - std::unordered_map replaced_structs; - Hashset padding_members; - - ctx.ReplaceAll([&](const ast::Struct* ast_str) -> const ast::Struct* { - auto* str = sem.Get(ast_str); - if (!str || !str->IsHostShareable()) { - return nullptr; - } - uint32_t offset = 0; - bool has_runtime_sized_array = false; - tint::Vector new_members; - Hashset member_names; - for (auto* mem : str->Members()) { - member_names.Add(mem->Name().Name()); - } - - int last_padding_index = 0; - for (auto* mem : str->Members()) { - auto name = mem->Name().Name(); - - if (offset < mem->Offset()) { - CreatePadding(&new_members, &padding_members, &member_names, last_padding_index, - ctx.dst, mem->Offset() - offset); - offset = mem->Offset(); - } - - auto* ty = mem->Type(); - auto type = CreateASTTypeFor(ctx, ty); - - new_members.Push(b.Member(name, type)); - - uint32_t size = ty->Size(); - if (ty->Is() && str->UsedAs(core::AddressSpace::kUniform)) { - // std140 structs should be padded out to 16 bytes. - size = tint::RoundUp(16u, size); - } else if (auto* array_ty = ty->As()) { - if (array_ty->Count()->Is()) { - has_runtime_sized_array = true; - } - } - offset += size; - } - - // Add any required padding after the last member, if it's not a runtime-sized array. - uint32_t struct_size = str->Size(); - if (str->UsedAs(core::AddressSpace::kUniform)) { - struct_size = tint::RoundUp(16u, struct_size); - } - if (offset < struct_size && !has_runtime_sized_array) { - CreatePadding(&new_members, &padding_members, &member_names, last_padding_index, - ctx.dst, struct_size - offset); - } - - tint::Vector struct_attribs = ctx.Clone(ast_str->attributes); - if (!padding_members.IsEmpty()) { - struct_attribs.Push(b.Disable(ast::DisabledValidation::kIgnoreStructMemberLimit)); - } - - auto* new_struct = b.create(ctx.Clone(ast_str->name), std::move(new_members), - std::move(struct_attribs)); - replaced_structs[ast_str] = new_struct; - return new_struct; - }); - - ctx.ReplaceAll([&](const ast::CallExpression* ast_call) -> const ast::CallExpression* { - if (ast_call->args.Length() == 0) { - return nullptr; - } - - auto* call = sem.Get(ast_call); - if (!call) { - return nullptr; - } - auto* cons = call->Target()->As(); - if (!cons) { - return nullptr; - } - auto* str = cons->ReturnType()->As(); - if (!str) { - return nullptr; - } - - auto* new_struct = replaced_structs[str->Declaration()]; - if (!new_struct) { - return nullptr; - } - - tint::Vector new_args; - - auto arg = ast_call->args.begin(); - for (auto* member : new_struct->members) { - if (padding_members.Contains(member)) { - new_args.Push(b.Expr(0_u)); - } else { - new_args.Push(ctx.Clone(*arg)); - ++arg; - } - } - return b.Call(CreateASTTypeFor(ctx, str), new_args); - }); - - ctx.Clone(); - return resolver::Resolve(b); -} - -} // namespace tint::glsl::writer diff --git a/references/writer/ast_raise/pad_structs.h b/references/writer/ast_raise/pad_structs.h deleted file mode 100644 index 3f2acbe9..00000000 --- a/references/writer/ast_raise/pad_structs.h +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2022 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef SRC_TINT_LANG_GLSL_WRITER_AST_RAISE_PAD_STRUCTS_H_ -#define SRC_TINT_LANG_GLSL_WRITER_AST_RAISE_PAD_STRUCTS_H_ - -#include "src/tint/lang/wgsl/ast/transform/transform.h" - -namespace tint::glsl::writer { - -/// This transform turns all explicit alignment and sizing into padding -/// members of structs. This is required for GLSL ES, since it not support -/// the offset= decoration. -/// -/// @note This transform requires the CanonicalizeEntryPointIO transform to have been run first. -class PadStructs final : public Castable { - public: - /// Constructor - PadStructs(); - - /// Destructor - ~PadStructs() override; - - /// @copydoc ast::transform::Transform::Apply - ApplyResult Apply(const Program& program, - const ast::transform::DataMap& inputs, - ast::transform::DataMap& outputs) const override; -}; - -} // namespace tint::glsl::writer - -#endif // SRC_TINT_LANG_GLSL_WRITER_AST_RAISE_PAD_STRUCTS_H_ diff --git a/references/writer/ast_raise/pad_structs_test.cc b/references/writer/ast_raise/pad_structs_test.cc deleted file mode 100644 index 91107c55..00000000 --- a/references/writer/ast_raise/pad_structs_test.cc +++ /dev/null @@ -1,757 +0,0 @@ -// Copyright 2022 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_raise/pad_structs.h" - -#include -#include - -#include "src/tint/lang/wgsl/ast/transform/helper_test.h" - -namespace tint::glsl::writer { -namespace { - -using PadStructsTest = ast::transform::TransformTest; - -TEST_F(PadStructsTest, EmptyModule) { - auto* src = ""; - auto* expect = src; - - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(PadStructsTest, Uniform) { - auto* src = R"( -struct S { - x : i32, -} - -@group(0) @binding(0) var u : S; - -fn main() { - let x = u.x; -} -)"; - auto* expect = R"( -@internal(disable_validation__ignore_struct_member) -struct S { - x : i32, - pad : u32, - pad_1 : u32, - pad_2 : u32, -} - -@group(0) @binding(0) var u : S; - -fn main() { - let x = u.x; -} -)"; - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(PadStructsTest, Size) { - auto* src = R"( -struct S { - @size(12) - x : i32, - y : i32, -} - -@group(0) @binding(0) var u : S; - -fn main() { - let x = u.x; -} -)"; - auto* expect = R"( -@internal(disable_validation__ignore_struct_member) -struct S { - x : i32, - pad : u32, - pad_1 : u32, - y : i32, -} - -@group(0) @binding(0) var u : S; - -fn main() { - let x = u.x; -} -)"; - - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(PadStructsTest, SizeUniformAndPrivate) { - auto* src = R"( -struct S { - @size(12) - x : i32, - y : i32, -} - -@group(0) @binding(0) var u : S; - -var p : S; - -fn main() { - p.x = u.x; -} -)"; - auto* expect = R"( -@internal(disable_validation__ignore_struct_member) -struct S { - x : i32, - pad : u32, - pad_1 : u32, - y : i32, -} - -@group(0) @binding(0) var u : S; - -var p : S; - -fn main() { - p.x = u.x; -} -)"; - - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(PadStructsTest, SizeStorageAndPrivate) { - auto* src = R"( -struct S { - @size(12) - x : i32, - y : i32, -} - -@group(0) @binding(0) var s : S; - -var p : S; - -fn main() { - p.x = 123; - s.x = p.x; -} -)"; - auto* expect = R"( -@internal(disable_validation__ignore_struct_member) -struct S { - x : i32, - pad : u32, - pad_1 : u32, - y : i32, -} - -@group(0) @binding(0) var s : S; - -var p : S; - -fn main() { - p.x = 123; - s.x = p.x; -} -)"; - - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(PadStructsTest, SizeUniformAndStorage) { - auto* src = R"( -struct S { - @size(12) - x : i32, - y : i32, -} - -@group(0) @binding(0) var u : S; - -@group(0) @binding(1) var s : S; - -fn main() { - s.x = u.x; -} -)"; - auto* expect = R"( -@internal(disable_validation__ignore_struct_member) -struct S { - x : i32, - pad : u32, - pad_1 : u32, - y : i32, -} - -@group(0) @binding(0) var u : S; - -@group(0) @binding(1) var s : S; - -fn main() { - s.x = u.x; -} -)"; - - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(PadStructsTest, SizePrivateOnly) { - // Structs that are not host-visible should have no explicit padding. - auto* src = R"( -struct S { - @size(12) - x : i32, - y : i32, -} - -var p : S; - -fn main() { - p.x = 123; -} -)"; - auto* expect = R"( -struct S { - @size(12) - x : i32, - y : i32, -} - -var p : S; - -fn main() { - p.x = 123; -} -)"; - - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(PadStructsTest, AlignUniformAndPrivate) { - auto* src = R"( -struct S { - a : i32, - @align(16) - b : i32, -} - -@group(0) @binding(0) var u : S; - -var p : S; - -fn main() { - p.a = u.b; - p.b = u.a; -} -)"; - auto* expect = R"( -@internal(disable_validation__ignore_struct_member) -struct S { - a : i32, - pad : u32, - pad_1 : u32, - pad_2 : u32, - b : i32, - pad_3 : u32, - pad_4 : u32, - pad_5 : u32, -} - -@group(0) @binding(0) var u : S; - -var p : S; - -fn main() { - p.a = u.b; - p.b = u.a; -} -)"; - - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(PadStructsTest, AlignStorageAndPrivate) { - auto* src = R"( -struct S { - a : i32, - @align(16) - b : i32, -} - -@group(0) @binding(0) var s : S; - -var p : S; - -fn main() { - p.a = 123; - p.b = 321; - s.a = p.b; - s.b = p.a; -} -)"; - auto* expect = R"( -@internal(disable_validation__ignore_struct_member) -struct S { - a : i32, - pad : u32, - pad_1 : u32, - pad_2 : u32, - b : i32, - pad_3 : u32, - pad_4 : u32, - pad_5 : u32, -} - -@group(0) @binding(0) var s : S; - -var p : S; - -fn main() { - p.a = 123; - p.b = 321; - s.a = p.b; - s.b = p.a; -} -)"; - - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(PadStructsTest, AlignUniformAndStorage) { - auto* src = R"( -struct S { - a : i32, - @align(16) - b : i32, -} - -@group(0) @binding(0) var u : S; - -@group(0) @binding(1) var s : S; - -fn main() { - s.a = u.b; - s.b = u.a; -} -)"; - auto* expect = R"( -@internal(disable_validation__ignore_struct_member) -struct S { - a : i32, - pad : u32, - pad_1 : u32, - pad_2 : u32, - b : i32, - pad_3 : u32, - pad_4 : u32, - pad_5 : u32, -} - -@group(0) @binding(0) var u : S; - -@group(0) @binding(1) var s : S; - -fn main() { - s.a = u.b; - s.b = u.a; -} -)"; - - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(PadStructsTest, AlignPrivateOnly) { - // Structs that are not host-visible should have no explicit padding. - auto* src = R"( -struct S { - a : i32, - @align(16) - b : i32, -} - -var p : S; - -fn main() { - p.a = 123; - p.b = 321; -} -)"; - auto* expect = R"( -struct S { - a : i32, - @align(16) - b : i32, -} - -var p : S; - -fn main() { - p.a = 123; - p.b = 321; -} -)"; - - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(PadStructsTest, LastMemberRuntimeSizeArray) { - // Structs with runtime-sized arrays should not be padded after the - // last member. - auto* src = R"( -struct T { - a : f32, - b : i32, -} - -struct S { - a : vec4, - b : array, -} - -@group(0) @binding(0) var s : S; - -fn main() { - s.b[0] = T(1.0f, 23); -} -)"; - auto* expect = R"( -struct T { - a : f32, - b : i32, -} - -struct S { - a : vec4, - b : array, -} - -@group(0) @binding(0) var s : S; - -fn main() { - s.b[0] = T(1.0f, 23); -} -)"; - - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(PadStructsTest, LastMemberFixedSizeArray) { - // Structs without runtime-sized arrays should be padded after the last - // member. - auto* src = R"( -struct T { - a : f32, - b : i32, -} - -struct S { - a : vec4, - b : array, -} - -@group(0) @binding(0) var s : S; - -fn main() { - s.b[0] = T(1.0f, 23); -} -)"; - auto* expect = R"( -struct T { - a : f32, - b : i32, -} - -@internal(disable_validation__ignore_struct_member) -struct S { - a : vec4, - b : array, - pad : u32, - pad_1 : u32, -} - -@group(0) @binding(0) var s : S; - -fn main() { - s.b[0] = T(1.0f, 23); -} -)"; - - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(PadStructsTest, Initializer) { - // Calls to a initializer of a padded struct must be modified to initialize the padding. - auto* src = R"( -struct S { - a : f32, - @align(8) - b : i32, -} - -@group(0) @binding(0) var s : S; - -fn main() { - s = S(1.0f, 2); -} -)"; - auto* expect = R"( -@internal(disable_validation__ignore_struct_member) -struct S { - a : f32, - pad : u32, - b : i32, - pad_1 : u32, -} - -@group(0) @binding(0) var s : S; - -fn main() { - s = S(1.0f, 0u, 2, 0u); -} -)"; - - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(PadStructsTest, MemberNamedPad) { - auto* src = R"( -struct S { - @align(8) pad_5 : u32, - @align(8) pad_3 : u32, - @align(8) pad : u32, - @align(8) pad_1 : u32, -} - -@group(0) @binding(0) var s : S; - -fn main() { - _ = s; -} -)"; - auto* expect = R"( -@internal(disable_validation__ignore_struct_member) -struct S { - pad_5 : u32, - pad_2 : u32, - pad_3 : u32, - pad_4 : u32, - pad : u32, - pad_6 : u32, - pad_1 : u32, - pad_7 : u32, -} - -@group(0) @binding(0) var s : S; - -fn main() { - _ = s; -} -)"; - - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(PadStructsTest, TwoStructs) { - auto* src = R"( -struct S { - @align(8) i : i32, - @align(8) u : u32, -} - -struct T { - @align(8) i : i32, - @align(8) u : u32, -} - -@group(0) @binding(0) var s : S; -@group(0) @binding(1) var t : T; - -fn main() { - _ = s; - _ = t; -} -)"; - auto* expect = R"( -@internal(disable_validation__ignore_struct_member) -struct S { - i : i32, - pad : u32, - u : u32, - pad_1 : u32, -} - -@internal(disable_validation__ignore_struct_member) -struct T { - i : i32, - pad : u32, - u : u32, - pad_1 : u32, -} - -@group(0) @binding(0) var s : S; - -@group(0) @binding(1) var t : T; - -fn main() { - _ = s; - _ = t; -} -)"; - - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(PadStructsTest, GlobalVariableNamedPad) { - auto* src = R"( -@group(0) @binding(0) var pad : u32; -@group(0) @binding(1) var pad_1 : u32; - -struct S { - @align(8) i : u32, - @align(8) j : u32, -} - -@group(0) @binding(1) var s : S; - -fn main() { - _ = s; -} -)"; - auto* expect = R"( -@group(0) @binding(0) var pad : u32; - -@group(0) @binding(1) var pad_1 : u32; - -@internal(disable_validation__ignore_struct_member) -struct S { - i : u32, - pad : u32, - j : u32, - pad_1 : u32, -} - -@group(0) @binding(1) var s : S; - -fn main() { - _ = s; -} -)"; - - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(PadStructsTest, InitializerZeroArgs) { - // Calls to a zero-argument initializer of a padded struct should not be modified. - auto* src = R"( -struct S { - a : f32, - @align(8) - b : i32, -} - -@group(0) @binding(0) var s : S; - -fn main() { - s = S(); -} -)"; - auto* expect = R"( -@internal(disable_validation__ignore_struct_member) -struct S { - a : f32, - pad : u32, - b : i32, - pad_1 : u32, -} - -@group(0) @binding(0) var s : S; - -fn main() { - s = S(); -} -)"; - - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_raise/texture_1d_to_2d.cc b/references/writer/ast_raise/texture_1d_to_2d.cc deleted file mode 100644 index df5452aa..00000000 --- a/references/writer/ast_raise/texture_1d_to_2d.cc +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright 2022 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_raise/texture_1d_to_2d.h" - -#include - -#include "src/tint/lang/core/type/texture_dimension.h" -#include "src/tint/lang/wgsl/program/clone_context.h" -#include "src/tint/lang/wgsl/program/program_builder.h" -#include "src/tint/lang/wgsl/resolver/resolve.h" -#include "src/tint/lang/wgsl/sem/function.h" -#include "src/tint/lang/wgsl/sem/statement.h" -#include "src/tint/lang/wgsl/sem/type_expression.h" -#include "src/tint/utils/rtti/switch.h" - -TINT_INSTANTIATE_TYPEINFO(tint::glsl::writer::Texture1DTo2D); - -using namespace tint::core::number_suffixes; // NOLINT - -namespace tint::glsl::writer { - -namespace { - -bool ShouldRun(const Program& program) { - for (auto* fn : program.AST().Functions()) { - if (auto* sem_fn = program.Sem().Get(fn)) { - for (auto* builtin : sem_fn->DirectlyCalledBuiltins()) { - const auto& signature = builtin->Signature(); - auto texture = signature.Parameter(core::ParameterUsage::kTexture); - if (texture) { - auto* tex = texture->Type()->As(); - if (tex->dim() == core::type::TextureDimension::k1d) { - return true; - } - } - } - } - } - for (auto* var : program.AST().GlobalVariables()) { - if (Switch( - program.Sem().Get(var)->Type()->UnwrapRef(), - [&](const core::type::SampledTexture* tex) { - return tex->dim() == core::type::TextureDimension::k1d; - }, - [&](const core::type::StorageTexture* storage_tex) { - return storage_tex->dim() == core::type::TextureDimension::k1d; - })) { - return true; - } - } - return false; -} - -} // namespace - -/// PIMPL state for the transform -struct Texture1DTo2D::State { - /// The source program - const Program& src; - /// The target program builder - ProgramBuilder b; - /// The clone context - program::CloneContext ctx = {&b, &src, /* auto_clone_symbols */ true}; - - /// Constructor - /// @param program the source program - explicit State(const Program& program) : src(program) {} - - /// Runs the transform - /// @returns the new program or SkipTransform if the transform is not required - ApplyResult Run() { - auto& sem = src.Sem(); - - if (!ShouldRun(src)) { - return SkipTransform; - } - - auto create_var = [&](const ast::Variable* v, ast::Type type) -> const ast::Variable* { - if (v->As()) { - return ctx.dst->Param(ctx.Clone(v->name->symbol), type, ctx.Clone(v->attributes)); - } else { - return ctx.dst->Var(ctx.Clone(v->name->symbol), type, ctx.Clone(v->attributes)); - } - }; - - ctx.ReplaceAll([&](const ast::Variable* v) -> const ast::Variable* { - const ast::Variable* r = Switch( - sem.Get(v)->Type()->UnwrapRef(), - [&](const core::type::SampledTexture* tex) -> const ast::Variable* { - if (tex->dim() == core::type::TextureDimension::k1d) { - auto type = ctx.dst->ty.sampled_texture(core::type::TextureDimension::k2d, - CreateASTTypeFor(ctx, tex->type())); - return create_var(v, type); - } else { - return nullptr; - } - }, - [&](const core::type::StorageTexture* storage_tex) -> const ast::Variable* { - if (storage_tex->dim() == core::type::TextureDimension::k1d) { - auto type = ctx.dst->ty.storage_texture(core::type::TextureDimension::k2d, - storage_tex->texel_format(), - storage_tex->access()); - return create_var(v, type); - } else { - return nullptr; - } - }, - [](Default) { return nullptr; }); - return r; - }); - - ctx.ReplaceAll([&](const ast::CallExpression* c) -> const ast::Expression* { - auto* call = sem.Get(c)->UnwrapMaterialize()->As(); - if (!call) { - return nullptr; - } - auto* builtin = call->Target()->As(); - if (!builtin) { - return nullptr; - } - const auto& signature = builtin->Signature(); - auto* texture = signature.Parameter(core::ParameterUsage::kTexture); - if (!texture) { - return nullptr; - } - auto* tex = texture->Type()->As(); - if (tex->dim() != core::type::TextureDimension::k1d) { - return nullptr; - } - - if (builtin->Fn() == wgsl::BuiltinFn::kTextureDimensions) { - auto* new_call = ctx.CloneWithoutTransform(c); - return ctx.dst->MemberAccessor(new_call, "x"); - } - - auto coords_index = signature.IndexOf(core::ParameterUsage::kCoords); - if (coords_index == -1) { - return nullptr; - } - - tint::Vector args; - int index = 0; - for (auto* arg : c->args) { - if (index == coords_index) { - auto* ctype = call->Arguments()[static_cast(coords_index)]->Type(); - auto* coords = c->args[static_cast(coords_index)]; - - const ast::LiteralExpression* half = nullptr; - if (ctype->is_integer_scalar()) { - half = ctx.dst->Expr(0_a); - } else { - half = ctx.dst->Expr(0.5_a); - } - args.Push( - ctx.dst->vec(CreateASTTypeFor(ctx, ctype), 2u, ctx.Clone(coords), half)); - } else { - args.Push(ctx.Clone(arg)); - } - index++; - } - return ctx.dst->Call(ctx.Clone(c->target), args); - }); - - ctx.Clone(); - return resolver::Resolve(b); - } -}; - -Texture1DTo2D::Texture1DTo2D() = default; - -Texture1DTo2D::~Texture1DTo2D() = default; - -ast::transform::Transform::ApplyResult Texture1DTo2D::Apply(const Program& src, - const ast::transform::DataMap&, - ast::transform::DataMap&) const { - return State(src).Run(); -} - -} // namespace tint::glsl::writer diff --git a/references/writer/ast_raise/texture_1d_to_2d.h b/references/writer/ast_raise/texture_1d_to_2d.h deleted file mode 100644 index 53237082..00000000 --- a/references/writer/ast_raise/texture_1d_to_2d.h +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2022 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef SRC_TINT_LANG_GLSL_WRITER_AST_RAISE_TEXTURE_1D_TO_2D_H_ -#define SRC_TINT_LANG_GLSL_WRITER_AST_RAISE_TEXTURE_1D_TO_2D_H_ - -#include "src/tint/lang/wgsl/ast/transform/transform.h" - -namespace tint::glsl::writer { - -/// This transform converts all 1D texture types and accesses to 2D. -/// This is required for GLSL ES, which does not support 1D textures. -class Texture1DTo2D final : public Castable { - public: - /// Constructor - Texture1DTo2D(); - - /// Destructor - ~Texture1DTo2D() override; - - /// @copydoc ast::transform::Transform::Apply - ApplyResult Apply(const Program& program, - const ast::transform::DataMap& inputs, - ast::transform::DataMap& outputs) const override; - - private: - struct State; -}; - -} // namespace tint::glsl::writer - -#endif // SRC_TINT_LANG_GLSL_WRITER_AST_RAISE_TEXTURE_1D_TO_2D_H_ diff --git a/references/writer/ast_raise/texture_1d_to_2d_test.cc b/references/writer/ast_raise/texture_1d_to_2d_test.cc deleted file mode 100644 index 7441fe32..00000000 --- a/references/writer/ast_raise/texture_1d_to_2d_test.cc +++ /dev/null @@ -1,318 +0,0 @@ -// Copyright 2022 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_raise/texture_1d_to_2d.h" - -#include "src/tint/lang/wgsl/ast/transform/helper_test.h" - -namespace tint::glsl::writer { -namespace { - -using Texture1DTo2DTest = ast::transform::TransformTest; - -TEST_F(Texture1DTo2DTest, EmptyModule) { - auto* src = ""; - - ast::transform::DataMap data; - EXPECT_FALSE(ShouldRun(src, data)); -} - -TEST_F(Texture1DTo2DTest, Global1DDecl) { - auto* src = R"( -@group(0) @binding(0) var t : texture_1d; - -@group(0) @binding(1) var s : sampler; -)"; - auto* expect = R"( -@group(0) @binding(0) var t : texture_2d; - -@group(0) @binding(1) var s : sampler; -)"; - - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(Texture1DTo2DTest, Global1DDeclAndSample) { - auto* src = R"( -@group(0) @binding(0) var t : texture_1d; - -@group(0) @binding(1) var s : sampler; - -fn main() -> vec4 { - return textureSample(t, s, 0.5); -} -)"; - auto* expect = R"( -@group(0) @binding(0) var t : texture_2d; - -@group(0) @binding(1) var s : sampler; - -fn main() -> vec4 { - return textureSample(t, s, vec2(0.5, 0.5)); -} -)"; - - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(Texture1DTo2DTest, Global1DDeclAndLoad) { - auto* src = R"( -@group(0) @binding(0) var t : texture_1d; - -fn main() -> vec4 { - return textureLoad(t, 1, 0); -} -)"; - auto* expect = R"( -@group(0) @binding(0) var t : texture_2d; - -fn main() -> vec4 { - return textureLoad(t, vec2(1, 0), 0); -} -)"; - - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(Texture1DTo2DTest, Global1DDeclAndTextureDimensions) { - auto* src = R"( -@group(0) @binding(0) var t : texture_1d; - -fn main() -> u32 { - return textureDimensions(t); -} -)"; - auto* expect = R"( -@group(0) @binding(0) var t : texture_2d; - -fn main() -> u32 { - return textureDimensions(t).x; -} -)"; - - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(Texture1DTo2DTest, Global1DDeclAndTextureNumLevels) { - auto* src = R"( -@group(0) @binding(0) var t : texture_1d; - -fn main() -> u32 { - return textureNumLevels(t); -} -)"; - auto* expect = R"( -@group(0) @binding(0) var t : texture_2d; - -fn main() -> u32 { - return textureNumLevels(t); -} -)"; - - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(Texture1DTo2DTest, Global1DDeclAndTextureDimensionsInCallStmt) { - auto* src = R"( -@group(0) @binding(0) var st : texture_storage_1d; - -@group(0) @binding(1) var t : texture_1d; - -fn main() { - textureStore(st, clamp(5, 0, i32((textureDimensions(st) - 1))), vec4u(0, 0, 0, 0)); -} -)"; - auto* expect = R"( -@group(0) @binding(0) var st : texture_storage_2d; - -@group(0) @binding(1) var t : texture_2d; - -fn main() { - textureStore(st, vec2(clamp(5, 0, i32((textureDimensions(st).x - 1))), 0), vec4u(0, 0, 0, 0)); -} -)"; - - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(Texture1DTo2DTest, GlobalStorage1DDecl) { - auto* src = R"( -@group(0) @binding(0) var t : texture_storage_1d; -)"; - auto* expect = R"( -@group(0) @binding(0) var t : texture_storage_2d; -)"; - - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(Texture1DTo2DTest, Global2DDeclAndSample) { - auto* src = R"( -@group(0) @binding(0) var t : texture_2d; - -@group(0) @binding(1) var s : sampler; - -fn main() -> vec4 { - return textureSample(t, s, vec2(0.5, 1.5)); -} -)"; - - ast::transform::DataMap data; - EXPECT_FALSE(ShouldRun(src, data)); -} - -TEST_F(Texture1DTo2DTest, PrivateIntNoop) { - auto* src = R"( -var i : i32; -)"; - - ast::transform::DataMap data; - EXPECT_FALSE(ShouldRun(src, data)); -} - -TEST_F(Texture1DTo2DTest, GlobalMatrixNoop) { - auto* src = R"( -@group(0) @binding(0) var m : mat2x2; -)"; - - ast::transform::DataMap data; - EXPECT_FALSE(ShouldRun(src, data)); -} - -TEST_F(Texture1DTo2DTest, Texture1DFuncParam) { - auto* src = R"( -@group(0) @binding(0) var tex : texture_1d; - -@group(0) @binding(1) var samp : sampler; - -fn f(t : texture_1d, s : sampler) -> vec4 { - return textureSample(t, s, 0.7); -} - -fn main() -> vec4 { - return f(tex, samp); -} -)"; - auto* expect = R"( -@group(0) @binding(0) var tex : texture_2d; - -@group(0) @binding(1) var samp : sampler; - -fn f(t : texture_2d, s : sampler) -> vec4 { - return textureSample(t, s, vec2(0.69999999999999995559, 0.5)); -} - -fn main() -> vec4 { - return f(tex, samp); -} -)"; - - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(Texture1DTo2DTest, TextureStorage1DFuncParam) { - auto* src = R"( -@group(0) @binding(0) var tex : texture_storage_1d; - -fn f(t : texture_storage_1d) { - textureStore(t, 3, vec4(42.0, 21.0, 84.0, 10.5)); -} - -fn main() { - f(tex); -} -)"; - - auto* expect = R"( -@group(0) @binding(0) var tex : texture_storage_2d; - -fn f(t : texture_storage_2d) { - textureStore(t, vec2(3, 0), vec4(42.0, 21.0, 84.0, 10.5)); -} - -fn main() { - f(tex); -} -)"; - - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(Texture1DTo2DTest, TextureAndNonTextureBuiltin) { - auto* src = R"( -@group(0) @binding(0) var tex : texture_1d; - -fn d() { - _ = textureLoad(tex, 1, 0); - let l = sin(3.0); -} -)"; - - auto* expect = R"( -@group(0) @binding(0) var tex : texture_2d; - -fn d() { - _ = textureLoad(tex, vec2(1, 0), 0); - let l = sin(3.0); -} -)"; - - ast::transform::DataMap data; - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/ast_raise/texture_builtins_from_uniform.cc b/references/writer/ast_raise/texture_builtins_from_uniform.cc deleted file mode 100644 index 450499a4..00000000 --- a/references/writer/ast_raise/texture_builtins_from_uniform.cc +++ /dev/null @@ -1,426 +0,0 @@ -// Copyright 2023 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_raise/texture_builtins_from_uniform.h" - -#include -#include -#include -#include - -#include "src/tint/lang/wgsl/program/clone_context.h" -#include "src/tint/lang/wgsl/program/program_builder.h" -#include "src/tint/lang/wgsl/resolver/resolve.h" -#include "src/tint/lang/wgsl/sem/call.h" -#include "src/tint/lang/wgsl/sem/function.h" -#include "src/tint/lang/wgsl/sem/module.h" -#include "src/tint/lang/wgsl/sem/statement.h" -#include "src/tint/lang/wgsl/sem/variable.h" - -#include "src/tint/utils/containers/hashmap.h" -#include "src/tint/utils/containers/vector.h" -#include "src/tint/utils/rtti/switch.h" - -TINT_INSTANTIATE_TYPEINFO(tint::glsl::writer::TextureBuiltinsFromUniform); -TINT_INSTANTIATE_TYPEINFO(tint::glsl::writer::TextureBuiltinsFromUniform::Config); - -namespace tint::glsl::writer { - -namespace { - -/// The member name of the texture builtin values. -constexpr std::string_view kTextureBuiltinValuesMemberNamePrefix = "texture_builtin_value_"; - -} // namespace - -TextureBuiltinsFromUniform::TextureBuiltinsFromUniform() = default; - -TextureBuiltinsFromUniform::~TextureBuiltinsFromUniform() = default; - -/// PIMPL state for the transform -struct TextureBuiltinsFromUniform::State { - /// Constructor - /// @param program the source program - /// @param in the input transform data - explicit State(const Program& program, const ast::transform::DataMap& in) - : src(program), inputs(in) {} - - /// Runs the transform - /// @returns the new program or SkipTransform if the transform is not required - ApplyResult Run() { - auto* cfg = inputs.Get(); - if (cfg == nullptr) { - b.Diagnostics().AddError(Source{}) - << "missing transform data for " - << tint::TypeInfo::Of().name; - return resolver::Resolve(b); - } - ubo_bindingpoint_ordering = cfg->ubo_bindingpoint_ordering; - - // If there's no interested texture builtin at all, skip the transform. - if (ubo_bindingpoint_ordering.empty()) { - return SkipTransform; - } - - // The dependency order declarations guaranteed that we traverse interested functions in the - // following order: - // 1. texture builtins - // 2. user function directly calls texture builtins - // 3. user function calls 2. - // 4. user function calls 3. - // ... - // n. entry point function. - for (auto* fn_decl : sem.Module()->DependencyOrderedDeclarations()) { - if (auto* fn = sem.Get(fn_decl)) { - for (auto* call : fn->DirectCalls()) { - auto* call_expr = call->Declaration(); - - tint::Switch( - call->Target(), - [&](const sem::BuiltinFn* builtin) { - if (builtin->Fn() != wgsl::BuiltinFn::kTextureNumLevels && - builtin->Fn() != wgsl::BuiltinFn::kTextureNumSamples) { - return; - } - if (auto* call_stmt = - call->Stmt()->Declaration()->As()) { - if (call_stmt->expr == call->Declaration()) { - // textureNumLevels() / textureNumSamples() is used as a - // statement. The argument expression must be side-effect free, - // so just drop the statement. - RemoveStatement(ctx, call_stmt); - return; - } - } - - auto* texture_expr = call->Declaration()->args[0]; - auto* texture_sem = sem.GetVal(texture_expr)->RootIdentifier(); - TINT_ASSERT(texture_sem); - - tint::Switch( - texture_sem, - [&](const sem::GlobalVariable* global) { - // This texture variable is a global variable. - auto binding = GetGlobalBinding(global); - // Record the call and binding to be replaced later. - builtin_to_replace.Add(call_expr, binding); - }, - [&](const sem::Variable* variable) { - // This texture variable is a user function parameter. - auto new_param = GetAndRecordFunctionParameter(fn, variable); - // Record the call and new_param to be replaced later. - builtin_to_replace.Add(call_expr, new_param); - }, // - TINT_ICE_ON_NO_MATCH); - }, - [&](const sem::Function* user_fn) { - auto user_param_to_info = fn_to_data.Get(user_fn); - if (!user_param_to_info) { - // Uninterested function not calling texture builtins with function - // texture param. - return; - } - TINT_ASSERT(call->Arguments().Length() == - user_fn->Declaration()->params.Length()); - for (size_t i = 0; i < call->Arguments().Length(); i++) { - auto param = user_fn->Declaration()->params[i]; - if (auto info = user_param_to_info->Get(param)) { - auto* arg = call->Arguments()[i]; - auto* texture_sem = arg->RootIdentifier(); - auto& args = call_to_data.GetOrAdd(call_expr, [&] { - return Vector< - std::variant, 4>(); - }); - - tint::Switch( - texture_sem, - [&](const sem::GlobalVariable* global) { - // This texture variable is a global variable. - auto binding = GetGlobalBinding(global); - // Record the binding to add to args. - args.Push(binding); - }, - [&](const sem::Variable* variable) { - // This texture variable is a user function parameter. - auto new_param = - GetAndRecordFunctionParameter(fn, variable); - // Record adding extra function parameter - args.Push(new_param); - }, // - TINT_ICE_ON_NO_MATCH); - } - } - }); - } - } - } - - // If any functions need extra params, add them now. - if (!fn_to_data.IsEmpty()) { - for (auto& pair : fn_to_data) { - auto* fn = pair.key.Value(); - - // Reorder the param to a vector to make sure params are in the correct order. - Vector extra_params_in_order; - extra_params_in_order.Resize(pair.value.Count()); - for (auto t_p : pair.value) { - TINT_ASSERT(t_p.value.extra_idx < extra_params_in_order.Length()); - extra_params_in_order[t_p.value.extra_idx] = t_p.value.param; - } - - for (auto p : extra_params_in_order) { - ctx.InsertBack(fn->Declaration()->params, p); - } - } - } - - // Replace all interested texture builtin calls. - for (auto& pair : builtin_to_replace) { - auto call = pair.key.Value(); - if (std::holds_alternative(pair.value)) { - // This texture is a global variable with binding point. - // Read builtin value from uniform buffer. - auto* builtin_value = GetUniformValue(std::get(pair.value)); - ctx.Replace(call, builtin_value); - } else { - // Otherwise this value comes from a function param - auto* param = std::get(pair.value); - ctx.Replace(call, b.Expr(param)); - } - } - - // Insert all extra args to interested function calls. - for (auto& pair : call_to_data) { - auto call = pair.key.Value(); - for (auto new_arg_info : pair.value) { - if (std::holds_alternative(new_arg_info)) { - // This texture is a global variable with binding point. - // Read builtin value from uniform buffer. - auto* builtin_value = GetUniformValue(std::get(new_arg_info)); - ctx.InsertBack(call->args, builtin_value); - } else { - // Otherwise this value comes from a function param - auto* param = std::get(new_arg_info); - ctx.InsertBack(call->args, b.Expr(param)); - } - } - } - - ctx.Clone(); - return resolver::Resolve(b); - } - - private: - /// The source program - const Program& src; - /// The transform inputs - const ast::transform::DataMap& inputs; - /// The target program builder - ProgramBuilder b; - /// The clone context - program::CloneContext ctx = {&b, &src, /* auto_clone_symbols */ true}; - /// Alias to the semantic info in ctx.src - const sem::Info& sem = src.Sem(); - - /// Ordered list of binding points for where they appear in the UBO - std::vector ubo_bindingpoint_ordering; - - struct FunctionExtraParamInfo { - // The extra passed in param that corresponds to the texture param. - const ast::Parameter* param = nullptr; - - // id of this extra param e.g. f(t0, foo, t1, e0, e1) e0 and e1 are extra params, their - // extra_idx are 0 and 1. This is to help sort extra ids in the correct order. - size_t extra_idx = 0; - }; - - /// Store a map from function to a collection of extra params that need adding. - /// The value of the map is made a map instead of a vector to make it easier to find the param. - /// for call sites. e.g. fn f(t: texture_2d) -> u32 { - /// return textureNumLevels(t); - /// } - /// -> - /// fn f(t : texture_2d, tint_symbol : u32) -> u32 { - /// return tint_symbol; - /// } - Hashmap, 8> - fn_to_data; - - /// For each callsite of the above functions, record a vector of extra call args that need - /// inserting. e.g. f(tex) - /// -> - /// f(tex, internal_uniform.texture_builtin_value), if tex is from a global - /// variable, store the BindingPoint. or f(tex, extra_param_tex), if tex is from a function - /// param, store the texture function parameter pointer. - Hashmap, 4>, - 8> - call_to_data; - - /// Texture builtin calls to be replaced by either uniform values or function parameters. - Hashmap, 8> - builtin_to_replace; - - /// A map from global texture bindpoint to the symbol storing its builtin value in the uniform - /// buffer struct. - Hashmap bindpoint_to_syms; - - /// The internal uniform buffer - const ast::Variable* ubo = nullptr; - - /// Get or create a UBO including u32 scalars for texture builtin values. - /// @returns the symbol of the uniform buffer variable. - Symbol GetUboSym() { - if (ubo) { - // Already created - return ubo->name->symbol; - } - - auto* cfg = inputs.Get(); - - Vector new_members; - new_members.Resize(ubo_bindingpoint_ordering.size()); - - for (size_t i = 0; i < ubo_bindingpoint_ordering.size(); ++i) { - // Append the vector index with the variable name to avoid unstable naming issue. - auto sym = b.Symbols().New(std::string(kTextureBuiltinValuesMemberNamePrefix) + - std::to_string(i)); - bindpoint_to_syms.Add(ubo_bindingpoint_ordering[i], sym); - new_members[i] = b.Member(sym, b.ty.u32()); - } - - // Find if there's any existing global variable using the same cfg->ubo_binding - for (auto* var : src.AST().Globals()) { - if (var->HasBindingPoint()) { - auto* global_sem = sem.Get(var); - - // The original binding point - BindingPoint binding_point = *global_sem->Attributes().binding_point; - - if (binding_point == cfg->ubo_binding) { - // This ubo_binding struct already exists. - // which should only be added by other *FromUniform transforms. - // Replace it with a new struct including the new_member. - // Then remove the old structure global declaration. - - ubo = var->As(); - - auto* ty = global_sem->Type()->UnwrapRef(); - auto* str = ty->As(); - if (TINT_UNLIKELY(!str)) { - TINT_ICE() - << "existing ubo binding " << cfg->ubo_binding << " is not a struct."; - } - - for (auto new_member : new_members) { - ctx.InsertBack(str->Declaration()->members, new_member); - } - return ctx.Clone(ubo->name->symbol); - } - } - } - - auto* buffer_struct = b.Structure(b.Sym(), std::move(new_members)); - ubo = b.GlobalVar(b.Sym(), b.ty.Of(buffer_struct), core::AddressSpace::kUniform, - b.Group(core::AInt(cfg->ubo_binding.group)), - b.Binding(core::AInt(cfg->ubo_binding.binding))); - return ubo->name->symbol; - } - - /// Get the expression of retrieving the builtin value from the uniform buffer. - /// @param binding of the global variable. - /// @returns an expression of the builtin value. - const ast::Expression* GetUniformValue(const BindingPoint& binding) { - // Make sure GetUboSym() is called first to initialize the uniform buffer struct. - auto ubo_sym = GetUboSym(); - - // Load the builtin value from the UBO. - auto member_sym = bindpoint_to_syms.Get(binding); - TINT_ASSERT(member_sym); - - return b.MemberAccessor(ubo_sym, *member_sym); - } - - /// Get and return the binding of the global texture variable. - /// @param global global variable of the texture variable. - /// @returns binding of the global variable. - BindingPoint GetGlobalBinding(const sem::GlobalVariable* global) { - return global->Attributes().binding_point.value(); - } - - /// Find which function param is the given texture variable. - /// Add a new u32 param relates to this texture param. Record in fn_to_data if first - /// visited. - /// @param fn the current function scope. - /// @param var the texture variable. - /// @returns the new u32 function parameter. - const ast::Parameter* GetAndRecordFunctionParameter(const sem::Function* fn, - const sem::Variable* var) { - auto& param_to_info = fn_to_data.GetOrAdd( - fn, [&] { return Hashmap(); }); - - const ast::Parameter* param = nullptr; - for (auto p : fn->Declaration()->params) { - if (p->As() == var->Declaration()) { - param = p; - break; - } - } - TINT_ASSERT(param); - - // Get or record a new u32 param to this function if first visited. - auto entry = param_to_info.Get(param); - if (entry) { - return entry->param; - } - - const ast::Parameter* new_param = b.Param(b.Sym(), b.ty.u32()); - size_t idx = param_to_info.Count(); - param_to_info.Add(param, FunctionExtraParamInfo{new_param, idx}); - return new_param; - } -}; - -ast::transform::Transform::ApplyResult TextureBuiltinsFromUniform::Apply( - const Program& src, - const ast::transform::DataMap& inputs, - ast::transform::DataMap&) const { - return State{src, inputs}.Run(); -} - -TextureBuiltinsFromUniform::Config::Config(BindingPoint ubo_bp, - const std::vector& ordering) - : ubo_binding(ubo_bp), ubo_bindingpoint_ordering(ordering) {} - -TextureBuiltinsFromUniform::Config::Config(const Config&) = default; - -TextureBuiltinsFromUniform::Config& TextureBuiltinsFromUniform::Config::operator=(const Config&) = - default; - -TextureBuiltinsFromUniform::Config::~Config() = default; - -} // namespace tint::glsl::writer diff --git a/references/writer/ast_raise/texture_builtins_from_uniform.h b/references/writer/ast_raise/texture_builtins_from_uniform.h deleted file mode 100644 index 550e0f83..00000000 --- a/references/writer/ast_raise/texture_builtins_from_uniform.h +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2023 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef SRC_TINT_LANG_GLSL_WRITER_AST_RAISE_TEXTURE_BUILTINS_FROM_UNIFORM_H_ -#define SRC_TINT_LANG_GLSL_WRITER_AST_RAISE_TEXTURE_BUILTINS_FROM_UNIFORM_H_ - -#include - -#include "src/tint/api/common/binding_point.h" -#include "src/tint/lang/wgsl/ast/transform/transform.h" - -// Forward declarations -namespace tint { -class CloneContext; -} // namespace tint - -namespace tint::glsl::writer { - -/// TextureBuiltinsFromUniform is a transform that implements calls to textureNumLevels() and -/// textureNumSamples() by retrieving the texture information from a uniform buffer, as those -/// builtin functions are not available in some version of GLSL. -/// -/// The generated uniform buffer will have the form: -/// -/// ``` -/// struct internal_uniform { -/// texture_builtin_value_0 : u32, -/// }; -/// -/// @group(0) @binding(0) var tex : texture_2d; -/// ``` -/// -/// The binding group and number used for this uniform buffer are provided via -/// the `Config` transform input. -/// -/// The transform coverts the texture builtins calls into values lookup from the internal buffer. If -/// the texture is a function parameter instead of a global variable, this transform also takes care -/// of adding extra parameters and arguments to these functions and their call-sites. -/// -/// This transform must run before `CombineSamplers` transform so that the binding point of the -/// original texture object can be preserved. -/// -class TextureBuiltinsFromUniform final - : public Castable { - public: - /// Constructor - TextureBuiltinsFromUniform(); - /// Destructor - ~TextureBuiltinsFromUniform() override; - - /// Configuration options for the TextureBuiltinsFromUniform transform. - struct Config final : public Castable { - /// Constructor - /// @param ubo_bp the binding point to use for the generated uniform buffer. - /// @param ordering the ordered list of binding points to appear in the UBO - Config(BindingPoint ubo_bp, const std::vector& ordering); - - /// Copy constructor - Config(const Config&); - - /// Copy assignment - /// @return this Config - Config& operator=(const Config&); - - /// Destructor - ~Config() override; - - /// The binding point to use for the generated uniform buffer. - BindingPoint ubo_binding; - - /// The set of binding points which will be in the extra UBO buffer. The binding points are - /// provided in the order that the data will be in the UBO buffer. - /// - /// Note, a given `BindingPoint` _must_ only appear once in the ordering list. This works - /// because the two types of calls we're substituting `textureNumLevels` and - /// `textureNumSamples` work on a disjoint set of texture types. - std::vector ubo_bindingpoint_ordering; - }; - - /// @copydoc ast::transform::Transform::Apply - ApplyResult Apply(const Program& program, - const ast::transform::DataMap& inputs, - ast::transform::DataMap& outputs) const override; - - private: - struct State; -}; - -} // namespace tint::glsl::writer - -#endif // SRC_TINT_LANG_GLSL_WRITER_AST_RAISE_TEXTURE_BUILTINS_FROM_UNIFORM_H_ diff --git a/references/writer/ast_raise/texture_builtins_from_uniform_test.cc b/references/writer/ast_raise/texture_builtins_from_uniform_test.cc deleted file mode 100644 index 3ee97751..00000000 --- a/references/writer/ast_raise/texture_builtins_from_uniform_test.cc +++ /dev/null @@ -1,575 +0,0 @@ -// Copyright 2023 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/ast_raise/texture_builtins_from_uniform.h" - -#include - -#include "src/tint/lang/wgsl/ast/transform/helper_test.h" - -namespace tint::glsl::writer { -namespace { - -using TextureBuiltinsFromUniformTest = ast::transform::TransformTest; - -TEST_F(TextureBuiltinsFromUniformTest, Error_MissingTransformData) { - auto* src = R"( -@group(0) @binding(0) var t : texture_2d; - -@compute @workgroup_size(1) -fn main() { - var len : u32 = textureNumLevels(t); -} -)"; - - auto* expect = - "error: missing transform data for tint::glsl::writer::TextureBuiltinsFromUniform"; - - auto got = Run(src); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(TextureBuiltinsFromUniformTest, BasicTextureNumLevels) { - auto* src = R"( -@group(0) @binding(0) var t : texture_2d; - -@compute @workgroup_size(1) -fn main() { - var len : u32 = textureNumLevels(t); -} -)"; - - auto* expect = R"( -struct tint_symbol { - texture_builtin_value_0 : u32, -} - -@group(0) @binding(30) var tint_symbol_1 : tint_symbol; - -@group(0) @binding(0) var t : texture_2d; - -@compute @workgroup_size(1) -fn main() { - var len : u32 = tint_symbol_1.texture_builtin_value_0; -} -)"; - - std::vector bps = {{0, 0}}; - TextureBuiltinsFromUniform::Config cfg({0, 30u}, bps); - - ast::transform::DataMap data; - data.Add(std::move(cfg)); - - auto got = Run(src, data); - EXPECT_EQ(expect, str(got)); -} - -TEST_F(TextureBuiltinsFromUniformTest, BasicTextureNumSamples) { - auto* src = R"( -@group(0) @binding(0) var t : texture_multisampled_2d; - -@compute @workgroup_size(1) -fn main() { - var samples : u32 = textureNumSamples(t); -} -)"; - - auto* expect = R"( -struct tint_symbol { - texture_builtin_value_0 : u32, -} - -@group(0) @binding(30) var tint_symbol_1 : tint_symbol; - -@group(0) @binding(0) var t : texture_multisampled_2d; - -@compute @workgroup_size(1) -fn main() { - var samples : u32 = tint_symbol_1.texture_builtin_value_0; -} -)"; - - std::vector bps = {{0, 0}}; - TextureBuiltinsFromUniform::Config cfg({0, 30u}, bps); - - ast::transform::DataMap data; - data.Add(std::move(cfg)); - - auto got = Run(src, data); - EXPECT_EQ(expect, str(got)); -} - -TEST_F(TextureBuiltinsFromUniformTest, SameBuiltinCalledMultipleTimes) { - auto* src = R"( -@group(0) @binding(0) var tex : texture_2d; - -@compute @workgroup_size(1) -fn main() { - var len : u32 = textureNumLevels(tex); - len = textureNumLevels(tex); -} -)"; - - auto* expect = R"( -struct tint_symbol { - texture_builtin_value_0 : u32, -} - -@group(0) @binding(30) var tint_symbol_1 : tint_symbol; - -@group(0) @binding(0) var tex : texture_2d; - -@compute @workgroup_size(1) -fn main() { - var len : u32 = tint_symbol_1.texture_builtin_value_0; - len = tint_symbol_1.texture_builtin_value_0; -} -)"; - - std::vector bps = {{0, 0}}; - TextureBuiltinsFromUniform::Config cfg({0, 30u}, bps); - - ast::transform::DataMap data; - data.Add(std::move(cfg)); - - auto got = Run(src, data); - EXPECT_EQ(expect, str(got)); -} - -TEST_F(TextureBuiltinsFromUniformTest, SameBuiltinCalledMultipleTimesTextureNumSamples) { - auto* src = R"( -@group(0) @binding(0) var tex : texture_multisampled_2d; - -@compute @workgroup_size(1) -fn main() { - var len : u32 = textureNumSamples(tex); - len = textureNumSamples(tex); -} -)"; - - auto* expect = R"( -struct tint_symbol { - texture_builtin_value_0 : u32, -} - -@group(0) @binding(30) var tint_symbol_1 : tint_symbol; - -@group(0) @binding(0) var tex : texture_multisampled_2d; - -@compute @workgroup_size(1) -fn main() { - var len : u32 = tint_symbol_1.texture_builtin_value_0; - len = tint_symbol_1.texture_builtin_value_0; -} -)"; - - std::vector bps = {{0, 0}}; - TextureBuiltinsFromUniform::Config cfg({0, 30u}, bps); - - ast::transform::DataMap data; - data.Add(std::move(cfg)); - - auto got = Run(src, data); - EXPECT_EQ(expect, str(got)); -} - -TEST_F(TextureBuiltinsFromUniformTest, TextureAsFunctionParameterBasic) { - auto* src = R"( -@group(0) @binding(0) var tex : texture_2d; - -fn f(t: texture_2d) -> u32 { - return textureNumLevels(t); -} - -@compute @workgroup_size(1) -fn main() { - var len : u32 = f(tex); -} -)"; - - auto* expect = R"( -struct tint_symbol_1 { - texture_builtin_value_0 : u32, -} - -@group(0) @binding(30) var tint_symbol_2 : tint_symbol_1; - -@group(0) @binding(0) var tex : texture_2d; - -fn f(t : texture_2d, tint_symbol : u32) -> u32 { - return tint_symbol; -} - -@compute @workgroup_size(1) -fn main() { - var len : u32 = f(tex, tint_symbol_2.texture_builtin_value_0); -} -)"; - - std::vector bps = {{0, 0}}; - TextureBuiltinsFromUniform::Config cfg({0, 30u}, bps); - - ast::transform::DataMap data; - data.Add(std::move(cfg)); - - auto got = Run(src, data); - EXPECT_EQ(expect, str(got)); -} - -TEST_F(TextureBuiltinsFromUniformTest, TextureAsFunctionParameterUsedTwice) { - auto* src = R"( -@group(0) @binding(0) var tex : texture_2d; - -fn f(t: texture_2d) -> u32 { - var len = textureNumLevels(t); - len += textureNumLevels(t); - return len; -} - -@compute @workgroup_size(1) -fn main() { - var len : u32 = f(tex); -} -)"; - - auto* expect = R"( -struct tint_symbol_1 { - texture_builtin_value_0 : u32, -} - -@group(0) @binding(30) var tint_symbol_2 : tint_symbol_1; - -@group(0) @binding(0) var tex : texture_2d; - -fn f(t : texture_2d, tint_symbol : u32) -> u32 { - var len = tint_symbol; - len += tint_symbol; - return len; -} - -@compute @workgroup_size(1) -fn main() { - var len : u32 = f(tex, tint_symbol_2.texture_builtin_value_0); -} -)"; - - std::vector bps = {{0, 0}}; - TextureBuiltinsFromUniform::Config cfg({0, 30u}, bps); - - ast::transform::DataMap data; - data.Add(std::move(cfg)); - - auto got = Run(src, data); - EXPECT_EQ(expect, str(got)); -} - -TEST_F(TextureBuiltinsFromUniformTest, TextureAsFunctionParameterMultipleParameters) { - auto* src = R"( -@group(0) @binding(0) var tex1 : texture_2d; -@group(0) @binding(1) var tex2 : texture_2d; -@group(0) @binding(2) var tex3 : texture_2d; - -fn f(t1: texture_2d, t2: texture_2d, t3: texture_2d) -> u32 { - return textureNumLevels(t1) + textureNumLevels(t2) + textureNumLevels(t3); -} - -@compute @workgroup_size(1) -fn main() { - var len : u32 = f(tex1, tex2, tex3); -} -)"; - - auto* expect = R"( -struct tint_symbol_3 { - texture_builtin_value_0 : u32, - texture_builtin_value_1 : u32, - texture_builtin_value_2 : u32, -} - -@group(0) @binding(30) var tint_symbol_4 : tint_symbol_3; - -@group(0) @binding(0) var tex1 : texture_2d; - -@group(0) @binding(1) var tex2 : texture_2d; - -@group(0) @binding(2) var tex3 : texture_2d; - -fn f(t1 : texture_2d, t2 : texture_2d, t3 : texture_2d, tint_symbol : u32, tint_symbol_1 : u32, tint_symbol_2 : u32) -> u32 { - return ((tint_symbol + tint_symbol_1) + tint_symbol_2); -} - -@compute @workgroup_size(1) -fn main() { - var len : u32 = f(tex1, tex2, tex3, tint_symbol_4.texture_builtin_value_0, tint_symbol_4.texture_builtin_value_1, tint_symbol_4.texture_builtin_value_2); -} -)"; - - std::vector bps = {{0, 0}, {0, 1}, {0, 2}}; - TextureBuiltinsFromUniform::Config cfg({0, 30u}, bps); - - ast::transform::DataMap data; - data.Add(std::move(cfg)); - - auto got = Run(src, data); - EXPECT_EQ(expect, str(got)); -} - -TEST_F(TextureBuiltinsFromUniformTest, TextureAsFunctionParameterNested) { - auto* src = R"( -@group(0) @binding(0) var tex : texture_2d; - -fn f2(tt: texture_2d) -> u32 { - return textureNumLevels(tt); -} - -fn f1(t: texture_2d) -> u32 { - return f2(t); -} - -@compute @workgroup_size(1) -fn main() { - var len : u32 = f1(tex); -} -)"; - - auto* expect = R"( -struct tint_symbol_2 { - texture_builtin_value_0 : u32, -} - -@group(0) @binding(30) var tint_symbol_3 : tint_symbol_2; - -@group(0) @binding(0) var tex : texture_2d; - -fn f2(tt : texture_2d, tint_symbol : u32) -> u32 { - return tint_symbol; -} - -fn f1(t : texture_2d, tint_symbol_1 : u32) -> u32 { - return f2(t, tint_symbol_1); -} - -@compute @workgroup_size(1) -fn main() { - var len : u32 = f1(tex, tint_symbol_3.texture_builtin_value_0); -} -)"; - - std::vector bps = {{0, 0}}; - TextureBuiltinsFromUniform::Config cfg({0, 30u}, bps); - - ast::transform::DataMap data; - data.Add(std::move(cfg)); - - auto got = Run(src, data); - EXPECT_EQ(expect, str(got)); -} - -TEST_F(TextureBuiltinsFromUniformTest, TextureAsFunctionParameterMixed) { - auto* src = R"( -@group(0) @binding(0) var tex0 : texture_2d; -@group(0) @binding(1) var tex1 : texture_2d; -@group(0) @binding(2) var tex2 : texture_2d; -@group(0) @binding(3) var tex3 : texture_2d; -@group(0) @binding(4) var tex4 : texture_2d_array; // unused for textureNumLevels - -fn f_nested(t1: texture_2d, t2: texture_2d) -> u32 { - return textureNumLevels(t1) + textureNumLevels(t2); -} - -fn f1(a: u32, t: texture_2d) -> u32 { - return a + f_nested(t, tex1) + textureNumLevels(tex3); -} - -@compute @workgroup_size(1) -fn main() { - _ = textureNumLayers(tex4); - _ = f1(9u, tex0); - _ = f_nested(tex2, tex2); - _ = f_nested(tex1, tex0); -} -)"; - - auto* expect = R"( -struct tint_symbol_3 { - texture_builtin_value_0 : u32, - texture_builtin_value_1 : u32, - texture_builtin_value_2 : u32, - texture_builtin_value_3 : u32, -} - -@group(0) @binding(30) var tint_symbol_4 : tint_symbol_3; - -@group(0) @binding(0) var tex0 : texture_2d; - -@group(0) @binding(1) var tex1 : texture_2d; - -@group(0) @binding(2) var tex2 : texture_2d; - -@group(0) @binding(3) var tex3 : texture_2d; - -@group(0) @binding(4) var tex4 : texture_2d_array; - -fn f_nested(t1 : texture_2d, t2 : texture_2d, tint_symbol : u32, tint_symbol_1 : u32) -> u32 { - return (tint_symbol + tint_symbol_1); -} - -fn f1(a : u32, t : texture_2d, tint_symbol_2 : u32) -> u32 { - return ((a + f_nested(t, tex1, tint_symbol_2, tint_symbol_4.texture_builtin_value_0)) + tint_symbol_4.texture_builtin_value_1); -} - -@compute @workgroup_size(1) -fn main() { - _ = textureNumLayers(tex4); - _ = f1(9u, tex0, tint_symbol_4.texture_builtin_value_2); - _ = f_nested(tex2, tex2, tint_symbol_4.texture_builtin_value_3, tint_symbol_4.texture_builtin_value_3); - _ = f_nested(tex1, tex0, tint_symbol_4.texture_builtin_value_0, tint_symbol_4.texture_builtin_value_2); -} -)"; - - std::vector bps = {{0, 1}, {0, 3}, {0, 0}, {0, 2}}; - TextureBuiltinsFromUniform::Config cfg({0, 30u}, bps); - - ast::transform::DataMap data; - data.Add(std::move(cfg)); - - auto got = Run(src, data); - EXPECT_EQ(expect, str(got)); -} - -TEST_F(TextureBuiltinsFromUniformTest, MultipleTextures) { - auto* src = R"( -@group(0) @binding(0) var t0 : texture_2d; -@group(0) @binding(1) var t1 : texture_multisampled_2d; -@group(0) @binding(2) var t2 : texture_2d_array; -@group(0) @binding(3) var t3 : texture_cube; -@group(0) @binding(4) var t4 : texture_depth_2d; -@group(1) @binding(0) var t5 : texture_depth_multisampled_2d; - -@compute @workgroup_size(1) -fn main() { - _ = textureNumLevels(t0); - _ = textureNumSamples(t1); - _ = textureNumLevels(t2); - _ = textureNumLevels(t3); - _ = textureNumLevels(t4); - _ = textureNumSamples(t5); -} -)"; - - auto* expect = R"( -struct tint_symbol { - texture_builtin_value_0 : u32, - texture_builtin_value_1 : u32, - texture_builtin_value_2 : u32, - texture_builtin_value_3 : u32, - texture_builtin_value_4 : u32, - texture_builtin_value_5 : u32, -} - -@group(0) @binding(30) var tint_symbol_1 : tint_symbol; - -@group(0) @binding(0) var t0 : texture_2d; - -@group(0) @binding(1) var t1 : texture_multisampled_2d; - -@group(0) @binding(2) var t2 : texture_2d_array; - -@group(0) @binding(3) var t3 : texture_cube; - -@group(0) @binding(4) var t4 : texture_depth_2d; - -@group(1) @binding(0) var t5 : texture_depth_multisampled_2d; - -@compute @workgroup_size(1) -fn main() { - _ = tint_symbol_1.texture_builtin_value_0; - _ = tint_symbol_1.texture_builtin_value_1; - _ = tint_symbol_1.texture_builtin_value_2; - _ = tint_symbol_1.texture_builtin_value_3; - _ = tint_symbol_1.texture_builtin_value_4; - _ = tint_symbol_1.texture_builtin_value_5; -} -)"; - - std::vector bps = {{0, 0}, {0, 1}, {0, 2}, {0, 3}, {0, 4}, {1, 0}}; - TextureBuiltinsFromUniform::Config cfg({0, 30u}, bps); - - ast::transform::DataMap data; - data.Add(std::move(cfg)); - - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -TEST_F(TextureBuiltinsFromUniformTest, BindingPointExist) { - auto* src = R"( -struct tint_symbol { - foo : array, 1u>, -} - -@group(0) @binding(30) var tint_symbol_1 : tint_symbol; - -@group(0) @binding(0) var t : texture_2d; - -@compute @workgroup_size(1) -fn main() { - var len : u32 = textureNumLevels(t); -} -)"; - - auto* expect = R"( -struct tint_symbol { - foo : array, 1u>, - texture_builtin_value_0 : u32, -} - -@group(0) @binding(30) var tint_symbol_1 : tint_symbol; - -@group(0) @binding(0) var t : texture_2d; - -@compute @workgroup_size(1) -fn main() { - var len : u32 = tint_symbol_1.texture_builtin_value_0; -} -)"; - - std::vector bps = {{0, 0}}; - TextureBuiltinsFromUniform::Config cfg({0, 30u}, bps); - - ast::transform::DataMap data; - data.Add(std::move(cfg)); - - auto got = Run(src, data); - - EXPECT_EQ(expect, str(got)); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/common/BUILD.bazel b/references/writer/common/BUILD.bazel deleted file mode 100644 index a3df8659..00000000 --- a/references/writer/common/BUILD.bazel +++ /dev/null @@ -1,125 +0,0 @@ -# Copyright 2023 The Dawn & Tint Authors -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -################################################################################ -# File generated by 'tools/src/cmd/gen' using the template: -# tools/src/cmd/gen/build/BUILD.bazel.tmpl -# -# To regenerate run: './tools/run gen' -# -# Do not modify this file directly -################################################################################ - -load("//src/tint:flags.bzl", "COPTS") -load("@bazel_skylib//lib:selects.bzl", "selects") -cc_library( - name = "common", - srcs = [ - "options.cc", - "printer_support.cc", - ], - hdrs = [ - "options.h", - "printer_support.h", - "version.h", - ], - deps = [ - "//src/tint/api/common", - "//src/tint/api/options", - "//src/tint/lang/core", - "//src/tint/lang/core/constant", - "//src/tint/lang/core/type", - "//src/tint/lang/wgsl", - "//src/tint/lang/wgsl/ast", - "//src/tint/lang/wgsl/ast/transform", - "//src/tint/lang/wgsl/program", - "//src/tint/lang/wgsl/sem", - "//src/tint/utils/containers", - "//src/tint/utils/diagnostic", - "//src/tint/utils/ice", - "//src/tint/utils/id", - "//src/tint/utils/macros", - "//src/tint/utils/math", - "//src/tint/utils/memory", - "//src/tint/utils/reflection", - "//src/tint/utils/result", - "//src/tint/utils/rtti", - "//src/tint/utils/strconv", - "//src/tint/utils/symbol", - "//src/tint/utils/text", - "//src/tint/utils/traits", - ], - copts = COPTS, - visibility = ["//visibility:public"], -) -cc_library( - name = "test", - alwayslink = True, - srcs = [ - "options_test.cc", - "version_test.cc", - ], - deps = [ - "//src/tint/api/common", - "//src/tint/api/options", - "//src/tint/lang/core", - "//src/tint/lang/core/constant", - "//src/tint/lang/core/type", - "//src/tint/lang/wgsl", - "//src/tint/lang/wgsl/ast", - "//src/tint/lang/wgsl/ast/transform", - "//src/tint/lang/wgsl/program", - "//src/tint/lang/wgsl/sem", - "//src/tint/utils/containers", - "//src/tint/utils/diagnostic", - "//src/tint/utils/ice", - "//src/tint/utils/id", - "//src/tint/utils/macros", - "//src/tint/utils/math", - "//src/tint/utils/memory", - "//src/tint/utils/reflection", - "//src/tint/utils/result", - "//src/tint/utils/rtti", - "//src/tint/utils/symbol", - "//src/tint/utils/text", - "//src/tint/utils/traits", - "@gtest", - ] + select({ - ":tint_build_glsl_writer": [ - "//src/tint/lang/glsl/writer/common", - ], - "//conditions:default": [], - }), - copts = COPTS, - visibility = ["//visibility:public"], -) - -alias( - name = "tint_build_glsl_writer", - actual = "//src/tint:tint_build_glsl_writer_true", -) - diff --git a/references/writer/common/BUILD.cfg b/references/writer/common/BUILD.cfg deleted file mode 100644 index 7459430e..00000000 --- a/references/writer/common/BUILD.cfg +++ /dev/null @@ -1,3 +0,0 @@ -{ - "condition": "tint_build_glsl_writer" -} diff --git a/references/writer/common/BUILD.cmake b/references/writer/common/BUILD.cmake deleted file mode 100644 index 19ce2763..00000000 --- a/references/writer/common/BUILD.cmake +++ /dev/null @@ -1,126 +0,0 @@ -# Copyright 2023 The Dawn & Tint Authors -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -################################################################################ -# File generated by 'tools/src/cmd/gen' using the template: -# tools/src/cmd/gen/build/BUILD.cmake.tmpl -# -# To regenerate run: './tools/run gen' -# -# Do not modify this file directly -################################################################################ - -if(TINT_BUILD_GLSL_WRITER) -################################################################################ -# Target: tint_lang_glsl_writer_common -# Kind: lib -# Condition: TINT_BUILD_GLSL_WRITER -################################################################################ -tint_add_target(tint_lang_glsl_writer_common lib - lang/glsl/writer/common/options.cc - lang/glsl/writer/common/options.h - lang/glsl/writer/common/printer_support.cc - lang/glsl/writer/common/printer_support.h - lang/glsl/writer/common/version.h -) - -tint_target_add_dependencies(tint_lang_glsl_writer_common lib - tint_api_common - tint_api_options - tint_lang_core - tint_lang_core_constant - tint_lang_core_type - tint_lang_wgsl - tint_lang_wgsl_ast - tint_lang_wgsl_ast_transform - tint_lang_wgsl_program - tint_lang_wgsl_sem - tint_utils_containers - tint_utils_diagnostic - tint_utils_ice - tint_utils_id - tint_utils_macros - tint_utils_math - tint_utils_memory - tint_utils_reflection - tint_utils_result - tint_utils_rtti - tint_utils_strconv - tint_utils_symbol - tint_utils_text - tint_utils_traits -) - -endif(TINT_BUILD_GLSL_WRITER) -if(TINT_BUILD_GLSL_WRITER) -################################################################################ -# Target: tint_lang_glsl_writer_common_test -# Kind: test -# Condition: TINT_BUILD_GLSL_WRITER -################################################################################ -tint_add_target(tint_lang_glsl_writer_common_test test - lang/glsl/writer/common/options_test.cc - lang/glsl/writer/common/version_test.cc -) - -tint_target_add_dependencies(tint_lang_glsl_writer_common_test test - tint_api_common - tint_api_options - tint_lang_core - tint_lang_core_constant - tint_lang_core_type - tint_lang_wgsl - tint_lang_wgsl_ast - tint_lang_wgsl_ast_transform - tint_lang_wgsl_program - tint_lang_wgsl_sem - tint_utils_containers - tint_utils_diagnostic - tint_utils_ice - tint_utils_id - tint_utils_macros - tint_utils_math - tint_utils_memory - tint_utils_reflection - tint_utils_result - tint_utils_rtti - tint_utils_symbol - tint_utils_text - tint_utils_traits -) - -tint_target_add_external_dependencies(tint_lang_glsl_writer_common_test test - "gtest" -) - -if(TINT_BUILD_GLSL_WRITER) - tint_target_add_dependencies(tint_lang_glsl_writer_common_test test - tint_lang_glsl_writer_common - ) -endif(TINT_BUILD_GLSL_WRITER) - -endif(TINT_BUILD_GLSL_WRITER) \ No newline at end of file diff --git a/references/writer/common/BUILD.gn b/references/writer/common/BUILD.gn deleted file mode 100644 index 2994d84e..00000000 --- a/references/writer/common/BUILD.gn +++ /dev/null @@ -1,120 +0,0 @@ -# Copyright 2023 The Dawn & Tint Authors -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -################################################################################ -# File generated by 'tools/src/cmd/gen' using the template: -# tools/src/cmd/gen/build/BUILD.gn.tmpl -# -# To regenerate run: './tools/run gen' -# -# Do not modify this file directly -################################################################################ - -import("../../../../../../scripts/tint_overrides_with_defaults.gni") - -import("${tint_src_dir}/tint.gni") - -if (tint_build_unittests || tint_build_benchmarks) { - import("//testing/test.gni") -} -if (tint_build_glsl_writer) { - libtint_source_set("common") { - sources = [ - "options.cc", - "options.h", - "printer_support.cc", - "printer_support.h", - "version.h", - ] - deps = [ - "${tint_src_dir}/api/common", - "${tint_src_dir}/api/options", - "${tint_src_dir}/lang/core", - "${tint_src_dir}/lang/core/constant", - "${tint_src_dir}/lang/core/type", - "${tint_src_dir}/lang/wgsl", - "${tint_src_dir}/lang/wgsl/ast", - "${tint_src_dir}/lang/wgsl/ast/transform", - "${tint_src_dir}/lang/wgsl/program", - "${tint_src_dir}/lang/wgsl/sem", - "${tint_src_dir}/utils/containers", - "${tint_src_dir}/utils/diagnostic", - "${tint_src_dir}/utils/ice", - "${tint_src_dir}/utils/id", - "${tint_src_dir}/utils/macros", - "${tint_src_dir}/utils/math", - "${tint_src_dir}/utils/memory", - "${tint_src_dir}/utils/reflection", - "${tint_src_dir}/utils/result", - "${tint_src_dir}/utils/rtti", - "${tint_src_dir}/utils/strconv", - "${tint_src_dir}/utils/symbol", - "${tint_src_dir}/utils/text", - "${tint_src_dir}/utils/traits", - ] - } -} -if (tint_build_unittests) { - if (tint_build_glsl_writer) { - tint_unittests_source_set("unittests") { - sources = [ - "options_test.cc", - "version_test.cc", - ] - deps = [ - "${tint_src_dir}:gmock_and_gtest", - "${tint_src_dir}/api/common", - "${tint_src_dir}/api/options", - "${tint_src_dir}/lang/core", - "${tint_src_dir}/lang/core/constant", - "${tint_src_dir}/lang/core/type", - "${tint_src_dir}/lang/wgsl", - "${tint_src_dir}/lang/wgsl/ast", - "${tint_src_dir}/lang/wgsl/ast/transform", - "${tint_src_dir}/lang/wgsl/program", - "${tint_src_dir}/lang/wgsl/sem", - "${tint_src_dir}/utils/containers", - "${tint_src_dir}/utils/diagnostic", - "${tint_src_dir}/utils/ice", - "${tint_src_dir}/utils/id", - "${tint_src_dir}/utils/macros", - "${tint_src_dir}/utils/math", - "${tint_src_dir}/utils/memory", - "${tint_src_dir}/utils/reflection", - "${tint_src_dir}/utils/result", - "${tint_src_dir}/utils/rtti", - "${tint_src_dir}/utils/symbol", - "${tint_src_dir}/utils/text", - "${tint_src_dir}/utils/traits", - ] - - if (tint_build_glsl_writer) { - deps += [ "${tint_src_dir}/lang/glsl/writer/common" ] - } - } - } -} diff --git a/references/writer/common/options.cc b/references/writer/common/options.cc deleted file mode 100644 index 3b0eb9b3..00000000 --- a/references/writer/common/options.cc +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2023 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/common/options.h" - -namespace tint::glsl::writer { - -Options::Options() = default; - -Options::~Options() = default; - -Options::Options(const Options&) = default; - -} // namespace tint::glsl::writer diff --git a/references/writer/common/options.h b/references/writer/common/options.h deleted file mode 100644 index 3ba7540a..00000000 --- a/references/writer/common/options.h +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright 2023 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef SRC_TINT_LANG_GLSL_WRITER_COMMON_OPTIONS_H_ -#define SRC_TINT_LANG_GLSL_WRITER_COMMON_OPTIONS_H_ - -#include -#include -#include - -#include "src/tint/api/options/binding_remapper.h" -#include "src/tint/api/options/depth_range_offsets.h" -#include "src/tint/api/options/external_texture.h" -#include "src/tint/api/options/texture_builtins_from_uniform.h" -#include "src/tint/lang/glsl/writer/common/version.h" -#include "src/tint/lang/wgsl/ast/transform/transform.h" - -namespace tint::glsl::writer::binding { - -/// A combined texture/sampler pair -// Note, these are the WGSL binding points that are used to create the combined samplers -struct CombinedTextureSamplerPair { - /// The WGSL texture binding - BindingPoint texture = {}; - /// The WGSL sampler binding - BindingPoint sampler = {}; - - /// Equality operator - /// @param rhs the CombinedTextureSamplerPair to compare against - /// @returns true if this CombinedTextureSamplerPair is equal to `rhs` - inline bool operator==(const CombinedTextureSamplerPair& rhs) const { - return texture == rhs.texture && sampler == rhs.sampler; - } - - /// Less then operator - /// @param rhs the CombinedTextureSamplerPair to compare against - /// @returns if this is less then rhs - inline bool operator<(const CombinedTextureSamplerPair& rhs) const { - if (texture < rhs.texture) { - return true; - } - if (texture == rhs.texture) { - return sampler < rhs.sampler; - } - return false; - } - - /// Reflect the fields of this class so that it can be used by tint::ForeachField() - TINT_REFLECT(CombinedTextureSamplerPair, texture, sampler); -}; - -} // namespace tint::glsl::writer::binding - -namespace std { -/// Custom std::hash specialization for tint::glsl::writer::binding::CombinedTextureSamplerPair -template <> -class hash { - public: - /// @param n the combined sampler texture pair - /// @return the hash value - inline std::size_t operator()( - const tint::glsl::writer::binding::CombinedTextureSamplerPair& n) const { - return tint::Hash(n.texture, n.sampler); - } -}; - -} // namespace std - -namespace tint::glsl::writer { - -using CombinedTextureSamplerInfo = - std::unordered_map; - -struct CombineSamplersInfo final - : public Castable { - /// Constructor - CombineSamplersInfo(); - - /// Constructor - /// @param map the texture/sampler to name map - /// @param placeholder the sampler placeholder binding point - CombineSamplersInfo(CombinedTextureSamplerInfo map, BindingPoint placeholder); - - CombineSamplersInfo(const CombineSamplersInfo&) = default; - CombineSamplersInfo(CombineSamplersInfo&&) = default; - - /// Destructor - ~CombineSamplersInfo() override; - - CombineSamplersInfo& operator=(const CombineSamplersInfo&) = default; - CombineSamplersInfo& operator=(CombineSamplersInfo&&) = default; - - /// A map of SamplerTexturePair to combined sampler names for the - /// CombineSamplers transform - CombinedTextureSamplerInfo sampler_texture_to_name; - - /// The binding point to use for placeholder samplers. - BindingPoint placeholder_sampler_binding; - - TINT_REFLECT(CombineSamplersInfo, sampler_texture_to_name, placeholder_sampler_binding); -}; - -/// Configuration options used for generating GLSL. -struct Options { - /// Constructor - Options(); - - /// Destructor - ~Options(); - - /// Copy constructor - Options(const Options&); - - /// Set to `true` to disable software robustness that prevents out-of-bounds accesses. - bool disable_robustness = false; - - /// Set to `true` to disable workgroup memory zero initialization - bool disable_workgroup_init = false; - - /// Set to `true` to disable the polyfills on integer division and modulo. - bool disable_polyfill_integer_div_mod = false; - - /// The GLSL version to emit - Version version; - - /// Combine Samplers transform information - CombineSamplersInfo combined_samplers_info = {}; - - /// Options used in the bindings remapper - BindingRemapperOptions binding_remapper_options = {}; - - /// Options used in the binding mappings for external textures - ExternalTextureOptions external_texture_options = {}; - - /// Offset of the firstVertex push constant. - std::optional first_vertex_offset; - - /// Offset of the firstInstance push constant. - std::optional first_instance_offset; - - /// Offsets of the minDepth and maxDepth push constants. - std::optional depth_range_offsets; - - /// Options used to map WGSL textureNumLevels/textureNumSamples builtins to internal uniform - /// buffer values. If not specified, emits corresponding GLSL builtins - /// textureQueryLevels/textureSamples directly. - TextureBuiltinsFromUniformOptions texture_builtins_from_uniform = {}; - - /// Reflect the fields of this class so that it can be used by tint::ForeachField() - TINT_REFLECT(Options, - disable_robustness, - disable_workgroup_init, - disable_polyfill_integer_div_mod, - version, - combined_samplers_info, - binding_remapper_options, - external_texture_options, - first_vertex_offset, - first_instance_offset, - depth_range_offsets, - texture_builtins_from_uniform); -}; - -} // namespace tint::glsl::writer - -#endif // SRC_TINT_LANG_GLSL_WRITER_COMMON_OPTIONS_H_ diff --git a/references/writer/common/options_test.cc b/references/writer/common/options_test.cc deleted file mode 100644 index 0309f316..00000000 --- a/references/writer/common/options_test.cc +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2024 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/common/options.h" - -#include - -namespace tint { -namespace { - -TEST(TintCheckAllFieldsReflected, GlslWriterCommonOptionsTest) { - TINT_ASSERT_ALL_FIELDS_REFLECTED(tint::glsl::writer::Options); -} - -} // namespace -} // namespace tint diff --git a/references/writer/common/printer_support.cc b/references/writer/common/printer_support.cc deleted file mode 100644 index d4c62de3..00000000 --- a/references/writer/common/printer_support.cc +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2023 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/common/printer_support.h" - -#include -#include - -#include "src/tint/utils/strconv/float_to_string.h" - -namespace tint::glsl::writer { - -void PrintI32(StringStream& out, int32_t value) { - // GLSL parses `-2147483648` as a unary minus and `2147483648` as separate tokens, and the - // latter doesn't fit into an (32-bit) `int`. Emit `(-2147483647 - 1)` instead, which ensures - // the expression type is `int`. - if (auto int_min = std::numeric_limits::min(); value == int_min) { - out << "(" << int_min + 1 << " - 1)"; - } else { - out << value; - } -} - -void PrintF32(StringStream& out, float value) { - if (std::isinf(value)) { - out << "0.0f " << (value >= 0 ? "/* inf */" : "/* -inf */"); - } else if (std::isnan(value)) { - out << "0.0f /* nan */"; - } else { - out << tint::strconv::FloatToString(value) << "f"; - } -} - -void PrintF16(StringStream& out, float value) { - if (std::isinf(value)) { - out << "0.0hf " << (value >= 0 ? "/* inf */" : "/* -inf */"); - } else if (std::isnan(value)) { - out << "0.0hf /* nan */"; - } else { - out << tint::strconv::FloatToString(value) << "hf"; - } -} - -} // namespace tint::glsl::writer diff --git a/references/writer/common/printer_support.h b/references/writer/common/printer_support.h deleted file mode 100644 index d36a1d8a..00000000 --- a/references/writer/common/printer_support.h +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2023 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef SRC_TINT_LANG_GLSL_WRITER_COMMON_PRINTER_SUPPORT_H_ -#define SRC_TINT_LANG_GLSL_WRITER_COMMON_PRINTER_SUPPORT_H_ - -#include - -#include "src/tint/utils/text/string_stream.h" - -namespace tint::glsl::writer { - -/// Prints a float32 to the output stream -/// @param out the stream to write too -/// @param value the float32 value -void PrintF32(StringStream& out, float value); - -/// Prints a float16 to the output stream -/// @param out the stream to write too -/// @param value the float16 value -void PrintF16(StringStream& out, float value); - -/// Prints an int32 to the output stream -/// @param out the stream to write too -/// @param value the int32 value -void PrintI32(StringStream& out, int32_t value); - -} // namespace tint::glsl::writer - -#endif // SRC_TINT_LANG_GLSL_WRITER_COMMON_PRINTER_SUPPORT_H_ diff --git a/references/writer/common/version.h b/references/writer/common/version.h deleted file mode 100644 index 182b4f2d..00000000 --- a/references/writer/common/version.h +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2022 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef SRC_TINT_LANG_GLSL_WRITER_COMMON_VERSION_H_ -#define SRC_TINT_LANG_GLSL_WRITER_COMMON_VERSION_H_ - -#include - -#include "src/tint/utils/reflection/reflection.h" - -namespace tint::glsl::writer { - -/// A structure representing the version of GLSL to be generated. -struct Version { - /// Is this version desktop GLSL, or GLSL ES? - enum class Standard { - kDesktop, - kES, - }; - - /// Constructor - /// @param standard_ Desktop or ES - /// @param major_ the major version - /// @param minor_ the minor version - Version(Standard standard_, uint32_t major_, uint32_t minor_) - : standard(standard_), major_version(major_), minor_version(minor_) {} - - /// Default constructor (see default values below) - Version() = default; - - /// @returns true if this version is GLSL ES - bool IsES() const { return standard == Standard::kES; } - - /// @returns true if this version is Desktop GLSL - bool IsDesktop() const { return standard == Standard::kDesktop; } - - /// Desktop or ES - Standard standard = Standard::kES; - - /// Major GLSL version - uint32_t major_version = 3; - - /// Minor GLSL version - uint32_t minor_version = 1; - - /// Reflect the fields of this class so that it can be used by tint::ForeachField() - TINT_REFLECT(Version, standard, major_version, minor_version); -}; - -} // namespace tint::glsl::writer - -namespace tint { - -/// Relect enum information for Version -TINT_REFLECT_ENUM_RANGE(glsl::writer::Version::Standard, kDesktop, kES); - -} // namespace tint - -#endif // SRC_TINT_LANG_GLSL_WRITER_COMMON_VERSION_H_ diff --git a/references/writer/common/version_test.cc b/references/writer/common/version_test.cc deleted file mode 100644 index 7110ff6d..00000000 --- a/references/writer/common/version_test.cc +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2024 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/common/version.h" - -#include - -namespace tint { -namespace { - -TEST(TintCheckAllFieldsReflected, GlslWriterCommonVersionTest) { - TINT_ASSERT_ALL_FIELDS_REFLECTED(tint::glsl::writer::Version); -} - -} // namespace -} // namespace tint diff --git a/references/writer/output.cc b/references/writer/output.cc deleted file mode 100644 index 26c78e93..00000000 --- a/references/writer/output.cc +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2023 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/output.h" - -namespace tint::glsl::writer { - -Output::Output() = default; - -Output::~Output() = default; - -Output::Output(const Output&) = default; - -} // namespace tint::glsl::writer diff --git a/references/writer/output.h b/references/writer/output.h deleted file mode 100644 index 8f205d14..00000000 --- a/references/writer/output.h +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2023 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef SRC_TINT_LANG_GLSL_WRITER_OUTPUT_H_ -#define SRC_TINT_LANG_GLSL_WRITER_OUTPUT_H_ - -#include -#include -#include - -#include "src/tint/lang/wgsl/ast/pipeline_stage.h" - -namespace tint::glsl::writer { - -/// The output produced when generating GLSL. -struct Output { - /// Constructor - Output(); - - /// Destructor - ~Output(); - - /// Copy constructor - Output(const Output&); - - /// The generated GLSL. - std::string glsl = ""; -}; - -} // namespace tint::glsl::writer - -#endif // SRC_TINT_LANG_GLSL_WRITER_OUTPUT_H_ diff --git a/references/writer/printer/BUILD.bazel b/references/writer/printer/BUILD.bazel deleted file mode 100644 index 8961c7aa..00000000 --- a/references/writer/printer/BUILD.bazel +++ /dev/null @@ -1,121 +0,0 @@ -# Copyright 2023 The Dawn & Tint Authors -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -################################################################################ -# File generated by 'tools/src/cmd/gen' using the template: -# tools/src/cmd/gen/build/BUILD.bazel.tmpl -# -# To regenerate run: './tools/run gen' -# -# Do not modify this file directly -################################################################################ - -load("//src/tint:flags.bzl", "COPTS") -load("@bazel_skylib//lib:selects.bzl", "selects") -cc_library( - name = "printer", - srcs = [ - "printer.cc", - ], - hdrs = [ - "printer.h", - ], - deps = [ - "//src/tint/api/common", - "//src/tint/lang/core", - "//src/tint/lang/core/constant", - "//src/tint/lang/core/ir", - "//src/tint/lang/core/type", - "//src/tint/utils/containers", - "//src/tint/utils/diagnostic", - "//src/tint/utils/generator", - "//src/tint/utils/ice", - "//src/tint/utils/id", - "//src/tint/utils/macros", - "//src/tint/utils/math", - "//src/tint/utils/memory", - "//src/tint/utils/reflection", - "//src/tint/utils/result", - "//src/tint/utils/rtti", - "//src/tint/utils/symbol", - "//src/tint/utils/text", - "//src/tint/utils/traits", - ] + select({ - ":tint_build_glsl_writer": [ - "//src/tint/lang/glsl/writer/common", - ], - "//conditions:default": [], - }), - copts = COPTS, - visibility = ["//visibility:public"], -) -cc_library( - name = "test", - alwayslink = True, - srcs = [ - "constant_test.cc", - "function_test.cc", - "helper_test.h", - ], - deps = [ - "//src/tint/api/common", - "//src/tint/lang/core", - "//src/tint/lang/core/constant", - "//src/tint/lang/core/intrinsic", - "//src/tint/lang/core/ir", - "//src/tint/lang/core/type", - "//src/tint/lang/glsl/writer/raise", - "//src/tint/utils/containers", - "//src/tint/utils/diagnostic", - "//src/tint/utils/ice", - "//src/tint/utils/id", - "//src/tint/utils/macros", - "//src/tint/utils/math", - "//src/tint/utils/memory", - "//src/tint/utils/reflection", - "//src/tint/utils/result", - "//src/tint/utils/rtti", - "//src/tint/utils/symbol", - "//src/tint/utils/text", - "//src/tint/utils/traits", - "@gtest", - ] + select({ - ":tint_build_glsl_writer": [ - "//src/tint/lang/glsl/writer/common", - "//src/tint/lang/glsl/writer/printer", - ], - "//conditions:default": [], - }), - copts = COPTS, - visibility = ["//visibility:public"], -) - -alias( - name = "tint_build_glsl_writer", - actual = "//src/tint:tint_build_glsl_writer_true", -) - diff --git a/references/writer/printer/BUILD.cfg b/references/writer/printer/BUILD.cfg deleted file mode 100644 index 7459430e..00000000 --- a/references/writer/printer/BUILD.cfg +++ /dev/null @@ -1,3 +0,0 @@ -{ - "condition": "tint_build_glsl_writer" -} diff --git a/references/writer/printer/BUILD.cmake b/references/writer/printer/BUILD.cmake deleted file mode 100644 index 31486ddf..00000000 --- a/references/writer/printer/BUILD.cmake +++ /dev/null @@ -1,123 +0,0 @@ -# Copyright 2023 The Dawn & Tint Authors -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -################################################################################ -# File generated by 'tools/src/cmd/gen' using the template: -# tools/src/cmd/gen/build/BUILD.cmake.tmpl -# -# To regenerate run: './tools/run gen' -# -# Do not modify this file directly -################################################################################ - -if(TINT_BUILD_GLSL_WRITER) -################################################################################ -# Target: tint_lang_glsl_writer_printer -# Kind: lib -# Condition: TINT_BUILD_GLSL_WRITER -################################################################################ -tint_add_target(tint_lang_glsl_writer_printer lib - lang/glsl/writer/printer/printer.cc - lang/glsl/writer/printer/printer.h -) - -tint_target_add_dependencies(tint_lang_glsl_writer_printer lib - tint_api_common - tint_lang_core - tint_lang_core_constant - tint_lang_core_ir - tint_lang_core_type - tint_utils_containers - tint_utils_diagnostic - tint_utils_generator - tint_utils_ice - tint_utils_id - tint_utils_macros - tint_utils_math - tint_utils_memory - tint_utils_reflection - tint_utils_result - tint_utils_rtti - tint_utils_symbol - tint_utils_text - tint_utils_traits -) - -if(TINT_BUILD_GLSL_WRITER) - tint_target_add_dependencies(tint_lang_glsl_writer_printer lib - tint_lang_glsl_writer_common - ) -endif(TINT_BUILD_GLSL_WRITER) - -endif(TINT_BUILD_GLSL_WRITER) -if(TINT_BUILD_GLSL_WRITER) -################################################################################ -# Target: tint_lang_glsl_writer_printer_test -# Kind: test -# Condition: TINT_BUILD_GLSL_WRITER -################################################################################ -tint_add_target(tint_lang_glsl_writer_printer_test test - lang/glsl/writer/printer/constant_test.cc - lang/glsl/writer/printer/function_test.cc - lang/glsl/writer/printer/helper_test.h -) - -tint_target_add_dependencies(tint_lang_glsl_writer_printer_test test - tint_api_common - tint_lang_core - tint_lang_core_constant - tint_lang_core_intrinsic - tint_lang_core_ir - tint_lang_core_type - tint_lang_glsl_writer_raise - tint_utils_containers - tint_utils_diagnostic - tint_utils_ice - tint_utils_id - tint_utils_macros - tint_utils_math - tint_utils_memory - tint_utils_reflection - tint_utils_result - tint_utils_rtti - tint_utils_symbol - tint_utils_text - tint_utils_traits -) - -tint_target_add_external_dependencies(tint_lang_glsl_writer_printer_test test - "gtest" -) - -if(TINT_BUILD_GLSL_WRITER) - tint_target_add_dependencies(tint_lang_glsl_writer_printer_test test - tint_lang_glsl_writer_common - tint_lang_glsl_writer_printer - ) -endif(TINT_BUILD_GLSL_WRITER) - -endif(TINT_BUILD_GLSL_WRITER) \ No newline at end of file diff --git a/references/writer/printer/BUILD.gn b/references/writer/printer/BUILD.gn deleted file mode 100644 index 59f838a2..00000000 --- a/references/writer/printer/BUILD.gn +++ /dev/null @@ -1,117 +0,0 @@ -# Copyright 2023 The Dawn & Tint Authors -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -################################################################################ -# File generated by 'tools/src/cmd/gen' using the template: -# tools/src/cmd/gen/build/BUILD.gn.tmpl -# -# To regenerate run: './tools/run gen' -# -# Do not modify this file directly -################################################################################ - -import("../../../../../../scripts/tint_overrides_with_defaults.gni") - -import("${tint_src_dir}/tint.gni") - -if (tint_build_unittests || tint_build_benchmarks) { - import("//testing/test.gni") -} -if (tint_build_glsl_writer) { - libtint_source_set("printer") { - sources = [ - "printer.cc", - "printer.h", - ] - deps = [ - "${tint_src_dir}/api/common", - "${tint_src_dir}/lang/core", - "${tint_src_dir}/lang/core/constant", - "${tint_src_dir}/lang/core/ir", - "${tint_src_dir}/lang/core/type", - "${tint_src_dir}/utils/containers", - "${tint_src_dir}/utils/diagnostic", - "${tint_src_dir}/utils/generator", - "${tint_src_dir}/utils/ice", - "${tint_src_dir}/utils/id", - "${tint_src_dir}/utils/macros", - "${tint_src_dir}/utils/math", - "${tint_src_dir}/utils/memory", - "${tint_src_dir}/utils/reflection", - "${tint_src_dir}/utils/result", - "${tint_src_dir}/utils/rtti", - "${tint_src_dir}/utils/symbol", - "${tint_src_dir}/utils/text", - "${tint_src_dir}/utils/traits", - ] - - if (tint_build_glsl_writer) { - deps += [ "${tint_src_dir}/lang/glsl/writer/common" ] - } - } -} -if (tint_build_unittests) { - if (tint_build_glsl_writer) { - tint_unittests_source_set("unittests") { - sources = [ - "constant_test.cc", - "function_test.cc", - "helper_test.h", - ] - deps = [ - "${tint_src_dir}:gmock_and_gtest", - "${tint_src_dir}/api/common", - "${tint_src_dir}/lang/core", - "${tint_src_dir}/lang/core/constant", - "${tint_src_dir}/lang/core/intrinsic", - "${tint_src_dir}/lang/core/ir", - "${tint_src_dir}/lang/core/type", - "${tint_src_dir}/lang/glsl/writer/raise", - "${tint_src_dir}/utils/containers", - "${tint_src_dir}/utils/diagnostic", - "${tint_src_dir}/utils/ice", - "${tint_src_dir}/utils/id", - "${tint_src_dir}/utils/macros", - "${tint_src_dir}/utils/math", - "${tint_src_dir}/utils/memory", - "${tint_src_dir}/utils/reflection", - "${tint_src_dir}/utils/result", - "${tint_src_dir}/utils/rtti", - "${tint_src_dir}/utils/symbol", - "${tint_src_dir}/utils/text", - "${tint_src_dir}/utils/traits", - ] - - if (tint_build_glsl_writer) { - deps += [ - "${tint_src_dir}/lang/glsl/writer/common", - "${tint_src_dir}/lang/glsl/writer/printer", - ] - } - } - } -} diff --git a/references/writer/printer/constant_test.cc b/references/writer/printer/constant_test.cc deleted file mode 100644 index 55eb4db2..00000000 --- a/references/writer/printer/constant_test.cc +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2023 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/printer/helper_test.h" - -using namespace tint::core::number_suffixes; // NOLINT -using namespace tint::core::fluent_types; // NOLINT - -namespace tint::glsl::writer { -namespace { - -TEST_F(GlslPrinterTest, DISABLED_Constant_Bool_True) { - FAIL(); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/printer/function_test.cc b/references/writer/printer/function_test.cc deleted file mode 100644 index 763a051c..00000000 --- a/references/writer/printer/function_test.cc +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2023 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/printer/helper_test.h" - -namespace tint::glsl::writer { -namespace { - -TEST_F(GlslPrinterTest, Function_Empty) { - auto* func = b.Function("foo", ty.void_()); - func->Block()->Append(b.Return(func)); - - ASSERT_TRUE(Generate()) << err_ << output_; - EXPECT_EQ(output_, GlslHeader() + R"( -void foo() { -} -)"); -} - -} // namespace -} // namespace tint::glsl::writer diff --git a/references/writer/printer/helper_test.h b/references/writer/printer/helper_test.h deleted file mode 100644 index 2dbcd000..00000000 --- a/references/writer/printer/helper_test.h +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2023 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef SRC_TINT_LANG_GLSL_WRITER_PRINTER_HELPER_TEST_H_ -#define SRC_TINT_LANG_GLSL_WRITER_PRINTER_HELPER_TEST_H_ - -#include - -#include "gtest/gtest.h" -#include "src/tint/lang/core/ir/builder.h" -#include "src/tint/lang/core/ir/validator.h" -#include "src/tint/lang/glsl/writer/common/version.h" -#include "src/tint/lang/glsl/writer/printer/printer.h" -#include "src/tint/lang/glsl/writer/raise/raise.h" - -namespace tint::glsl::writer { - -/// Base helper class for testing the GLSL generator implementation. -template -class GlslPrinterTestHelperBase : public BASE { - public: - /// The test module. - core::ir::Module mod; - /// The test builder. - core::ir::Builder b{mod}; - /// The type manager. - core::type::Manager& ty{mod.Types()}; - /// The GLSL version - Version version{}; - - protected: - /// Validation errors - std::string err_; - - /// Generated GLSL - std::string output_; - - /// Run the writer on the IR module and validate the result. - /// @returns true if generation and validation succeeded - bool Generate() { - if (auto raised = Raise(mod); raised != Success) { - err_ = raised.Failure().reason.Str(); - return false; - } - - auto result = Print(mod, version); - if (result != Success) { - err_ = result.Failure().reason.Str(); - return false; - } - output_ = result.Get(); - - return true; - } - - /// @returns the metal header string - std::string GlslHeader() const { - std::stringstream ver; - ver << "#version " << version.major_version << version.minor_version << "0"; - if (version.IsES()) { - ver << " es"; - } - ver << "\n"; - return ver.str(); - } -}; - -/// Test class -using GlslPrinterTest = GlslPrinterTestHelperBase; - -/// Test param class -template -using GlslPrinterTestWithParam = GlslPrinterTestHelperBase>; - -} // namespace tint::glsl::writer - -#endif // SRC_TINT_LANG_GLSL_WRITER_PRINTER_HELPER_TEST_H_ diff --git a/references/writer/printer/printer.cc b/references/writer/printer/printer.cc deleted file mode 100644 index dfee449b..00000000 --- a/references/writer/printer/printer.cc +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright 2023 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/printer/printer.h" - -#include -#include - -#include "src/tint/lang/core/ir/function.h" -#include "src/tint/lang/core/ir/module.h" -#include "src/tint/lang/core/ir/return.h" -#include "src/tint/lang/core/ir/unreachable.h" -#include "src/tint/lang/core/ir/validator.h" -#include "src/tint/lang/glsl/writer/common/version.h" -#include "src/tint/utils/generator/text_generator.h" -#include "src/tint/utils/macros/scoped_assignment.h" -#include "src/tint/utils/rtti/switch.h" - -using namespace tint::core::fluent_types; // NOLINT - -namespace tint::glsl::writer { -namespace { - -/// PIMPL class for the MSL generator -class Printer : public tint::TextGenerator { - public: - /// Constructor - /// @param module the Tint IR module to generate - explicit Printer(const core::ir::Module& module) : ir_(module) {} - - /// @param version the GLSL version information - /// @returns the generated GLSL shader - tint::Result Generate(const Version& version) { - auto valid = core::ir::ValidateAndDumpIfNeeded(ir_, "GLSL writer"); - if (valid != Success) { - return std::move(valid.Failure()); - } - - { - TINT_SCOPED_ASSIGNMENT(current_buffer_, &preamble_buffer_); - - auto out = Line(); - out << "#version " << version.major_version << version.minor_version << "0"; - if (version.IsES()) { - out << " es"; - } - } - - // Emit module-scope declarations. - EmitBlockInstructions(ir_.root_block); - - // Emit functions. - for (auto& func : ir_.functions) { - EmitFunction(func); - } - - StringStream ss; - ss << preamble_buffer_.String() << '\n' << main_buffer_.String(); - return ss.str(); - } - - private: - const core::ir::Module& ir_; - - /// The buffer holding preamble text - TextBuffer preamble_buffer_; - - /// The current function being emitted - const core::ir::Function* current_function_ = nullptr; - /// The current block being emitted - const core::ir::Block* current_block_ = nullptr; - - /// Emit the function - /// @param func the function to emit - void EmitFunction(const core::ir::Function* func) { - TINT_SCOPED_ASSIGNMENT(current_function_, func); - - { - auto out = Line(); - - // TODO(dsinclair): Emit function stage if any - // TODO(dsinclair): Handle return type attributes - - EmitType(out, func->ReturnType()); - out << " " << ir_.NameOf(func).Name() << "() {"; - - // TODO(dsinclair): Emit Function parameters - } - { - ScopedIndent si(current_buffer_); - EmitBlock(func->Block()); - } - - Line() << "}"; - } - - /// Emit a block - /// @param block the block to emit - void EmitBlock(const core::ir::Block* block) { - // TODO(dsinclair): Handle marking inline - // MarkInlinable(block); - - EmitBlockInstructions(block); - } - - /// Emit the instructions in a block - /// @param block the block with the instructions to emit - void EmitBlockInstructions(const core::ir::Block* block) { - TINT_SCOPED_ASSIGNMENT(current_block_, block); - - for (auto* inst : *block) { - Switch( - inst, // - [&](const core::ir::Return* r) { EmitReturn(r); }, // - [&](const core::ir::Unreachable*) { EmitUnreachable(); }, // - TINT_ICE_ON_NO_MATCH); - } - } - - /// Emit a type - /// @param out the stream to emit too - /// @param ty the type to emit - void EmitType(StringStream& out, [[maybe_unused]] const core::type::Type* ty) { out << "void"; } - - /// Emit a return instruction - /// @param r the return instruction - void EmitReturn(const core::ir::Return* r) { - // If this return has no arguments and the current block is for the function which is - // being returned, skip the return. - if (current_block_ == current_function_->Block() && r->Args().IsEmpty()) { - return; - } - - auto out = Line(); - out << "return"; - // TODO(dsinclair): Handle return args - // if (!r->Args().IsEmpty()) { - // out << " " << Expr(r->Args().Front()); - // } - out << ";"; - } - - /// Emit an unreachable instruction - void EmitUnreachable() { Line() << "/* unreachable */"; } -}; -} // namespace - -Result Print(const core::ir::Module& module, const Version& version) { - return Printer{module}.Generate(version); -} - -} // namespace tint::glsl::writer diff --git a/references/writer/printer/printer.h b/references/writer/printer/printer.h deleted file mode 100644 index 6f75b39c..00000000 --- a/references/writer/printer/printer.h +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2023 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef SRC_TINT_LANG_GLSL_WRITER_PRINTER_PRINTER_H_ -#define SRC_TINT_LANG_GLSL_WRITER_PRINTER_PRINTER_H_ - -#include - -#include "src/tint/utils/result/result.h" - -// Forward declarations -namespace tint::core::ir { -class Module; -} // namespace tint::core::ir -namespace tint::glsl::writer { -struct Version; -} // namespace tint::glsl::writer - -namespace tint::glsl::writer { - -/// @returns the generated GLSL shader on success, or failure -/// @param module the Tint IR module to generate -/// @param version the GLSL version information -Result Print(const core::ir::Module& module, const Version& version); - -} // namespace tint::glsl::writer - -#endif // SRC_TINT_LANG_GLSL_WRITER_PRINTER_PRINTER_H_ diff --git a/references/writer/raise/BUILD.bazel b/references/writer/raise/BUILD.bazel deleted file mode 100644 index ab458ab8..00000000 --- a/references/writer/raise/BUILD.bazel +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright 2023 The Dawn & Tint Authors -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -################################################################################ -# File generated by 'tools/src/cmd/gen' using the template: -# tools/src/cmd/gen/build/BUILD.bazel.tmpl -# -# To regenerate run: './tools/run gen' -# -# Do not modify this file directly -################################################################################ - -load("//src/tint:flags.bzl", "COPTS") -load("@bazel_skylib//lib:selects.bzl", "selects") -cc_library( - name = "raise", - srcs = [ - "raise.cc", - ], - hdrs = [ - "raise.h", - ], - deps = [ - "//src/tint/utils/containers", - "//src/tint/utils/diagnostic", - "//src/tint/utils/ice", - "//src/tint/utils/macros", - "//src/tint/utils/math", - "//src/tint/utils/memory", - "//src/tint/utils/result", - "//src/tint/utils/rtti", - "//src/tint/utils/text", - "//src/tint/utils/traits", - ], - copts = COPTS, - visibility = ["//visibility:public"], -) - diff --git a/references/writer/raise/BUILD.cmake b/references/writer/raise/BUILD.cmake deleted file mode 100644 index 90ff6d39..00000000 --- a/references/writer/raise/BUILD.cmake +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright 2023 The Dawn & Tint Authors -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -################################################################################ -# File generated by 'tools/src/cmd/gen' using the template: -# tools/src/cmd/gen/build/BUILD.cmake.tmpl -# -# To regenerate run: './tools/run gen' -# -# Do not modify this file directly -################################################################################ - -################################################################################ -# Target: tint_lang_glsl_writer_raise -# Kind: lib -################################################################################ -tint_add_target(tint_lang_glsl_writer_raise lib - lang/glsl/writer/raise/raise.cc - lang/glsl/writer/raise/raise.h -) - -tint_target_add_dependencies(tint_lang_glsl_writer_raise lib - tint_utils_containers - tint_utils_diagnostic - tint_utils_ice - tint_utils_macros - tint_utils_math - tint_utils_memory - tint_utils_result - tint_utils_rtti - tint_utils_text - tint_utils_traits -) diff --git a/references/writer/raise/BUILD.gn b/references/writer/raise/BUILD.gn deleted file mode 100644 index bd20a514..00000000 --- a/references/writer/raise/BUILD.gn +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright 2023 The Dawn & Tint Authors -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -################################################################################ -# File generated by 'tools/src/cmd/gen' using the template: -# tools/src/cmd/gen/build/BUILD.gn.tmpl -# -# To regenerate run: './tools/run gen' -# -# Do not modify this file directly -################################################################################ - -import("../../../../../../scripts/tint_overrides_with_defaults.gni") - -import("${tint_src_dir}/tint.gni") - -libtint_source_set("raise") { - sources = [ - "raise.cc", - "raise.h", - ] - deps = [ - "${tint_src_dir}/utils/containers", - "${tint_src_dir}/utils/diagnostic", - "${tint_src_dir}/utils/ice", - "${tint_src_dir}/utils/macros", - "${tint_src_dir}/utils/math", - "${tint_src_dir}/utils/memory", - "${tint_src_dir}/utils/result", - "${tint_src_dir}/utils/rtti", - "${tint_src_dir}/utils/text", - "${tint_src_dir}/utils/traits", - ] -} diff --git a/references/writer/raise/raise.cc b/references/writer/raise/raise.cc deleted file mode 100644 index 1985ec7e..00000000 --- a/references/writer/raise/raise.cc +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2023 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/raise/raise.h" - -namespace tint::glsl::writer { - -Result Raise(core::ir::Module&) { - // #define RUN_TRANSFORM(name) - // do { - // auto result = core::ir::transform::name(module); - // if (!result) { - // return result; - // } - // } while (false) - - return Success; -} - -} // namespace tint::glsl::writer diff --git a/references/writer/raise/raise.h b/references/writer/raise/raise.h deleted file mode 100644 index 846790dc..00000000 --- a/references/writer/raise/raise.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2023 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef SRC_TINT_LANG_GLSL_WRITER_RAISE_RAISE_H_ -#define SRC_TINT_LANG_GLSL_WRITER_RAISE_RAISE_H_ - -#include "src/tint/utils/result/result.h" - -// Forward declarations -namespace tint::core::ir { -class Module; -} // namespace tint::core::ir - -namespace tint::glsl::writer { - -/// Raise a core IR module to the MSL dialect of the IR. -/// @param mod the core IR module to raise to MSL dialect -/// @returns success or failure -Result Raise(core::ir::Module& mod); - -} // namespace tint::glsl::writer - -#endif // SRC_TINT_LANG_GLSL_WRITER_RAISE_RAISE_H_ diff --git a/references/writer/writer.cc b/references/writer/writer.cc deleted file mode 100644 index 17bd7efd..00000000 --- a/references/writer/writer.cc +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "src/tint/lang/glsl/writer/writer.h" - -#include -#include - -#include "src/tint/lang/glsl/writer/ast_printer/ast_printer.h" -#include "src/tint/lang/glsl/writer/printer/printer.h" -#include "src/tint/lang/glsl/writer/raise/raise.h" - -namespace tint::glsl::writer { - -Result Generate(core::ir::Module& ir, const Options& options, const std::string&) { - Output output; - - // Raise from core-dialect to GLSL-dialect. - if (auto res = Raise(ir); res != Success) { - return res.Failure(); - } - - // Generate the GLSL code. - auto result = Print(ir, options.version); - if (result != Success) { - return result.Failure(); - } - output.glsl = result.Get(); - - return output; -} - -Result Generate(const Program& program, - const Options& options, - const std::string& entry_point) { - if (!program.IsValid()) { - return Failure{program.Diagnostics()}; - } - - Output output; - - // Sanitize the program. - auto sanitized_result = Sanitize(program, options, entry_point); - if (!sanitized_result.program.IsValid()) { - return Failure{sanitized_result.program.Diagnostics()}; - } - - // Generate the GLSL code. - auto impl = std::make_unique(sanitized_result.program, options.version); - if (!impl->Generate()) { - return Failure{impl->Diagnostics()}; - } - - output.glsl = impl->Result(); - - return output; -} - -} // namespace tint::glsl::writer diff --git a/references/writer/writer.h b/references/writer/writer.h deleted file mode 100644 index 299ee080..00000000 --- a/references/writer/writer.h +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2021 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef SRC_TINT_LANG_GLSL_WRITER_WRITER_H_ -#define SRC_TINT_LANG_GLSL_WRITER_WRITER_H_ - -#include - -#include "src/tint/lang/glsl/writer/common/options.h" -#include "src/tint/lang/glsl/writer/output.h" -#include "src/tint/utils/diagnostic/diagnostic.h" -#include "src/tint/utils/result/result.h" - -// Forward declarations -namespace tint { -class Program; -} // namespace tint -namespace tint::core::ir { -class Module; -} // namespace tint::core::ir - -namespace tint::glsl::writer { - -/// Generate GLSL for a program, according to a set of configuration options. -/// The result will contain the GLSL and supplementary information, or failure. -/// information. -/// @param ir the IR module to translate to GLSL -/// @param options the configuration options to use when generating GLSL -/// @param entry_point the entry point to generate GLSL for -/// @returns the resulting GLSL and supplementary information, or failure -Result Generate(core::ir::Module& ir, - const Options& options, - const std::string& entry_point); - -/// Generate GLSL for a program, according to a set of configuration options. -/// The result will contain the GLSL and supplementary information, or failure. -/// information. -/// @param program the program to translate to GLSL -/// @param options the configuration options to use when generating GLSL -/// @param entry_point the entry point to generate GLSL for -/// @returns the resulting GLSL and supplementary information, or failure -Result Generate(const Program& program, - const Options& options, - const std::string& entry_point); - -} // namespace tint::glsl::writer - -#endif // SRC_TINT_LANG_GLSL_WRITER_WRITER_H_ diff --git a/references/writer/writer_ast_fuzz.cc b/references/writer/writer_ast_fuzz.cc deleted file mode 100644 index e555a758..00000000 --- a/references/writer/writer_ast_fuzz.cc +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2024 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// GEN_BUILD:CONDITION(tint_build_wgsl_reader) - -#include "src/tint/cmd/fuzz/wgsl/fuzz.h" -#include "src/tint/lang/glsl/writer/writer.h" -#include "src/tint/lang/wgsl/ast/module.h" -#include "src/tint/lang/wgsl/inspector/inspector.h" - -namespace tint::glsl::writer { -namespace { - -bool CanRun(const tint::Program& program, const Options& options) { - if (program.AST().HasOverrides()) { - return false; // Overrides are not supported. - } - - // Excessive values can cause OOM / timeouts in the PadStructs transform. - static constexpr uint32_t kMaxOffset = 0x1000; - - if (options.first_instance_offset && options.first_instance_offset > kMaxOffset) { - return false; - } - - if (options.first_vertex_offset && options.first_vertex_offset > kMaxOffset) { - return false; - } - - if (options.depth_range_offsets) { - if (options.depth_range_offsets->max > kMaxOffset || - options.depth_range_offsets->min > kMaxOffset) { - return false; - } - } - - return true; -} - -void ASTFuzzer(const tint::Program& program, const Options& options) { - if (!CanRun(program, options)) { - return; - } - - auto inspector = tint::inspector::Inspector(program); - auto entrypoints = inspector.GetEntryPoints(); - - // Test all of the entry points as GLSL requires specifying which one to generate. - for (const auto& ep : entrypoints) { - [[maybe_unused]] auto res = tint::glsl::writer::Generate(program, options, ep.name); - } -} - -} // namespace -} // namespace tint::glsl::writer - -TINT_WGSL_PROGRAM_FUZZER(tint::glsl::writer::ASTFuzzer); diff --git a/references/writer/writer_bench.cc b/references/writer/writer_bench.cc deleted file mode 100644 index 8505dac2..00000000 --- a/references/writer/writer_bench.cc +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2022 The Dawn & Tint Authors -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include - -#include "src/tint/cmd/bench/bench.h" -#include "src/tint/lang/glsl/writer/writer.h" -#include "src/tint/lang/wgsl/ast/identifier.h" -#include "src/tint/lang/wgsl/ast/module.h" - -namespace tint::glsl::writer { -namespace { - -void GenerateGLSL(benchmark::State& state, std::string input_name) { - auto res = bench::LoadProgram(input_name); - if (res != Success) { - state.SkipWithError(res.Failure().reason.Str()); - return; - } - auto& program = res->program; - std::vector entry_points; - for (auto& fn : program.AST().Functions()) { - if (fn->IsEntryPoint()) { - entry_points.emplace_back(fn->name->symbol.Name()); - } - } - - for (auto _ : state) { - for (auto& ep : entry_points) { - auto gen_res = Generate(program, {}, ep); - if (gen_res != Success) { - state.SkipWithError(gen_res.Failure().reason.Str()); - } - } - } -} - -TINT_BENCHMARK_PROGRAMS(GenerateGLSL); - -} // namespace -} // namespace tint::glsl::writer diff --git a/src/reduce_bench.cpp b/src/reduce_bench.cpp index c0349037..5cac227a 100644 --- a/src/reduce_bench.cpp +++ b/src/reduce_bench.cpp @@ -58,6 +58,15 @@ double time_call(F&& f, int iters) return std::chrono::duration(t1 - t0).count() / iters; } +// 扣除启动/同步开销后的内核吞吐(GB/s)。overhead 用同算子小矩阵(内核≈0)测得。 +// 目的:去除逐调用 submit+wait 的固定开销,还原真实内核带宽,判断跨步访问是否真瓶颈。 +double kernel_gbps(double bytes, double shape_s, double overhead_s) +{ + const double kt = shape_s - overhead_s; + if (kt <= 0.0) return 0.0; + return bytes / kt / 1e9; +} + void bench_shape(GpuEngine& gpu, std::size_t rows, std::size_t cols, int iters) { const double bytes = static_cast(rows) * cols * sizeof(Scalar); @@ -69,10 +78,15 @@ void bench_shape(GpuEngine& gpu, std::size_t rows, std::size_t cols, int iters) for (auto& v : x.cpu_matrix().span()) v = dist(rng); const Tensor xt = x; + // ── 启动/同步开销:小矩阵上的归约(内核时间≈0),同算子测一遍 ── + Tensor xoh = Tensor::cpu(256, 256); + for (auto& v : xoh.cpu_matrix().span()) v = 1.0f; + const Tensor xoht = xoh; + const double overhead = time_call([&]{ return gpu.col_reduce_sum(xoht); }, iters); + // ── 行求和:旧(reduce_gpu) vs 新(融合 row_reduce_sum)── // 新路径用 compute_reduce(输出归约向量 (rows,1)),与旧路径输出对齐, // 避免 compute() 把归约结果广播写到整个 (rows,cols)(多一次整矩阵写)。 - // leaf() 走表达式重载 → 归约**指令**,命中融合 shader。 const double old_rs = time_call([&]{ return gpu.row_reduce_sum(xt); }, iters); const double new_rs = time_call([&]{ return dsl::compute_reduce(gpu, dsl::row_reduce_sum(dsl::leaf(xt)), rows, cols); }, iters); @@ -82,15 +96,43 @@ void bench_shape(GpuEngine& gpu, std::size_t rows, std::size_t cols, int iters) const double new_cs = time_call([&]{ return dsl::compute_reduce(gpu, dsl::col_reduce_sum(dsl::leaf(xt)), rows, cols); }, iters); - std::printf("shape %zux%-8zu (%.0f MB)\n", rows, cols, mbytes); - std::printf(" 行求和: 旧 %.2f GB/s 新 %.2f GB/s 加速 %s\n", - old_rs > 0 ? bytes / old_rs / 1e9 : 0.0, - new_rs > 0 ? bytes / new_rs / 1e9 : 0.0, - (old_rs > 0 && new_rs > 0) ? (std::to_string(old_rs / new_rs) + "x").c_str() : "-"); - std::printf(" 列求和: 旧 %.2f GB/s 新 %.2f GB/s 加速 %s\n", - old_cs > 0 ? bytes / old_cs / 1e9 : 0.0, - new_cs > 0 ? bytes / new_cs / 1e9 : 0.0, - (old_cs > 0 && new_cs > 0) ? (std::to_string(old_cs / new_cs) + "x").c_str() : "-"); + std::printf("shape %zux%-8zu (%.0f MB) 启动开销≈%.0fus\n", + rows, cols, mbytes, overhead * 1e6); + const double old_rs_g = kernel_gbps(bytes, old_rs, overhead); + const double new_rs_g = kernel_gbps(bytes, new_rs, overhead); + const double old_cs_g = kernel_gbps(bytes, old_cs, overhead); + const double new_cs_g = kernel_gbps(bytes, new_cs, overhead); + std::printf(" 行求和(内核): 旧 %7.1f GB/s 新 %7.1f GB/s 加速 %s\n", + old_rs_g, new_rs_g, + (old_rs_g > 0 && new_rs_g > 0) ? (std::to_string(old_rs / new_rs) + "x").c_str() : "-"); + std::printf(" 列求和(内核): 旧 %7.1f GB/s 新 %7.1f GB/s 加速 %s\n", + old_cs_g, new_cs_g, + (old_cs_g > 0 && new_cs_g > 0) ? (std::to_string(old_cs / new_cs) + "x").c_str() : "-"); + std::printf(" 跨步惩罚: 列/行(新) = %s (1.0=无惩罚;<1=列跨步访问更慢)\n", + (new_rs_g > 0 && new_cs_g > 0) ? (std::to_string(new_cs_g / new_rs_g) + "x").c_str() : "-"); +} + +// 纯内核基准:数据一次性上传到显存后驻留,反复调 reduce_gpu(不再上传)。 +// 目的:去掉 CPU→GPU 上传(PRIME/PCIe)对测量的污染,还原真实内核带宽, +// 判断列归约跨步访问的纯内核惩罚到底多大(决定 tile 化是否值得)。 +void bench_pure_kernel(GpuBackend& backend, std::size_t rows, std::size_t cols, int iters) +{ + std::mt19937 rng(777); + std::uniform_real_distribution dist(-1.0f, 1.0f); + nn::Matrix m(rows, cols); + for (auto& v : m.span()) v = dist(rng); + auto gt_r = nn::GpuTensor::from_matrix(m, backend); + if (!gt_r) { std::printf("[ERR] 上传到显存失败: %s\n", gt_r.error().message.c_str()); return; } + nn::GpuTensor gt = std::move(*gt_r); + const double bytes = static_cast(rows) * cols * sizeof(Scalar); + + const double t_row = time_call([&]{ return backend.reduce_gpu(gt, 0u, 0u); }, iters); // 行,sum + const double t_col = time_call([&]{ return backend.reduce_gpu(gt, 1u, 0u); }, iters); // 列,sum + const double row_g = t_row > 0 ? bytes / t_row / 1e9 : 0.0; + const double col_g = t_col > 0 ? bytes / t_col / 1e9 : 0.0; + std::printf(" 纯内核(数据驻留): 行归约 %7.1f GB/s 列归约 %7.1f GB/s 列/行 = %s\n", + row_g, col_g, + (row_g > 0 && col_g > 0) ? (std::to_string(col_g / row_g) + "x").c_str() : "-"); } } // namespace @@ -126,6 +168,7 @@ int main(int argc, char** argv) // 迭代次数随规模缩放,避免总耗时过长 const int iters = (r * c >= 256ull * 1024 * 1024) ? 3 : 10; bench_shape(gpu, r, c, iters); + bench_pure_kernel(backend, r, c, iters); } return 0; } From eeaa5afe7eb4cfa7ec78cf00f0b9c4c7f4a0eadc Mon Sep 17 00:00:00 2001 From: EthanPeng-2048 Date: Tue, 25 Aug 2026 02:04:27 +0800 Subject: [PATCH 8/9] =?UTF-8?q?=E6=97=A0=E7=94=A8=E4=B8=9C=E8=A5=BF?= =?UTF-8?q?=E5=88=A0=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 39 +++--- src/attn_debug.cpp | 317 ------------------------------------------- src/matmul_probe.cpp | 211 ---------------------------- 3 files changed, 21 insertions(+), 546 deletions(-) delete mode 100644 src/attn_debug.cpp delete mode 100644 src/matmul_probe.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 829cea73..20572f58 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,25 +91,23 @@ option(NN_ENABLE_TESTS "Build test/verification executables" OFF) set(NN_APP_TARGETS "") set(NN_TEST_TARGETS "") -# ── MNIST 目标 ────────────────────────────────────────────────────────────── +# 收集实际构建的目标,供 GPU/CUDA 统一启用(避免引用未定义目标) +set(NN_APP_TARGETS "") +set(NN_TEST_TARGETS "") +set(NN_BENCH_TARGETS "") # 性能基准(仅测试构建,手工运行,不注册 ctest) + +# ── MNIST 目标(标准构建核心应用) ─────────────────────────────────────── nn_add_executable(mnist_train src/mnist_train.cpp) nn_add_executable(mnist_infer src/mnist_infer.cpp) -nn_add_executable(mnist_bench src/mnist_bench.cpp) -list(APPEND NN_APP_TARGETS mnist_train mnist_infer mnist_bench) +list(APPEND NN_APP_TARGETS mnist_train mnist_infer) -# ── 文本目标(GPT 训练/推理 + BPE 分词器训练/推理) ─────────────────── +# ── 文本目标(GPT 训练/推理 + BPE 分词器训练/推理,标准构建核心应用) ── nn_add_executable(text_train src/text_train.cpp) nn_add_executable(text_infer src/text_infer.cpp) nn_add_executable(tokenizer_train src/tokenizer_train.cpp) nn_add_executable(tokenizer_infer src/tokenizer_infer.cpp) list(APPEND NN_APP_TARGETS text_train text_infer tokenizer_train tokenizer_infer) -# ── 计算性能基准工具 ───────────────────────────────────────────────────── -nn_add_executable(compute_bench src/compute_bench.cpp) -nn_add_executable(bench_thresholds src/bench_thresholds.cpp) -nn_add_executable(reduce_bench src/reduce_bench.cpp) -list(APPEND NN_APP_TARGETS compute_bench bench_thresholds reduce_bench) - # ── 测试 / 验证目标(默认关闭,NN_ENABLE_TESTS=ON 时构建)───────────── if(NN_ENABLE_TESTS) @@ -193,14 +191,6 @@ if(NN_ENABLE_TESTS) nn_add_executable(softmax_gradcheck src/softmax_gradcheck.cpp) list(APPEND NN_TEST_TARGETS softmax_gradcheck) - # ── 注意力反向逐级调试探针(对比暴力 Matrix 参考与引擎原语) ──────────── - nn_add_executable(attn_debug src/attn_debug.cpp) - list(APPEND NN_TEST_TARGETS attn_debug) - - # ── batched_matmul 原语探针(四种转置组合 vs 手工参考) ─────────────────── - nn_add_executable(matmul_probe src/matmul_probe.cpp) - list(APPEND NN_TEST_TARGETS matmul_probe) - # ── M4 matmul 融合原语测试(bmm_reduce/bmm_denom/bmm_apply,CPU+GPU) ── # ── M6 两趟式注意力反向原语测试(bmm_q_backward/bmm_kv_backward,CPU+GPU) # ── M5 列式 softmax 融合原语测试(col_softmax_denom/col_softmax_sparse_forward) @@ -211,6 +201,13 @@ if(NN_ENABLE_TESTS) nn_add_executable(ce_fusion_test src/ce_fusion_test.cpp) list(APPEND NN_TEST_TARGETS ce_fusion_test) + # ── 性能基准工具(仅测试构建;手工运行,不注册 ctest) ── + nn_add_executable(mnist_bench src/mnist_bench.cpp) + nn_add_executable(compute_bench src/compute_bench.cpp) + nn_add_executable(bench_thresholds src/bench_thresholds.cpp) + nn_add_executable(reduce_bench src/reduce_bench.cpp) + list(APPEND NN_BENCH_TARGETS mnist_bench compute_bench bench_thresholds reduce_bench) + endif() # ── 测试目标统一处理:输出到 <默认可执行目录>/test,并注册到 ctest ────────── @@ -229,6 +226,11 @@ if(NN_ENABLE_TESTS) RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/test) add_test(NAME ${TARGET} COMMAND ${TARGET}) endforeach() + # 基准工具输出到 test/,但不注册 ctest(手工运行) + foreach(TARGET ${NN_BENCH_TARGETS}) + set_target_properties(${TARGET} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/test) + endforeach() endif() # ── Vulkan GPU 加速(可选) ──────────────────────────────────────────────── @@ -354,6 +356,7 @@ if(Vulkan_FOUND) set(NN_GPU_TARGETS ${NN_APP_TARGETS}) if(NN_ENABLE_TESTS) list(APPEND NN_GPU_TARGETS ${NN_TEST_TARGETS}) + list(APPEND NN_GPU_TARGETS ${NN_BENCH_TARGETS}) endif() foreach(TARGET ${NN_GPU_TARGETS}) diff --git a/src/attn_debug.cpp b/src/attn_debug.cpp deleted file mode 100644 index d40674b7..00000000 --- a/src/attn_debug.cpp +++ /dev/null @@ -1,317 +0,0 @@ -// ── 注意力反向逐级调试探针 ──────────────────────────────────────────────── -// -// 目的:attn_gradcheck 显示 wq/wk FAIL 但 wv/wo OK。本程序用「暴力 Matrix -// 参考」完整复现 CausalSelfAttention 的 forward + backward,并逐项对比 -// 引擎 batched_matmul / Softmax 原语计算出的中间量与参考,定位第一个 -// 出错的中间量(grad_S / grad_A / grad_Q / grad_K / batched_matmul 原语)。 -// -// 用法:attn_debug -// ───────────────────────────────────────────────────────────────────────── - -#include -#include - -#include -#include -#include -#include - -using nn::Scalar; -using nn::Matrix; -using nn::Tensor; -using nn::ComputeEngine; -using nn::CausalSelfAttention; -using nn::Softmax; -using nn::PosEncodingType; - -namespace { - -// ── 简单矩阵代数辅助(行主序 (rows, cols))─────────────────────────────── -Matrix mmul(const Matrix& A, const Matrix& B) -{ - Matrix C(A.rows(), B.cols()); - for (std::size_t i = 0; i < A.rows(); ++i) - for (std::size_t j = 0; j < B.cols(); ++j) - { - Scalar s{0}; - for (std::size_t k = 0; k < A.cols(); ++k) - s += A.at_unchecked(i, k) * B.at_unchecked(k, j); - C.set_value_unchecked(i, j, s); - } - return C; -} - -Matrix mtrans(const Matrix& A) -{ - Matrix T(A.cols(), A.rows()); - for (std::size_t i = 0; i < A.rows(); ++i) - for (std::size_t j = 0; j < A.cols(); ++j) - T.set_value_unchecked(j, i, A.at_unchecked(i, j)); - return T; -} - -Matrix madd(const Matrix& A, const Matrix& B) -{ - Matrix C = A; - for (std::size_t i = 0; i < C.size(); ++i) - C.span()[i] += B.span()[i]; - return C; -} - -// 行偏置广播加:bias (rows,1) 逐行加到 A (rows, cols) -Matrix madd_bias(const Matrix& A, const Matrix& B) -{ - Matrix C = A; - for (std::size_t i = 0; i < A.rows(); ++i) - { - const Scalar b = B.at_unchecked(i, 0); - for (std::size_t j = 0; j < A.cols(); ++j) - C.set_value_unchecked(i, j, C.at_unchecked(i, j) + b); - } - return C; -} - -Matrix mscale(const Matrix& A, Scalar s) -{ - Matrix C = A; - for (std::size_t i = 0; i < C.size(); ++i) - C.span()[i] *= s; - return C; -} - -Matrix rows(const Matrix& A, std::size_t r0, std::size_t n) -{ - Matrix R(n, A.cols()); - for (std::size_t i = 0; i < n; ++i) - for (std::size_t j = 0; j < A.cols(); ++j) - R.set_value_unchecked(i, j, A.at_unchecked(r0 + i, j)); - return R; -} - -// 逐行 softmax(含 -inf 处理) -Matrix softmax_rows(const Matrix& S) -{ - Matrix A(S.rows(), S.cols()); - for (std::size_t i = 0; i < S.rows(); ++i) - { - Scalar mx = -std::numeric_limits::infinity(); - for (std::size_t j = 0; j < S.cols(); ++j) - mx = std::max(mx, S.at_unchecked(i, j)); - Scalar sum{0}; - for (std::size_t j = 0; j < S.cols(); ++j) - { - Scalar e = std::exp(S.at_unchecked(i, j) - mx); - A.set_value_unchecked(i, j, e); - sum += e; - } - for (std::size_t j = 0; j < S.cols(); ++j) - A.set_value_unchecked(i, j, A.at_unchecked(i, j) / sum); - } - return A; -} - -// 逐行 softmax 反向: dS = A ⊙ (gA - row_dot) -Matrix softmax_bwd(const Matrix& A, const Matrix& gA) -{ - Matrix dS(A.rows(), A.cols()); - for (std::size_t i = 0; i < A.rows(); ++i) - { - Scalar dot{0}; - for (std::size_t j = 0; j < A.cols(); ++j) - dot += A.at_unchecked(i, j) * gA.at_unchecked(i, j); - for (std::size_t j = 0; j < A.cols(); ++j) - dS.set_value_unchecked(i, j, - A.at_unchecked(i, j) * (gA.at_unchecked(i, j) - dot)); - } - return dS; -} - -void cmp(const std::string& name, const Matrix& got, const Matrix& ref) -{ - bool ok = true; - Scalar max_err{0}; - std::size_t bad = 0; - for (std::size_t i = 0; i < got.rows(); ++i) - for (std::size_t j = 0; j < got.cols(); ++j) - { - const Scalar e = std::fabs(got.at_unchecked(i, j) - ref.at_unchecked(i, j)); - if (e > max_err) max_err = e; - if (e > Scalar{1e-4}) - { - ok = false; - if (bad < 6) - std::printf(" [%s] (%zu,%zu) got=%.6f ref=%.6f err=%.3e\n", - name.c_str(), i, j, - static_cast(got.at_unchecked(i, j)), - static_cast(ref.at_unchecked(i, j)), - static_cast(e)); - ++bad; - } - } - std::cout << " " << name << ": " << (ok ? "OK" : "FAIL") - << " max_err=" << max_err << " bad=" << bad << "\n"; -} - -} // namespace - -int main() -{ - nn::cli::EngineConfig ecfg; - auto engine_res = nn::cli::create_engine(ecfg, std::cout); - if (!engine_res) { std::cerr << "引擎创建失败: " << engine_res.error().message << "\n"; return 1; } - auto engine = std::move(*engine_res); - ComputeEngine& eng = *engine; - - const std::size_t d_model = 16; - const std::size_t H = 2; - const std::size_t dk = 8; - const std::size_t seq = 8; - const std::size_t batch = 1; - const Scalar scale = Scalar{1} / std::sqrt(static_cast(dk)); - - std::cout << "========================================\n"; - std::cout << " Attention 反向逐级调试 (d_model=16 H=2 seq=8 batch=1)\n"; - std::cout << "========================================\n"; - - CausalSelfAttention attn(eng, d_model, H, seq, seq, PosEncodingType::Learned); - - std::mt19937_64 rng(123); - std::uniform_real_distribution dist(-1, 1); - const std::size_t total = seq * batch; - Matrix x_m(d_model, total), go_m(d_model, total); - for (std::size_t i = 0; i < x_m.size(); ++i) x_m.span()[i] = dist(rng); - for (std::size_t i = 0; i < go_m.size(); ++i) go_m.span()[i] = dist(rng); - auto x = eng.from_matrix(x_m); - auto go = eng.from_matrix(go_m); - - // ── 提取权重 ── - auto params = attn.parameters(); - Matrix Wq = *eng.to_matrix(params[0]); // (16,16) - Matrix bq = *eng.to_matrix(params[1]); // (16,1) - Matrix Wk = *eng.to_matrix(params[2]); - Matrix bk = *eng.to_matrix(params[3]); - Matrix Wv = *eng.to_matrix(params[4]); - Matrix bv = *eng.to_matrix(params[5]); - Matrix Wo = *eng.to_matrix(params[6]); - Matrix bo = *eng.to_matrix(params[7]); - - // ── 参考 forward ── - Matrix Q = madd_bias(mmul(Wq, x_m), bq); - Matrix K = madd_bias(mmul(Wk, x_m), bk); - Matrix V = madd_bias(mmul(Wv, x_m), bv); - std::vector Ah(H, Matrix(seq, seq)); - for (std::size_t h = 0; h < H; ++h) - { - Matrix Qh = rows(Q, h * dk, dk); - Matrix Kh = rows(K, h * dk, dk); - Matrix Sh = mscale(mmul(mtrans(Qh), Kh), scale); - // 因果掩码:j > i → -inf - for (std::size_t i = 0; i < seq; ++i) - for (std::size_t j = i + 1; j < seq; ++j) - Sh.set_value_unchecked(i, j, -std::numeric_limits::infinity()); - Ah[h] = softmax_rows(Sh); - if (h == 0) - { - std::cout << " [ref] head0 Sh (masked) col0..7:\n"; - for (std::size_t i = 0; i < seq; ++i) - { - std::cout << " row" << i << ":"; - for (std::size_t j = 0; j < seq; ++j) - std::printf(" %9.4f", static_cast(Sh.at_unchecked(i, j))); - std::cout << "\n"; - } - std::cout << " [ref] head0 A:\n"; - for (std::size_t i = 0; i < seq; ++i) - { - std::cout << " row" << i << ":"; - for (std::size_t j = 0; j < seq; ++j) - std::printf(" %9.6f", static_cast(Ah[h].at_unchecked(i, j))); - std::cout << "\n"; - } - } - } - Matrix concat(d_model, seq); - for (std::size_t h = 0; h < H; ++h) - { - Matrix Vh = rows(V, h * dk, dk); - Matrix Oh = mmul(Vh, mtrans(Ah[h])); // (dk, seq) - for (std::size_t i = 0; i < dk; ++i) - for (std::size_t j = 0; j < seq; ++j) - concat.set_value_unchecked(h * dk + i, j, Oh.at_unchecked(i, j)); - } - Matrix y_ref = madd_bias(mmul(Wo, concat), bo); - - auto y_fwd = attn.forward(eng, *x); - Matrix y_got = *eng.to_matrix(*y_fwd); - cmp("forward out (wo concat)", y_got, y_ref); - std::cout << " (forward out 一致性验证 wv/wo 依赖的中间量)\n\n"; - - // ── 参考 backward ── - Matrix gc = mmul(mtrans(Wo), go_m); // (16, seq) - std::vector gQh(H, Matrix(dk, seq)), gKh(H, Matrix(dk, seq)), gVh(H, Matrix(dk, seq)); - for (std::size_t h = 0; h < H; ++h) - { - Matrix Vh = rows(V, h * dk, dk); - Matrix Kh = rows(K, h * dk, dk); - Matrix Qh = rows(Q, h * dk, dk); - Matrix gch = rows(gc, h * dk, dk); - Matrix gV = mmul(gch, Ah[h]); // (dk, seq) - Matrix gA = mmul(mtrans(Vh), gch); // (seq, seq) - Matrix gS = softmax_bwd(Ah[h], gA); // (seq, seq) - gQh[h] = mscale(mmul(Kh, mtrans(gS)), scale); - gKh[h] = mscale(mmul(Qh, gS), scale); - gVh[h] = gV; - } - Matrix gQ(d_model, seq), gK(d_model, seq), gV(d_model, seq); - for (std::size_t h = 0; h < H; ++h) - for (std::size_t i = 0; i < dk; ++i) - for (std::size_t j = 0; j < seq; ++j) - { - gQ.set_value_unchecked(h * dk + i, j, gQh[h].at_unchecked(i, j)); - gK.set_value_unchecked(h * dk + i, j, gKh[h].at_unchecked(i, j)); - gV.set_value_unchecked(h * dk + i, j, gVh[h].at_unchecked(i, j)); - } - Matrix ref_gwq = mmul(gQ, mtrans(x_m)); - Matrix ref_gwk = mmul(gK, mtrans(x_m)); - Matrix ref_gwv = mmul(gV, mtrans(x_m)); - Matrix ref_gwo = mmul(gc, mtrans(concat)); - Matrix ref_gbq(d_model, 1), ref_gbk(d_model, 1), ref_gbv(d_model, 1), ref_gbo(d_model, 1); - for (std::size_t i = 0; i < d_model; ++i) - { - Scalar sq{0}, sk{0}, sv{0}, so{0}; - for (std::size_t j = 0; j < seq; ++j) - { - sq += gQ.at_unchecked(i, j); - sk += gK.at_unchecked(i, j); - sv += gV.at_unchecked(i, j); - so += gc.at_unchecked(i, j); - } - ref_gbq.set_value_unchecked(i, 0, sq); - ref_gbk.set_value_unchecked(i, 0, sk); - ref_gbv.set_value_unchecked(i, 0, sv); - ref_gbo.set_value_unchecked(i, 0, so); - } - Matrix ref_gx = madd(madd(mmul(mtrans(Wq), gQ), mmul(mtrans(Wk), gK)), mmul(mtrans(Wv), gV)); - - // ── 引擎 backward ── - auto grads = attn.param_gradients(); - for (auto& g : grads) { auto rz = eng.zero(g.get()); if (!rz) return 1; } - auto gx = attn.backward(eng, *go); - if (!gx) { std::cerr << "backward failed\n"; return 1; } - - Matrix gwq = *eng.to_matrix(grads[0]); - Matrix gbk = *eng.to_matrix(grads[3]); - Matrix gwv = *eng.to_matrix(grads[4]); - Matrix gwo = *eng.to_matrix(grads[6]); - Matrix gx_got = *eng.to_matrix(*gx); - - std::cout << "── 引擎 vs 暴力参考(仅比较重量级梯度) ──\n"; - cmp("grad_wq", gwq, ref_gwq); - cmp("grad_wk", *eng.to_matrix(grads[2]), ref_gwk); - cmp("grad_wv", gwv, ref_gwv); - cmp("grad_wo", gwo, ref_gwo); - cmp("grad_bk", gbk, ref_gbk); - cmp("grad_x", gx_got, ref_gx); - - return 0; -} diff --git a/src/matmul_probe.cpp b/src/matmul_probe.cpp deleted file mode 100644 index a082e607..00000000 --- a/src/matmul_probe.cpp +++ /dev/null @@ -1,211 +0,0 @@ -// ── batched_matmul 原语探针 ───────────────────────────────────────────── -// -// 目的:直接对比 CpuEngine::batched_matmul 四种转置组合与手工参考计算。 -// 若某组合输出不匹配,即证明底层原语有 bug。 -// -// 语义(见 cpu_engine.hpp::batched_matmul): -// A 按行切成 batch 个纵向块(每块 a_rows_per × a.cols()),B 同理。 -// transA=true 表示使用 A 块的转置,transB=true 表示使用 B 块的转置。 -// 块内维度须匹配:所有四种转置组合都要求块为方阵(a_rows_per == a.cols())。 -// -// 用法:matmul_probe [--gpu|--cuda] -// --gpu 使用 Vulkan GpuEngine(验证 tiled batched_matmul shader) -// --cuda 使用 CUDA CudaEngine -// 默认 CpuEngine -// ───────────────────────────────────────────────────────────────────────── - -#include -#include - -#include -#include -#include -#include - -using nn::Scalar; -using nn::Matrix; -using nn::Tensor; -using nn::ComputeEngine; - -namespace { - -// 逐块参考:C_b = A_b^T × B_b(A_b/B_b 为原矩阵中的第 bi 个行块) -// A_b 以 (a_rows_per, a_cols) 行主序存储;transA/transB 语义与引擎一致。 -Matrix ref_batched(const Matrix& A, const Matrix& B, - std::size_t batch, bool transA, bool transB) -{ - const std::size_t a_rows_per = A.rows() / batch; - const std::size_t b_rows_per = B.rows() / batch; - - const std::size_t M = transA ? A.cols() : a_rows_per; - const std::size_t N = transB ? b_rows_per : B.cols(); - - Matrix C(batch * M, N); - for (std::size_t bi = 0; bi < batch; ++bi) - { - const std::size_t a_off = bi * a_rows_per; - const std::size_t b_off = bi * b_rows_per; - for (std::size_t i = 0; i < M; ++i) - { - for (std::size_t j = 0; j < N; ++j) - { - Scalar s{0}; - // 块内 K 维 = 逻辑 A 的列数 = 逻辑 B 的行数 - const std::size_t K = transA ? a_rows_per : A.cols(); - for (std::size_t k = 0; k < K; ++k) - { - // A_b[i][k](考虑转置) - const Scalar a_val = transA - ? A.at_unchecked(a_off + k, i) // A_b^T[i][k] = A_b[k][i] - : A.at_unchecked(a_off + i, k); - // B_b[k][j](考虑转置) - const Scalar b_val = transB - ? B.at_unchecked(b_off + j, k) // B_b^T[k][j] = B_b[j][k] - : B.at_unchecked(b_off + k, j); - s += a_val * b_val; - } - C.set_value_unchecked(bi * M + i, j, s); - } - } - } - return C; -} - -void dump_diff(const std::string& name, const Matrix& got, const Matrix& ref) -{ - bool ok = true; - Scalar max_err{0}; - std::size_t bad = 0; - for (std::size_t i = 0; i < got.rows(); ++i) - for (std::size_t j = 0; j < got.cols(); ++j) - { - const Scalar e = std::fabs(got.at_unchecked(i, j) - ref.at_unchecked(i, j)); - if (e > max_err) max_err = e; - if (e > Scalar{1e-4}) - { - ok = false; - if (bad < 5) - std::cout << " [FAIL] " << name << "(" << i << "," << j - << ") got=" << got.at_unchecked(i, j) - << " ref=" << ref.at_unchecked(i, j) - << " err=" << e << "\n"; - ++bad; - } - } - std::cout << " " << name << ": " << (ok ? "OK" : "FAIL") - << " max_err=" << max_err << " bad=" << bad << "\n"; -} - -} // namespace - -int main(int argc, char* argv[]) -{ - nn::cli::EngineConfig ecfg; - for (int i = 1; i < argc; ++i) - { - const std::string_view arg{argv[i]}; - if (arg == "--gpu") ecfg.use_gpu = true; - else if (arg == "--cuda") ecfg.use_cuda = true; - else { std::cerr << "未知参数: " << arg << "\n"; return 1; } - } - auto engine_res = nn::cli::create_engine(ecfg, std::cout); - if (!engine_res) { std::cerr << "引擎创建失败: " << engine_res.error().message << "\n"; return 1; } - auto engine = std::move(*engine_res); - ComputeEngine& eng = *engine; - - std::cout << "========================================\n"; - std::cout << " batched_matmul 原语探针\n"; - std::cout << "========================================\n"; - - std::mt19937_64 rng(7); - std::uniform_real_distribution dist(-1, 1); - - // 方块尺寸 K × K,H 个块纵向堆叠 → A/B 均为 (H*K, K) - // 该形状使四种转置组合的块内维度全部合法。 - const std::size_t K = 4; - const std::size_t H = 2; - - Matrix Am(H * K, K), Bm(H * K, K); - for (std::size_t i = 0; i < Am.size(); ++i) Am.span()[i] = dist(rng); - for (std::size_t i = 0; i < Bm.size(); ++i) Bm.span()[i] = dist(rng); - - auto A = eng.from_matrix(Am); - auto B = eng.from_matrix(Bm); - if (!A || !B) { std::cerr << "from_matrix failed\n"; return 1; } - - // ── (F,F): C = A × B ── - { - auto out = eng.batched_matmul(*A, *B, H, false, false); - if (!out) { std::cerr << " (F,F) error: " << out.error().message << "\n"; return 1; } - auto got = eng.to_matrix(*out); - if (!got) { std::cerr << " (F,F) to_matrix failed\n"; return 1; } - const Matrix ref = ref_batched(Am, Bm, H, false, false); - dump_diff("(F,F) A*B", *got, ref); - } - // ── (F,T): C = A × B^T ── - { - auto out = eng.batched_matmul(*A, *B, H, false, true); - if (!out) { std::cerr << " (F,T) error: " << out.error().message << "\n"; return 1; } - auto got = eng.to_matrix(*out); - if (!got) { std::cerr << " (F,T) to_matrix failed\n"; return 1; } - const Matrix ref = ref_batched(Am, Bm, H, false, true); - dump_diff("(F,T) A*B^T", *got, ref); - } - // ── (T,F): C = A^T × B ── - { - auto out = eng.batched_matmul(*A, *B, H, true, false); - if (!out) { std::cerr << " (T,F) error: " << out.error().message << "\n"; return 1; } - auto got = eng.to_matrix(*out); - if (!got) { std::cerr << " (T,F) to_matrix failed\n"; return 1; } - const Matrix ref = ref_batched(Am, Bm, H, true, false); - dump_diff("(T,F) A^T*B", *got, ref); - } - // ── (T,T): C = A^T × B^T ── - { - auto out = eng.batched_matmul(*A, *B, H, true, true); - if (!out) { std::cerr << " (T,T) error: " << out.error().message << "\n"; return 1; } - auto got = eng.to_matrix(*out); - if (!got) { std::cerr << " (T,T) to_matrix failed\n"; return 1; } - const Matrix ref = ref_batched(Am, Bm, H, true, true); - dump_diff("(T,T) A^T*B^T", *got, ref); - } - - // ── 大尺寸 + alpha:K=100 不被 BK=16 整除(边界填零路径),M=N=100 - // 超过 64×64 tile(多 WorkGroup 分块路径),H=8 模拟 batch*num_heads。 - // alpha=0.176777 模拟 attention 的 1/sqrt(d_k) 折叠。 - { - const std::size_t K2 = 100; - const std::size_t H2 = 8; - const Scalar alpha{0.176777f}; - - Matrix Am2(H2 * K2, K2), Bm2(H2 * K2, K2); - for (std::size_t i = 0; i < Am2.size(); ++i) Am2.span()[i] = dist(rng); - for (std::size_t i = 0; i < Bm2.size(); ++i) Bm2.span()[i] = dist(rng); - - auto A2 = eng.from_matrix(Am2); - auto B2 = eng.from_matrix(Bm2); - if (!A2 || !B2) { std::cerr << "from_matrix (large) failed\n"; return 1; } - - struct Case { const char* name; bool tA; bool tB; }; - for (const Case cs : {Case{"(F,F)", false, false}, Case{"(T,F)", true, false}, - Case{"(F,T)", false, true}, Case{"(T,T)", true, true}}) - { - auto out = eng.batched_matmul(*A2, *B2, H2, cs.tA, cs.tB, alpha); - if (!out) { std::cerr << " large " << cs.name << " error: " - << out.error().message << "\n"; return 1; } - auto got = eng.to_matrix(*out); - if (!got) { std::cerr << " large " << cs.name << " to_matrix failed\n"; return 1; } - const Matrix ref = ref_batched(Am2, Bm2, H2, cs.tA, cs.tB); - - // ref 乘 alpha 后对比(参考实现不含 alpha) - Matrix ref_s = ref; - for (std::size_t i = 0; i < ref_s.size(); ++i) - ref_s.span()[i] *= alpha; - std::string label = std::string("large ") + cs.name + " alpha"; - dump_diff(label.c_str(), *got, ref_s); - } - } - - std::cout << "----------------------------------------\n"; - return 0; -} From caf354f5bac847be0f1cfd615c3110153ca2ab2c Mon Sep 17 00:00:00 2001 From: EthanPeng-2048 Date: Tue, 25 Aug 2026 16:24:36 +0800 Subject: [PATCH 9/9] =?UTF-8?q?=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 4 - docs/12-innovative-designs.md | 270 ++++++++++++ include/neuralnet.cpp/compute_layer.hpp | 398 ++++++++++++------ include/neuralnet.cpp/compute_optimizer.hpp | 54 ++- include/neuralnet.cpp/domain_cnn.hpp | 25 +- include/neuralnet.cpp/domain_gpt.hpp | 9 +- include/neuralnet.cpp/domain_mnist.hpp | 30 +- include/neuralnet.cpp/model_container.hpp | 18 +- include/neuralnet.cpp/model_serialization.hpp | 7 +- report.html | 18 +- tools/scan_exprs.cpp | 11 +- 11 files changed, 652 insertions(+), 192 deletions(-) create mode 100644 docs/12-innovative-designs.md diff --git a/CMakeLists.txt b/CMakeLists.txt index 20572f58..e430d5bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,10 +87,6 @@ endfunction() # 需要时用 -DNN_ENABLE_TESTS=ON 开启全部 gradcheck / *_test / *_probe / *_debug。 option(NN_ENABLE_TESTS "Build test/verification executables" OFF) -# 收集实际构建的目标,供 GPU/CUDA 统一启用(避免引用未定义目标) -set(NN_APP_TARGETS "") -set(NN_TEST_TARGETS "") - # 收集实际构建的目标,供 GPU/CUDA 统一启用(避免引用未定义目标) set(NN_APP_TARGETS "") set(NN_TEST_TARGETS "") diff --git a/docs/12-innovative-designs.md b/docs/12-innovative-designs.md new file mode 100644 index 00000000..350522cc --- /dev/null +++ b/docs/12-innovative-designs.md @@ -0,0 +1,270 @@ +# 🚀 neuralnet.cpp 创新设计总览 + +> 本文档汇总本项目中所有具有**原创性或系统性思考**的设计。它不是架构/性能/融合等专项文档的重复,而是从"为什么这么做、创新点在哪、带来什么收益"的角度,把这些设计串成一张全景图。 +> +> 关联文档:[01-architecture](./01-architecture.md) · [02-performance](./02-performance.md) · [09-operator-fusion](./09-operator-fusion.md) · [10-memory-optimization](./10-memory-optimization.md) · [11-ir-optimization](./11-ir-optimization.md) · [05-algorithm-reference](./05-algorithm-reference.md) + +--- + +## 0. 总览 + +一个好的深度学习框架,设计难点往往不在"算法本身",而在于**如何把算法用干净、可维护、可并行、可扩展的方式组织起来**。本项目的创新集中在以下几个维度: + +| 维度 | 核心创新 | 一句话 | +|------|---------|--------| +| **架构** | 分层引擎化 | 一次编写 Layer,CPU/GPU 零修改双跑 | +| **架构** | 铁律式职责划分 | 算法留 Layer、原语进引擎、Shader 是内部实现 | +| **编译** | AOT 闭合世界表达系统 | 表达式文本唯一、构建期扫描生成、运行时按 key 精确分发 | +| **性能** | SmartPolicy + 零分配线程池 | 自适应并行、消除每次任务的堆分配 | +| **性能** | 表达式模板零临时矩阵 | `a + b * c` 不物化中间结果 | +| **显存** | 结构融合(而非手写 kernel) | 三粒度融合:逐元素链 / matmul+归约 / 两趟注意力 | +| **显存** | 两趟式注意力 + 反向重算 | 不物化 `O(seq²)` 分数矩阵 | +| **显存** | 稀疏 CE + 梯度检查点 + 池归还 | 把显存峰值从 29GB 压到 27GB(并持续下探) | +| **IR** | 带确定性 pass 的规范 IR | DCE/常量折叠/CSE/寄存器分配,key 定义在 canonical IR 上 | +| **算法** | 纯 BPE + 兜底词表自举 | 小模型也能稳定启动、支持中文 | + +下面逐条展开。 + +--- + +## 1. 核心架构创新 + +### 1.1 引擎化架构(Engine-Based) + +**创新点**:让 `Layer::forward/backward` 只写一次,通过传入的 `ComputeEngine` 自动适配 CPU / GPU / CUDA,而不是像多数教学框架那样为每后端各写一份。 + +```cpp +class Layer { + virtual Result forward(ComputeEngine& engine, const Tensor& input) = 0; + virtual Result backward(ComputeEngine& engine, const Tensor& grad) = 0; +}; +``` + +**收益**: +- 新增一个后端 = 只实现 `ComputeEngine` 抽象,零 Layer 改动。 +- 算法正确性在 CPU 上验证后可放心跑 GPU。 + +### 1.2 分层"铁律"(Red Lines) + +这是本项目最鲜明的**原创治理原则**,贯穿所有文档: + +``` +引擎只提供 op-level 原语(matmul/reduce/broadcast/exp…),绝不出现 softmax/attention/layernorm 命名接口 +算法文本只写在 Layer(ReLU/GeLU/Softmax/Attention 的公式只在 compute_layer.hpp / compute_loss.hpp) +Shader / 融合逻辑是引擎内部实现,用户不可见 +``` + +**创新点**:不是把"引擎是否认识 softmax"当实现细节,而是把它当一条**不可逾越的红线**。引擎只认**结构**(如 `reduce(matmul(A,B))`),绝不认**算法名**。这使得算子融合、IR 优化、新后端都能在一个稳定的契约上展开。 + +配套约束: +- `ComputEngine` 原语可"专"(matmul+归约、matmul+softmax分母都是合法原语)但必须通用可复用、不叫算法名。 +- 全程 `noexcept` / `Result`(`std::expected`),零手动内存管理(`-fno-exceptions`)。 + +### 1.3 统一张量 `Tensor` + 零拷贝 + +`Tensor` 用 `shared_ptr` 持有 CPU/GPU 两份互斥存储,拷贝=引用计数,跨层传递零拷贝。修改需显式 `clone`。这为 GPU 训练"全程驻留显存、仅 evaluate 时下载"提供了容器基础。 + +--- + +## 2. AOT 闭合世界表达系统(Compiler-Style) + +这是本项目**最具工程创新性**的设计,把"表达式"当成一门可编译的语言: + +### 2.1 单一来源(Single Source of Truth) + +算法表达式**直接写在 C++ 源码里**(如 `exp(x - row_max) / sum(...)`),而不是单独维护一份"计算图定义"。 + +### 2.2 构建期扫描 + 生成 + +``` +Layer 内联表达式 → to_expr_spec 折叠 → ExprSpec(扁平 SSA IR) + → expr_spec_key(规范结构 key)→ scan_exprs 收集 + → gen_fused 用 glsl_gen 合成 GLSL → glslc → 内联 SPIR-V → fused_registry.hpp +运行时:fold → key → find_fused(key) → 精确 dispatch(闭合世界) +``` + +**创新点**: +- **闭合世界(closed world)**:所有可能出现的融合 kernel 都在构建期预编译、内联进最终程序。运行时**没有编译器、没有 eager 分发**。 +- **派生而非手写**:`scan_exprs` 通过 dry-run 执行 Layer 的 forward/backward 触达表达式,把折叠后的**结构(派生物)**采集进注册表,而不是手工维护一份 kernel 清单。 +- **未命中硬报错**:GPU 上遇到未预生成的表达式结构,**硬报错**并且绝不静默回退 CPU,保持项目"不降级"哲学——据此暴露扫描覆盖缺口。 + +### 2.3 确定性 key 铁律 + +key 定义在 **canonical(优化后)IR** 上,scan 与 runtime 两端必须先 canonicalize 再算 key。且 pass 遍历顺序固定、CSE 胜利者选择固定、寄存器分配固定——避免跨编译器(Clang/MSVC)漂移。这条铁律来自一次真实事故:C++ 实参求值顺序未指定曾导致 key 跨编译器不稳定。 + +--- + +## 3. 算子融合(结构融合,而非手写 kernel) + +### 3.1 设计哲学 + +**不手写任何命名算法 kernel**(如不手写 flash-attention、不手写 softmax kernel),而是: +1. Layer 用 `eval_expr` / `begin_expr/end_expr` 录制表达算法; +2. 引擎/工具按**结构**合成融合 kernel; +3. 所有中间 Tensor 由融合 kernel 内部消解,不落显存。 + +### 3.2 三粒度融合 + +| 粒度 | 覆盖 | 消除的中间量 | +|------|------|-------------| +| **逐元素链** | ReLU/GeLU/SwiGLU 连续逐元素 | 链上的全尺寸中间 Tensor | +| **matmul + 归约** | softmax 分子分母、norm 统计 | `A·B` 全尺寸物化 | +| **两趟注意力 / 稀疏 CE** | attention、大词表交叉熵 | `O(seq²)` / `O(vocab×seq)` 物化 | + +### 3.3 三个 matmul 融合原语 + +为承载两趟注意力,新增了一组**通用、可复用**的原语(不是"attention",而是"matmul 后接结构"): + +- `batched_matmul_reduce`:matmul 后沿输出维度归约,不物化中间 `A·B`。 +- `batched_matmul_softmax_denom`:减行 max → exp → 按列求和(softmax 分母,数值稳定)。 +- `batched_matmul_softmax_apply`:行 softmax 归一化后与 V 相乘累加,逐 tile 流式、不物化权重矩阵。 + +反向同样有 `..._softmax_backward_q` / `..._softmax_backward_kv`,kernel 内部重算权重矩阵。 + +### 3.4 形状无关融合(Key 创新) + +RoPE 的 `RowMod/RotateHalf` 参数如果以**结构常量**折进 key,每个 `d_k` 就要一个新融合 shader → 闭合世界无法穷举。 + +**解决**:把形状相关参数(mod/block)从 key 里**拿出来变成运行时 push constant**。结构相同、形状不同共享同一融合 shader——任意 `d_k`(含非 2 的幂)都全融合,零额外显存。这是"闭合世界"与"形状多变"矛盾的一个优雅解法。 + +--- + +## 4. 两趟式注意力(内存高效 Attention) + +### 4.1 问题 + +传统 `scores/masked/attn_cache_` 各占一份 `H·batch·seq²`,且 `attn_cache_` 永久缓存用于反向。这是 GPT+Vulkan 训练显存高的元凶之一。 + +### 4.2 方案 + +把 `scores → mask → softmax → ×V` 拆成**不物化 `seq²` 矩阵**的算法: + +- **Forward**:`m = max of QᵀK`(bmm_reduce)→ `l = Σ exp(QᵀK - m)`(bmm_denom)→ `O = W·V` 逐 tile(bmm_apply)。只留下 `m/l`(`H·batch·seq`)与 `O`(`H·batch·d_k·seq`)。 +- **Backward**:默认**反向重算 W**(用原语再算一次),放弃 `attn_cache_`。代价是 2× FLOPs,换整份 `BH·seq²` 缓存。 + +### 4.3 收益 + +每层从 ~3×`BH·seq²` 降到 `O(BH·seq·d_k)`。这是典型的**用计算换显存**,在训练显存吃紧、而算力相对有余的场景下是最优取舍。 + +--- + +## 5. 稀疏交叉熵(Sparse CE) + +### 5.1 问题 + +大词表(vocab≈25k)交叉熵若物化整个 `(classes, total)` 的 softmax,显存与下载开销巨大。 + +### 5.2 方案 + +两个 op-level 原语,单 kernel 完成全部计算: +- `col_softmax_denom`:exp 后列内树形归约求分母,不物化 exp 张量。 +- `col_softmax_sparse_forward`:单 kernel 同时产出**稠密梯度**(每列只对合法列施加)与**标签位置的 log_softmax loss**,不从整张 softmax gather。 + +**创新点**:labels 以 `(1,N)` 浮点打包上传(`vocab ≤ 2²⁴` 时 float 可精确表示类别索引),kernel 内转 `uint` 读取;非法/被屏蔽列整列置 0,与 CPU 参考完全一致。显存从 ~3-4×`(classes,total)` 降到 ~2×。 + +--- + +## 6. IR 优化(带确定性 pass 的规范 IR) + +### 6.1 动机 + +`ExprSpec` 已是事实上的轻量 IR(SSA、扁平、可序列化、确定性 key),但没有优化 pass,直接导致真实问题: +- 子表达式 `grad*gamma` 重复 3 次 → 超 `EXPR_MAX_INPUTS=8`,被迫手工拆表达式。 +- 无 DCE/常量折叠,shader 携带冗余计算。 +- 无寄存器分配,`num_regs` 受 `EXPR_MAX_REGS=16` 约束。 + +### 6.2 方案 + +- **IR-A canonicalize**:DCE + 常量折叠 + 代数化简 + 稳定重编号(保守、不改变浮点语义)。 +- **IR-B CSE + 寄存器分配**:哈希指令去重,liveness 线性扫描确定性贪心分配寄存器。 +- **IR-C 图 IR + 融合分析**:`begin_expr/end_expr` 录制虚拟寄存器 DAG;**逐元素链拼接**——B 以 Linear 视图消费 A 输出且 A 无其他消费者、形状相同、均无归约 → 指令内联进 B,单 kernel。归约节点/归约输出作为融合边界。 +- **IR-D emitter 抽象**:`ExprEmitter` 接口 + 注册表,一份 canonical IR 产出 GLSL/CPU 多后端(验证"一份 IR 多后端")。 + +### 6.3 关键不变量 + +canonicalize 不改变 views/inputs 的顺序与内容,只优化 instrs/consts/num_regs → 运行时输入绑定布局不变。寄存器"先声明后赋值"以兼容 liveness 复用同号寄存器。 + +--- + +## 7. 显存优化体系(三级) + +围绕 GPT 训练峰值显存(29GB → 27GB,持续下探)的三条独立路径: + +### L1 激活重计算(梯度检查点) +- `Layer` 契约扩展 `recompute_supported / forward_recompute`;`GPTModel` 实现**块级检查点**(每 N 个 GPTBlock 存一次输入,backward 重算中间)。 +- 与全存基线**逐位一致(max_abs=0)**。 + +### L2 内存池整块归还 + 统计 +- `MemoryPool` 新增 `PoolStats` / `release_idle_blocks()`(整块 `vkFreeMemory`,带保留阈值防抖动)。 +- **结构融合宣示**:中间 Tensor 的归还路径安全——每个原语录制都包裹全内存屏障(写入→`SHADER_READ` / 输出→`MEMORY_READ`),保证 batch 命令缓冲内前序写入对后续可见。 + +### L3(远期) +- 自动融合 + 无 m/v 优化器(Adafactor 类)降参数状态内存。 + +### 设计红线 +全程**不引入 f16/bf16**(数值统一 fp32)——"省内存但不牺牲精度",这本身也是一个收敛的取舍决策。 + +--- + +## 8. CPU 性能优化(L1/L2 层) + +### 8.1 SmartPolicy 自适应并行 +根据数据规模自动决定串行/并行,避免小数据量时线程调度开销(约 50~200μs)大于计算收益。实测在 ~512K 元素处首次稳定 >1.5x,此前串行更优。 + +### 8.2 线程池 latch 零分配 +旧版每任务 = N 次堆分配 + N 次加锁 + N 个 future 同步;新版 = 1 次加锁 + 0 次堆分配 + 1 个原子计数器,配合三阶段等待(短自旋 → work-stealing → 条件变量短超时)与"调用者参与计算"。 + +### 8.3 缓存分块 + 栈分配 +`BLOCK_SIZE=64`(64×64×4B=16KB 装入 L1),B 块预取转置,b_block 栈分配零堆分配。 + +### 8.4 算子融合(原语级) +- `axpy_inplace`:`clone+scale+add` 三步并一步。 +- `elementwise_select_scalar_cond`:条件选择融合(ReLU backward)。 +- 多头注意力**批量化**:`rearrange_3d → 单次 batched_matmul → 转回`,把 H 次 matmul 融为 1 次。 +- 因果掩码 / 位置编码缓存:相同 `(batch,seq)` 只构造一次。 + +### 8.5 表达式模板零临时矩阵 +`a + b * c` 在编译期构造 AST,单次遍历执行,不创建 `b*c` 中间矩阵,编译器可整体内联向量化。 + +--- + +## 9. 分词器与算法创新 + +### 9.1 纯 BPE(CharBPE)支持中文 +默认 `bpe` 为纯 BPE(优先于 BBPE),**可支持中文**:每个汉字作为基础字符单元,学习常用字词合并——比跨语言字节切分更能捕捉词义;BBPE 作为备选。词表含 256 个 ASCII 兜底 token(ID 4~259)+ 4 个特殊符号,保证最小 `vocab_size=260` 也能稳定启动不崩溃。 + +### 9.2 GPT 训练约定 +每行作为独立样本,`[BOS]` 开头、`[EOS]` 仅行尾;`generate()` 带 `min_new_tokens` 防止小模型过早输出 EOS;`seq_len` 降到覆盖 >99% 样本的规模以避 `n²` 显存。 + +--- + +## 10. 创新背后的工程方法 + +除了具体技术,项目在**方法论**上也有鲜明的、可复用的设计: + +| 方法 | 体现 | +|------|------| +| **合规红线先行** | 每次演进(融合/IR/显存)都先写"不可逾越红线"表,再谈实现 | +| **结构 > 命名的哲学** | 引擎认结构不认算法名,是融合与 IR 能落地的共同前提 | +| **闭合世界 + 硬报错** | 不静默降级,宁可报错暴露覆盖缺口 | +| **恶意保守的正确性安全网** | GPU 融合未命中/失败回退原语组合;融合路径与回退路径数值一致并互相验证 | +| **数值确定性** | 全程 fp32、定点可复现,优化 pass 不改变浮点语义 | +| **可验证增量里程碑** | M1-M6 每步有独立测试验证(gradcheck / GRUD/CPU 对照),先小层数回归再全面放开 | + +--- + +## 11. 收益一栏 + +| 设计 | 量化收益(示例配置) | +|------|---------------------| +| 两趟注意力 | 每层 `~3×BH·seq²` → `O(BH·seq·d_k)` | +| 稀疏 CE | 全 softmax `(vocab×seq)` 物化 → 仅标签 gather | +| 算子融合 + 显存体系 | GPT 训练峰值 ~29GB → ~27GB(并持续下探) | +| IR CSE + 寄存器分配 | 消除"手工拆表达式",`num_regs` 受控在 16 内 | +| SmartPolicy | 小矩阵 0.3x 退化 → 串行;大矩阵最高 ~7.8x 加速 | +| 融合 axpy | 每 step 减少 ~600 次 GPU buffer 分配 | +| 注意力批量化 | H 次 matmul → 1 次 batched_matmul | + +--- + +> 🌙 本文档为创新设计的**全景速览**,不替代各专项文档的细节。想要深入的读者请跳转文首的关联文档。 \ No newline at end of file diff --git a/include/neuralnet.cpp/compute_layer.hpp b/include/neuralnet.cpp/compute_layer.hpp index 72a9ae59..cc1d069a 100644 --- a/include/neuralnet.cpp/compute_layer.hpp +++ b/include/neuralnet.cpp/compute_layer.hpp @@ -85,6 +85,11 @@ class Layer // 非可学习状态收集(默认空,BatchNorm 的 running 统计量等需要 override) [[nodiscard]] virtual std::vector extra_state() { return {}; } + // 引擎相关初始化(创建/上传权重张量),替换构造函数中的 NN_ASSERT 模式。 + // 默认实现空操作;各层在构造后由 Model::add() 调用。 + // 返回 Result 以正确传播引擎错误,而非在 Release 下吞掉。 + [[nodiscard]] virtual Result init(ComputeEngine& /*engine*/) { return {}; } + // 梯度检查点(激活重计算)契约 ────────────────────────────────── // checkpoint_mode_ = true 时,forward 不保留中间激活(供 L1 激活重计算); // forward_recompute 重算 forward 并重建缓存(供 backward 使用)。 @@ -147,6 +152,8 @@ class Layer class Linear final : public Layer { private: + std::size_t in_features_; + std::size_t out_features_; Tensor w_; // 权重 (out_features, in_features) Tensor b_; // 偏置 (out_features, 1) Tensor grad_w_; // 权重梯度 @@ -156,33 +163,36 @@ class Linear final : public Layer inline static thread_local std::mt19937_64 rng_{std::random_device{}()}; public: - Linear(ComputeEngine& engine, - std::size_t in_features, std::size_t out_features) + Linear(std::size_t in_features, std::size_t out_features) + : in_features_(in_features), out_features_(out_features) {} + + [[nodiscard]] Result init(ComputeEngine& engine) override { // ── 在 CPU 上初始化权重(Xavier 均匀分布) ── - Matrix w_cpu(out_features, in_features); - const Scalar limit = std::sqrt(6.0 / static_cast(in_features + out_features)); + Matrix w_cpu(out_features_, in_features_); + const Scalar limit = std::sqrt(6.0 / static_cast(in_features_ + out_features_)); std::uniform_real_distribution dist(-limit, limit); auto w_span = w_cpu.span(); for (std::size_t i = 0; i < w_cpu.size(); ++i) w_span[i] = dist(rng_); - Matrix b_cpu(out_features, 1); // 零初始化 + Matrix b_cpu(out_features_, 1); // 零初始化 // ── 通过 engine 上传到目标设备 ── auto w_res = engine.from_matrix(w_cpu); - NN_ASSERT(w_res, w_res ? "" : w_res.error().message.c_str()); + if (!w_res) return std::unexpected(w_res.error()); w_ = std::move(*w_res); auto b_res = engine.from_matrix(b_cpu); - NN_ASSERT(b_res, b_res ? "" : b_res.error().message.c_str()); + if (!b_res) return std::unexpected(b_res.error()); b_ = std::move(*b_res); // ── 创建梯度张量(零初始化) ── - grad_w_ = engine.create_tensor(out_features, in_features); - grad_b_ = engine.create_tensor(out_features, 1); - { auto r1 = engine.zero(grad_w_); NN_ASSERT(r1, r1 ? "" : r1.error().message.c_str()); } - { auto r2 = engine.zero(grad_b_); NN_ASSERT(r2, r2 ? "" : r2.error().message.c_str()); } + grad_w_ = engine.create_tensor(out_features_, in_features_); + grad_b_ = engine.create_tensor(out_features_, 1); + { auto r1 = engine.zero(grad_w_); if (!r1) return std::unexpected(r1.error()); } + { auto r2 = engine.zero(grad_b_); if (!r2) return std::unexpected(r2.error()); } + return {}; } [[nodiscard]] std::vector parameters() override @@ -591,26 +601,28 @@ class LayerNorm final : public Layer static constexpr Scalar EPSILON = 1e-5; public: - explicit LayerNorm(ComputeEngine& engine, - std::size_t normalized_shape, Scalar epsilon = EPSILON) - : normalized_shape_(normalized_shape), epsilon_(epsilon) + explicit LayerNorm(std::size_t normalized_shape, Scalar epsilon = EPSILON) + : normalized_shape_(normalized_shape), epsilon_(epsilon) {} + + [[nodiscard]] Result init(ComputeEngine& engine) override { // gamma 初始化为 1, beta 初始化为 0 - Matrix gamma_cpu(normalized_shape, 1, Scalar{1}); - Matrix beta_cpu(normalized_shape, 1, Scalar{0}); + Matrix gamma_cpu(normalized_shape_, 1, Scalar{1}); + Matrix beta_cpu(normalized_shape_, 1, Scalar{0}); auto g = engine.from_matrix(gamma_cpu); - NN_ASSERT(g, g ? "" : g.error().message.c_str()); + if (!g) return std::unexpected(g.error()); gamma_ = std::move(*g); auto bv = engine.from_matrix(beta_cpu); - NN_ASSERT(bv, bv ? "" : bv.error().message.c_str()); + if (!bv) return std::unexpected(bv.error()); beta_ = std::move(*bv); - grad_gamma_ = engine.create_tensor(normalized_shape, 1); - grad_beta_ = engine.create_tensor(normalized_shape, 1); - { auto r1 = engine.zero(grad_gamma_); NN_ASSERT(r1, r1 ? "" : r1.error().message.c_str()); } - { auto r2 = engine.zero(grad_beta_); NN_ASSERT(r2, r2 ? "" : r2.error().message.c_str()); } + grad_gamma_ = engine.create_tensor(normalized_shape_, 1); + grad_beta_ = engine.create_tensor(normalized_shape_, 1); + { auto r1 = engine.zero(grad_gamma_); if (!r1) return std::unexpected(r1.error()); } + { auto r2 = engine.zero(grad_beta_); if (!r2) return std::unexpected(r2.error()); } + return {}; } [[nodiscard]] std::vector parameters() override @@ -801,18 +813,20 @@ class RMSNorm final : public Layer static constexpr Scalar EPSILON = 1e-5; public: - explicit RMSNorm(ComputeEngine& engine, - std::size_t normalized_shape, Scalar epsilon = EPSILON) - : normalized_shape_(normalized_shape), epsilon_(epsilon) + explicit RMSNorm(std::size_t normalized_shape, Scalar epsilon = EPSILON) + : normalized_shape_(normalized_shape), epsilon_(epsilon) {} + + [[nodiscard]] Result init(ComputeEngine& engine) override { // gamma 初始化为 1(无 beta) - Matrix gamma_cpu(normalized_shape, 1, Scalar{1}); + Matrix gamma_cpu(normalized_shape_, 1, Scalar{1}); auto g = engine.from_matrix(gamma_cpu); - NN_ASSERT(g, g ? "" : g.error().message.c_str()); + if (!g) return std::unexpected(g.error()); gamma_ = std::move(*g); - grad_gamma_ = engine.create_tensor(normalized_shape, 1); - { auto r1 = engine.zero(grad_gamma_); NN_ASSERT(r1, r1 ? "" : r1.error().message.c_str()); } + grad_gamma_ = engine.create_tensor(normalized_shape_, 1); + { auto r1 = engine.zero(grad_gamma_); if (!r1) return std::unexpected(r1.error()); } + return {}; } [[nodiscard]] std::vector parameters() override @@ -950,15 +964,19 @@ class RMSNorm final : public Layer class FusedChainLayer final : public Layer { private: + std::size_t features_; Tensor gamma_; // (F, 1) 按行广播系数 public: - explicit FusedChainLayer(ComputeEngine& engine, std::size_t features) + FusedChainLayer(std::size_t features) : features_(features) {} + + [[nodiscard]] Result init(ComputeEngine& engine) override { - Matrix g(features, 1, Scalar{0.5f}); // 演示用固定系数 + Matrix g(features_, 1, Scalar{0.5f}); // 演示用固定系数 auto g_t = engine.from_matrix(g); - NN_ASSERT(g_t, g_t ? "" : g_t.error().message.c_str()); + if (!g_t) return std::unexpected(g_t.error()); gamma_ = std::move(*g_t); + return {}; } [[nodiscard]] Result forward( @@ -999,11 +1017,11 @@ class FusedChainLayer final : public Layer // ── 归一化层工厂:按 NormType 创建 LayerNorm 或 RMSNorm ────────────────── [[nodiscard]] inline std::unique_ptr make_norm_layer( - ComputeEngine& engine, std::size_t d_model, NormType norm_type) + std::size_t d_model, NormType norm_type) { if (norm_type == NormType::RMSNorm) - return std::make_unique(engine, d_model); - return std::make_unique(engine, d_model); + return std::make_unique(d_model); + return std::make_unique(d_model); } // ══════════════════════════════════════════════════════════════════════════ @@ -1150,8 +1168,7 @@ class Conv2D final : public Layer } public: - Conv2D(ComputeEngine& engine, - std::size_t in_channels, std::size_t out_channels, + Conv2D(std::size_t in_channels, std::size_t out_channels, std::size_t kernel, std::size_t stride = 1, std::size_t padding = 0, std::size_t in_h = 0, std::size_t in_w = 0) : in_channels_(in_channels), out_channels_(out_channels), @@ -1161,28 +1178,34 @@ class Conv2D final : public Layer const std::size_t fan_in = in_channels * kernel * kernel; out_h_ = (in_h_ + 2 * padding_ - kernel_) / stride_ + 1; out_w_ = (in_w_ + 2 * padding_ - kernel_) / stride_ + 1; + } + + [[nodiscard]] Result init(ComputeEngine& engine) override + { + const std::size_t fan_in = in_channels_ * kernel_ * kernel_; // 权重 (C_out, C_in*k*k) — He 风格均匀初始化 - Matrix w_cpu(out_channels, fan_in); - const Scalar limit = std::sqrt(6.0 / static_cast(fan_in + out_channels)); + Matrix w_cpu(out_channels_, fan_in); + const Scalar limit = std::sqrt(6.0 / static_cast(fan_in + out_channels_)); std::uniform_real_distribution dist(-limit, limit); auto w_span = w_cpu.span(); for (std::size_t i = 0; i < w_cpu.size(); ++i) w_span[i] = dist(rng_); - Matrix b_cpu(out_channels, 1); // 零初始化 + Matrix b_cpu(out_channels_, 1); // 零初始化 auto w_res = engine.from_matrix(w_cpu); - NN_ASSERT(w_res, w_res ? "" : w_res.error().message.c_str()); + if (!w_res) return std::unexpected(w_res.error()); w_ = std::move(*w_res); auto b_res = engine.from_matrix(b_cpu); - NN_ASSERT(b_res, b_res ? "" : b_res.error().message.c_str()); + if (!b_res) return std::unexpected(b_res.error()); b_ = std::move(*b_res); - grad_w_ = engine.create_tensor(out_channels, fan_in); - grad_b_ = engine.create_tensor(out_channels, 1); - { auto r1 = engine.zero(grad_w_); NN_ASSERT(r1, r1 ? "" : r1.error().message.c_str()); } - { auto r2 = engine.zero(grad_b_); NN_ASSERT(r2, r2 ? "" : r2.error().message.c_str()); } + grad_w_ = engine.create_tensor(out_channels_, fan_in); + grad_b_ = engine.create_tensor(out_channels_, 1); + { auto r1 = engine.zero(grad_w_); if (!r1) return std::unexpected(r1.error()); } + { auto r2 = engine.zero(grad_b_); if (!r2) return std::unexpected(r2.error()); } + return {}; } std::vector parameters() override { return {w_, b_}; } @@ -1491,18 +1514,19 @@ class RotaryEmbedding } // 重建全表 (d_k, seq):cos/sin 按维度对交错重复 - void rebuild(ComputeEngine& engine, std::size_t seq) + [[nodiscard]] Result rebuild(ComputeEngine& engine, std::size_t seq) { Matrix c(d_k_, seq), s(d_k_, seq); for (std::size_t pos = 0; pos < seq; ++pos) fill_pos_column_(c, s, pos, pos); auto cr = engine.from_matrix(c); - NN_ASSERT(cr, cr ? "" : cr.error().message.c_str()); + if (!cr) return std::unexpected(cr.error()); cos_cache_ = std::move(*cr); auto sr = engine.from_matrix(s); - NN_ASSERT(sr, sr ? "" : sr.error().message.c_str()); + if (!sr) return std::unexpected(sr.error()); sin_cache_ = std::move(*sr); seq_cached_ = seq; + return {}; } // 构造 RoPE 内联表达式(forward 用 Add 结尾,backward 用 Sub 结尾) @@ -1511,11 +1535,13 @@ class RotaryEmbedding public: RotaryEmbedding() = default; - RotaryEmbedding(ComputeEngine& /*engine*/, std::size_t d_k) + explicit RotaryEmbedding(std::size_t d_k) : d_k_(d_k) { } + [[nodiscard]] Result init(ComputeEngine& /*engine*/) { return {}; } + [[nodiscard]] std::size_t d_k() const noexcept { return d_k_; } // 全表应用:q 为 (batch*H*d_k, seq),cos/sin 为 (d_k, seq) 短表 @@ -1526,7 +1552,10 @@ class RotaryEmbedding if (d_k_ == 0 || d_k_ % 2 != 0) return std::unexpected(Error{"RotaryEmbedding::apply: d_k must be positive and even"}); if (seq != seq_cached_) - rebuild(engine, seq); + { + auto r = rebuild(engine, seq); + if (!r) return std::unexpected(r.error()); + } const std::uint32_t dk = static_cast(d_k_); // 内联 RoPE 表达式(LLaMA half-swap;backward 为旋转正交,逆 = 反角): // forward: out = q·cos + rotate_half(q)·sin @@ -1669,25 +1698,37 @@ class AttentionBase : public Layer std::size_t /*batch*/, std::size_t /*seq*/) {} public: - AttentionBase(ComputeEngine& engine, - std::size_t d_model, std::size_t num_heads, + AttentionBase(std::size_t d_model, std::size_t num_heads, std::size_t seq_len = 0, PosEncodingType pos_enc = PosEncodingType::Learned) : d_model_(d_model), num_heads_(num_heads), d_k_(d_model / num_heads), seq_len_(seq_len), scale_(Scalar{1} / std::sqrt(static_cast(d_model / num_heads))), - w_q_(engine, d_model, d_model), - w_k_(engine, d_model, d_model), - w_v_(engine, d_model, d_model), - w_o_(engine, d_model, d_model), + w_q_(d_model, d_model), + w_k_(d_model, d_model), + w_v_(d_model, d_model), + w_o_(d_model, d_model), use_rope_(pos_enc == PosEncodingType::RoPE), - rope_(engine, d_model / num_heads) + rope_(d_model / num_heads) { NN_ASSERT(d_model % num_heads == 0, "AttentionBase: d_model must be divisible by num_heads"); } + [[nodiscard]] Result init(ComputeEngine& engine) override + { + auto r1 = w_q_.init(engine); if (!r1) return std::unexpected(r1.error()); + auto r2 = w_k_.init(engine); if (!r2) return std::unexpected(r2.error()); + auto r3 = w_v_.init(engine); if (!r3) return std::unexpected(r3.error()); + auto r4 = w_o_.init(engine); if (!r4) return std::unexpected(r4.error()); + if (use_rope_) + { + auto r5 = rope_.init(engine); if (!r5) return std::unexpected(r5.error()); + } + return {}; + } + std::vector parameters() override { auto p = w_q_.parameters(); @@ -2156,10 +2197,9 @@ class AttentionBase : public Layer class MultiHeadAttention final : public AttentionBase { public: - MultiHeadAttention(ComputeEngine& engine, - std::size_t d_model, std::size_t num_heads, + MultiHeadAttention(std::size_t d_model, std::size_t num_heads, std::size_t seq_len = 0) - : AttentionBase(engine, d_model, num_heads, seq_len) {} + : AttentionBase(d_model, num_heads, seq_len) {} }; // ══════════════════════════════════════════════════════════════════════════ @@ -2185,7 +2225,7 @@ class PositionalEncoding final : public Layer Tensor encoding_cache_; std::size_t cached_total_ = 0; - void rebuild_encoding(ComputeEngine& engine, std::size_t total_len) + [[nodiscard]] Result rebuild_encoding(ComputeEngine& engine, std::size_t total_len) { const std::size_t base_len = (tile_size_ > 0) ? tile_size_ : total_len; const std::size_t batch = (tile_size_ > 0) ? total_len / tile_size_ : 1; @@ -2219,14 +2259,14 @@ class PositionalEncoding final : public Layer } } auto r = engine.from_matrix(enc); - NN_ASSERT(r, r ? "" : r.error().message.c_str()); + if (!r) return std::unexpected(r.error()); encoding_cache_ = std::move(*r); cached_total_ = total_len; + return {}; } public: - PositionalEncoding(ComputeEngine& /*engine*/, - std::size_t d_model, std::size_t max_len = 5000, + PositionalEncoding(std::size_t d_model, std::size_t max_len = 5000, std::size_t tile_size = 0) : d_model_(d_model), max_len_(max_len), tile_size_(tile_size) { @@ -2244,7 +2284,10 @@ class PositionalEncoding final : public Layer return std::unexpected(Error{"PE forward: seq_len exceeds max_len"}); if (cached_total_ != total_len) - rebuild_encoding(engine, total_len); + { + auto r = rebuild_encoding(engine, total_len); + if (!r) return std::unexpected(r.error()); + } return engine.elementwise_binary(BinaryOp::Add, input, encoding_cache_); } @@ -2269,15 +2312,21 @@ class FeedForward final : public Layer bool use_swiglu_ = false; public: - FeedForward(ComputeEngine& engine, - std::size_t d_model, std::size_t d_ff, + FeedForward(std::size_t d_model, std::size_t d_ff, ActivationType activation = ActivationType::GeLU) - : fc1_(engine, d_model, + : fc1_(d_model, activation == ActivationType::SwiGLU ? 2 * d_ff : d_ff), - fc2_(engine, d_ff, d_model), + fc2_(d_ff, d_model), swiglu_(d_ff), use_swiglu_(activation == ActivationType::SwiGLU) {} + [[nodiscard]] Result init(ComputeEngine& engine) override + { + auto r1 = fc1_.init(engine); if (!r1) return std::unexpected(r1.error()); + auto r2 = fc2_.init(engine); if (!r2) return std::unexpected(r2.error()); + return {}; + } + std::vector parameters() override { auto p = fc1_.parameters(); @@ -2376,13 +2425,21 @@ class TransformerEncoderLayer final : public Layer Tensor residual2_cache_; public: - TransformerEncoderLayer(ComputeEngine& engine, - std::size_t d_model, std::size_t num_heads, + TransformerEncoderLayer(std::size_t d_model, std::size_t num_heads, std::size_t d_ff, std::size_t seq_len = 0) - : self_attn_(engine, d_model, num_heads, seq_len), - norm1_(engine, d_model), - ff_(engine, d_model, d_ff), - norm2_(engine, d_model) {} + : self_attn_(d_model, num_heads, seq_len), + norm1_(d_model), + ff_(d_model, d_ff), + norm2_(d_model) {} + + [[nodiscard]] Result init(ComputeEngine& engine) override + { + auto r1 = self_attn_.init(engine); if (!r1) return std::unexpected(r1.error()); + auto r2 = norm1_.init(engine); if (!r2) return std::unexpected(r2.error()); + auto r3 = ff_.init(engine); if (!r3) return std::unexpected(r3.error()); + auto r4 = norm2_.init(engine); if (!r4) return std::unexpected(r4.error()); + return {}; + } std::vector parameters() override { @@ -2533,24 +2590,32 @@ class TransformerEncoder final : public Layer Tensor ones_row_; // (1, num_patches) 全1,用于 backward 池化梯度广播 public: - TransformerEncoder(ComputeEngine& engine, - std::size_t d_model, std::size_t num_heads, + TransformerEncoder(std::size_t d_model, std::size_t num_heads, std::size_t d_ff, std::size_t num_layers, std::size_t num_patches) : d_model_(d_model), num_patches_(num_patches), inv_num_patches_(Scalar{1} / static_cast(num_patches)), // PE 启用 tiling:每个样本独立使用 (d_model, num_patches) 的编码 - pos_encoding_(engine, d_model, num_patches, num_patches) + pos_encoding_(d_model, num_patches, num_patches) { // 所有 EncoderLayer 传入 seq_len=num_patches,启用 MHA 批量化路径 for (std::size_t i = 0; i < num_layers; ++i) - layers_.emplace_back(engine, d_model, num_heads, d_ff, num_patches); + layers_.emplace_back(d_model, num_heads, d_ff, num_patches); + } + [[nodiscard]] Result init(ComputeEngine& engine) override + { + for (auto& layer : layers_) + { + auto r = layer.init(engine); + if (!r) return std::unexpected(r.error()); + } // 预创建 ones_row_ (1, num_patches) 全1,用于 backward 广播 Matrix ones_cpu(1, num_patches_, Scalar{1}); auto or_t = engine.from_matrix(ones_cpu); - NN_ASSERT(or_t, or_t ? "" : or_t.error().message.c_str()); + if (!or_t) return std::unexpected(or_t.error()); ones_row_ = std::move(*or_t); + return {}; } std::vector parameters() override @@ -2677,20 +2742,24 @@ class PatchEmbedding final : public Layer Tensor input_cache_; public: - PatchEmbedding(ComputeEngine& engine, - std::size_t img_size, std::size_t patch_size, + PatchEmbedding(std::size_t img_size, std::size_t patch_size, std::size_t d_model) : img_size_(img_size), patch_size_(patch_size), grid_size_(img_size / patch_size), num_patches_(grid_size_ * grid_size_), patch_dim_(patch_size * patch_size), d_model_(d_model), - projection_(engine, patch_dim_, d_model) + projection_(patch_dim_, d_model) { NN_ASSERT(img_size % patch_size == 0, "PatchEmbedding: img_size must be divisible by patch_size"); } + [[nodiscard]] Result init(ComputeEngine& engine) override + { + return projection_.init(engine); + } + [[nodiscard]] std::size_t num_patches() const noexcept { return num_patches_; } [[nodiscard]] std::size_t d_model() const noexcept { return d_model_; } @@ -2897,20 +2966,21 @@ class CausalSelfAttention final : public AttentionBase } } - void ensure_mask_(ComputeEngine& engine, std::size_t batch, std::size_t seq_len) + [[nodiscard]] Result ensure_mask_(ComputeEngine& engine, std::size_t batch, std::size_t seq_len) { // 用 (batch, seq_len) 作为缓存键 if (mask_cached_batch_ == batch && mask_cached_seq_ == seq_len) - return; + return {}; // 纯因果掩码:委托 build_attention_mask(doc_ids 为空) auto mask = build_attention_mask(batch, seq_len, num_heads_, use_alibi_, slopes_); auto r = engine.from_matrix(mask); - NN_ASSERT(r, r ? "" : r.error().message.c_str()); + if (!r) return std::unexpected(r.error()); mask_cache_ = std::move(*r); mask_cached_batch_ = batch; mask_cached_seq_ = seq_len; + return {}; } public: @@ -2944,7 +3014,10 @@ class CausalSelfAttention final : public AttentionBase if (!mt) return std::unexpected(mt.error()); return engine.elementwise_binary(BinaryOp::Add, scores, *mt); } - ensure_mask_(engine, batch, seq); + { + auto r = ensure_mask_(engine, batch, seq); + if (!r) return std::unexpected(r.error()); + } return engine.elementwise_binary(BinaryOp::Add, scores, mask_cache_); } @@ -3014,12 +3087,11 @@ class CausalSelfAttention final : public AttentionBase } public: - CausalSelfAttention(ComputeEngine& engine, - std::size_t d_model, std::size_t num_heads, + CausalSelfAttention(std::size_t d_model, std::size_t num_heads, std::size_t /*max_len*/ = 1024, std::size_t seq_len = 0, PosEncodingType pos_enc = PosEncodingType::Learned) - : AttentionBase(engine, d_model, num_heads, seq_len, pos_enc), + : AttentionBase(d_model, num_heads, seq_len, pos_enc), use_alibi_(pos_enc == PosEncodingType::ALiBi) { if (use_alibi_) @@ -3053,17 +3125,25 @@ class GPTBlock final : public Layer std::vector offload_offsets_; // 各激活在 slab 中的 float 偏移 public: - GPTBlock(ComputeEngine& engine, - std::size_t d_model, std::size_t num_heads, + GPTBlock(std::size_t d_model, std::size_t num_heads, std::size_t d_ff, std::size_t max_len = 1024, std::size_t seq_len = 0, PosEncodingType pos_enc = PosEncodingType::Learned, ActivationType activation = ActivationType::GeLU, NormType norm_type = NormType::LayerNorm) - : self_attn_(engine, d_model, num_heads, max_len, seq_len, pos_enc), - norm1_(make_norm_layer(engine, d_model, norm_type)), - ff_(engine, d_model, d_ff, activation), - norm2_(make_norm_layer(engine, d_model, norm_type)) {} + : self_attn_(d_model, num_heads, max_len, seq_len, pos_enc), + norm1_(make_norm_layer(d_model, norm_type)), + ff_(d_model, d_ff, activation), + norm2_(make_norm_layer(d_model, norm_type)) {} + + [[nodiscard]] Result init(ComputeEngine& engine) override + { + auto r1 = self_attn_.init(engine); if (!r1) return std::unexpected(r1.error()); + if (norm1_) { auto r = norm1_->init(engine); if (!r) return std::unexpected(r.error()); } + auto r2 = ff_.init(engine); if (!r2) return std::unexpected(r2.error()); + if (norm2_) { auto r = norm2_->init(engine); if (!r) return std::unexpected(r.error()); } + return {}; + } std::vector parameters() override { @@ -3342,6 +3422,9 @@ class PositionEncoder ComputeEngine& engine, const Tensor& grad_T, std::size_t batch, std::size_t seq) = 0; + // 初始化(引擎相关操作,如创建张量等) + [[nodiscard]] virtual Result init(ComputeEngine& /*engine*/) { return {}; } + [[nodiscard]] virtual std::vector parameters() { return {}; } [[nodiscard]] virtual std::vector param_gradients() { return {}; } }; @@ -3362,20 +3445,21 @@ class AdditivePositionEncoder : public PositionEncoder std::size_t pos_indices_seq_ = 0; // 缓存键:seq_len // 用给定的位置编码矩阵初始化 pos_emb_(learnable 时额外分配梯度) - void init_(ComputeEngine& engine, Matrix&& pe, bool learnable) + [[nodiscard]] Result init_(ComputeEngine& engine, Matrix&& pe, bool learnable) { const std::size_t rows = pe.rows(); const std::size_t cols = pe.cols(); learnable_ = learnable; auto pe_r = engine.from_matrix(pe); - NN_ASSERT(pe_r, pe_r ? "" : pe_r.error().message.c_str()); + if (!pe_r) return std::unexpected(pe_r.error()); pos_emb_ = std::move(*pe_r); if (learnable_) { grad_pos_emb_ = engine.create_tensor(rows, cols); auto r = engine.zero(grad_pos_emb_); - NN_ASSERT(r, r ? "" : r.error().message.c_str()); + if (!r) return std::unexpected(r.error()); } + return {}; } // 确保 pos_indices 缓存有效(batch-major:i = b*seq + t → position=t) @@ -3460,16 +3544,23 @@ class AdditivePositionEncoder : public PositionEncoder // 与 token_emb_ 共享同一 rng 序列(保持与旧实现完全一致的可复现性)。 class LearnedPositionEncoder final : public AdditivePositionEncoder { + std::size_t d_model_; + std::size_t seq_len_; + std::mt19937_64* rng_; // 非拥有指针 + std::normal_distribution* dist_; // 非拥有指针 + public: - LearnedPositionEncoder(ComputeEngine& engine, std::size_t d_model, - std::size_t seq_len, + LearnedPositionEncoder(std::size_t d_model, std::size_t seq_len, std::mt19937_64& rng, std::normal_distribution& dist) + : d_model_(d_model), seq_len_(seq_len), rng_(&rng), dist_(&dist) {} + + [[nodiscard]] Result init(ComputeEngine& engine) override { - Matrix pe(seq_len, d_model); + Matrix pe(seq_len_, d_model_); auto pe_s = pe.span(); - for (std::size_t i = 0; i < pe.size(); ++i) pe_s[i] = dist(rng); - init_(engine, std::move(pe), /*learnable=*/true); + for (std::size_t i = 0; i < pe.size(); ++i) pe_s[i] = (*dist_)(*rng_); + return init_(engine, std::move(pe), /*learnable=*/true); } }; @@ -3477,20 +3568,25 @@ class LearnedPositionEncoder final : public AdditivePositionEncoder // PE(pos, 2i) = sin(pos/10000^(2i/d)), PE(pos, 2i+1) = cos(...) class SinusoidalPositionEncoder final : public AdditivePositionEncoder { + std::size_t d_model_; + std::size_t seq_len_; + public: - SinusoidalPositionEncoder(ComputeEngine& engine, std::size_t d_model, - std::size_t seq_len) + SinusoidalPositionEncoder(std::size_t d_model, std::size_t seq_len) + : d_model_(d_model), seq_len_(seq_len) {} + + [[nodiscard]] Result init(ComputeEngine& engine) override { - Matrix pe(seq_len, d_model); + Matrix pe(seq_len_, d_model_); auto pe_s = pe.span(); - for (std::size_t pos = 0; pos < seq_len; ++pos) - for (std::size_t i = 0; i < d_model; ++i) + for (std::size_t pos = 0; pos < seq_len_; ++pos) + for (std::size_t i = 0; i < d_model_; ++i) { Scalar angle = static_cast(pos) / - std::pow(Scalar{10000}, static_cast(2 * (i / 2)) / static_cast(d_model)); - pe_s[pos * d_model + i] = (i % 2 == 0) ? std::sin(angle) : std::cos(angle); + std::pow(Scalar{10000}, static_cast(2 * (i / 2)) / static_cast(d_model_)); + pe_s[pos * d_model_ + i] = (i % 2 == 0) ? std::sin(angle) : std::cos(angle); } - init_(engine, std::move(pe), /*learnable=*/false); + return init_(engine, std::move(pe), /*learnable=*/false); } }; @@ -3566,52 +3662,76 @@ class GPTModel final : public Layer bool activation_offload_ = false; public: - GPTModel(ComputeEngine& engine, - std::size_t vocab_size, std::size_t d_model, std::size_t seq_len, + GPTModel(std::size_t vocab_size, std::size_t d_model, std::size_t seq_len, std::size_t num_heads, std::size_t d_ff, std::size_t num_layers, PosEncodingType pos_enc_type = PosEncodingType::Learned, ActivationType activation = ActivationType::GeLU, NormType norm_type = NormType::LayerNorm) : vocab_size_(vocab_size), d_model_(d_model), seq_len_(seq_len), - ln_f_(make_norm_layer(engine, d_model, norm_type)), - lm_head_(engine, d_model, vocab_size) + ln_f_(make_norm_layer(d_model, norm_type)), + lm_head_(d_model, vocab_size) { - // 初始化 token_emb_ - Matrix te(vocab_size, d_model); - constexpr Scalar emb_init_std = 0.02; - std::mt19937_64 rng{42}; - std::normal_distribution dist(0.0, emb_init_std); - auto te_s = te.span(); - for (std::size_t i = 0; i < te.size(); ++i) te_s[i] = dist(rng); - - auto te_r = engine.from_matrix(te); - NN_ASSERT(te_r, te_r ? "" : te_r.error().message.c_str()); - token_emb_ = std::move(*te_r); - - grad_token_emb_ = engine.create_tensor(vocab_size, d_model); - { auto r1 = engine.zero(grad_token_emb_); NN_ASSERT(r1, r1 ? "" : r1.error().message.c_str()); } + blocks_.reserve(num_layers); + for (std::size_t i = 0; i < num_layers; ++i) + blocks_.emplace_back(d_model, num_heads, d_ff, seq_len, seq_len, + pos_enc_type, activation, norm_type); // 初始化位置编码器(Learned / Sinusoidal / ALiBi / RoPE) - // 注意:Learned 与 token_emb_ 共享同一 rng,保证与旧实现完全一致的可复现性。 switch (pos_enc_type) { case PosEncodingType::Learned: - pos_encoder_ = std::make_unique( - engine, d_model, seq_len, rng, dist); + { + std::mt19937_64 rng{42}; + std::normal_distribution dist(0.0, 0.02); + pos_encoder_ = std::make_unique( + d_model, seq_len, rng, dist); + } break; case PosEncodingType::Sinusoidal: pos_encoder_ = std::make_unique( - engine, d_model, seq_len); + d_model, seq_len); break; default: // ALiBi / RoPE:位置信息由注意力层注入 pos_encoder_ = std::make_unique(); break; } + } - // GPTBlock 传入 seq_len、pos_enc_type、activation 和 norm_type - for (std::size_t i = 0; i < num_layers; ++i) - blocks_.emplace_back(engine, d_model, num_heads, d_ff, seq_len, seq_len, - pos_enc_type, activation, norm_type); + [[nodiscard]] Result init(ComputeEngine& engine) override + { + // 初始化 token_emb_ + Matrix te(vocab_size_, d_model_); + constexpr Scalar emb_init_std = 0.02; + std::mt19937_64 rng{42}; + std::normal_distribution dist(0.0, emb_init_std); + auto te_s = te.span(); + for (std::size_t i = 0; i < te.size(); ++i) te_s[i] = dist(rng); + + auto te_r = engine.from_matrix(te); + if (!te_r) return std::unexpected(te_r.error()); + token_emb_ = std::move(*te_r); + + grad_token_emb_ = engine.create_tensor(vocab_size_, d_model_); + { auto r1 = engine.zero(grad_token_emb_); if (!r1) return std::unexpected(r1.error()); } + + // 初始化子层 + if (pos_encoder_) + { + auto r = pos_encoder_->init(engine); + if (!r) return std::unexpected(r.error()); + } + for (auto& block : blocks_) + { + auto r = block.init(engine); + if (!r) return std::unexpected(r.error()); + } + if (ln_f_) + { + auto r = ln_f_->init(engine); + if (!r) return std::unexpected(r.error()); + } + { auto r = lm_head_.init(engine); if (!r) return std::unexpected(r.error()); } + return {}; } std::vector parameters() override diff --git a/include/neuralnet.cpp/compute_optimizer.hpp b/include/neuralnet.cpp/compute_optimizer.hpp index ea314472..b9ad2097 100644 --- a/include/neuralnet.cpp/compute_optimizer.hpp +++ b/include/neuralnet.cpp/compute_optimizer.hpp @@ -56,19 +56,15 @@ class Optimizer } // 为每个参数创建同形状的零初始化 Tensor(供 Momentum/Adam/Muon 复用) - // TODO(1.1, S2): 本方法及各 Layer 构造函数/LayerNorm/RMSNorm 构造器/RoPE::rebuild - // 中的 `NN_ASSERT(, ...)` 在 Release(NDEBUG) 下会吞掉引擎错误。 - // 正确做法(符合项目 Result/expected 规范):把这些 void/构造函数改为返回 - // Result,并将 `NN_ASSERT(r, ...)` 改为 `if (!r) return std::unexpected(r.error());` - // (或前置条件用 NN_REQUIRE)。因属签名级重构,1.0.0 暂缓。 - std::vector create_zero_buffers_() const + [[nodiscard]] Result> create_zero_buffers_() const { std::vector buffers; buffers.reserve(params_.size()); for (auto& p : params_) { auto buf = engine_.create_tensor(p.get().rows(), p.get().cols()); - { auto r = engine_.zero(buf); NN_ASSERT(r, r ? "" : r.error().message.c_str()); } + auto r = engine_.zero(buf); + if (!r) return std::unexpected(r.error()); buffers.push_back(std::move(buf)); } return buffers; @@ -206,8 +202,17 @@ class SGDWithMomentum : public Optimizer std::vector grads, Scalar lr, Scalar beta = 0.9) : Optimizer(engine, std::move(params), std::move(grads)), - lr_(lr), beta_(beta), - velocities_(create_zero_buffers_()) {} + lr_(lr), beta_(beta) + { + auto v_r = create_zero_buffers_(); + if (!v_r) + { + std::fprintf(stderr, "SGDWithMomentum init failed: %s\n", + v_r.error().message.c_str()); + std::abort(); + } + velocities_ = std::move(*v_r); + } void set_lr(Scalar lr) override { lr_ = lr; } @@ -310,8 +315,22 @@ class Adam : public Optimizer void init_moments_() { - m_ = create_zero_buffers_(); - v_ = create_zero_buffers_(); + auto m_r = create_zero_buffers_(); + if (!m_r) + { + std::fprintf(stderr, "Adam init (m_) failed: %s\n", + m_r.error().message.c_str()); + std::abort(); + } + m_ = std::move(*m_r); + auto v_r = create_zero_buffers_(); + if (!v_r) + { + std::fprintf(stderr, "Adam init (v_) failed: %s\n", + v_r.error().message.c_str()); + std::abort(); + } + v_ = std::move(*v_r); } // 偏差修正系数(Adam/AdamW 共用):给定下一步步数 t_next, @@ -539,8 +558,17 @@ class Muon : public Optimizer Scalar ns_eps = 1e-7f) : Optimizer(engine, std::move(params), std::move(grads)), lr_(lr), momentum_(momentum), nesterov_(nesterov), - ns_steps_(ns_steps), ns_eps_(ns_eps), - velocities_(create_zero_buffers_()) {} + ns_steps_(ns_steps), ns_eps_(ns_eps) + { + auto v_r = create_zero_buffers_(); + if (!v_r) + { + std::fprintf(stderr, "Muon init failed: %s\n", + v_r.error().message.c_str()); + std::abort(); + } + velocities_ = std::move(*v_r); + } void set_lr(Scalar lr) override { lr_ = lr; } diff --git a/include/neuralnet.cpp/domain_cnn.hpp b/include/neuralnet.cpp/domain_cnn.hpp index b08b9f55..95571e46 100644 --- a/include/neuralnet.cpp/domain_cnn.hpp +++ b/include/neuralnet.cpp/domain_cnn.hpp @@ -87,15 +87,19 @@ inline const std::vector MNIST_CNN_FC = {120, 10}; if (cv.kernel > h + 2 * cv.padding || cv.kernel > w + 2 * cv.padding) return std::unexpected(Error{"CNN: kernel larger than input spatial size"}); - model.add(engine, c, cv.out_channels, cv.kernel, - cv.stride, cv.padding, h, w); + { + auto r = model.add(c, cv.out_channels, cv.kernel, + cv.stride, cv.padding, h, w); + if (!r) return std::unexpected(r.error()); + } h = conv_out_size(h, cv.kernel, cv.stride, cv.padding); w = conv_out_size(w, cv.kernel, cv.stride, cv.padding); c = cv.out_channels; if (cfg.pool > 0) { - model.add(c, h, w, cfg.pool); + auto r = model.add(c, h, w, cfg.pool); + if (!r) return std::unexpected(r.error()); h = (h - cfg.pool) / cfg.pool + 1; w = (w - cfg.pool) / cfg.pool + 1; } @@ -106,11 +110,20 @@ inline const std::vector MNIST_CNN_FC = {120, 10}; return std::unexpected(Error{"CNN: flattened size is zero"}); // 全连接头:Linear(flatten → fc_dims[0]) → ReLU → ... → Linear(→ num_classes) - model.add(engine, flattened, cfg.fc_dims[0]); + { + auto r = model.add(flattened, cfg.fc_dims[0]); + if (!r) return std::unexpected(r.error()); + } for (std::size_t i = 1; i < cfg.fc_dims.size(); ++i) { - model.add(); - model.add(engine, cfg.fc_dims[i - 1], cfg.fc_dims[i]); + { + auto r = model.add(); + if (!r) return std::unexpected(r.error()); + } + { + auto r = model.add(cfg.fc_dims[i - 1], cfg.fc_dims[i]); + if (!r) return std::unexpected(r.error()); + } } return model; } diff --git a/include/neuralnet.cpp/domain_gpt.hpp b/include/neuralnet.cpp/domain_gpt.hpp index 29d26276..423bb5c7 100644 --- a/include/neuralnet.cpp/domain_gpt.hpp +++ b/include/neuralnet.cpp/domain_gpt.hpp @@ -56,9 +56,12 @@ struct GptConfig { return std::unexpected(Error{"GPT d_model must be divisible by num_heads"}); Model model(engine); - model.add(engine, cfg.vocab_size, cfg.d_model, cfg.seq_len, - cfg.num_heads, cfg.d_ff, cfg.num_layers, cfg.pos_enc, - cfg.activation, cfg.norm_type); + { + auto r = model.add(cfg.vocab_size, cfg.d_model, cfg.seq_len, + cfg.num_heads, cfg.d_ff, cfg.num_layers, cfg.pos_enc, + cfg.activation, cfg.norm_type); + if (!r) return std::unexpected(r.error()); + } return model; } diff --git a/include/neuralnet.cpp/domain_mnist.hpp b/include/neuralnet.cpp/domain_mnist.hpp index 51eb8b33..b5d756a4 100644 --- a/include/neuralnet.cpp/domain_mnist.hpp +++ b/include/neuralnet.cpp/domain_mnist.hpp @@ -110,12 +110,21 @@ inline const std::vector MNIST_LAYER_DIMS = { std::size_t in_dim = layer_dims[i]; std::size_t out_dim = layer_dims[i + 1]; - model.add(engine, in_dim, out_dim); + { + auto r = model.add(in_dim, out_dim); + if (!r) return std::unexpected(r.error()); + } if (i < layer_dims.size() - 2) { - model.add_layer(make_norm_layer(engine, out_dim, norm_type)) - .add(); + { + auto r = model.add_layer(make_norm_layer(out_dim, norm_type)); + if (!r) return std::unexpected(r.error()); + } + { + auto r = model.add(); + if (!r) return std::unexpected(r.error()); + } } } return model; @@ -146,9 +155,18 @@ inline const std::vector MNIST_LAYER_DIMS = { const std::size_t num_patches = grid_size * grid_size; Model model(engine); - model.add(engine, img_size, patch_size, d_model); - model.add(engine, d_model, num_heads, d_ff, num_layers, num_patches); - model.add(engine, d_model, MNIST_NUM_CLASSES); + { + auto r = model.add(img_size, patch_size, d_model); + if (!r) return std::unexpected(r.error()); + } + { + auto r = model.add(d_model, num_heads, d_ff, num_layers, num_patches); + if (!r) return std::unexpected(r.error()); + } + { + auto r = model.add(d_model, MNIST_NUM_CLASSES); + if (!r) return std::unexpected(r.error()); + } return model; } diff --git a/include/neuralnet.cpp/model_container.hpp b/include/neuralnet.cpp/model_container.hpp index a2fd5fa9..3d9b3694 100644 --- a/include/neuralnet.cpp/model_container.hpp +++ b/include/neuralnet.cpp/model_container.hpp @@ -58,22 +58,28 @@ class Model } // ── 构建网络(通用模板接口) ──────────────────────────────────────── - // 用法:model.add(engine, in, out) / model.add() / ... + // 用法:model.add(in, out) / model.add() / ... + // 注意:构造后自动调用 Layer::init(),无需手动调用。 // 注意:MultiHeadAttention 的构造函数是 protected(设计为基类, // 由 TransformerEncoderLayer / CausalSelfAttention 内部组合), // 因此不直接通过 add 添加。 template - Model& add(Args&&... args) + Result add(Args&&... args) { - layers_.emplace_back(std::make_unique(std::forward(args)...)); - return *this; + auto layer = std::make_unique(std::forward(args)...); + auto r = layer->init(engine()); + if (!r) return std::unexpected(r.error()); + layers_.emplace_back(std::move(layer)); + return {}; } // 添加已由工厂构造的 Layer(如 make_norm_layer 按 NormType 创建归一化层) - Model& add_layer(std::unique_ptr layer) + Result add_layer(std::unique_ptr layer) { + auto r = layer->init(engine()); + if (!r) return std::unexpected(r.error()); layers_.emplace_back(std::move(layer)); - return *this; + return {}; } // ── 访问 ───────────────────────────────────────────────────────────── diff --git a/include/neuralnet.cpp/model_serialization.hpp b/include/neuralnet.cpp/model_serialization.hpp index 237f947a..2b92ff75 100644 --- a/include/neuralnet.cpp/model_serialization.hpp +++ b/include/neuralnet.cpp/model_serialization.hpp @@ -528,11 +528,14 @@ inline void apply_spec_version_defaults(KeyValueRecord &kv, uint32_t version) // 非可学习状态(如 BatchNorm 的 running_mean/running_var),紧跟在参数之后。 // 旧文件(无额外状态)读到 EOF 时保持默认(running_mean=0, running_var=1)。 - // TODO(1.1, M2): 上述"读到 EOF 保持默认"的语义与下方实现不符——read_matrix - // 失败时这里直接 return 错误而非回退默认值。需统一为按版本回退。 + // 注意:读取前用 peek() 检查 EOF,而非依赖 read_matrix 读失败后回退—— + // read_matrix 在 shape 不匹配时也会报错,不应吞掉真正的损坏。 auto extras = model.extra_state(); for (auto& e_tensor : extras) { + // 已经没有更多数据 → 保持默认值(零/一),跳过剩余 extras + if (ifs.peek() == EOF) break; + // 先读入临时 Matrix(按状态 Tensor 的形状) Matrix tmp(e_tensor.get().rows(), e_tensor.get().cols()); auto mr = detail::read_matrix(ifs, tmp); diff --git a/report.html b/report.html index 6330fdab..48e84c0a 100644 --- a/report.html +++ b/report.html @@ -59,7 +59,7 @@

🌸 屎山代码分析报告 🌸

67.7
-
总体评分: 67.66 / 100
+
总体评分: 67.69 / 100
😷 屎气扑鼻 - 代码开始散发气味,谨慎维护
@@ -73,7 +73,7 @@

🌸 屎山代码分析报告 🌸

已跳过
-
903ms
+
935ms
耗时
@@ -85,17 +85,17 @@

📊 评分指标详情

指标摘要评分状态 - 循环复杂度20.6%✓ - 认知复杂度16.8%✅ + 循环复杂度20.7%✓ + 认知复杂度17.0%✅ 嵌套深度10.2%✅ 函数长度6.0%✅ 文件长度12.6%✅ 参数数量19.5%✅ 代码重复6.7%✅ - 结构分析13.2%✅ + 结构分析13.5%✅ 错误处理10.1%✅ - 注释比例28.1%✓ - 命名规范10.3%✅ + 注释比例28.3%✓ + 命名规范10.2%✅ @@ -127,7 +127,7 @@

📋 最屎代码排行榜

复杂度问题: 21其他问题: 7重复问题: 6结构问题: 12错误处理问题: 4注释问题: 1
-
  • forward() L651: 复杂度: 12
  • forward() L1325: 复杂度: 32
  • backward() L1477: 复杂度: 29
  • forward_step() L1631: 复杂度: 20
  • export_activations() L2717: 复杂度: 12
+
  • forward() L651: 复杂度: 12
  • forward() L1325: 复杂度: 32
  • backward() L1477: 复杂度: 29
  • forward_step() L1631: 复杂度: 20
  • export_activations() L2717: 复杂度: 13
@@ -167,7 +167,7 @@

📋 最屎代码排行榜

复杂度问题: 31其他问题: 17结构问题: 5命名问题: 10
-
  • initialize() L215: 复杂度: 11
  • initialize() L1021: 复杂度: 45
  • bmm_reduce_gpu() L1384: 复杂度: 14
  • bmm_denom_gpu() L1423: 复杂度: 14
  • bmm_apply_gpu() L1463: 复杂度: 14
+
  • initialize() L215: 复杂度: 11
  • initialize() L1042: 复杂度: 45
  • bmm_reduce_gpu() L1405: 复杂度: 14
  • bmm_denom_gpu() L1444: 复杂度: 14
  • bmm_apply_gpu() L1484: 复杂度: 14
diff --git a/tools/scan_exprs.cpp b/tools/scan_exprs.cpp index c981b64e..6d8184d8 100644 --- a/tools/scan_exprs.cpp +++ b/tools/scan_exprs.cpp @@ -47,7 +47,7 @@ int main(int argc, char* argv[]) for (const std::size_t dk : {std::size_t{16}, std::size_t{32}, std::size_t{64}, std::size_t{128}}) { - nn::RotaryEmbedding rope(engine, dk); + nn::RotaryEmbedding rope(dk); nn::Tensor q = nn::Tensor::cpu(2 * dk, 8); // rows 为 dk 的整数倍 (void)rope.apply(engine, q, /*seq=*/8, /*backward=*/false); (void)rope.apply(engine, q, /*seq=*/8, /*backward=*/true); @@ -85,7 +85,8 @@ int main(int argc, char* argv[]) // backward: grad_x 列归约表达式 + grad_gamma 行归约表达式 { const std::size_t F = 8, B = 5; - nn::RMSNorm rms(engine, F); + nn::RMSNorm rms(F); + (void)rms.init(engine); nn::Tensor input = nn::Tensor::cpu(F, B); (void)rms.forward(engine, input); // 填充 normed/rms_inv 缓存 + 登记 fwd 结构 nn::Tensor grad = nn::Tensor::cpu(F, B); @@ -97,7 +98,8 @@ int main(int argc, char* argv[]) // backward: grad_x 列归约表达式 + grad_gamma/grad_beta 行归约表达式 { const std::size_t F = 8, B = 5; - nn::LayerNorm ln(engine, F); + nn::LayerNorm ln(F); + (void)ln.init(engine); nn::Tensor input = nn::Tensor::cpu(F, B); (void)ln.forward(engine, input); nn::Tensor grad = nn::Tensor::cpu(F, B); @@ -111,7 +113,8 @@ int main(int argc, char* argv[]) // backward 为独立表达式(非录制段),单独登记。 { const std::size_t F = 8, B = 5; - nn::FusedChainLayer chain(engine, F); + nn::FusedChainLayer chain(F); + (void)chain.init(engine); nn::Tensor input = nn::Tensor::cpu(F, B); (void)chain.forward(engine, input); nn::Tensor grad = nn::Tensor::cpu(F, B);