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