Revision dd435512219f435ea13498e6124930fd4cf823a9 authored by Tim Foley on 18 November 2019, 18:36:38 UTC, committed by GitHub on 18 November 2019, 18:36:38 UTC
* Split apart `SemanticsVisitor`

The existing `SemanticsVisitor` type was the visitor for expressions, statements, and declarations, and its monolithic nature made it hard to introduce distinct visitors for different phases of checking (despite the fact that we had, de facto, multiple phases of declaration checking).

This change splits up `SemanticsVisitor` as follows:

* There is nosw a `SharedSemanticsContext` type which holds the shared state that all semantics visiting logic needs. This includes state that gets mutated during the course of semantic checking.

* The `SemanticsVisitor` type is now a base class that holds a pointer to a `SharedSemanticsContext`. Most of the non-visitor functions are still defined here, just to keep the code as simple as possible. The `SemanticsVisitor` type is no longer a "visitor" in any meaningful way, but retaining the old name minimizes the diffs to client code.

* There are distinct `Semantics{Expr|Stmt|Decl}Visitor` types that have the actual `visit*` methods for an appropriate subset of the AST hierarchy. These all inherit from `SemanticsVisitor` primarily so that they can have easy access to all the helper methods it defines (which used to be accessible because these were all the same object).

Any client code that was constructing a `SemanticsVisitor` now needs to construct a `SharedSemanticsContext` and then use that to initialize a `SemanticsVisitor`. Similarly, any code that was using `dispatch()` to invoke the visitor on an AST node needs to construct the appropriate sub-class and then invoke `dispatch()` on it instead.

This is a pure refactoring change, so no effort has been made to move state or logic onto the visitor sub-types even when it is logical. Similarly, no attempt has been made to hoist any code out of the common headers to avoid duplication between `.h` and `.cpp` files. Those cleanups will follow.

The one cleanup I allowed myself while doing this was getting rid of the `typeResult` member in `SemanticsVisitor` that appears to be a do-nothing field that got written to in a few places (for unclear reasons) but never read.

* Remove some statefulness around statement checking

Some of the state from the old `SemanticsVisitor` was used in a mutable way during semantic checking:

* The `function` field would be set and the restored when checking the body of a function so that things like `return` statements could find the outer function.

* The `outerStmts` list was used like a stack to track lexically surrounding statements to resolve things like `break` and `continue` targets.

Both of these meant that semantic checking code was doing fine-grained mutations on the shared semantic checking state even though the statefullness wasn't needed.

This change moves the relevant state down to `SemanticsStmtVisitor`, which is a type we create on-the-fly to check each statement, so that we now only need to establish the state once at creation time.

The list of outer statements is handled as a linked list threaded up through the stack (a recurring idiom through the codebase).

There was one place where the `function` field was being used that wasn't strictly inside statement checking: it appears that we were using it to detect whether a variable declaration represents a local, so I added an `_isLocalVar` function to serve the same basic purpose.

With this change, the only stateful part of `SharedSemanticsContext` is the information to track imported modules, which seems like a necessary thing (since deduplication requires statefullness).

* Refactor declaration checking to avoid recursion

The flexiblity of the Slang language makes enforcing ordering on semantic checking difficult. In particular, generics (including some of the built-in standard library types) can take value arguments, so that type expressions can include value expressions. This means that being able to determine the type of a function parameter may require checking expressions, which may in turn require resolving calls to an overloaded function, which in turn requires knowing the types of the parameters of candidate callees.

Up to this point there have been two dueling approaches to handling the ordering problem in the semantic checking logic:

1. There was the `EnsureDecl` operation, supported by the `DeclCheckState` type. Every declaration would track "how checked" it is, and `EnsureDecl(d, s)` would try to perform whatever checks are needed to bring declaration `d` up to state `s`.

2. There was top-down orchestration logic in `visitModuleDecl()` that tried to perform checking of declarations in a set of fixed phases that ensure things like all function declarations being checked before any function bodies.

Each of these options had problems:

1. The `EnsureDecl()` approach wasn't implemented completely or consistently. It only understood two basic levels of checking: the "header" of a declaration was checked, and then the "body," and it relied on a single `visit*()` routine to try and handle both cases. Things ended up being checked twice, or in a circular fashion.

2. Rather than fix the problems with `EnsureDecl()` we layered on the top-down orchestration logic, but doing so ignores the fact that no fixed set of phases can work for our language. The orchestration logic was also done in a relatively ad hoc fashion that relied on using a single visitor to implement all phases of checking, but it added a second metric of "checked-ness" that worked alongside `DeclCheckState`.

This change strives to unify the two worlds and make them consistent. One of the key changes is that instead of doing everything through a single visitor type, we now have distinct visitors for distinct phases of semantic checking, and those phases are one-to-one aligned with the values of the `DeclCheckState` type.

More detailed notes:

* Existing sites that used to call `checkDecl` to directly invoke semantic checking recursively now use `ensureDecl` instead. This makes sure that `ensureDecl` is the one bottleneck that everything passes through, so that it can guarantee that each phase of checking gets applied to each declaration at most once.

* The existing `visitModuleDecl` was revamped into a `checkModule` routine that does the global orchestration, but now it is just a driver routine that makes sure `ensureDecl` gets called on everything in an order that represents an idealized "default schedule" for checking, while not ruling out cases where `ensureDecl()` will change the ordering to handle cases where the global order is insufficient.

* Because `checkModule` handles much of the recursion over the declaration hierarchy, many cases where a declaration `visit*()` would recurse on its members have been eliminated. The only case where a declaration should recursively `ensureDecl()` its members is when its validity for a certain phase depends on those members being checked (e.g., determining the type of a function declaration depends on its parameters having been checked).

* All cases where a `visit*()` routine was manually checking the state/phase of checking have been eliminated. It is now the responsibility of `ensureDecl` to make sure that checking logic doesn't get invoked twice or in an inappropriate order.

* Most cases where a `visit*()` routine was manually *setting* the `DeclCheckState` of a declaration have been eliminated. The common case is now handled by `ensureDecl()` directly, and `visit*()` methods only need to override that logic when special cases arise. E.g., when a variable is declared without a type `(e.g., `let foo = ...;`) then we need to check its initial-value expression to determine its type, so that we must check it further than was initially expected/required.

* This change goes to some lengths to try and keep semantic checking logic at the same location in the `slang-check-decl.cpp` file, so each of the per-phase visitor types is forward declared at the top of the file, and then the actual `visit*()` routines are interleaved throughout the rest of the file. A future change could do pure code movement (no semantic changes) to arrive at a more logical organization, but for now I tried to stick with what would minimize the diffs (although the resulting diffs can still be messy at times).

* One important change to the semantic checking logic was that the test for use of a local variable ahead of its declaration (or as part of its own initial-value expression) was moved around, since its old location in the middle of the `ensureDecl` logic made the overall flow and intention of that function less clear. There is still a need to fix this check to be more robust in the future.

* Add some design documentation on semantic checking

The main thing this tries to lay out is the strategy for declaration checking and the rules/constraints on programmers that follow from it.

* fixup: typos found during review
1 parent 1123ff2
Raw File
premake5.lua
-- premake5.lua

-- This file describes the build configuration for Slang so
-- that premake can generate platform-specific build files
-- using Premake 5 (https://premake.github.io/).
--
-- To update the build files that are checked in to the Slang repository,
-- run a `premake5` binary and specify the appropriate action, e.g.:
--
--      premake5.exe --os=windows vs2015
--
-- If you are trying to build Slang on another platform, then you
-- can try invoking `premake5` for your desired OS and build format
-- and see what happens.
--
-- If you are going to modify this file to change/customize the Slang
-- buidl, then you may need to read up on Premake's approach and
-- how it uses/abuses Lua syntax. A few important things to note:
--
-- * Everything that *looks* like a declarative (e.g., `kind "SharedLib"`)
-- is actually a Lua function call (e.g., `kind("SharedLib")`) that
-- modifies the behind-the-scenes state that describes the build.
--
-- * Many of these function calls are "sticky" and affect subsequent
-- calls, so ordering matters a *lot*. This file uses indentation to
-- represent some of the flow of state, but it is important to recognize
-- that the indentation is not semantically significant.
--
-- * Because the configuration logic is just executable Lua code, we
-- can capture and re-use bits of configuration logic in ordinary
-- Lua subroutines.
--
-- Now let's move on to the actual build:

-- The "workspace" represents the overall build (the "solution" in
-- Visual Studio terms). It sets up basic build settings that will
-- apply across all projects.
--

-- To output linux will output to linux 
-- % premake5 --os=linux gmake --build-location="build.linux"
--
-- % cd build.linux
-- % make config=release_x64
-- or 
-- % make config=debug_x64
--
-- From in the build directory you can use
-- % premake5 --file=../premake5.lua --os=linux gmake 


newoption {
   trigger     = "override-module",
   description = "(Optional) Specify a lua file that can override functions",
   value       = "path"   
}

newoption { 
   trigger     = "build-location",
   description = "(Optional) Specifiy the location to place solution on root Makefile",
   value       = "path"
}

newoption {
   trigger     = "execute-binary",
   description = "(Optional) If true binaries used in build will be executed (disable on cross compilation)",
   value       = "bool",
   default     = "true",
   allowed     = { { "true", "True"}, { "false", "False" } }
}

newoption {
   trigger     = "target-detail",
   description = "(Optional) More specific target information",
   value       = "string",
   allowed     = { {"cygwin"}, {"mingw"} }
}

newoption {
   trigger     = "build-glslang",
   description = "(Optional) If true glslang and spirv-opt will be built",
   value       = "bool",
   default     = "false",
   allowed     = { { "true", "True"}, { "false", "False" } }
}

buildLocation = _OPTIONS["build-location"]
executeBinary = (_OPTIONS["execute-binary"] == "true")
targetDetail = _OPTIONS["target-detail"]
buildGlslang = (_OPTIONS["build-glslang"] == "true")

-- Is true when the target is really windows (ie not something on top of windows like cygwin)
local isTargetWindows = (os.target() == "windows") and not (targetDetail == "mingw" or targetDetail == "cygwin")

overrideModule = {}
local overrideModulePath = _OPTIONS["override-module"]
if overrideModulePath then
    overrideModule = require(overrideModulePath)
end

targetName = "%{cfg.system}-%{cfg.platform:lower()}"

if not (targetDetail == nil) then
    targetName = targetDetail .. "-%{cfg.platform:lower()}"
end

-- This is needed for gcc, for the 'fileno' functions on cygwin
-- _GNU_SOURCE makes realpath available in gcc
if targetDetail == "cygwin" then
    buildoptions { "-D_POSIX_SOURCE" }
    filter { "toolset:gcc*" }
        buildoptions { "-D_GNU_SOURCE" }
end

workspace "slang"
    -- We will support debug/release configuration and x86/x64 builds.
    configurations { "Debug", "Release" }
    platforms { "x86", "x64"}
    
    if os.target() == "linux" then
        platforms {"aarch64" }
    end
    
    if buildLocation then
        location(buildLocation)
    end
    
    -- The output binary directory will be derived from the OS
    -- and configuration options, e.g. `bin/windows-x64/debug/`
    targetdir("bin/" .. targetName .. "/%{cfg.buildcfg:lower()}")

    -- Statically link to the C/C++ runtime rather than create a DLL dependency.
    flags { "StaticRuntime" }

    -- Once we've set up the common settings, we will make some tweaks
    -- that only apply in a subset of cases. Each call to `filter()`
    -- changes the "active" filter for subsequent commands. In
    -- effect, those commands iwll be ignored when the conditions of
    -- the filter aren't satisfied.

    -- Our `x64` platform should (obviously) target the x64
    -- architecture and similarly for x86.
    filter { "platforms:x64" }
        architecture "x64"
    filter { "platforms:x86" }
        architecture "x86"
    filter { "platforms:aarch64"}
        architecture "ARM"

    filter { "toolset:clang or gcc*" }
        buildoptions { "-Wno-unused-parameter", "-Wno-type-limits", "-Wno-sign-compare", "-Wno-unused-variable", "-Wno-reorder", "-Wno-switch", "-Wno-return-type", "-Wno-unused-local-typedefs", "-Wno-parentheses",  "-std=c++11", "-fvisibility=hidden" , "-Wno-ignored-optimization-argument", "-Wno-unknown-warning-option"} 
    	
	filter { "toolset:gcc*"}
		buildoptions { "-Wno-unused-but-set-variable", "-Wno-implicit-fallthrough"  }
		
    filter { "toolset:clang" }
         buildoptions { "-Wno-deprecated-register", "-Wno-tautological-compare", "-Wno-missing-braces", "-Wno-undefined-var-template", "-Wno-unused-function", "-Wno-return-std-move"}
		
    -- When compiling the debug configuration, we want to turn
    -- optimization off, make sure debug symbols are output,
    -- and add the same preprocessor definition that VS
    -- would add by default.
    filter { "configurations:debug" }
        optimize "Off"
        symbols "On"
        defines { "_DEBUG" }

    -- For the release configuration we will turn optimizations on
    -- (we do not yet micro-manage the optimization settings)
    -- and set the preprocessor definition that VS would add by default.
    filter { "configurations:release" }
        optimize "On"
        defines { "NDEBUG" }
    		
    filter { "system:linux" }
        linkoptions{  "-Wl,-rpath,'$$ORIGIN',--no-as-needed", "-ldl"}
            
function dump(o)
    if type(o) == 'table' then
        local s = '{ '
        for k,v in pairs(o) do
            if type(k) ~= 'number' then k = '"'..k..'"' end
            s = s .. '['..k..'] = ' .. dump(v) .. ','
        end
        return s .. '} '
    else
        return tostring(o)
     end
end
	
function dumpTable(o)
	local s = '{ '
	for k,v in pairs(o) do
		if type(k) ~= 'number' then k = '"'..k..'"' end
		s = s .. '['..k..'] = ' .. tostring(v) .. ',\n'
	end
	return s .. '} '
end


--
-- We are now going to start defining the projects, where
-- each project builds some binary artifact (an executable,
-- library, etc.).
--
-- All of our projects follow a common structure, so rather
-- than reiterate a bunch of build settings, we define
-- some subroutines that make the configuration as concise
-- as possible.
--
-- First, we will define a helper routine for adding all
-- the relevant files from a given directory path:
--
function addSourceDir(path)
    files
    {
        path .. "/*.cpp",       -- C++ source files
        path .. "/*.slang",     -- Slang files (for our stdlib)
        path .. "/*.h",         -- Header files
        path .. "/*.hpp",       -- C++ style headers (for glslang)
        path .. "/*.natvis",    -- Visual Studio debugger visualization files
    }
end
--
-- Next we will define a helper routine that all of our
-- projects will bottleneck through. Here `name` is
-- the name for the project (and the base name for
-- whatever output file it produces), while `sourceDir`
-- is the directory that holds the source.
--
-- E.g., for the `slangc` project, the source code
-- is nested in `source/`, so we'd (indirectly) call:
--
--      baseSlangProject("slangc", "source/slangc")
--
function baseSlangProject(name, sourceDir)

    -- Start a new project in premake. This switches
    -- the "current" project over to the newly created
    -- one, so that subsequent commands affect this project.
    --
    project(name)

    -- We need every project to have a stable UUID for
    -- output formats (like Visual Studio and XCode projects)
    -- that use UUIDs rather than names to uniquely identify
    -- projects. If we don't have a stable UUID, then the
    -- output files might have spurious diffs whenever we
    -- re-run premake generation.
    uuid(os.uuid(name .. '|' .. sourceDir))

    -- Set the location where the project file will be placed.
    -- We set the project files to reside in their source
    -- directory, because in Visual Studio the default
    -- working directory when launching a project in the
    -- debugger is its project directory. This ensures that
    -- examples will work as expected for VS users.
    --
    -- TODO: consider only setting this for examples, since
    -- it is less relevant to other projects.
    --

    location(sourceDir)
    
    if os.target() == "windows" then
    else
        location "intermediate/project/%{prj.name}"
    end
    
    -- The intermediate ("object") directory will use a similar
    -- naming scheme to the output directory, but will also use
    -- the project name to avoid cases where multiple projects
    -- have source files with the same name.
    objdir("intermediate/" .. targetName .. "/%{cfg.buildcfg:lower()}/%{prj.name}")
    
    -- All of our projects are written in C++.
    --
    language "C++"

    -- Since we know the project directory, we can go ahead
    -- and add any source files locate there.
    --
    -- Note that we do *not* recurse into subdirectories,
    -- so projects that spread their source over multiple
    -- directories will need to take more steps.
    --
    addSourceDir(sourceDir)

    -- By default, Premake generates VS project files that
    -- reflect the directory structure of the source code.
    -- While this is nice in principle, it creates messy
    -- results in practice for our projects.
    --
    -- Instead, we will use the `vpaths` feature to imitate
    -- the default VS behavior of grouping files into
    -- virtual subdirectories (VS calls them "filters") for
    -- header and source files respectively.
    --
    -- Note: We are setting `vpaths` using a list of key/value
    -- tables instead of just a key/value table, since this
    -- appears to be an (undocumented) way to fix the order
    -- in which the filters are tested. Otherwise we have
    -- issues where premake will nondeterministically decide
    -- the check something against the `**.cpp` filter first,
    -- and decide that a `foo.cpp.h` file should go into
    -- the `"Source Files"` vpath. That behavior seems buggy,
    -- but at least we appear to have a workaround.
    --
    vpaths {
       { ["Header Files"] = { "**.h", "**.hpp"} },
       { ["Source Files"] = { "**.cpp", "**.slang", "**.natvis" } },
    }

    -- Override default options for a project if necessary

    if overrideModule.addBaseProjectOptions then
        overrideModule.addBaseProjectOptions()
    end
end

-- We can now use the `baseSlangProject()` subroutine to
-- define helpers for the different categories of project
-- in our source tree.
--
-- For example, the Slang project has several tools that
-- are used during building/testing, but don't need to
-- be distributed. These always have their source code in
-- `tools/<project-name>/`.
--
function tool(name)
    -- We use the `group` command here to specify that the
    -- next project we create shold be placed into a group
    -- named "tools" in a generated IDE solution/workspace.
    --
    -- This is used in the generated Visual Studio solution
    -- to group all the tools projects together in a logical
    -- sub-directory of the solution.
    --
    group "tools"

    -- Now we invoke our shared project configuration logic,
    -- specifying that the project lives under the `tools/` path.
    --
    baseSlangProject(name, "tools/" .. name)

    -- Finally, we set the project "kind" to produce a console
    -- application. This is a reasonable default for tools,
    -- and it can be overriden because Premake is stateful,
    -- and a subsequent call to `kind()` would overwrite this
    -- default.
    --
    kind "ConsoleApp"
end

-- "Standard" projects will be those that go to make the binary
-- packages for slang: the shared libraries and executables.
--
function standardProject(name)
    -- Because Premake is stateful, any `group()` call by another
    -- project would still be in effect when we create a project
    -- here (e.g., if somebody had called `tool()` before
    -- `standardProject()`), so we are careful here to set the
    -- group to an emptry string, which Premake treats as "no group."
    --
    group ""

    -- A standard project has its code under `source/`
    --
    baseSlangProject(name, "source/" .. name)
end

function toolSharedLibrary(name)
    group "test-tool"
    -- specifying that the project lives under the `tools/` path.
    --
    baseSlangProject(name .. "-tool", "tools/" .. name)
    
    defines { "SLANG_SHARED_LIBRARY_TOOL" }
   
    kind "SharedLib"
end

-- Finally we have the example programs that show how to use Slang.
--
function example(name)
    -- Example programs go into an "example" group
    group "examples"

    -- They have their source code under `examples/<project-name>/`
    baseSlangProject(name, "examples/" .. name)

    -- By default, all of our examples are GUI applications. One some
    -- platforms there is no meaningful distinction between GUI and
    -- command-line applications, but it is significant on Windows and MacOS
    --
    kind "WindowedApp"

    -- Every example needs to be able to include the `slang.h` header
    -- if it is going to use Slang, so we might as well set up a suitable
    -- include path here rather than make each example do it.
    --
    -- Most of the examples also need the `gfx` library,
    -- which lives under `tools/`, so we will add that to the path as well.
    --
    includedirs { ".", "tools" }

    -- The examples also need to link against the slang library,
    -- and the `gfx` abstraction layer (which in turn
    -- depends on the `core` library). We specify all of that here,
    -- rather than in each example.
    links { "slang", "core", "gfx" }
end

if isTargetWindows then
    --
    -- With all of these helper routines defined, we can now define the
    -- actual projects quite simply. For example, here is the entire
    -- declaration of the "Hello, World" example project:
    --
    example "hello-world"
    --
    -- Note how we are calling our custom `example()` subroutine with
    -- the same syntax sugar that Premake usually advocates for their
    -- `project()` function. This allows us to treat `example` as
    -- a kind of specialized "subclass" of `project`
    --

    -- Let's go ahead and set up the projects for our other example now.
    example "model-viewer"
end

example "cpu-hello-world"
    kind "ConsoleApp"

-- Most of the other projects have more interesting configuration going
-- on, so let's walk through them in order of increasing complexity.
--
-- The `core` project is a static library that has all the basic types
-- and routines that get shared across both the Slang compiler/runtime
-- and the various tool projects. It's build is pretty simple:
--

standardProject "core"
    uuid "F9BE7957-8399-899E-0C49-E714FDDD4B65"
    kind "StaticLib"

    -- For our core implementation, we want to use the most
    -- aggressive warning level supported by the target, and
    -- to treat every warning as an error to make sure we
    -- keep our code free of warnings.
    --
    warnings "Extra"
    flags { "FatalWarnings" }
    
    if isTargetWindows then
        addSourceDir "source/core/windows"
    else
        addSourceDir "source/core/unix"
    end
    
    -- We need the core library to be relocatable to be able to link with slang.so
    filter { "system:linux" }
        buildoptions{"-fPIC"}
    
--
-- `slang-generate` is a tool we use for source code generation on
-- the compiler. It depends on the `core` library, so we need to
-- declare that:
--

tool "slang-generate"
    uuid "66174227-8541-41FC-A6DF-4764FC66F78E"
    links { "core" }


--
-- The `slang-test` test driver also uses the `core` library, and it
-- currently relies on include paths being set up so that it can find
-- the core headers:
--

tool "slang-test"
    uuid "0C768A18-1D25-4000-9F37-DA5FE99E3B64"
    includedirs { "." }
    links { "core", "slang" }

--
-- The reflection test harness `slang-reflection-test` is pretty
-- simple, in that it only needs to link against the slang library
-- to do its job:
--

toolSharedLibrary "slang-reflection-test"
    uuid "C5ACCA6E-C04D-4B36-8516-3752B3C13C2F"
    
    includedirs { "." }
    kind "SharedLib"
    links { "core", "slang" }      
    
--
-- The most complex testing tool we have is `render-test`, but from
-- a build perspective the most interesting thing about it is that for
-- our Windows build it requires a Windows 10 SDK.
--
-- TODO: Try to make the build not require a fixed version of the Windows SDK.
-- Ideally we should just specify a *minimum* version.
--
-- This test also requires Vulkan headers which we've placed in the
-- `external/` directory, and it also includes files from the `core`
-- library in ways that require us to set up `source/` as an include path.
--
-- TODO: Fix that requirement.
--

toolSharedLibrary "render-test"
    uuid "61F7EB00-7281-4BF3-9470-7C2EA92620C3"
    
    includedirs { ".", "external", "source", "tools/gfx" }
    links { "core", "slang", "gfx" }
   
    if isTargetWindows then    
        addSourceDir "tools/render-test/windows"
        
        systemversion "10.0.14393.0"
     
        -- For Windows targets, we want to copy 
        -- dxcompiler.dll, and dxil.dll from the Windows SDK redistributable
        -- directory into the output directory.
        -- d3dcompiler_47.dll is copied from the external/slang-binaries submodule.
        postbuildcommands { '"$(SolutionDir)tools\\copy-hlsl-libs.bat" "$(WindowsSdkDir)Redist/D3D/%{cfg.platform:lower()}/" "%{cfg.targetdir}/" "windows-%{cfg.platform:lower()}"'}    
    end
  
--
-- `gfx` is a utility library for doing GPU rendering
-- and compute, which is used by both our testing and exmaples.
-- It depends on teh `core` library, so we need to declare that:
--

tool "gfx" 
    uuid "222F7498-B40C-4F3F-A704-DDEB91A4484A"
    -- Unlike most of the code under `tools/`, this is a library
    -- rather than a stand-alone executable.
    kind "StaticLib"
    
    includedirs { ".", "external", "source", "external/imgui" }

    -- To special case that we may be building using cygwin on windows. If 'true windows' we build for dx12/vk and run the script
    -- If not we assume it's a cygwin/mingw type situation and remove files that aren't appropriate
    if isTargetWindows then
        systemversion "10.0.14393.0"

        -- For Windows targets, we want to copy 
        -- dxcompiler.dll, and dxil.dll from the Windows SDK redistributable
        -- directory into the output directory. 
        -- d3dcompiler_47.dll is copied from the external/slang-binaries submodule.
        postbuildcommands { '"$(SolutionDir)tools\\copy-hlsl-libs.bat" "$(WindowsSdkDir)Redist/D3D/%{cfg.platform:lower()}/" "%{cfg.targetdir}/"'}
        
        addSourceDir "tools/gfx/vulkan"
        addSourceDir "tools/gfx/open-gl"
        addSourceDir "tools/gfx/d3d" 
        addSourceDir "tools/gfx/d3d11"
        addSourceDir "tools/gfx/d3d12"
        
        addSourceDir "tools/gfx/windows"
        
    elseif targetDetail == "mingw" or targetDetail == "cygwin" then
        -- Don't support any render techs...
    elseif os.target() == "macosx" then
        --addSourceDir "tools/gfx/open-gl"
    else
        -- Linux like
        --addSourceDir "tools/gfx/vulkan"
        --addSourceDir "tools/gfx/open-gl"
    end
        
    filter { "system:linux" }
        -- might be able to do pic(true)
        buildoptions{"-fPIC"}
    
--
-- The `slangc` command-line application is just a very thin wrapper
-- around the Slang dynamic library, so its build is extermely simple.
-- One windows `slangc` uses the the `core` library for some UTF-16
-- to UTF-8 string conversion before calling into `slang.dll`, so
-- it also depends on `core`:
--

standardProject "slangc"
    uuid "D56CBCEB-1EB5-4CA8-AEC4-48EA35ED61C7"
    kind "ConsoleApp"
    links { "core", "slang" }

--
-- TODO: Slang's current `Makefile` build does some careful incantations
-- to make sure that the binaries it generates use a "relative `RPATH`"
-- for loading shared libraries, so that Slang is not dependent on
-- being installed to a fixed path on end-user machines. Before we
-- can use Premake for the Linux build (or eventually MacOS) we would
-- need to figure out how to replicate this incantation in premake.
--

--
-- Now that we've gotten all the simple projects out of the way, it is time
-- to get into the more serious build steps.
--
-- First up is the `slang` dynamic library project:
--

standardProject "slang"
    uuid "DB00DA62-0533-4AFD-B59F-A67D5B3A0808"
    kind "SharedLib"
    links { "core" }
    warnings "Extra"
    flags { "FatalWarnings" }

    -- The way that we currently configure things through `slang.h`,
    -- we need to set a preprocessor definitions to ensure that
    -- we declare the Slang API functions for *export* and not *import*.
    --
    defines { "SLANG_DYNAMIC_EXPORT" }

    includedirs { "external/spirv-headers/include" }

    -- The `standardProject` operation already added all the code in
    -- `source/slang/*`, but we also want to incldue the umbrella
    -- `slang.h` header in this prject, so we do that manually here.
    files { "slang.h" }

    files { "source/core/core.natvis" }

    -- The most challenging part of building `slang` is that we need
    -- to invoke the `slang-generate` tool to generate the version
    -- of the Slang standard library that we embed into the compiler.
    --
    -- First, we need to ensure that `slang-generate` gets built
    -- before `slang`, so we declare a non-linking dependency between
    -- the projects here:
    --
    dependson { "slang-generate" }

    -- If we are not building glslang from source, then be
    -- sure to copy a binary copy over to the output directory
    if not buildGlslang then
        filter { "system:windows" }
            postbuildcommands {
                "{COPY} ../../external/slang-binaries/bin/" .. targetName .. "/slang-glslang.dll %{cfg.targetdir}"
            }

        filter { "system:linux" }
            postbuildcommands {
                "{COPY} ../../../external/slang-binaries/bin/" .. targetName .. "/libslang-glslang.so %{cfg.targetdir}"
            }
    end

    filter { "system:linux" }
	-- might be able to do pic(true)
        buildoptions{"-fPIC"}
       
    -- Next, we want to add a custom build rule for each of the
    -- files that makes up the standard library. Those are
    -- always named `*.meta.slang`, so we can select for them
    -- using a `filter` and then use Premake's support for
    -- defining custom build commands:
    --
    if executeBinary then
        filter "files:**.meta.slang"
            -- Specify the "friendly" message that should print to the build log for the action
            buildmessage "slang-generate %{file.relpath}"

            -- Specify the actual command to run for this action.
            --
            -- Note that we use a single-quoted Lua string and wrap the path
            -- to the `slang-generate` command in double quotes to avoid
            -- confusing the Windows shell. It seems that Premake outputs that
            -- path with forward slashes, which confused the shell if we don't
            -- quote the executable path.
            --
            buildcommands { '"%{cfg.targetdir}/slang-generate" %{file.relpath}' }

            -- Given `foo.meta.slang` we woutput `foo.meta.slang.h`.
            -- This needs to be specified because the custom action will only
            -- run when this file needs to be generated.
            --
            buildoutputs { "%{file.relpath}.h" }

            -- We will specify an additional build input dependency on the `slang-generate`
            -- tool itself, so that changes to the code for the tool cause the generation
            -- step to be re-run.
            --
            -- In order to get the file name right, we need to know the executable suffix
            -- that the target platform will use. Premake might have a built-in way to
            -- query this, but I couldn't find it, so I am just winging it for now:
            --
            local executableSuffix = "";
            if(os.target() == "windows") then
                executableSuffix = ".exe";
            end
            --
            buildinputs { "%{cfg.targetdir}/slang-generate" .. executableSuffix }
    end

if buildGlslang then

--
-- The single most complicated part of our build is our custom version of glslang.
-- Is not really set up to produce a shared library with a usable API, so we have
-- our own custom shim API around it to invoke GLSL->SPIRV compilation.
--
-- Glslang normally relies on a CMake-based build process, and its code is spread
-- across multiple directories with implicit dependencies on certain command-line
-- definitions.
--
-- The following is a tailored build of glslang that pulls in the pieces we care
-- about whle trying to leave out the rest:
--
standardProject "slang-glslang"
    uuid "C495878A-832C-485B-B347-0998A90CC936"
    kind "SharedLib"
    includedirs { "external/glslang", "external/spirv-tools", "external/spirv-tools/include", "external/spirv-headers/include", "external/spirv-tools-generated" }

    defines
    {
        -- `ENABLE_OPT` must be defined (to either zero or one) for glslang to compile at all
        "ENABLE_OPT=1",

        -- We want to build a version of glslang that supports every feature possible,
        -- so we will enable all of the supported vendor-specific extensions so
        -- that they can be used in Slang-generated GLSL code when required.
        --
        "AMD_EXTENSIONS",
        "NV_EXTENSIONS",
    }

    -- We will add source code from every directory that is required to get a
    -- minimal GLSL->SPIR-V compilation path working.
    addSourceDir("external/glslang/glslang/GenericCodeGen")
    addSourceDir("external/glslang/glslang/MachineIndependent")
    addSourceDir("external/glslang/glslang/MachineIndependent/preprocessor")
    addSourceDir("external/glslang/glslang/OSDependent")
    addSourceDir("external/glslang/OGLCompilersDLL")
    addSourceDir("external/glslang/SPIRV")
    addSourceDir("external/glslang/StandAlone")
    addSourceDir("external/spirv-tools/source")
    addSourceDir("external/spirv-tools/source/opt")
    addSourceDir("external/spirv-tools/source/util")
    addSourceDir("external/spirv-tools/source/val")

    -- Unfortunately, blindly adding files like that also pulled in a declaration
    -- of a main entry point that we do *not* want, so we will specifically
    -- exclude that file from our build.
    removefiles { "external/glslang/StandAlone/StandAlone.cpp" }

    -- Glslang includes some platform-specific code around DLL setup/teardown
    -- and handling of thread-local storage for its multi-threaded mode. We
    -- don't really care about *any* of that, but we can't remove it from the
    -- build so we need to include the appropriate platform-specific sources.

    filter { "system:windows" }
        -- On Windows we need to add the platform-specific sources and then
        -- remove the `main.cpp` file since it tries to define a `DllMain`
        -- and we don't want the default glslang one.
        addSourceDir( "external/glslang/glslang/OSDependent/Windows" )
        removefiles { "external/glslang/glslang/OSDependent/Windows/main.cpp" }

    filter { "system:linux or macosx" }
        links { "dl", "pthread" }
        addSourceDir("external/glslang/glslang/OSDependent/Unix")
        buildoptions{"-fPIC", "-pthread"}
        
--
-- With glslang's build out of the way, we've now covered everything we have
-- to build to get Slang and its tools/examples built.
--
-- What is not included in this file yet is support for any custom `make`
-- targets for:
--
-- * Invoking the test runner
-- * Packaging up binaries
-- * "Installing" Slang on a user's machine
--

end
back to top