Commit 1c5d3401 by Philipp Adolf

Enable querying information of devices

parent c02b04cb
......@@ -9,9 +9,9 @@ It is mainly developed and tested on Linux and with Intel and Nvidia GPUs. It sh
- [x] querying platforms
- [x] enumerate platforms
- [x] query info
- [ ] querying devices
- [x] querying devices
- [x] enumerate devices of a platform
- [ ] querying info
- [x] querying info
- [ ] subdevices (not planned for now)
- [ ] context
- [ ] creating and releasing contexts
......
......@@ -47,8 +47,86 @@ func main() {
log.Fatal(err)
}
for i := range devices {
for i, d := range devices {
log.Printf(" Device %d", i)
var deviceType opencl.DeviceType
var vendorID uint
var maxWorkItemSizes []uintptr
var maxWorkGroupSize uintptr
var imageSupport bool
var singleFP, doubleFP opencl.FPConfig
var memCacheType opencl.MemCacheType
var localMemType opencl.LocalMemType
var queueProperties opencl.CommandQueueProperties
var name, version, vendor, profile, driverVersion string
var builtInKernels []string
var err error
if name, err = d.Name(); err != nil {
log.Fatal(err)
}
if version, err = d.Version(); err != nil {
log.Fatal(err)
}
if deviceType, err = d.Type(); err != nil {
log.Fatal(err)
}
if profile, err = d.Profile(); err != nil {
log.Fatal(err)
}
if vendor, err = d.Vendor(); err != nil {
log.Fatal(err)
}
if vendorID, err = d.VendorID(); err != nil {
log.Fatal(err)
}
if driverVersion, err = d.DriverVersion(); err != nil {
log.Fatal(err)
}
if maxWorkItemSizes, err = d.MaxWorkItemSizes(); err != nil {
log.Fatal(err)
}
if maxWorkGroupSize, err = d.MaxWorkGroupSize(); err != nil {
log.Fatal(err)
}
if imageSupport, err = d.ImageSupport(); err != nil {
log.Fatal(err)
}
if singleFP, err = d.SingleFPConfig(); err != nil {
log.Fatal(err)
}
if doubleFP, err = d.DoubleFPConfig(); err != nil {
log.Fatal(err)
}
if memCacheType, err = d.GlobalMemCacheType(); err != nil {
log.Fatal(err)
}
if localMemType, err = d.LocalMemType(); err != nil {
log.Fatal(err)
}
if queueProperties, err = d.QueueProperties(); err != nil {
log.Fatal(err)
}
if builtInKernels, err = d.BuiltInKernels(); err != nil {
log.Fatal(err)
}
log.Printf(" Name: %s", name)
log.Printf(" Version: %s", version)
log.Printf(" Type: %s", deviceType)
log.Printf(" Profile: %s", profile)
log.Printf(" Vendor: %s (ID: %d)", vendor, vendorID)
log.Printf(" Driver version: %s", driverVersion)
log.Printf(" Max work item sizes: %d", maxWorkItemSizes)
log.Printf(" Max work group size: %d", maxWorkGroupSize)
log.Printf(" Image support: %t", imageSupport)
log.Printf(" Single Floating Point: %s", singleFP)
log.Printf(" Double Floating Point: %s", doubleFP)
log.Printf(" Global memory cache type: %s", memCacheType.Name())
log.Printf(" Local memory type: %s", localMemType.Name())
log.Printf(" Queue properties: %s", queueProperties)
log.Printf(" Built in kernels: %s", strings.Join(builtInKernels, " "))
}
}
}
package opencl
/*
#ifdef __APPLE__
#include <OpenCL/opencl.h>
#else
#include <CL/cl.h>
#endif
*/
import "C"
import "fmt"
type CommandQueueProperties struct {
OutOfOrderExec bool
Profiling bool
}
func fromCLProperties(properties C.cl_command_queue_properties) CommandQueueProperties {
var result CommandQueueProperties
result.OutOfOrderExec = ((properties & C.CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE) != 0)
result.Profiling = ((properties & C.CL_QUEUE_PROFILING_ENABLE) != 0)
return result
}
func (p CommandQueueProperties) String() string {
return fmt.Sprintf("QueueProperties{OutOfOrderExec: %t, Profiling: %t}", p.OutOfOrderExec, p.Profiling)
}
This diff is collapsed. Click to expand it.
// Code {{/* not */}}generated go generate DO NOT EDIT.
package opencl
/*
#include <stdlib.h>
#ifdef __APPLE__
#include <OpenCL/opencl.h>
#else
#include <CL/cl.h>
#endif
*/
import "C"
import (
"fmt"
"strings"
"unsafe"
)
func (d Device) getInfoType(name string, id C.cl_device_info) (DeviceType, error) {
var buf C.cl_device_type
if err := C.clGetDeviceInfo(d.id, id, C.size_t(unsafe.Sizeof(buf)), unsafe.Pointer(&buf), nil); err != C.CL_SUCCESS {
return 0, fmt.Errorf("error getting device info %s: %d", name, err)
}
return DeviceType(buf), nil
}
func (d Device) getInfoBool(name string, id C.cl_device_info) (bool, error) {
var buf C.cl_bool
if err := C.clGetDeviceInfo(d.id, id, C.size_t(unsafe.Sizeof(buf)), unsafe.Pointer(&buf), nil); err != C.CL_SUCCESS {
return false, fmt.Errorf("error getting device info %s: %d", name, err)
}
return buf == C.CL_TRUE, nil
}
func (d Device) getInfoUint(name string, id C.cl_device_info) (uint, error) {
var buf C.cl_uint
if err := C.clGetDeviceInfo(d.id, id, C.size_t(unsafe.Sizeof(buf)), unsafe.Pointer(&buf), nil); err != C.CL_SUCCESS {
return 0, fmt.Errorf("error getting device info %s: %d", name, err)
}
return uint(buf), nil
}
func (d Device) getInfoUlong(name string, id C.cl_device_info) (uint64, error) {
var buf C.cl_long
if err := C.clGetDeviceInfo(d.id, id, C.size_t(unsafe.Sizeof(buf)), unsafe.Pointer(&buf), nil); err != C.CL_SUCCESS {
return 0, fmt.Errorf("error getting device info %s: %d", name, err)
}
return uint64(buf), nil
}
func (d Device) getInfoSize(name string, id C.cl_device_info) (uintptr, error) {
var buf C.size_t
if err := C.clGetDeviceInfo(d.id, id, C.size_t(unsafe.Sizeof(buf)), unsafe.Pointer(&buf), nil); err != C.CL_SUCCESS {
return 0, fmt.Errorf("error getting device info %s: %d", name, err)
}
return uintptr(buf), nil
}
func (d Device) getInfoSizeArray(name string, id C.cl_device_info) ([]uintptr, error) {
var bytes C.size_t
if err := C.clGetDeviceInfo(d.id, id, 0, nil, &bytes); err != C.CL_SUCCESS {
return nil, fmt.Errorf("error getting length of device info %s: %d", name, err)
}
n := bytes / C.size_t(unsafe.Sizeof(C.size_t(0)))
bytes = n * C.size_t(unsafe.Sizeof(C.size_t(0)))
buf := make([]C.size_t, n)
if err := C.clGetDeviceInfo(d.id, id, bytes, unsafe.Pointer(&buf[0]), nil); err != C.CL_SUCCESS {
return nil, fmt.Errorf("error getting device info %s: %d", name, err)
}
result := make([]uintptr, n)
for i, v := range buf {
result[i] = uintptr(v)
}
return result, nil
}
func (d Device) getInfoString(name string, id C.cl_device_info) (string, error) {
var n C.size_t
if err := C.clGetDeviceInfo(d.id, id, 0, nil, &n); err != C.CL_SUCCESS {
return "", fmt.Errorf("error getting length of device info %s: %d", name, err)
}
buf := make([]C.char, n)
if err := C.clGetDeviceInfo(d.id, id, n, unsafe.Pointer(&buf[0]), nil); err != C.CL_SUCCESS {
return "", fmt.Errorf("error getting device info %s: %d", name, err)
}
return C.GoString(&buf[0]), nil
}
func (d Device) getInfoFPConfig(name string, id C.cl_device_info) (FPConfig, error) {
var buf C.cl_device_fp_config
var result FPConfig
if err := C.clGetDeviceInfo(d.id, id, C.size_t(unsafe.Sizeof(buf)), unsafe.Pointer(&buf), nil); err != C.CL_SUCCESS {
return result, fmt.Errorf("error getting device info %s: %d", name, err)
}
result.config = buf
return result, nil
}
func (d Device) getInfoMemCacheType(name string, id C.cl_device_info) (MemCacheType, error) {
var buf C.cl_device_mem_cache_type
if err := C.clGetDeviceInfo(d.id, id, C.size_t(unsafe.Sizeof(buf)), unsafe.Pointer(&buf), nil); err != C.CL_SUCCESS {
return CacheNone, fmt.Errorf("error getting device inf %s: %d", name, err)
}
return memCacheType(buf)
}
func (d Device) getInfoLocalMemType(name string, id C.cl_device_info) (LocalMemType, error) {
var buf C.cl_device_local_mem_type
if err := C.clGetDeviceInfo(d.id, id, C.size_t(unsafe.Sizeof(buf)), unsafe.Pointer(&buf), nil); err != C.CL_SUCCESS {
return LocalMemNone, fmt.Errorf("error getting device inf %s: %d", name, err)
}
return localMemType(buf)
}
func (d Device) getInfoQueueProperties(name string, id C.cl_device_info) (CommandQueueProperties, error) {
var buf C.cl_command_queue_properties
if err := C.clGetDeviceInfo(d.id, id, C.size_t(unsafe.Sizeof(buf)), unsafe.Pointer(&buf), nil); err != C.CL_SUCCESS {
return CommandQueueProperties{}, fmt.Errorf("error getting device inf %s: %d", name, err)
}
return fromCLProperties(buf), nil
}
func (d Device) dummyUseStrings() ([]string) {
// a dummy function so that strings is not unused even if template space-delim is not used
return strings.Split("", "")
}
{{define "type"}}func (d Device) {{.Name}}() (DeviceType, error) {
return d.getInfoType({{printf "%#v" .Name}}, {{.ID}})
}
{{end}}
{{define "bool"}}func (d Device) {{.Name}}() (bool, error) {
return d.getInfoBool({{printf "%#v" .Name}}, {{.ID}})
}
{{end}}
{{define "uint"}}func (d Device) {{.Name}}() (uint, error) {
return d.getInfoUint({{printf "%#v" .Name}}, {{.ID}})
}
{{end}}
{{define "ulong"}}func (d Device) {{.Name}}() (uint64, error) {
return d.getInfoUlong({{printf "%#v" .Name}}, {{.ID}})
}
{{end}}
{{define "size"}}func (d Device) {{.Name}}() (uintptr, error) {
return d.getInfoSize({{printf "%#v" .Name}}, {{.ID}})
}
{{end}}
{{define "size-array"}}func (d Device) {{.Name}}() ([]uintptr, error) {
return d.getInfoSizeArray({{printf "%#v" .Name}}, {{.ID}})
}
{{end}}
{{define "string"}}func (d Device) {{.Name}}() (string, error) {
return d.getInfoString({{printf "%#v" .Name}}, {{.ID}})
}
{{end}}
{{define "space-delim"}}func (d Device) {{.Name}}() ([]string, error) {
str, err := d.getInfoString({{printf "%#v" .Name}}, {{.ID}})
if err != nil {
return nil, err
}
return strings.Split(str, " "), nil
}
{{end}}
{{define "semicolon-delim"}}func (d Device) {{.Name}}() ([]string, error) {
str, err := d.getInfoString({{printf "%#v" .Name}}, {{.ID}})
if err != nil {
return nil, err
}
return strings.Split(str, ";"), nil
}
{{end}}
{{define "fpconfig"}}func (d Device) {{.Name}}() (FPConfig, error) {
return d.getInfoFPConfig({{printf "%#v" .Name}}, {{.ID}})
}
{{end}}
{{define "memcachetype"}}func (d Device) {{.Name}}() (MemCacheType, error) {
return d.getInfoMemCacheType({{printf "%#v" .Name}}, {{.ID}})
}
{{end}}
{{define "localmemtype"}}func (d Device) {{.Name}}() (LocalMemType, error) {
return d.getInfoLocalMemType({{printf "%#v" .Name}}, {{.ID}})
}
{{end}}
{{define "queueproperties"}}func (d Device) {{.Name}}() (CommandQueueProperties, error) {
return d.getInfoQueueProperties({{printf "%#v" .Name}}, {{.ID}})
}
{{end}}
{{- range . -}}
{{- if (eq .ReturnType "type")}}{{template "type" .}}
{{- else if (eq .ReturnType "bool")}}{{template "bool" .}}
{{- else if (eq .ReturnType "uint")}}{{template "uint" .}}
{{- else if (eq .ReturnType "ulong")}}{{template "ulong" .}}
{{- else if (eq .ReturnType "size")}}{{template "size" .}}
{{- else if (eq .ReturnType "size-array")}}{{template "size-array" .}}
{{- else if (eq .ReturnType "string")}}{{template "string" .}}
{{- else if (eq .ReturnType "space-delim")}}{{template "space-delim" .}}
{{- else if (eq .ReturnType "semicolon-delim")}}{{template "semicolon-delim" .}}
{{- else if (eq .ReturnType "fpconfig")}}{{template "fpconfig" .}}
{{- else if (eq .ReturnType "memcachetype")}}{{template "memcachetype" .}}
{{- else if (eq .ReturnType "localmemtype")}}{{template "localmemtype" .}}
{{- else if (eq .ReturnType "queueproperties")}}{{template "queueproperties" .}}
{{- else}}Unknown return type {{.ReturnType}} for {{.Name}}{{end}}
{{end -}}
package opencl
/*
#ifdef __APPLE__
#include <OpenCL/opencl.h>
#else
#include <CL/cl.h>
#endif
*/
import "C"
import (
"fmt"
"sort"
"strings"
)
var fpconfigNames = map[C.cl_device_fp_config]string{
C.CL_FP_DENORM: "denorm",
C.CL_FP_INF_NAN: "inf NaN",
C.CL_FP_ROUND_TO_NEAREST: "round to nearest",
C.CL_FP_ROUND_TO_ZERO: "round to zero",
C.CL_FP_ROUND_TO_INF: "round to inf",
C.CL_FP_FMA: "fused multiply-add",
C.CL_FP_SOFT_FLOAT: "software",
}
type FPConfig struct {
config C.cl_device_fp_config
}
func (c FPConfig) Denorm() bool {
return (c.config & C.CL_FP_DENORM) != 0
}
func (c FPConfig) InfNaN() bool {
return (c.config & C.CL_FP_INF_NAN) != 0
}
func (c FPConfig) RoundToNearest() bool {
return (c.config & C.CL_FP_ROUND_TO_NEAREST) != 0
}
func (c FPConfig) RoundToZero() bool {
return (c.config & C.CL_FP_ROUND_TO_ZERO) != 0
}
func (c FPConfig) RoundToInf() bool {
return (c.config & C.CL_FP_ROUND_TO_INF) != 0
}
func (c FPConfig) FMA() bool {
return (c.config & C.CL_FP_FMA) != 0
}
func (c FPConfig) CorrectlyRoundedDivideSqrt() bool {
return (c.config & C.CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT) != 0
}
func (c FPConfig) SoftFloat() bool {
return (c.config & C.CL_FP_SOFT_FLOAT) != 0
}
func (c FPConfig) String() string {
var features []string
for bitmask, name := range fpconfigNames {
if (c.config & bitmask) != 0 {
features = append(features, name)
}
}
sort.Strings(features)
return fmt.Sprintf("FPConfig{%s}", strings.Join(features, ", "))
}
......@@ -30,12 +30,79 @@ var platformInfo = []struct {
{"Extension", "C.CL_PLATFORM_EXTENSIONS", "space-delim"},
}
var deviceInfo = []struct {
Name string
ID string
ReturnType string
}{
{"Type", "C.CL_DEVICE_TYPE", "type"},
{"VendorID", "C.CL_DEVICE_VENDOR_ID", "uint"},
{"MaxComputeUnits", "C.CL_DEVICE_MAX_COMPUTE_UNITS", "uint"},
{"MaxWorkItemDimensions", "C.CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS", "uint"},
{"MaxWorkItemSizes", "C.CL_DEVICE_MAX_WORK_ITEM_SIZES", "size-array"},
{"MaxWorkGroupSize", "C.CL_DEVICE_MAX_WORK_GROUP_SIZE", "size"},
{"PreferredVectorWidthChar", "C.CL_DEVICE_PREFERRED_VECTOR_WIDTH_CHAR", "uint"},
{"PreferredVectorWidthShort", "C.CL_DEVICE_PREFERRED_VECTOR_WIDTH_SHORT", "uint"},
{"PreferredVectorWidthInt", "C.CL_DEVICE_PREFERRED_VECTOR_WIDTH_INT", "uint"},
{"PreferredVectorWidthLong", "C.CL_DEVICE_PREFERRED_VECTOR_WIDTH_LONG", "uint"},
{"PreferredVectorWidthFloat", "C.CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT", "uint"},
{"PreferredVectorWidthDouble", "C.CL_DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE", "uint"},
{"PreferredVectorWidthHalf", "C.CL_DEVICE_PREFERRED_VECTOR_WIDTH_HALF", "uint"},
{"NativeVectorWidthChar", "C.CL_DEVICE_NATIVE_VECTOR_WIDTH_CHAR", "uint"},
{"NativeVectorWidthShort", "C.CL_DEVICE_NATIVE_VECTOR_WIDTH_SHORT", "uint"},
{"NativeVectorWidthInt", "C.CL_DEVICE_NATIVE_VECTOR_WIDTH_INT", "uint"},
{"NativeVectorWidthLong", "C.CL_DEVICE_NATIVE_VECTOR_WIDTH_LONG", "uint"},
{"NativeVectorWidthFloat", "C.CL_DEVICE_NATIVE_VECTOR_WIDTH_FLOAT", "uint"},
{"NativeVectorWidthDouble", "C.CL_DEVICE_NATIVE_VECTOR_WIDTH_DOUBLE", "uint"},
{"NativeVectorWidthHalf", "C.CL_DEVICE_NATIVE_VECTOR_WIDTH_HALF", "uint"},
{"MaxClockFrequency", "C.CL_DEVICE_MAX_CLOCK_FREQUENCY", "uint"},
{"MaxMemAllocSize", "C.CL_DEVICE_MAX_MEM_ALLOC_SIZE", "ulong"},
{"ImageSupport", "C.CL_DEVICE_IMAGE_SUPPORT", "bool"},
{"MaxReadImageArgs", "C.CL_DEVICE_MAX_READ_IMAGE_ARGS", "uint"},
{"MaxWriteImageArgs", "C.CL_DEVICE_MAX_WRITE_IMAGE_ARGS", "uint"},
{"Image2DMaxWidth", "C.CL_DEVICE_IMAGE2D_MAX_WIDTH", "size"},
{"Image2DMaxHeight", "C.CL_DEVICE_IMAGE2D_MAX_HEIGHT", "size"},
{"Image3DMaxWidth", "C.CL_DEVICE_IMAGE3D_MAX_WIDTH", "size"},
{"Image3DMaxHeight", "C.CL_DEVICE_IMAGE3D_MAX_HEIGHT", "size"},
{"Image3DMaxDepth", "C.CL_DEVICE_IMAGE3D_MAX_DEPTH", "size"},
{"ImageMaxBufferSize", "C.CL_DEVICE_IMAGE_MAX_BUFFER_SIZE", "size"},
{"ImageMaxArraySize", "C.CL_DEVICE_IMAGE_MAX_ARRAY_SIZE", "size"},
{"MaxSamplers", "C.CL_DEVICE_MAX_SAMPLERS", "uint"},
{"MaxParameterSize", "C.CL_DEVICE_MAX_PARAMETER_SIZE", "size"},
{"MemBaseAddrAlign", "C.CL_DEVICE_MEM_BASE_ADDR_ALIGN", "uint"},
{"SingleFPConfig", "C.CL_DEVICE_SINGLE_FP_CONFIG", "fpconfig"},
{"DoubleFPConfig", "C.CL_DEVICE_SINGLE_FP_CONFIG", "fpconfig"},
{"GlobalMemCacheType", "C.CL_DEVICE_GLOBAL_MEM_CACHE_TYPE", "memcachetype"},
{"GlobalMemCachelineSize", "C.CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE", "uint"},
{"GlobalMemCacheSize", "C.CL_DEVICE_GLOBAL_MEM_CACHE_SIZE", "ulong"},
{"GlobalMemSize", "C.CL_DEVICE_GLOBAL_MEM_SIZE", "ulong"},
{"MaxConstantBufferSize", "C.CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE", "ulong"},
{"MaxConstantArgs", "C.CL_DEVICE_MAX_CONSTANT_ARGS", "uint"},
{"LocalMemType", "C.CL_DEVICE_LOCAL_MEM_TYPE", "localmemtype"},
{"LocalMemSize", "C.CL_DEVICE_LOCAL_MEM_SIZE", "ulong"},
{"ErrorCorrectionSupport", "C.CL_DEVICE_ERROR_CORRECTION_SUPPORT", "bool"},
{"ProfilingTimerResolution", "C.CL_DEVICE_PROFILING_TIMER_RESOLUTION", "size"},
{"HostUnifiedMemory", "C.CL_DEVICE_HOST_UNIFIED_MEMORY", "bool"},
{"EndianLittle", "C.CL_DEVICE_ENDIAN_LITTLE", "bool"},
{"Available", "C.CL_DEVICE_AVAILABLE", "bool"},
{"CompilerAvailable", "C.CL_DEVICE_COMPILER_AVAILABLE", "bool"},
{"LinkerAvailable", "C.CL_DEVICE_LINKER_AVAILABLE", "bool"},
{"QueueProperties", "C.CL_DEVICE_QUEUE_PROPERTIES", "queueproperties"},
{"BuiltInKernels", "C.CL_DEVICE_BUILT_IN_KERNELS", "semicolon-delim"},
{"Name", "C.CL_DEVICE_NAME", "string"},
{"Vendor", "C.CL_DEVICE_VENDOR", "string"},
{"DriverVersion", "C.CL_DRIVER_VERSION", "string"},
{"Profile", "C.CL_DEVICE_PROFILE", "string"},
{"Version", "C.CL_DEVICE_VERSION", "string"},
}
var templates = []struct {
in string
out string
data interface{}
}{
{"platforminfo.go.tmpl", "platforminfo.go", platformInfo},
{"deviceinfo.go.tmpl", "deviceinfo.go", deviceInfo},
}
func loadTemplate(filename string) (*template.Template, error) {
......
package opencl
/*
#ifdef __APPLE__
#include <OpenCL/opencl.h>
#else
#include <CL/cl.h>
#endif
*/
import "C"
import (
"fmt"
)
type MemCacheType int
const (
CacheNone = MemCacheType(iota)
CacheReadOnly
CacheReadWrite
)
func memCacheType(t C.cl_device_mem_cache_type) (MemCacheType, error) {
switch t {
case C.CL_NONE:
return CacheNone, nil
case C.CL_READ_ONLY_CACHE:
return CacheReadOnly, nil
case C.CL_READ_WRITE_CACHE:
return CacheReadWrite, nil
default:
return CacheNone, fmt.Errorf("unknown mem cache type: %d", t)
}
}
func (t MemCacheType) Name() string {
switch t {
case CacheNone:
return "none"
case CacheReadOnly:
return "read only"
case CacheReadWrite:
return "read write"
default:
return "unknown"
}
}
func (t MemCacheType) String() string {
return fmt.Sprintf("MemCacheType{%s}", t.Name())
}
type LocalMemType int
const (
LocalMemNone = LocalMemType(iota)
LocalMemLocal
LocalMemGlobal
)
func localMemType(t C.cl_device_local_mem_type) (LocalMemType, error) {
switch t {
case C.CL_NONE:
return LocalMemNone, nil
case C.CL_LOCAL:
return LocalMemLocal, nil
case C.CL_GLOBAL:
return LocalMemGlobal, nil
default:
return LocalMemNone, fmt.Errorf("unknown local memory type: %d", t)
}
}
func (t LocalMemType) Name() string {
switch t {
case LocalMemNone:
return "none"
case LocalMemLocal:
return "local"
case LocalMemGlobal:
return "global"
default:
return "unknown"
}
}
func (t LocalMemType) String() string {
return fmt.Sprintf("LocalMemType{%s}", t.Name())
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment