diff --git a/slang.sln b/slang.sln index fcce05cf93..8adbb38220 100644 --- a/slang.sln +++ b/slang.sln @@ -1,36 +1,36 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core", "source\core\core.vcxproj", "{F9BE7957-8399-899E-0C49-E714FDDD4B65}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{EB5FC2C6-D72D-B6CC-C0C1-26F3AC2E9231}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hello-world", "examples\hello-world\hello-world.vcxproj", "{5CF41E7B-4883-A844-F1A1-BC3FDD0FB9EA}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "model-viewer", "examples\model-viewer\model-viewer.vcxproj", "{639B13F2-CF07-CFEC-98FB-664A0427F154}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "slang", "source\slang\slang.vcxproj", "{DB00DA62-0533-4AFD-B59F-A67D5B3A0808}" - ProjectSection(ProjectDependencies) = postProject - {66174227-8541-41FC-A6DF-4764FC66F78E} = {66174227-8541-41FC-A6DF-4764FC66F78E} - EndProjectSection +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core", "source\core\core.vcxproj", "{F9BE7957-8399-899E-0C49-E714FDDD4B65}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "slang-glslang", "source\slang-glslang\slang-glslang.vcxproj", "{C495878A-832C-485B-B347-0998A90CC936}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{FD47AE19-69FD-260F-F2F1-20E65EA61D13}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "slangc", "source\slangc\slangc.vcxproj", "{D56CBCEB-1EB5-4CA8-AEC4-48EA35ED61C7}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "slang-generate", "tools\slang-generate\slang-generate.vcxproj", "{66174227-8541-41FC-A6DF-4764FC66F78E}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{FD47AE19-69FD-260F-F2F1-20E65EA61D13}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "slang-test", "tools\slang-test\slang-test.vcxproj", "{0C768A18-1D25-4000-9F37-DA5FE99E3B64}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gfx", "tools\gfx\gfx.vcxproj", "{222F7498-B40C-4F3F-A704-DDEB91A4484A}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "slang-reflection-test", "tools\slang-reflection-test\slang-reflection-test.vcxproj", "{22C45F4F-FB6B-4535-BED1-D3F5D0C71047}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "slang-eval-test", "tools\slang-eval-test\slang-eval-test.vcxproj", "{205FCAB9-A13F-4980-86FA-F6221A7095EE}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "render-test", "tools\render-test\render-test.vcxproj", "{96610759-07B9-4EEB-A974-5C634A2E742B}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "slang-eval-test", "tools\slang-eval-test\slang-eval-test.vcxproj", "{205FCAB9-A13F-4980-86FA-F6221A7095EE}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gfx", "tools\gfx\gfx.vcxproj", "{222F7498-B40C-4F3F-A704-DDEB91A4484A}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "slang-generate", "tools\slang-generate\slang-generate.vcxproj", "{66174227-8541-41FC-A6DF-4764FC66F78E}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "slangc", "source\slangc\slangc.vcxproj", "{D56CBCEB-1EB5-4CA8-AEC4-48EA35ED61C7}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "slang-reflection-test", "tools\slang-reflection-test\slang-reflection-test.vcxproj", "{22C45F4F-FB6B-4535-BED1-D3F5D0C71047}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "slang", "source\slang\slang.vcxproj", "{DB00DA62-0533-4AFD-B59F-A67D5B3A0808}" + ProjectSection(ProjectDependencies) = postProject + {66174227-8541-41FC-A6DF-4764FC66F78E} = {66174227-8541-41FC-A6DF-4764FC66F78E} + EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "slang-test", "tools\slang-test\slang-test.vcxproj", "{0C768A18-1D25-4000-9F37-DA5FE99E3B64}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "slang-glslang", "source\slang-glslang\slang-glslang.vcxproj", "{C495878A-832C-485B-B347-0998A90CC936}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -40,14 +40,6 @@ Global Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {F9BE7957-8399-899E-0C49-E714FDDD4B65}.Debug|Win32.ActiveCfg = Debug|Win32 - {F9BE7957-8399-899E-0C49-E714FDDD4B65}.Debug|Win32.Build.0 = Debug|Win32 - {F9BE7957-8399-899E-0C49-E714FDDD4B65}.Debug|x64.ActiveCfg = Debug|x64 - {F9BE7957-8399-899E-0C49-E714FDDD4B65}.Debug|x64.Build.0 = Debug|x64 - {F9BE7957-8399-899E-0C49-E714FDDD4B65}.Release|Win32.ActiveCfg = Release|Win32 - {F9BE7957-8399-899E-0C49-E714FDDD4B65}.Release|Win32.Build.0 = Release|Win32 - {F9BE7957-8399-899E-0C49-E714FDDD4B65}.Release|x64.ActiveCfg = Release|x64 - {F9BE7957-8399-899E-0C49-E714FDDD4B65}.Release|x64.Build.0 = Release|x64 {5CF41E7B-4883-A844-F1A1-BC3FDD0FB9EA}.Debug|Win32.ActiveCfg = Debug|Win32 {5CF41E7B-4883-A844-F1A1-BC3FDD0FB9EA}.Debug|Win32.Build.0 = Debug|Win32 {5CF41E7B-4883-A844-F1A1-BC3FDD0FB9EA}.Debug|x64.ActiveCfg = Debug|x64 @@ -64,54 +56,14 @@ Global {639B13F2-CF07-CFEC-98FB-664A0427F154}.Release|Win32.Build.0 = Release|Win32 {639B13F2-CF07-CFEC-98FB-664A0427F154}.Release|x64.ActiveCfg = Release|x64 {639B13F2-CF07-CFEC-98FB-664A0427F154}.Release|x64.Build.0 = Release|x64 - {DB00DA62-0533-4AFD-B59F-A67D5B3A0808}.Debug|Win32.ActiveCfg = Debug|Win32 - {DB00DA62-0533-4AFD-B59F-A67D5B3A0808}.Debug|Win32.Build.0 = Debug|Win32 - {DB00DA62-0533-4AFD-B59F-A67D5B3A0808}.Debug|x64.ActiveCfg = Debug|x64 - {DB00DA62-0533-4AFD-B59F-A67D5B3A0808}.Debug|x64.Build.0 = Debug|x64 - {DB00DA62-0533-4AFD-B59F-A67D5B3A0808}.Release|Win32.ActiveCfg = Release|Win32 - {DB00DA62-0533-4AFD-B59F-A67D5B3A0808}.Release|Win32.Build.0 = Release|Win32 - {DB00DA62-0533-4AFD-B59F-A67D5B3A0808}.Release|x64.ActiveCfg = Release|x64 - {DB00DA62-0533-4AFD-B59F-A67D5B3A0808}.Release|x64.Build.0 = Release|x64 - {C495878A-832C-485B-B347-0998A90CC936}.Debug|Win32.ActiveCfg = Debug|Win32 - {C495878A-832C-485B-B347-0998A90CC936}.Debug|Win32.Build.0 = Debug|Win32 - {C495878A-832C-485B-B347-0998A90CC936}.Debug|x64.ActiveCfg = Debug|x64 - {C495878A-832C-485B-B347-0998A90CC936}.Debug|x64.Build.0 = Debug|x64 - {C495878A-832C-485B-B347-0998A90CC936}.Release|Win32.ActiveCfg = Release|Win32 - {C495878A-832C-485B-B347-0998A90CC936}.Release|Win32.Build.0 = Release|Win32 - {C495878A-832C-485B-B347-0998A90CC936}.Release|x64.ActiveCfg = Release|x64 - {C495878A-832C-485B-B347-0998A90CC936}.Release|x64.Build.0 = Release|x64 - {D56CBCEB-1EB5-4CA8-AEC4-48EA35ED61C7}.Debug|Win32.ActiveCfg = Debug|Win32 - {D56CBCEB-1EB5-4CA8-AEC4-48EA35ED61C7}.Debug|Win32.Build.0 = Debug|Win32 - {D56CBCEB-1EB5-4CA8-AEC4-48EA35ED61C7}.Debug|x64.ActiveCfg = Debug|x64 - {D56CBCEB-1EB5-4CA8-AEC4-48EA35ED61C7}.Debug|x64.Build.0 = Debug|x64 - {D56CBCEB-1EB5-4CA8-AEC4-48EA35ED61C7}.Release|Win32.ActiveCfg = Release|Win32 - {D56CBCEB-1EB5-4CA8-AEC4-48EA35ED61C7}.Release|Win32.Build.0 = Release|Win32 - {D56CBCEB-1EB5-4CA8-AEC4-48EA35ED61C7}.Release|x64.ActiveCfg = Release|x64 - {D56CBCEB-1EB5-4CA8-AEC4-48EA35ED61C7}.Release|x64.Build.0 = Release|x64 - {222F7498-B40C-4F3F-A704-DDEB91A4484A}.Debug|Win32.ActiveCfg = Debug|Win32 - {222F7498-B40C-4F3F-A704-DDEB91A4484A}.Debug|Win32.Build.0 = Debug|Win32 - {222F7498-B40C-4F3F-A704-DDEB91A4484A}.Debug|x64.ActiveCfg = Debug|x64 - {222F7498-B40C-4F3F-A704-DDEB91A4484A}.Debug|x64.Build.0 = Debug|x64 - {222F7498-B40C-4F3F-A704-DDEB91A4484A}.Release|Win32.ActiveCfg = Release|Win32 - {222F7498-B40C-4F3F-A704-DDEB91A4484A}.Release|Win32.Build.0 = Release|Win32 - {222F7498-B40C-4F3F-A704-DDEB91A4484A}.Release|x64.ActiveCfg = Release|x64 - {222F7498-B40C-4F3F-A704-DDEB91A4484A}.Release|x64.Build.0 = Release|x64 - {96610759-07B9-4EEB-A974-5C634A2E742B}.Debug|Win32.ActiveCfg = Debug|Win32 - {96610759-07B9-4EEB-A974-5C634A2E742B}.Debug|Win32.Build.0 = Debug|Win32 - {96610759-07B9-4EEB-A974-5C634A2E742B}.Debug|x64.ActiveCfg = Debug|x64 - {96610759-07B9-4EEB-A974-5C634A2E742B}.Debug|x64.Build.0 = Debug|x64 - {96610759-07B9-4EEB-A974-5C634A2E742B}.Release|Win32.ActiveCfg = Release|Win32 - {96610759-07B9-4EEB-A974-5C634A2E742B}.Release|Win32.Build.0 = Release|Win32 - {96610759-07B9-4EEB-A974-5C634A2E742B}.Release|x64.ActiveCfg = Release|x64 - {96610759-07B9-4EEB-A974-5C634A2E742B}.Release|x64.Build.0 = Release|x64 - {205FCAB9-A13F-4980-86FA-F6221A7095EE}.Debug|Win32.ActiveCfg = Debug|Win32 - {205FCAB9-A13F-4980-86FA-F6221A7095EE}.Debug|Win32.Build.0 = Debug|Win32 - {205FCAB9-A13F-4980-86FA-F6221A7095EE}.Debug|x64.ActiveCfg = Debug|x64 - {205FCAB9-A13F-4980-86FA-F6221A7095EE}.Debug|x64.Build.0 = Debug|x64 - {205FCAB9-A13F-4980-86FA-F6221A7095EE}.Release|Win32.ActiveCfg = Release|Win32 - {205FCAB9-A13F-4980-86FA-F6221A7095EE}.Release|Win32.Build.0 = Release|Win32 - {205FCAB9-A13F-4980-86FA-F6221A7095EE}.Release|x64.ActiveCfg = Release|x64 - {205FCAB9-A13F-4980-86FA-F6221A7095EE}.Release|x64.Build.0 = Release|x64 + {F9BE7957-8399-899E-0C49-E714FDDD4B65}.Debug|Win32.ActiveCfg = Debug|Win32 + {F9BE7957-8399-899E-0C49-E714FDDD4B65}.Debug|Win32.Build.0 = Debug|Win32 + {F9BE7957-8399-899E-0C49-E714FDDD4B65}.Debug|x64.ActiveCfg = Debug|x64 + {F9BE7957-8399-899E-0C49-E714FDDD4B65}.Debug|x64.Build.0 = Debug|x64 + {F9BE7957-8399-899E-0C49-E714FDDD4B65}.Release|Win32.ActiveCfg = Release|Win32 + {F9BE7957-8399-899E-0C49-E714FDDD4B65}.Release|Win32.Build.0 = Release|Win32 + {F9BE7957-8399-899E-0C49-E714FDDD4B65}.Release|x64.ActiveCfg = Release|x64 + {F9BE7957-8399-899E-0C49-E714FDDD4B65}.Release|x64.Build.0 = Release|x64 {66174227-8541-41FC-A6DF-4764FC66F78E}.Debug|Win32.ActiveCfg = Debug|Win32 {66174227-8541-41FC-A6DF-4764FC66F78E}.Debug|Win32.Build.0 = Debug|Win32 {66174227-8541-41FC-A6DF-4764FC66F78E}.Debug|x64.ActiveCfg = Debug|x64 @@ -120,14 +72,6 @@ Global {66174227-8541-41FC-A6DF-4764FC66F78E}.Release|Win32.Build.0 = Release|Win32 {66174227-8541-41FC-A6DF-4764FC66F78E}.Release|x64.ActiveCfg = Release|x64 {66174227-8541-41FC-A6DF-4764FC66F78E}.Release|x64.Build.0 = Release|x64 - {22C45F4F-FB6B-4535-BED1-D3F5D0C71047}.Debug|Win32.ActiveCfg = Debug|Win32 - {22C45F4F-FB6B-4535-BED1-D3F5D0C71047}.Debug|Win32.Build.0 = Debug|Win32 - {22C45F4F-FB6B-4535-BED1-D3F5D0C71047}.Debug|x64.ActiveCfg = Debug|x64 - {22C45F4F-FB6B-4535-BED1-D3F5D0C71047}.Debug|x64.Build.0 = Debug|x64 - {22C45F4F-FB6B-4535-BED1-D3F5D0C71047}.Release|Win32.ActiveCfg = Release|Win32 - {22C45F4F-FB6B-4535-BED1-D3F5D0C71047}.Release|Win32.Build.0 = Release|Win32 - {22C45F4F-FB6B-4535-BED1-D3F5D0C71047}.Release|x64.ActiveCfg = Release|x64 - {22C45F4F-FB6B-4535-BED1-D3F5D0C71047}.Release|x64.Build.0 = Release|x64 {0C768A18-1D25-4000-9F37-DA5FE99E3B64}.Debug|Win32.ActiveCfg = Debug|Win32 {0C768A18-1D25-4000-9F37-DA5FE99E3B64}.Debug|Win32.Build.0 = Debug|Win32 {0C768A18-1D25-4000-9F37-DA5FE99E3B64}.Debug|x64.ActiveCfg = Debug|x64 @@ -136,6 +80,62 @@ Global {0C768A18-1D25-4000-9F37-DA5FE99E3B64}.Release|Win32.Build.0 = Release|Win32 {0C768A18-1D25-4000-9F37-DA5FE99E3B64}.Release|x64.ActiveCfg = Release|x64 {0C768A18-1D25-4000-9F37-DA5FE99E3B64}.Release|x64.Build.0 = Release|x64 + {22C45F4F-FB6B-4535-BED1-D3F5D0C71047}.Debug|Win32.ActiveCfg = Debug|Win32 + {22C45F4F-FB6B-4535-BED1-D3F5D0C71047}.Debug|Win32.Build.0 = Debug|Win32 + {22C45F4F-FB6B-4535-BED1-D3F5D0C71047}.Debug|x64.ActiveCfg = Debug|x64 + {22C45F4F-FB6B-4535-BED1-D3F5D0C71047}.Debug|x64.Build.0 = Debug|x64 + {22C45F4F-FB6B-4535-BED1-D3F5D0C71047}.Release|Win32.ActiveCfg = Release|Win32 + {22C45F4F-FB6B-4535-BED1-D3F5D0C71047}.Release|Win32.Build.0 = Release|Win32 + {22C45F4F-FB6B-4535-BED1-D3F5D0C71047}.Release|x64.ActiveCfg = Release|x64 + {22C45F4F-FB6B-4535-BED1-D3F5D0C71047}.Release|x64.Build.0 = Release|x64 + {205FCAB9-A13F-4980-86FA-F6221A7095EE}.Debug|Win32.ActiveCfg = Debug|Win32 + {205FCAB9-A13F-4980-86FA-F6221A7095EE}.Debug|Win32.Build.0 = Debug|Win32 + {205FCAB9-A13F-4980-86FA-F6221A7095EE}.Debug|x64.ActiveCfg = Debug|x64 + {205FCAB9-A13F-4980-86FA-F6221A7095EE}.Debug|x64.Build.0 = Debug|x64 + {205FCAB9-A13F-4980-86FA-F6221A7095EE}.Release|Win32.ActiveCfg = Release|Win32 + {205FCAB9-A13F-4980-86FA-F6221A7095EE}.Release|Win32.Build.0 = Release|Win32 + {205FCAB9-A13F-4980-86FA-F6221A7095EE}.Release|x64.ActiveCfg = Release|x64 + {205FCAB9-A13F-4980-86FA-F6221A7095EE}.Release|x64.Build.0 = Release|x64 + {96610759-07B9-4EEB-A974-5C634A2E742B}.Debug|Win32.ActiveCfg = Debug|Win32 + {96610759-07B9-4EEB-A974-5C634A2E742B}.Debug|Win32.Build.0 = Debug|Win32 + {96610759-07B9-4EEB-A974-5C634A2E742B}.Debug|x64.ActiveCfg = Debug|x64 + {96610759-07B9-4EEB-A974-5C634A2E742B}.Debug|x64.Build.0 = Debug|x64 + {96610759-07B9-4EEB-A974-5C634A2E742B}.Release|Win32.ActiveCfg = Release|Win32 + {96610759-07B9-4EEB-A974-5C634A2E742B}.Release|Win32.Build.0 = Release|Win32 + {96610759-07B9-4EEB-A974-5C634A2E742B}.Release|x64.ActiveCfg = Release|x64 + {96610759-07B9-4EEB-A974-5C634A2E742B}.Release|x64.Build.0 = Release|x64 + {222F7498-B40C-4F3F-A704-DDEB91A4484A}.Debug|Win32.ActiveCfg = Debug|Win32 + {222F7498-B40C-4F3F-A704-DDEB91A4484A}.Debug|Win32.Build.0 = Debug|Win32 + {222F7498-B40C-4F3F-A704-DDEB91A4484A}.Debug|x64.ActiveCfg = Debug|x64 + {222F7498-B40C-4F3F-A704-DDEB91A4484A}.Debug|x64.Build.0 = Debug|x64 + {222F7498-B40C-4F3F-A704-DDEB91A4484A}.Release|Win32.ActiveCfg = Release|Win32 + {222F7498-B40C-4F3F-A704-DDEB91A4484A}.Release|Win32.Build.0 = Release|Win32 + {222F7498-B40C-4F3F-A704-DDEB91A4484A}.Release|x64.ActiveCfg = Release|x64 + {222F7498-B40C-4F3F-A704-DDEB91A4484A}.Release|x64.Build.0 = Release|x64 + {D56CBCEB-1EB5-4CA8-AEC4-48EA35ED61C7}.Debug|Win32.ActiveCfg = Debug|Win32 + {D56CBCEB-1EB5-4CA8-AEC4-48EA35ED61C7}.Debug|Win32.Build.0 = Debug|Win32 + {D56CBCEB-1EB5-4CA8-AEC4-48EA35ED61C7}.Debug|x64.ActiveCfg = Debug|x64 + {D56CBCEB-1EB5-4CA8-AEC4-48EA35ED61C7}.Debug|x64.Build.0 = Debug|x64 + {D56CBCEB-1EB5-4CA8-AEC4-48EA35ED61C7}.Release|Win32.ActiveCfg = Release|Win32 + {D56CBCEB-1EB5-4CA8-AEC4-48EA35ED61C7}.Release|Win32.Build.0 = Release|Win32 + {D56CBCEB-1EB5-4CA8-AEC4-48EA35ED61C7}.Release|x64.ActiveCfg = Release|x64 + {D56CBCEB-1EB5-4CA8-AEC4-48EA35ED61C7}.Release|x64.Build.0 = Release|x64 + {DB00DA62-0533-4AFD-B59F-A67D5B3A0808}.Debug|Win32.ActiveCfg = Debug|Win32 + {DB00DA62-0533-4AFD-B59F-A67D5B3A0808}.Debug|Win32.Build.0 = Debug|Win32 + {DB00DA62-0533-4AFD-B59F-A67D5B3A0808}.Debug|x64.ActiveCfg = Debug|x64 + {DB00DA62-0533-4AFD-B59F-A67D5B3A0808}.Debug|x64.Build.0 = Debug|x64 + {DB00DA62-0533-4AFD-B59F-A67D5B3A0808}.Release|Win32.ActiveCfg = Release|Win32 + {DB00DA62-0533-4AFD-B59F-A67D5B3A0808}.Release|Win32.Build.0 = Release|Win32 + {DB00DA62-0533-4AFD-B59F-A67D5B3A0808}.Release|x64.ActiveCfg = Release|x64 + {DB00DA62-0533-4AFD-B59F-A67D5B3A0808}.Release|x64.Build.0 = Release|x64 + {C495878A-832C-485B-B347-0998A90CC936}.Debug|Win32.ActiveCfg = Debug|Win32 + {C495878A-832C-485B-B347-0998A90CC936}.Debug|Win32.Build.0 = Debug|Win32 + {C495878A-832C-485B-B347-0998A90CC936}.Debug|x64.ActiveCfg = Debug|x64 + {C495878A-832C-485B-B347-0998A90CC936}.Debug|x64.Build.0 = Debug|x64 + {C495878A-832C-485B-B347-0998A90CC936}.Release|Win32.ActiveCfg = Release|Win32 + {C495878A-832C-485B-B347-0998A90CC936}.Release|Win32.Build.0 = Release|Win32 + {C495878A-832C-485B-B347-0998A90CC936}.Release|x64.ActiveCfg = Release|x64 + {C495878A-832C-485B-B347-0998A90CC936}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -143,11 +143,11 @@ Global GlobalSection(NestedProjects) = preSolution {5CF41E7B-4883-A844-F1A1-BC3FDD0FB9EA} = {EB5FC2C6-D72D-B6CC-C0C1-26F3AC2E9231} {639B13F2-CF07-CFEC-98FB-664A0427F154} = {EB5FC2C6-D72D-B6CC-C0C1-26F3AC2E9231} - {222F7498-B40C-4F3F-A704-DDEB91A4484A} = {FD47AE19-69FD-260F-F2F1-20E65EA61D13} - {96610759-07B9-4EEB-A974-5C634A2E742B} = {FD47AE19-69FD-260F-F2F1-20E65EA61D13} - {205FCAB9-A13F-4980-86FA-F6221A7095EE} = {FD47AE19-69FD-260F-F2F1-20E65EA61D13} {66174227-8541-41FC-A6DF-4764FC66F78E} = {FD47AE19-69FD-260F-F2F1-20E65EA61D13} - {22C45F4F-FB6B-4535-BED1-D3F5D0C71047} = {FD47AE19-69FD-260F-F2F1-20E65EA61D13} {0C768A18-1D25-4000-9F37-DA5FE99E3B64} = {FD47AE19-69FD-260F-F2F1-20E65EA61D13} + {22C45F4F-FB6B-4535-BED1-D3F5D0C71047} = {FD47AE19-69FD-260F-F2F1-20E65EA61D13} + {205FCAB9-A13F-4980-86FA-F6221A7095EE} = {FD47AE19-69FD-260F-F2F1-20E65EA61D13} + {96610759-07B9-4EEB-A974-5C634A2E742B} = {FD47AE19-69FD-260F-F2F1-20E65EA61D13} + {222F7498-B40C-4F3F-A704-DDEB91A4484A} = {FD47AE19-69FD-260F-F2F1-20E65EA61D13} EndGlobalSection EndGlobal diff --git a/source/core/core.vcxproj b/source/core/core.vcxproj index d1ed4715f8..2d0c673428 100644 --- a/source/core/core.vcxproj +++ b/source/core/core.vcxproj @@ -213,7 +213,7 @@ - + diff --git a/source/core/core.vcxproj.filters b/source/core/core.vcxproj.filters index 7936ca11b3..1548217b40 100644 --- a/source/core/core.vcxproj.filters +++ b/source/core/core.vcxproj.filters @@ -130,8 +130,8 @@ - + Source Files - + \ No newline at end of file diff --git a/source/slang/bytecode.cpp b/source/slang/bytecode.cpp index b92476cb1f..7ceb06cdcf 100644 --- a/source/slang/bytecode.cpp +++ b/source/slang/bytecode.cpp @@ -409,7 +409,7 @@ void generateBytecodeForInst( } break; - case kIROp_boolConst: + case kIROp_BoolLit: { auto ii = (IRConstant*) inst; encodeUInt(context, ii->op); diff --git a/source/slang/diagnostic-defs.h b/source/slang/diagnostic-defs.h index 1c27dde0e4..1df7e23f52 100644 --- a/source/slang/diagnostic-defs.h +++ b/source/slang/diagnostic-defs.h @@ -364,6 +364,7 @@ DIAGNOSTIC(40008, Error, invalidLValueForRefParameter, "the form of this l-value DIAGNOSTIC(41000, Warning, unreachableCode, "unreachable code detected") +DIAGNOSTIC(41010, Warning, missingReturn, "control flow may reach end of non-'void' function") // // 5xxxx - Target code generation. diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp index 03c788ddb2..9411c6fdf2 100644 --- a/source/slang/emit.cpp +++ b/source/slang/emit.cpp @@ -2308,7 +2308,7 @@ struct EmitVisitor Emit(((IRConstant*) inst)->value.floatVal); break; - case kIROp_boolConst: + case kIROp_BoolLit: { bool val = ((IRConstant*)inst)->value.intVal != 0; emit(val ? "true" : "false"); @@ -2357,7 +2357,7 @@ struct EmitVisitor // case kIROp_IntLit: case kIROp_FloatLit: - case kIROp_boolConst: + case kIROp_BoolLit: return true; // Always fold these in, because their results @@ -3484,7 +3484,7 @@ struct EmitVisitor { case kIROp_IntLit: case kIROp_FloatLit: - case kIROp_boolConst: + case kIROp_BoolLit: emitIRSimpleValue(ctx, inst); break; diff --git a/source/slang/ir-constexpr.cpp b/source/slang/ir-constexpr.cpp index 0cd35161da..1f80bc010f 100644 --- a/source/slang/ir-constexpr.cpp +++ b/source/slang/ir-constexpr.cpp @@ -48,7 +48,7 @@ bool isConstExpr(IRInst* value) { case kIROp_IntLit: case kIROp_FloatLit: - case kIROp_boolConst: + case kIROp_BoolLit: case kIROp_Func: return true; @@ -68,7 +68,7 @@ bool opCanBeConstExpr(IROp op) { case kIROp_IntLit: case kIROp_FloatLit: - case kIROp_boolConst: + case kIROp_BoolLit: case kIROp_Add: case kIROp_Sub: case kIROp_Mul: diff --git a/source/slang/ir-inst-defs.h b/source/slang/ir-inst-defs.h index b8e4a979f3..14318bc91b 100644 --- a/source/slang/ir-inst-defs.h +++ b/source/slang/ir-inst-defs.h @@ -183,11 +183,11 @@ INST_RANGE(Type, VoidType, StructType) INST_RANGE(ParentInst, StructType, Block) /* IRConstant */ - INST(boolConst, boolConst, 0, 0) + INST(BoolLit, boolConst, 0, 0) INST(IntLit, integer_constant, 0, 0) INST(FloatLit, float_constant, 0, 0) INST(StringLit, string_constant, 0, 0) -INST_RANGE(Constant, boolConst, StringLit) +INST_RANGE(Constant, BoolLit, StringLit) INST(undefined, undefined, 0, 0) @@ -294,25 +294,34 @@ INST(SwizzledStore, swizzledStore, 2, 0) INST(ReturnVal, return_val, 1, 0) INST(ReturnVoid, return_void, 1, 0) - // unconditionalBranch - INST(unconditionalBranch, unconditionalBranch, 1, 0) + /* IRUnconditionalBranch */ + // unconditionalBranch + INST(unconditionalBranch, unconditionalBranch, 1, 0) - // loop - INST(loop, loop, 3, 0) + // loop + INST(loop, loop, 3, 0) + INST_RANGE(UnconditionalBranch, unconditionalBranch, loop) - // conditionalBranch - INST(conditionalBranch, conditionalBranch, 3, 0) + /* IRConditionalbranch */ - // ifElse - INST(ifElse, ifElse, 4, 0) + // conditionalBranch + INST(conditionalBranch, conditionalBranch, 3, 0) + + // ifElse + INST(ifElse, ifElse, 4, 0) + INST_RANGE(ConditionalBranch, conditionalBranch, ifElse) // switch ... - INST(switch, switch, 3, 0) + INST(Switch, switch, 3, 0) INST(discard, discard, 0, 0) - INST(unreachable, unreachable, 0, 0) -INST_RANGE(TerminatorInst, ReturnVal, unreachable) + /* IRUnreachable */ + INST(MissingReturn, missingReturn, 0, 0) + INST(Unreachable, unreachable, 0, 0) + INST_RANGE(Unreachable, MissingReturn, Unreachable) + +INST_RANGE(TerminatorInst, ReturnVal, Unreachable) INST(Add, add, 2, 0) INST(Sub, sub, 2, 0) diff --git a/source/slang/ir-insts.h b/source/slang/ir-insts.h index d38dec0f52..1bce75b882 100644 --- a/source/slang/ir-insts.h +++ b/source/slang/ir-insts.h @@ -227,7 +227,14 @@ struct IRDiscard : IRTerminatorInst // that a block ending in one of these can actually be // executed. struct IRUnreachable : IRTerminatorInst -{}; +{ + IR_PARENT_ISA(Unreachable); +}; + +struct IRMissingReturn : IRUnreachable +{ + IR_LEAF_ISA(MissingReturn); +}; struct IRBlock; @@ -236,6 +243,12 @@ struct IRUnconditionalBranch : IRTerminatorInst IRUse block; IRBlock* getTargetBlock() { return (IRBlock*)block.get(); } + + UInt getArgCount(); + IRUse* getArgs(); + IRInst* getArg(UInt index); + + IR_PARENT_ISA(UnconditionalBranch); }; // Special cases of unconditional branch, to handle @@ -264,6 +277,8 @@ struct IRLoop : IRUnconditionalBranch struct IRConditionalBranch : IRTerminatorInst { + IR_PARENT_ISA(ConditionalBranch) + IRUse condition; IRUse trueBlock; IRUse falseBlock; @@ -303,6 +318,8 @@ struct IRIfElse : IRConditionalBranch // A multi-way branch that represents a source-level `switch` struct IRSwitch : IRTerminatorInst { + IR_LEAF_ISA(Switch); + IRUse condition; IRUse breakLabel; IRUse defaultLabel; @@ -772,6 +789,7 @@ struct IRBuilder IRInst* emitDiscard(); IRInst* emitUnreachable(); + IRInst* emitMissingReturn(); IRInst* emitBranch( IRBlock* block); diff --git a/source/slang/ir-missing-return.cpp b/source/slang/ir-missing-return.cpp new file mode 100644 index 0000000000..0396fbce98 --- /dev/null +++ b/source/slang/ir-missing-return.cpp @@ -0,0 +1,46 @@ +// ir-missing-return.cpp +#include "ir-missing-return.h" + +#include "ir.h" +#include "ir-insts.h" + +namespace Slang { + +class DiagnosticSink; +struct IRModule; + +void checkForMissingReturnsRec( + IRInst* inst, + DiagnosticSink* sink) +{ + if( auto code = as(inst) ) + { + for( auto block : code->getBlocks() ) + { + auto terminator = block->getTerminator(); + + if( auto missingReturn = as(terminator) ) + { + sink->diagnose(missingReturn, Diagnostics::missingReturn); + } + } + } + + if( auto parentInst = as(inst) ) + { + for( auto childInst : parentInst->getChildren() ) + { + checkForMissingReturnsRec(childInst, sink); + } + } +} + +void checkForMissingReturns( + IRModule* module, + DiagnosticSink* sink) +{ + // Look for any `missingReturn` instructions + checkForMissingReturnsRec(module->getModuleInst(), sink); +} + +} diff --git a/source/slang/ir-missing-return.h b/source/slang/ir-missing-return.h new file mode 100644 index 0000000000..0d22a07c44 --- /dev/null +++ b/source/slang/ir-missing-return.h @@ -0,0 +1,12 @@ +// ir-missing-return.h +#pragma once + +namespace Slang +{ + class DiagnosticSink; + struct IRModule; + + void checkForMissingReturns( + IRModule* module, + DiagnosticSink* sink); +} diff --git a/source/slang/ir-restructure.cpp b/source/slang/ir-restructure.cpp index 98311b11b4..dc35a8aeee 100644 --- a/source/slang/ir-restructure.cpp +++ b/source/slang/ir-restructure.cpp @@ -251,7 +251,8 @@ namespace Slang // SLANG_UNEXPECTED("unhandled terminator instruction opcode"); // fall through to: - case kIROp_unreachable: + case kIROp_Unreachable: + case kIROp_MissingReturn: case kIROp_ReturnVal: case kIROp_ReturnVoid: case kIROp_discard: @@ -446,7 +447,7 @@ namespace Slang } break; - case kIROp_switch: + case kIROp_Switch: { // A `switch` instruction will always translate // to a `SwitchRegion` and then to a `switch` statement. diff --git a/source/slang/ir-sccp.cpp b/source/slang/ir-sccp.cpp new file mode 100644 index 0000000000..6c7f637c1c --- /dev/null +++ b/source/slang/ir-sccp.cpp @@ -0,0 +1,954 @@ +// ir-sccp.cpp +#include "ir-sccp.h" + +#include "ir.h" +#include "ir-insts.h" + +namespace Slang { + + +// This file implements the Spare Conditional Constant Propagation (SCCP) optimization. +// +// We will apply the optimization over individual functions, so we will start with +// a context struct for the state that we will share across functions: +// +struct SharedSCCPContext +{ + IRModule* module; + SharedIRBuilder sharedBuilder; +}; +// +// Next we have a context struct that will be applied for each function (or other +// code-bearing value) that we optimize: +// +struct SCCPContext +{ + SharedSCCPContext* shared; // shared state across functions + IRGlobalValueWithCode* code; // the function/code we are optimizing + + // The SCCP algorithm applies abstract interpretation to the code of the + // function using a "lattice" of values. We can think of a node on the + // lattice as representing a set of values that a given instruction + // might take on. + // + struct LatticeVal + { + // We will use three "flavors" of values on our lattice. + // + enum class Flavor + { + // The `None` flavor represent an empty set of values, meaning + // that we've never seen any indication that the instruction + // produces a (well-defined) value. This could indicate an + // instruction that does not appear to execute, but it could + // also indicate an instruction that we know invokes undefined + // behavior, so we can freely pick a value for it on a whim. + None, + + // The `Constant` flavor represents an instuction that we + // have only ever seen produce a single, fixed value. It's + // `value` field will hold that constant value. + Constant, + + // The `Any` flavor represents an instruction that might produce + // different values at runtime, so we go ahead and approximate + // this as it potentially yielding any value whatsoever. A + // more precise analysis could use sets or intervals of values, + // but for SCCP anything that could take on more than 1 value + // at runtime is assumed to be able to take on *any* value. + Any, + }; + + // The flavor of this value (`None`, `Constant`, or `Any`) + Flavor flavor; + + // If this is a `Constant` lattice value, then this field + // points to the IR instruction that defines the actual constant value. + // For all other flavors it should be null. + IRInst* value = nullptr; + + // For convenience, we define `static` factory functions to + // produce values of each of the flavors. + + static LatticeVal getNone() + { + LatticeVal result; + result.flavor = Flavor::None; + return result; + } + + static LatticeVal getAny() + { + LatticeVal result; + result.flavor = Flavor::Any; + return result; + } + + static LatticeVal getConstant(IRInst* value) + { + LatticeVal result; + result.flavor = Flavor::Constant; + result.value = value; + return result; + } + + // We also need to be able to test if two lattice + // values are equal, so that we can avoid updating + // downstream dependencies if our knowledge about + // an instruction hasn't actually changed. + // + bool operator==(LatticeVal const& that) + { + return this->flavor == that.flavor + && this->value == that.value; + } + + bool operator!=(LatticeVal const& that) + { + return !( *this == that ); + } + }; + + // If we imagine a variable (actually an SSA phi node...) that + // might be assigned lattice value A at one point in the code, + // and lattice value B at another point, we need a way to + // combine these to form our knowledge of the possible value(s) + // for the variable. + // + // In terms of computation on a lattice, we want the "meet" + // operation, which computes the lower bound on what we know. + // If we interpret our lattice values as sets, then we are + // trying to compute the union. + // + LatticeVal meet(LatticeVal const& left, LatticeVal const& right) + { + // If either value is `None` (the empty set), then the union + // will be the other value. + // + if(left.flavor == LatticeVal::Flavor::None) return right; + if(right.flavor == LatticeVal::Flavor::None) return left; + + // If either value is `Any` (the universal set), then + // the union is also the universal set. + // + if(left.flavor == LatticeVal::Flavor::Any) return LatticeVal::getAny(); + if(right.flavor == LatticeVal::Flavor::Any) return LatticeVal::getAny(); + + // At this point we've ruled out the case where either value + // is `None` *or* `Any`, so we can assume both values are + // `Constant`s. + SLANG_ASSERT(left.flavor == LatticeVal::Flavor::Constant); + // + SLANG_ASSERT(right.flavor == LatticeVal::Flavor::Constant); + + // If the two lattice values represent the *same* constant value + // (they are the same singleton set) then the union is that + // singleton set as well. + // + // TODO: This comparison assumes that constants with + // the same value with be represented with the + // same instruction, which is not *always* + // guaranteed in the IR today. + // + if(left.value == right.value) + return left; + + // Otherwise, we have two distinct singleton sets, and their + // union should be a set with two elements. We can't represent + // that on the lattice for SCCP, so the proper lower bound + // is the universal set (`Any`) + // + return LatticeVal::getAny(); + } + + // During the execution of the SCCP algorithm, we will track our best + // "estimate" so far of the set of values each instruction could take + // on. This amounts to a mapping from IR instructions to lattice values, + // where any instruction not present in the map is assumed to default + // to the `None` case (the empty set) + // + Dictionary mapInstToLatticeVal; + + // Updating the lattice value for an instruction is easy, but we'll + // use a simple function to make our intention clear. + // + void setLatticeVal(IRInst* inst, LatticeVal const& val) + { + mapInstToLatticeVal[inst] = val; + } + + // Querying the lattice value for an instruction isn't *just* a matter + // of looking it up in the dictionary, because we need to account for + // cases of lattice values that might come from outside the current + // function. + // + LatticeVal getLatticeVal(IRInst* inst) + { + // Instructions that represent constant values should always + // have a lattice value that reflects this. + // + switch( inst->op ) + { + case kIROp_IntLit: + case kIROp_FloatLit: + case kIROp_StringLit: + case kIROp_BoolLit: + return LatticeVal::getConstant(inst); + break; + + // TODO: We might want to start having support for constant + // values of aggregate types (e.g., a `makeArray` or `makeStruct` + // where all the operands are constant is itself a constant). + + default: + break; + } + + // We might be asked for the lattice value of an instruction + // not contained in the current function. When that happens, + // we will treat it as having potentially any value, rather + // than the default of none. + // + auto parentBlock = as(inst->getParent()); + if(!parentBlock || parentBlock->getParent() != code) return LatticeVal::getAny(); + + // Once the special cases are dealt with, we can look up in + // the dictionary and just return the value we get from it, + // or default to the `None` (empty set) case. + LatticeVal latticeVal; + if(mapInstToLatticeVal.TryGetValue(inst, latticeVal)) + return latticeVal; + return LatticeVal::getNone(); + } + + // Along the way we might need to create new IR instructions + // to represnet new constant values we find, or new control + // flow instructiosn when we start simplifying things. + // + IRBuilder builderStorage; + IRBuilder* getBuilder() { return &builderStorage; } + + // In order to perform constant folding, we need to be able to + // interpret an instruction over the lattice values. + // + LatticeVal interpretOverLattice(IRInst* inst) + { + SLANG_UNUSED(inst); + + // Certain instruction always produce constants, and we + // want to special-case them here. + switch( inst->op ) + { + case kIROp_IntLit: + case kIROp_FloatLit: + case kIROp_StringLit: + case kIROp_BoolLit: + return LatticeVal::getConstant(inst); + + // TODO: we might also want to special-case certain + // instructions where we shouldn't bother trying to + // constant-fold them and should just default to the + // `Any` value right away. + + default: + break; + } + + // TODO: We should now look up the lattice values for + // the operands of the instruction. + // + // If all of the operands have `Constant` lattice values, + // then we can potential execute the operation directly + // on those constant values, create a fresh `IRConstant`, + // and return a `Constant` lattice value for it. This + // would allow us to achieve true constant folding here. + // + // Textbook discussions of SCCP often point out that it + // is also possible to perform certain algebraic simplifications + // here, such as evaluating a multiply by a `Constant` zero + // to zero. + // + // As a default, if any operand has the `Any` value + // then the result of the operation should be treated as + // `Any`. There are exceptions to this, however, with the + // multiply-by-zero example being an important example. + // If we had previously decided that (Any * None) -> Any + // but then we refine our estimates and have (Any * Constant(0)) -> Constant(0) + // then we have violated the monotonicity rules for how + // our values move through the lattice, and we may break + // the convergence guarantees of the analysis. + // + // When we have a mix of `None` and `Constant` operands, + // then the `None` values imply that our operation is using + // uninitialized data or the results of undefined behavior. + // We could try to propagate the `None` through, and allow + // the compiler to speculatively assume that the operation + // produces whatever value we find convenient. Alternatively, + // we can be less aggressive and treat an operation with + // `None` inputs as producing `Any` to make sure we don't + // optimize the code based on non-obvious assumptions. + // + // For now we aren't implementing *any* folding logic here, + // for simplicity. This is the right place to add folding + // optimizations if/when we need them. + // + + // A safe default is to assume that every instruction not + // handled by one of the cases above could produce *any* + // value whatsoever. + return LatticeVal::getAny(); + } + + + // For basic blocks, we will do tracking very similar to what we do for + // ordinary instructions, just with a simpler lattice: every block + // will either be marked as "never executed" or in a "possibly executed" + // state. We track this as a set of the blocks that have been + // marked as possibly executed, plus a getter and setter function. + + HashSet executedBlocks; + + bool isMarkedAsExecuted(IRBlock* block) + { + return executedBlocks.Contains(block); + } + + void markAsExecuted(IRBlock* block) + { + executedBlocks.Add(block); + } + + // The core of the algorithm is based on two work lists. + // One list holds CFG nodes (basic blocks) that we have + // discovered might execute, and thus need to be processed, + // and the other holds SSA nodes (instructions) that need + // their "estimated" value to be updated. + + List cfgWorkList; + List ssaWorkList; + + // A key operation is to take an IR instruction and update + // its "estimated" value on the lattice. This might happen when + // we first discover the instruction could be executed, or + // when we discover that one or more of its operands has + // changed its lattice value so that we need to update our estimate. + // + void updateValueForInst(IRInst* inst) + { + // Block parameters are conceptually SSA "phi nodes", and it + // doesn't make sense to update their values here, because the + // actual candidate values for them comes from the predecessor blocks + // that provide arguments. We will see that logic shortly, when + // handling `IRUnconditionalBranch`. + // + if(as(inst)) + return; + + // We want to special-case terminator instructions here, + // since abstract interpretation of them should cause blocks to + // be marked as executed, etc. + // + if( auto terminator = as(inst) ) + { + if( auto unconditionalBranch = as(inst) ) + { + // When our abstract interpreter "executes" an unconditional + // branch, it needs to mark the target block as potentially + // executed. We do this by adding the target to our CFG work list. + // + auto target = unconditionalBranch->getTargetBlock(); + cfgWorkList.Add(target); + + // Besides transferring control to another block, the other + // thing our unconditional branch instructions do is provide + // the arguments for phi nodes in the target block. + // We thus need to interpret each argument on the branch + // instruction like an "assignment" to the corresponding + // parameter of the target block. + // + UInt argCount = unconditionalBranch->getArgCount(); + IRParam* pp = target->getFirstParam(); + for( UInt aa = 0; aa < argCount; ++aa, pp = pp->getNextParam() ) + { + IRInst* arg = unconditionalBranch->getArg(aa); + IRInst* param = pp; + + // We expect the number of arguments and parameters to match, + // or else the IR is violating its own invariants. + // + SLANG_ASSERT(param); + + // We will update the value for the target block's parameter + // using our "meet" operation (union of sets of possible values) + // + LatticeVal oldVal = getLatticeVal(param); + + // If we've already determined that the block parameter could + // have any value whatsoever, there is no reason to bother + // updating it. + // + if(oldVal.flavor == LatticeVal::Flavor::Any) + continue; + + // We can look up the lattice value for the argument, + // because we should have interpreted it already + // + LatticeVal argVal = getLatticeVal(arg); + + // Now we apply the meet operation and see if the value changed. + // + LatticeVal newVal = meet(oldVal, argVal); + if( newVal != oldVal ) + { + // If the "estimated" value for the parameter has changed, + // then we need to update it in our dictionary, and then + // make sure that all of the users of the parameter get + // their estimates updated as well. + // + setLatticeVal(param, newVal); + for( auto use = param->firstUse; use; use = use->nextUse ) + { + ssaWorkList.Add(use->getUser()); + } + } + } + } + else if( auto conditionalBranch = as(inst) ) + { + // An `IRConditionalBranch` is used for two-way branches. + // We will look at the lattice value for the condition, + // to see if we can narrow down which of the two ways + // might actually be taken. + // + auto condVal = getLatticeVal(conditionalBranch->getCondition()); + + // We do not expect to see a `None` value here, because that + // would mean the user is branching based on an undefined + // value. + // + // TODO: We should make sure there is no way for the user + // to trigger this assert with bad code that involves + // uninitialized variables. Right now we don't special + // case the `undefined` instruction when computing lattice + // values, so it shouldn't be a problem. + // + SLANG_ASSERT(condVal.flavor != LatticeVal::Flavor::None); + + // If the branch condition is a constant, we expect it to + // be a Boolean constant. We won't assert that it is the + // case here, just to be defensive. + // + if( condVal.flavor == LatticeVal::Flavor::Constant ) + { + if( auto boolConst = as(condVal.value) ) + { + // Only one of the two targe blocks is possible to + // execute, based on what we know of the condition, + // so we will add that target to our work list and + // bail out now. + // + auto target = boolConst->getValue() ? conditionalBranch->getTrueBlock() : conditionalBranch->getFalseBlock(); + cfgWorkList.Add(target); + return; + } + } + + // As a fallback, if the condition isn't constant + // (or somehow wasn't a Boolean constnat), we will + // assume that either side of the branch could be + // taken, so that both of the target blocks are + // potentially executed. + // + cfgWorkList.Add(conditionalBranch->getTrueBlock()); + cfgWorkList.Add(conditionalBranch->getFalseBlock()); + } + else if( auto switchInst = as(inst) ) + { + // The handling of a `switch` instruction is similar to the + // case for a two-way branch, with the main difference that + // we have to deal with an integer condition value. + + auto condVal = getLatticeVal(switchInst->getCondition()); + SLANG_ASSERT(condVal.flavor != LatticeVal::Flavor::None); + + UInt caseCount = switchInst->getCaseCount(); + if( condVal.flavor == LatticeVal::Flavor::Constant ) + { + if( auto condConst = as(condVal.value) ) + { + // At this point we have a constant integer condition + // value, and we just need to find the case (if any) + // that matches it. We will default to considering + // the `default` label as the target. + // + auto target = switchInst->getDefaultLabel(); + for( UInt cc = 0; cc < caseCount; ++cc ) + { + if( auto caseConst = as(switchInst->getCaseValue(cc)) ) + { + if(caseConst->getValue() == condConst->getValue()) + { + target = switchInst->getCaseLabel(cc); + break; + } + } + } + + // Whatever single block we decided will get executed, + // we need to make sure it gets processed and then bail. + // + cfgWorkList.Add(target); + return; + } + } + + // The fallback is to assume that the `switch` instruction might + // branch to any of its cases, or the `default` label. + // + for( UInt cc = 0; cc < caseCount; ++cc ) + { + cfgWorkList.Add(switchInst->getCaseLabel(cc)); + } + cfgWorkList.Add(switchInst->getDefaultLabel()); + } + + // There are other cases of terminator instructions not handled + // above (e.g., `return` instructions), but these can't cause + // additional basic blocks in the CFG to execute, so we don't + // need to consider them here. + // + // No matter what, we are done with a terminator instruction + // after inspecting it, and there is no reason we have to + // try and compute its "value." + return; + } + + // For an "ordinary" instruction, we will first check what value + // has been registered for it already. + // + LatticeVal oldVal = getLatticeVal(inst); + + // If we have previous decided that the instruction could take + // on any value whatsoever, then any further update to our + // guess can't expand things more, and so there is nothing to do. + // + if( oldVal.flavor == LatticeVal::Flavor::Any ) + { + return; + } + + // Otherwise, we compute a new guess at the value of + // the instruction based on the lattice values of the + // stuff it depends on. + // + LatticeVal newVal = interpretOverLattice(inst); + + // If nothing changed about our guess, then there is nothing + // further to do, because users of this instruction have + // already computed their guess based on its current value. + // + if(newVal == oldVal) + { + return; + } + + // If the guess did change, then we want to register our + // new guess as the lattice value for this instruction. + // + setLatticeVal(inst, newVal); + + // Next we iterate over all the users of this instruction + // and add them to our work list so that we can update + // their values based on the new information. + // + for( auto use = inst->firstUse; use; use = use->nextUse ) + { + ssaWorkList.Add(use->getUser()); + } + } + + // The `apply()` function will run the full algorithm. + // + void apply() + { + // We start with the busy-work of setting up our IR builder. + // + builderStorage.sharedBuilder = &shared->sharedBuilder; + + // We expect the caller to have filtered out functions with + // no bodies, so there should always be at least one basic block. + // + auto firstBlock = code->getFirstBlock(); + SLANG_ASSERT(firstBlock); + + // The entry block is always going to be executed when the + // function gets called, so we will process it right away. + // + cfgWorkList.Add(firstBlock); + + // The parameters of the first block are our function parameters, + // and we want to operate on the assumption that they could have + // any value possible, so we will record that in our dictionary. + // + for( auto pp : firstBlock->getParams() ) + { + setLatticeVal(pp, LatticeVal::getAny()); + } + + // Now we will iterate until both of our work lists go dry. + // + while(cfgWorkList.Count() || ssaWorkList.Count()) + { + // Note: there is a design choice to be had here + // around whether we do `if if` or `while while` + // for these nested checks. The choice can affect + // how long things take to converge. + + // We will start by processing any blocks that we + // have determined are potentially reachable. + // + while( cfgWorkList.Count() ) + { + // We pop one block off of the work list. + // + auto block = cfgWorkList[0]; + cfgWorkList.FastRemoveAt(0); + + // We only want to process blocks that haven't + // already been marked as executed, so that we + // don't do redundant work. + // + if( !isMarkedAsExecuted(block) ) + { + // We should mark this new block as executed, + // so we can ignore it if it ever ends up on + // the work list again. + // + markAsExecuted(block); + + // If the block is potentially executed, then + // that means the instructions in the block are too. + // We will walk through the block and update our + // guess at the value of each instruction, which + // may in turn add other blocks/instructions to + // the work lists. + // + for( auto inst : block->getChildren() ) + { + updateValueForInst(inst); + } + } + } + + // Once we've cleared the work list of blocks, we + // will start looking at individual instructions that + // need to be updated. + // + while( ssaWorkList.Count() ) + { + // We pop one instruction that needs an update. + // + auto inst = ssaWorkList[0]; + ssaWorkList.FastRemoveAt(0); + + // Before updating the instruction, we will check if + // the parent block of the instructin is marked as + // being executed. If it isn't, there is no reason + // to update the value for the instruction, since + // it might never be used anyway. + // + IRBlock* block = as(inst->getParent()); + + // It is possible that an instruction ended up on + // our SSA work list because it is a user of an + // instruction in a block of `code`, but it is not + // itself an instruction a block of `code`. + // + // For example, if `code` is an `IRGeneric` that + // yields a function, then `inst` might be an + // instruction of that nested function, and not + // an instruction of the generic itself. + // Note that in such a case, the `inst` cannot + // possible affect the values computed in the outer + // generic, or the control-flow paths it might take, + // so there is no reason to consider it. + // + // We guard against this case by only processing `inst` + // if it is a child of a block in the current `code`. + // + if(!block || block->getParent() != code) + continue; + + if( isMarkedAsExecuted(block) ) + { + // If the instruction is potentially executed, we update + // its lattice value based on our abstraction interpretation. + // + updateValueForInst(inst); + } + } + } + + // Once the work lists are empty, our "guesses" at the value + // of different instructions and the potentially-executed-ness + // of blocks should have converged to a conservative steady state. + // + // We are now equiped to start using the information we've gathered + // to modify the code. + + // First, we will walk through all the code and replace instructions + // with constants where it is possible. + // + List instsToRemove; + for( auto block : code->getBlocks() ) + { + for( auto inst : block->getChildren() ) + { + // We look for instructions that have a constnat value on + // the lattice. + // + LatticeVal latticeVal = getLatticeVal(inst); + if(latticeVal.flavor != LatticeVal::Flavor::Constant) + continue; + + // As a small sanity check, we won't go replacing an + // instruction with itself (this shouldn't really come + // up, since constants are supposed to be at the global + // scope right now) + // + IRInst* constantVal = latticeVal.value; + if(constantVal == inst) + continue; + + // We replace any uses of the instruction with its + // constant expected value, and add it to a list of + // instructions to be removed *iff* the instruction + // is known to have no obersvable side effects. + // + inst->replaceUsesWith(constantVal); + if( !inst->mightHaveSideEffects() ) + { + instsToRemove.Add(inst); + } + } + } + + // Once we've replaced the uses of instructions that evaluate + // to constants, we make a second pass to remove the instructions + // themselves (or at least those without side effects). + // + for( auto inst : instsToRemove ) + { + inst->removeAndDeallocate(); + } + + // Next we are going to walk through all of the terminator + // instructions on blocks and look for ones that branch + // based on a constant condition. These will be rewritten + // to use direct branching instructions, which will of course + // need to be emitted using a builder. + // + auto builder = getBuilder(); + for( auto block : code->getBlocks() ) + { + auto terminator = block->getTerminator(); + + // We check if we have a `switch` instruction with a constant + // integer as its condition. + // + if( auto switchInst = as(terminator) ) + { + if( auto constVal = as(switchInst->getCondition()) ) + { + // We will select the one branch that gets taken, based + // on the constant condition value. The `default` label + // will of course be taken if no `case` label matches. + // + IRBlock* target = switchInst->getDefaultLabel(); + UInt caseCount = switchInst->getCaseCount(); + for(UInt cc = 0; cc < caseCount; ++cc) + { + auto caseVal = switchInst->getCaseValue(cc); + if(auto caseConst = as(caseVal)) + { + if( caseConst->getValue() == constVal->getValue() ) + { + target = switchInst->getCaseLabel(cc); + break; + } + } + } + + // Once we've found the target, we will emit a direct + // branch to it before the old terminator, and then remove + // the old terminator instruction. + // + builder->setInsertBefore(terminator); + builder->emitBranch(target); + terminator->removeAndDeallocate(); + } + } + else if(auto condBranchInst = as(terminator)) + { + if( auto constVal = as(condBranchInst->getCondition()) ) + { + // The case for a two-sided conditional branch is similar + // to the `switch` case, but simpler. + + IRBlock* target = constVal->getValue() ? condBranchInst->getTrueBlock() : condBranchInst->getFalseBlock(); + + builder->setInsertBefore(terminator); + builder->emitBranch(target); + terminator->removeAndDeallocate(); + } + + } + } + + // At this point we've replaced some conditional branches + // that would always go the same way (e.g., a `while(true)`), + // which should render some of our blocks unreachable. + // We will collect all those unreachable blocks into a list + // of blocks to be removed, and then go about trying to + // remove them. + // + List unreachableBlocks; + for( auto block : code->getBlocks() ) + { + if( !isMarkedAsExecuted(block) ) + { + unreachableBlocks.Add(block); + } + } + // + // It might seem like we could just do: + // + // block->removeAndDeallocate(); + // + // for each of the blocks in `unreachableBlocks`, but there + // is a subtle point that has to be considered: + // + // We have a structured control-flow representation where + // certain branching instructions name "join points" where + // control flow logically re-converges. It is possible that + // one of our unreachable blocks is still being used as + // a join point. + // + // For example: + // + // if(A) + // return B; + // else + // return C; + // D; + // + // In the above example, the block that computes `D` is + // unreachable, but it is still the join point for the `if(A)` + // branch. + // + // Rather than complicate the encoding of join points to + // try to special-case an unreachable join point, we will + // instead retain the join point as a block with only a single + // `unreachable` instruction. + // + // To detect which blocks are unreachable and unreferenced, + // we will check which blocks have any uses. Of course, it + // might be that some of our unreachable blocks still reference + // one another (e.g., an unreachable loop) so we will start + // by removing the instructions from the bodies of our unreachable + // blocks to eliminate any cross-references between them. + // + for( auto block : unreachableBlocks ) + { + // TODO: In principle we could produce a diagnostic here + // if any of these unreachable blocks appears to have + // "non-trivial" code in it (that is, any code explicitly + // written by the user, and not just code synthesized by + // the compiler to satisfy language rules). Making that + // determination could be tricky, so for now we will + // err on the side of allowing unreachable code without + // a warning. + // + block->removeAndDeallocateAllChildren(); + } + // + // At this point every one of our unreachable blocks is empty, + // and there should be no branches from reachable blocks + // to unreachable ones. + // + // We will iterate over our unreachable blocks, and process + // them differently based on whether they have any remaining uses. + // + for( auto block : unreachableBlocks ) + { + // At this point there had better be no edges branching to + // our block. We determined it was unreachable, so there had + // better not be branches from reachable blocks to this one, + // and all the unreachable blocks had their instructions + // removed, so there should be no branches to it from other + // unreachable blocks (or itself). + // + SLANG_ASSERT(block->getPredecessors().isEmpty()); + + // If the block is completely unreferenced, we can safely + // remove and deallocate it now. + // + if( !block->hasUses() ) + { + block->removeAndDeallocate(); + } + else + { + // Otherwise, the block has at least one use (but + // no predecessors), which should indicate that it + // is an unreachable join point. + // + // We will keep the block around, but its entire + // body will consist of a single `unreachable` + // instruction. + // + builder->setInsertInto(block); + builder->emitUnreachable(); + } + } + } +}; + +static void applySparseConditionalConstantPropagationRec( + SharedSCCPContext* shared, + IRInst* inst) +{ + if( auto code = as(inst) ) + { + if( code->getFirstBlock() ) + { + SCCPContext context; + context.shared = shared; + context.code = code; + context.apply(); + } + } + + if( auto parentInst = as(inst) ) + { + for( auto childInst : parentInst->getChildren() ) + { + applySparseConditionalConstantPropagationRec(shared, childInst); + } + } + +} + +void applySparseConditionalConstantPropagation( + IRModule* module) +{ + SharedSCCPContext shared; + shared.module = module; + shared.sharedBuilder.module = module; + shared.sharedBuilder.session = module->getSession(); + + applySparseConditionalConstantPropagationRec(&shared, module->getModuleInst()); +} + +} + diff --git a/source/slang/ir-sccp.h b/source/slang/ir-sccp.h new file mode 100644 index 0000000000..cd075761ac --- /dev/null +++ b/source/slang/ir-sccp.h @@ -0,0 +1,18 @@ +// ir-sccp.h +#pragma once + +namespace Slang +{ + struct IRModule; + + /// Apply Sparse Conditional Constant Propagation (SCCP) to a module. + /// + /// This optimization replaces instructions that can only ever evaluate + /// to a single (well-defined) value with that constant value, and + /// also eliminates conditional branches where the condition will + /// always evaluate to a constant (which can lead to entire blocks + /// becoming dead code) + void applySparseConditionalConstantPropagation( + IRModule* module); +} + diff --git a/source/slang/ir-serialize.cpp b/source/slang/ir-serialize.cpp index d82641168c..3b0e45e23d 100644 --- a/source/slang/ir-serialize.cpp +++ b/source/slang/ir-serialize.cpp @@ -371,7 +371,7 @@ Result IRSerialWriter::write(IRModule* module, SourceManager* sourceManager, Opt dstInst.m_payload.m_float64 = irConst->value.floatVal; break; } - case kIROp_boolConst: + case kIROp_BoolLit: { dstInst.m_payloadType = PayloadType::UInt32; dstInst.m_payload.m_uint32 = irConst->value.intVal ? 1 : 0; @@ -1604,7 +1604,7 @@ IRDecoration* IRSerialReader::_createDecoration(const Ser::Inst& srcInst) IRConstant* irConst = nullptr; switch (op) { - case kIROp_boolConst: + case kIROp_BoolLit: { SLANG_ASSERT(srcInst.m_payloadType == PayloadType::UInt32); irConst = static_cast(createEmptyInstWithSize(module, op, prefixSize + sizeof(IRIntegerValue))); diff --git a/source/slang/ir.cpp b/source/slang/ir.cpp index 0b4427f356..3ba2aa0dd8 100644 --- a/source/slang/ir.cpp +++ b/source/slang/ir.cpp @@ -319,7 +319,8 @@ namespace Slang { case kIROp_ReturnVal: case kIROp_ReturnVoid: - case kIROp_unreachable: + case kIROp_Unreachable: + case kIROp_MissingReturn: case kIROp_discard: break; @@ -337,7 +338,7 @@ namespace Slang end = begin + 2; break; - case kIROp_switch: + case kIROp_Switch: // switch ... begin = operands + 2; @@ -423,6 +424,12 @@ namespace Slang return count; } + bool IRBlock::PredecessorList::isEmpty() + { + return !(begin() != end()); + } + + void IRBlock::PredecessorList::Iterator::operator++() { if (!use) return; @@ -471,6 +478,43 @@ namespace Slang return (IRBlock*)use->get(); } + UInt IRUnconditionalBranch::getArgCount() + { + switch(op) + { + case kIROp_unconditionalBranch: + return getOperandCount() - 1; + + case kIROp_loop: + return getOperandCount() - 3; + + default: + SLANG_UNEXPECTED("unhandled unconditional branch opcode"); + UNREACHABLE_RETURN(0); + } + } + + IRUse* IRUnconditionalBranch::getArgs() + { + switch(op) + { + case kIROp_unconditionalBranch: + return getOperands() + 1; + + case kIROp_loop: + return getOperands() + 3; + + default: + SLANG_UNEXPECTED("unhandled unconditional branch opcode"); + UNREACHABLE_RETURN(0); + } + } + + IRInst* IRUnconditionalBranch::getArg(UInt index) + { + return getArgs()[index].usedValue; + } + IRParam* IRGlobalValueWithParams::getFirstParam() { auto entryBlock = getFirstBlock(); @@ -506,8 +550,9 @@ namespace Slang case kIROp_loop: case kIROp_ifElse: case kIROp_discard: - case kIROp_switch: - case kIROp_unreachable: + case kIROp_Switch: + case kIROp_Unreachable: + case kIROp_MissingReturn: return true; } } @@ -1162,7 +1207,7 @@ namespace Slang } switch (op) { - case kIROp_boolConst: + case kIROp_BoolLit: case kIROp_FloatLit: case kIROp_IntLit: { @@ -1188,7 +1233,7 @@ namespace Slang switch (op) { - case kIROp_boolConst: + case kIROp_BoolLit: case kIROp_FloatLit: case kIROp_IntLit: { @@ -1236,7 +1281,7 @@ namespace Slang switch (keyInst.op) { - case kIROp_boolConst: + case kIROp_BoolLit: case kIROp_IntLit: { irValue = static_cast(createInstWithSizeImpl(builder, keyInst.op, keyInst.getFullType(), prefixSize + sizeof(IRIntegerValue))); @@ -1284,7 +1329,7 @@ namespace Slang { IRConstant keyInst; memset(&keyInst, 0, sizeof(keyInst)); - keyInst.op = kIROp_boolConst; + keyInst.op = kIROp_BoolLit; keyInst.typeUse.usedValue = getBoolType(); keyInst.value.intVal = IRIntegerValue(inValue); return findOrEmitConstant(this, keyInst); @@ -2320,7 +2365,17 @@ namespace Slang { auto inst = createInst( this, - kIROp_unreachable, + kIROp_Unreachable, + nullptr); + addInst(inst); + return inst; + } + + IRInst* IRBuilder::emitMissingReturn() + { + auto inst = createInst( + this, + kIROp_MissingReturn, nullptr); addInst(inst); return inst; @@ -2444,7 +2499,7 @@ namespace Slang auto inst = createInstWithTrailingArgs( this, - kIROp_switch, + kIROp_Switch, nullptr, fixedArgCount, fixedArgs, @@ -2741,7 +2796,7 @@ namespace Slang dump(context, ((IRConstant*)inst)->value.floatVal); return; - case kIROp_boolConst: + case kIROp_BoolLit: dump(context, ((IRConstant*)inst)->value.intVal ? "true" : "false"); return; case kIROp_StringLit: @@ -3092,7 +3147,7 @@ namespace Slang { case kIROp_IntLit: case kIROp_FloatLit: - case kIROp_boolConst: + case kIROp_BoolLit: case kIROp_StringLit: dumpOperand(context, inst); break; @@ -3409,10 +3464,29 @@ namespace Slang removeFromParent(); removeArguments(); + // If this is a parent instruction then we had + // better remove all its children as well. + // + if(auto parentInst = as(this)) + { + parentInst->removeAndDeallocateAllChildren(); + } + // Run destructor to be sure... this->~IRInst(); } + void IRParentInst::removeAndDeallocateAllChildren() + { + IRInst* nextChild = nullptr; + for( IRInst* child = getFirstChild(); child; child = nextChild ) + { + nextChild = child->getNextInst(); + child->removeAndDeallocate(); + } + } + + bool IRInst::mightHaveSideEffects() { // TODO: We should drive this based on flags specified @@ -5311,7 +5385,7 @@ namespace Slang switch (originalValue->op) { - case kIROp_boolConst: + case kIROp_BoolLit: { IRConstant* c = (IRConstant*)originalValue; return builder->getBoolValue(c->value.intVal != 0); diff --git a/source/slang/ir.h b/source/slang/ir.h index 2e3d61f8d0..ef257a759b 100644 --- a/source/slang/ir.h +++ b/source/slang/ir.h @@ -516,6 +516,14 @@ struct IRIntLit : IRConstant IR_LEAF_ISA(IntLit); }; +struct IRBoolLit : IRConstant +{ + bool getValue() { return value.intVal != 0; } + + IR_LEAF_ISA(BoolLit); +}; + + // Get the compile-time constant integer value of an instruction, // if it has one, and assert-fail otherwise. IRIntegerValue GetIntVal(IRInst* inst); @@ -561,6 +569,8 @@ struct IRParentInst : IRInst IRInst* getLastChild() { return children.last; } IRInstListBase getChildren() { return children; } + void removeAndDeallocateAllChildren(); + IR_PARENT_ISA(ParentInst) }; @@ -642,6 +652,7 @@ struct IRBlock : IRParentInst IRUse* b; UInt getCount(); + bool isEmpty(); struct Iterator { diff --git a/source/slang/lower-to-ir.cpp b/source/slang/lower-to-ir.cpp index 86cf63b259..3c4067af5c 100644 --- a/source/slang/lower-to-ir.cpp +++ b/source/slang/lower-to-ir.cpp @@ -6,6 +6,8 @@ #include "ir.h" #include "ir-constexpr.h" #include "ir-insts.h" +#include "ir-missing-return.h" +#include "ir-sccp.h" #include "ir-ssa.h" #include "ir-validate.h" #include "mangle.h" @@ -5114,7 +5116,7 @@ struct DeclLoweringVisitor : DeclVisitor // this by putting an `unreachable` terminator here, // and then emit a dataflow error if this block // can't be eliminated. - subContext->irBuilder->emitUnreachable(); + subContext->irBuilder->emitMissingReturn(); } } } @@ -5563,7 +5565,10 @@ IRModule* generateIRForTranslationUnit( // temporaries whenever possible. constructSSA(module); - // TODO: Do basic constant folding and DCE + // Do basic constant folding and dead code elimination + // using Sparse Conditional Constant Propagation (SCCP) + // + applySparseConditionalConstantPropagation(module); // Propagate `constexpr`-ness through the dataflow graph (and the // call graph) based on constraints imposed by different instructions. @@ -5572,6 +5577,8 @@ IRModule* generateIRForTranslationUnit( // TODO: give error messages if any `undefined` or // `unreachable` instructions remain. + checkForMissingReturns(module, &compileRequest->mSink); + // TODO: consider doing some more aggressive optimizations // (in particular specialization of generics) here, so // that we can avoid doing them downstream. diff --git a/source/slang/slang.vcxproj b/source/slang/slang.vcxproj index ba8aabaf54..db745b8006 100644 --- a/source/slang/slang.vcxproj +++ b/source/slang/slang.vcxproj @@ -185,8 +185,10 @@ + + @@ -231,8 +233,10 @@ + + @@ -258,6 +262,9 @@ + + + Document @@ -265,7 +272,7 @@ "../../bin/windows-x64/debug/slang-generate" %(Identity) "../../bin/windows-x86/release/slang-generate" %(Identity) "../../bin/windows-x64/release/slang-generate" %(Identity) - ../../core.meta.slang.h + %(Identity).h slang-generate %(Identity) ../../bin/windows-x86/debug/slang-generate.exe ../../bin/windows-x64/debug/slang-generate.exe @@ -278,7 +285,7 @@ "../../bin/windows-x64/debug/slang-generate" %(Identity) "../../bin/windows-x86/release/slang-generate" %(Identity) "../../bin/windows-x64/release/slang-generate" %(Identity) - ../../hlsl.meta.slang.h + %(Identity).h slang-generate %(Identity) ../../bin/windows-x86/debug/slang-generate.exe ../../bin/windows-x64/debug/slang-generate.exe @@ -286,9 +293,6 @@ ../../bin/windows-x64/release/slang-generate.exe - - - {F9BE7957-8399-899E-0C49-E714FDDD4B65} diff --git a/source/slang/slang.vcxproj.filters b/source/slang/slang.vcxproj.filters index c45e7f0ee8..a17717b2d6 100644 --- a/source/slang/slang.vcxproj.filters +++ b/source/slang/slang.vcxproj.filters @@ -54,12 +54,18 @@ Header Files + + Header Files + Header Files Header Files + + Header Files + Header Files @@ -188,12 +194,18 @@ Source Files + + Source Files + Source Files Source Files + + Source Files + Source Files @@ -267,6 +279,11 @@ Source Files + + + Source Files + + Source Files @@ -275,9 +292,4 @@ Source Files - - - Source Files - - \ No newline at end of file diff --git a/tests/front-end/rasterizer-ordered-uav.slang b/tests/front-end/rasterizer-ordered-uav.slang index 368dcc4e93..60d09dfd1e 100644 --- a/tests/front-end/rasterizer-ordered-uav.slang +++ b/tests/front-end/rasterizer-ordered-uav.slang @@ -17,4 +17,9 @@ RasterizerOrderedTexture3D b7; float4 test(uint4 coord, float4 value) { -} \ No newline at end of file + value += b0[coord.x]; + + // TODO: use the rest of the `b*` + + return value; +} diff --git a/tools/slang-test/slang-test.vcxproj b/tools/slang-test/slang-test.vcxproj index adc12f8d1c..54ea6f342f 100644 --- a/tools/slang-test/slang-test.vcxproj +++ b/tools/slang-test/slang-test.vcxproj @@ -183,4 +183,4 @@ - \ No newline at end of file + \ No newline at end of file