--!A cross-platform build utility based on Lua -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. -- You may obtain a copy of the License at -- -- http://www.apache.org/licenses/LICENSE-2.0 -- -- Unless required by applicable law or agreed to in writing, software -- distributed under the License is distributed on an "AS IS" BASIS, -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- See the License for the specific language governing permissions and -- limitations under the License. -- -- Copyright (C) 2015-present, Xmake Open Source Community. -- -- @author ruki -- @file xmake.lua -- -- define rule: gencodes rule("cuda.gencodes") -- add cuda `-gencode` flags to target -- -- the gpu arch format syntax -- - compute_xx --> `-gencode arch=compute_xx,code=compute_xx` -- - sm_xx --> `-gencode arch=compute_xx,code=sm_xx` -- - sm_xx,sm_yy --> `-gencode arch=compute_xx,code=[sm_xx,sm_yy]` -- - compute_xx,sm_yy --> `-gencode arch=compute_xx,code=sm_yy` -- - compute_xx,sm_yy,sm_zz --> `-gencode arch=compute_xx,code=[sm_yy,sm_zz]` -- - native --> match the fastest cuda device on current host, -- eg. for a Tesla P100, `-gencode arch=compute_60,code=sm_60` will be added, -- if no available device is found, no `-gencode` flags will be added -- @seealso xmake/modules/lib/detect/find_cudadevices -- on_config(function (target) -- imports import("core.platform.platform") import("lib.detect.find_cudadevices") import("core.base.hashset") -- sm_20 and compute_20 is supported until CUDA 8 -- sm_30 and compute_30 is supported until CUDA 10 -- sm_37 and compute_37 is supported until CUDA 11 -- sm_72 and compute_72 is supported until CUDA 12 local known_v_archs = hashset.of(20, 30, 32, 35, 37, 50, 52, 53, 60, 61, 62, 70, 72, 75, 80, 86, 87, 89, 90, 100, 103, 110, 120, 121) local known_r_archs = hashset.of(20, 30, 32, 35, 37, 50, 52, 53, 60, 61, 62, 70, 72, 75, 80, 86, 87, 89, 90, 100, 103, 110, 120, 121) local function nf_cugencode(archs) if type(archs) ~= "string" then return nil end archs = archs:trim():lower() if archs == "native" then local cuda_envs for _, toolchain_inst in ipairs(target:toolchains()) do if toolchain_inst:name() == "cuda" then cuda_envs = toolchain_inst:runenvs() break end end local device = find_cudadevices({skip_compute_mode_prohibited = true, order_by_flops = true, envs = cuda_envs, plat = target:plat(), arch = target:arch()})[1] if device then return nf_cugencode("sm_" .. device.major .. device.minor) end return nil end local v_arch = nil local r_archs = {} -- full legal value list could be found in nvcc docs -- https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html#gpu-name-gpuname-arch -- examples: sm_75, compute_75, sm_90a, compute_100, sm_100f, compute_100a, etc. -- For robustness, xmake support a unoffical format: sm75, compute75, etc. New version still support it. -- examples: sm75, compute75, sm90a, compute100, sm100f, compute100a, etc. local function parse_arch(value, prefix, know_list) if not value:startswith(prefix) then return nil end local arch_str = value:sub(#prefix + 1) if arch_str:startswith("_") then arch_str = arch_str:sub(2) end -- a legal arch_str should be like: 75, 90a, 100f, etc. local arch_ver, suffix = arch_str:match("^(%d+)([af]?)$") local arch = tonumber(arch_ver) if arch == nil then raise("unknown architecture: " .. value) end if suffix == 'a' and arch < 90 then raise("unknown architecture: " .. prefix .. "_" .. arch .. suffix) end if suffix == 'f' and arch < 100 then raise("unknown architecture: " .. prefix .. "_" .. arch .. suffix) end if not know_list:has(arch) then if arch <= table.maxn(know_list:data()) then raise("unknown architecture: " .. prefix .. "_" .. arch) else utils.warning("unknown architecture: " .. prefix .. "_" .. arch) end end if suffix and #suffix > 0 then return arch .. suffix end return arch end for _, v in ipairs(archs:split(',')) do local arch = v:trim() local temp_r_arch = parse_arch(arch, "sm", known_r_archs) if temp_r_arch then table.insert(r_archs, temp_r_arch) end local temp_v_arch = parse_arch(arch, "compute", known_v_archs) if temp_v_arch then if v_arch ~= nil then raise("more than one virtual architecture is defined in one gpu gencode option: compute_" .. v_arch .. " and compute_" .. temp_v_arch) end v_arch = temp_v_arch end if not (temp_r_arch or temp_v_arch) then raise("unknown architecture: " .. arch) end end if v_arch == nil and #r_archs == 0 then return nil end if #r_archs == 0 then return { clang = "--cuda-gpu-arch=sm_" .. v_arch, nvcc = "-gencode arch=compute_" .. v_arch .. ",code=compute_" .. v_arch } end if v_arch then table.insert(r_archs, v_arch) else v_arch = r_archs[1] local v_arch_ver = type(v_arch) == "string" and tonumber(v_arch:match("^(%d+)")) or v_arch for i = 2, #r_archs do local r_arch = r_archs[i] local r_arch_ver = type(r_arch) == "string" and tonumber(r_arch:match("^(%d+)")) or r_arch if r_arch_ver < v_arch_ver then v_arch = r_arch v_arch_ver = r_arch_ver elseif r_arch_ver == v_arch_ver and type(r_arch) == "number" and type(v_arch) == "string" then v_arch = r_arch end end end r_archs = table.unique(r_archs) local clang_flags = {} for _, r_arch in ipairs(r_archs) do table.insert(clang_flags, "--cuda-gpu-arch=sm_" .. r_arch) end local nvcc_flags = nil if #r_archs == 1 then nvcc_flags = "-gencode arch=compute_" .. v_arch .. ",code=sm_" .. r_archs[1] else nvcc_flags = "-gencode arch=compute_" .. v_arch .. ",code=[sm_" .. table.concat(r_archs, ",sm_") .. "]" end return { clang = clang_flags, nvcc = nvcc_flags } end local cugencodes = table.wrap(target:get("cugencodes")) for _, opt in ipairs(target:orderopts()) do table.join2(cugencodes, opt:get("cugencodes")) end for _, v in ipairs(cugencodes) do local flag = nf_cugencode(v) if flag then if target:has_tool("cu", "nvcc") then target:add("cuflags", flag.nvcc) else target:add("cuflags", flag.clang) end target:add("culdflags", flag.nvcc) end end end) rule_end()