Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding/removing pragmas like {.inline.} causes breaking changes #18220

Open
zqqw opened this issue Jun 9, 2021 · 20 comments
Open

adding/removing pragmas like {.inline.} causes breaking changes #18220

zqqw opened this issue Jun 9, 2021 · 20 comments

Comments

@zqqw
Copy link

zqqw commented Jun 9, 2021

zqqw/pakku#12
d32ab61
https://github.com/nim-lang/Nim/blob/devel/lib/pure/hashes.nim
Building the latest nim-git today, all that is needed to fix this error is delete the {.inline.} from line 512 in lib/pure/hashes.nim

proc hash*[T: tuple | object | proc](x: T): Hash {.inline.} =

Does it need to be inline? (gcc will often inline things itself when it optimizes the code anyway.) If it does, have you any suggestions how to avoid the build error in Pakku, as I've tried various things and not found a solution yet. toHashSet is being run on an opaque object (C struct) from libalpm, so perhaps it isn't very amenable to being inlined at compile time, I'm not sure of the exact reason for the failure.

links

@kaushalmodi
Copy link
Contributor

Ref: d32ab61

/cc @timotheecour

@timotheecour
Copy link
Member

timotheecour commented Jun 9, 2021

Does it need to be inline? (gcc will often inline things itself when it optimizes the code anyway.)

within-module inlining works without {.inline.}, but cross-module inlininig requires {.inline.}
stdlib procs should be allowed to change the {.inline.} pragma without breaking API, so this needs further investigation;

can you try to minimize the issue (ideally with a self-contained module with no imports) ?
also, please provide the commands needed to reproduce the error (EDIT: nim c src/common.nim)

@zqqw
Copy link
Author

zqqw commented Jun 9, 2021

I began trying to minimize it by first adding what seemed to be the relevant sections to alpm.nim, the bindings to libalpm, but it fails on toHashSet with any version of Nim:

import
  strutils, sets, options, tables, sequtils, sugar, system
#  "./utils"

type
  AlpmHandle* = object
  AlpmDatabase* = object
  AlpmPackage* = object

  AlpmList*[T] = object
    data*: T
    prev*: ptr AlpmList[T]
    next*: ptr AlpmList[T]

  AlpmDepMod* {.pure, size: sizeof(cint).} = enum
    no = 1,
    eq = 2,
    ge = 3,
    le = 4,
    gt = 5,
    lt = 6

  AlpmReason* {.pure, size: sizeof(cint).} = enum
    explicit = 0,
    depend = 1

  AlpmDependency* = object
    name*: cstring
    version*: cstring
    desc*: cstring
    nameHash: culong
    depMod*: AlpmDepMod

  AlpmGroup* = object
    name*: cstring
    packages*: ptr AlpmList[ptr AlpmPackage]

{.passL: "-lalpm".}

proc newAlpmHandle*(root: cstring, dbpath: cstring, err: var cint): ptr AlpmHandle
  {.cdecl, importc: "alpm_initialize".}

proc release*(handle: ptr AlpmHandle): cint
  {.cdecl, importc: "alpm_release".}

proc setArch*(handle: ptr AlpmHandle, arch: cstring): cint
  {.cdecl, importc: "alpm_option_set_arch".}

proc vercmp*(a: cstring, b: cstring): cint
  {.cdecl, importc: "alpm_pkg_vercmp".}

proc errno*(handle: ptr AlpmHandle): cint
  {.cdecl, importc: "alpm_errno".}

proc errorAlpm*(errno: cint): cstring
  {.cdecl, importc: "alpm_strerror".}

proc register*(handle: ptr AlpmHandle, treeName: cstring, level: cint): ptr AlpmDatabase
  {.cdecl, importc: "alpm_register_syncdb".}

proc local*(handle: ptr AlpmHandle): ptr AlpmDatabase
  {.cdecl, importc: "alpm_get_localdb".}

proc packages*(db: ptr AlpmDatabase): ptr AlpmList[ptr AlpmPackage]
  {.cdecl, importc: "alpm_db_get_pkgcache".}

proc groups*(db: ptr AlpmDatabase): ptr AlpmList[ptr AlpmGroup]
  {.cdecl, importc: "alpm_db_get_groupcache".}

proc name*(db: ptr AlpmDatabase): cstring
  {.cdecl, importc: "alpm_db_get_name".}

proc `[]`*(db: ptr AlpmDatabase, name: cstring): ptr AlpmPackage
  {.cdecl, importc: "alpm_db_get_pkg".}

proc base*(pkg: ptr AlpmPackage): cstring
  {.cdecl, importc: "alpm_pkg_get_base".}

proc name*(pkg: ptr AlpmPackage): cstring
  {.cdecl, importc: "alpm_pkg_get_name".}

proc version*(pkg: ptr AlpmPackage): cstring
  {.cdecl, importc: "alpm_pkg_get_version".}

proc arch*(pkg: ptr AlpmPackage): cstring
  {.cdecl, importc: "alpm_pkg_get_arch".}

proc groups*(pkg: ptr AlpmPackage): ptr AlpmList[cstring]
  {.cdecl, importc: "alpm_pkg_get_groups".}

proc reason*(pkg: ptr AlpmPackage): AlpmReason
  {.cdecl, importc: "alpm_pkg_get_reason".}

proc depends*(pkg: ptr AlpmPackage): ptr AlpmList[ptr AlpmDependency]
  {.cdecl, importc: "alpm_pkg_get_depends".}

proc optional*(pkg: ptr AlpmPackage): ptr AlpmList[ptr AlpmDependency]
  {.cdecl, importc: "alpm_pkg_get_optdepends".}

proc provides*(pkg: ptr AlpmPackage): ptr AlpmList[ptr AlpmDependency]
  {.cdecl, importc: "alpm_pkg_get_provides".}

proc replaces*(pkg: ptr AlpmPackage): ptr AlpmList[ptr AlpmDependency]
  {.cdecl, importc: "alpm_pkg_get_replaces".}

proc cfree*(data: pointer)
  {.cdecl, importc: "free", header: "<stdlib.h>".}

proc freeList*[T](list: ptr AlpmList[T])
  {.cdecl, importc: "alpm_list_free".}

proc freeListInner*[T](list: ptr AlpmList[T], fn: proc (data: pointer): void {.cdecl.})
  {.cdecl, importc: "alpm_list_free_inner".}

proc freeListFull*[T](list: ptr AlpmList[T]) =
  list.freeListInner(cfree)
  list.freeList()

template withAlpm*(root: string, db: string, dbs: seq[string], arch: string,
  handle: untyped, alpmDbs: untyped, errors: untyped, body: untyped): untyped =
  block:
    var errno: cint = 0
    let handle = newAlpmHandle(root, db, errno)

    if handle == nil:
      raise commandError(trp("failed to initialize alpm library\n(%s: %s)\n").strip
        .replace("%s", "$#") % [$errno.errorAlpm, db])

    var alpmDbs = newSeq[ptr AlpmDatabase]()
    var errors = newSeq[string]()
    for dbName in dbs:
      let alpmDb = handle.register(dbName, 1 shl 30)
      if alpmDb != nil:
        alpmDbs &= alpmDb
      else:
        errors &= trp("could not register '%s' database (%s)\n").strip
          .replace("%s", "$#") % [dbName, $handle.errno.errorAlpm]

    try:
      discard handle.setArch(arch)
      body
    finally:
      discard handle.release()

iterator items*[T](list: ptr AlpmList[T]): T =
  var listi = list
  while listi != nil:
    yield listi.data
    listi = listi.next





type
  ConstraintOperation* {.pure.} = enum
    ge = ">=",
    gt = ">",
    eq = "=",
    lt = "<",
    le = "<="

  VersionConstraint* = tuple[
    operation: ConstraintOperation,
    version: string,
    impliedVersion: bool
  ]


  PackageReference = tuple[
    name: string,
    description: Option[string],
    constraint: Option[VersionConstraint]
  ]

proc toPackageReference*(dependency: ptr AlpmDependency): PackageReference =
  let op = case dependency.depmod:
    of AlpmDepMod.eq: some(ConstraintOperation.eq)
    of AlpmDepMod.ge: some(ConstraintOperation.ge)
    of AlpmDepMod.le: some(ConstraintOperation.le)
    of AlpmDepMod.gt: some(ConstraintOperation.gt)
    of AlpmDepMod.lt: some(ConstraintOperation.lt)
    else: none(ConstraintOperation)

  let description = if dependency.desc != nil: some($dependency.desc) else: none(string)
  ($dependency.name, description, op.map(o => (o, $dependency.version, false)))

template toPackageReference*(pkg: ptr AlpmPackage): PackageReference =
  ($pkg.name, none(string), some((ConstraintOperation.eq, $pkg.version, false)))

#template hash*[T](opt: Option[T]): int =
#  opt.map(hash).get(0)

template hash*[T](opt: Option[system.string]): int =
  opt.map(hash).get(0)

#template hash*[T](opt: Option[T], p: proc, q: proc): int =
#  opt.map(hash).get(0)


proc queryUnrequired*(handle: ptr AlpmHandle, assumeExplicit: HashSet[string]): (seq[PackageReference], HashSet[string], HashSet[string],
  Table[string, HashSet[PackageReference]]) =
  let (installed, explicit, dependsTable, alternatives) = block:
    var installed = newSeq[PackageReference]()
    var explicit = newSeq[string]()
    var dependsTable = initTable[string,
      HashSet[tuple[reference: PackageReference, optional: bool]]]()
    var alternatives = initTable[string, HashSet[PackageReference]]()

    for pkg in handle.local.packages:
      proc fixProvides(reference: PackageReference): PackageReference =
        if reference.constraint.isNone:
          (reference.name, reference.description,
            some((ConstraintOperation.eq, $pkg.version, true)))
        else:
          reference

      let depends = toSeq(pkg.depends.items)
        .map(d => d.toPackageReference).toHashSet
      let optional = toSeq(pkg.optional.items)
        .map(d => d.toPackageReference).toHashSet
      let provides = toSeq(pkg.provides.items)
        .map(d => d.toPackageReference).map(fixProvides).toHashSet

I also tried adding the original hash proc from hashes.nim in common.nim as an overload, and various variations, none worked. Perhaps the compiler needs the flexibility to not inline this section, because there are several possibilities what the item being operated on can take? Originally the inline was not there, the rest of the proc body is the same as before when not dealing with closures, so removing it would be restoring the original operation. IDK, it's a complicated section of code.
Simple tests of toHashSet work fine.

@timotheecour
Copy link
Member

timotheecour commented Jun 9, 2021

after a painful reduction (until nimdustmite lands, refs timotheecour#703), here's a reduction that involves only stdlib modules:

import sets, options, hashes

template hash*[T](optz: Option[T]): int =
  optz.map(hash).get(0)

type
  VersionConstraint* = tuple[
    version: string,
    impliedVersion: bool
  ]

  PackageReference* = tuple[
    name: string,
    constraint: Option[VersionConstraint]
  ]

proc main =
  var b: seq[PackageReference]
  let c = toHashSet(b)

remove {.inline.} in proc hash*[T: tuple | object | proc](x: T): Hash {.inline.} = and it makes this work, otherwise it gives:

/Users/timothee/git_clone/nim/temp/pakku/src/common.nim(17, 1) template/generic instantiation from here
/Users/timothee/git_clone/nim/temp/pakku/src/common.nim(19, 20) template/generic instantiation of `toHashSet` from here
/Users/timothee/git_clone/nim/Nim_prs/lib/pure/collections/sets.nim(241, 33) template/generic instantiation of `incl` from here
/Users/timothee/git_clone/nim/Nim_prs/lib/pure/collections/setimpl.nim(49, 21) template/generic instantiation of `rawGet` from here
/Users/timothee/git_clone/nim/Nim_prs/lib/pure/collections/hashcommon.nim(74, 14) template/generic instantiation of `genHashImpl` from here
/Users/timothee/git_clone/nim/Nim_prs/lib/pure/collections/hashcommon.nim(64, 12) template/generic instantiation of `hash` from here
/Users/timothee/git_clone/nim/Nim_prs/lib/pure/hashes.nim(589, 7) template/generic instantiation from here
/Users/timothee/git_clone/nim/temp/pakku/src/common.nim(4, 7) Error: type mismatch: got <Option[common.VersionConstraint], proc (s: HashSet[hash.A]): Hash | proc (s: OrderedSet[hash.A]): Hash | proc (x: openArray[A]): Hash | proc (x: pointer): Hash{.inline, noSideEffect, gcsafe, locks: 0.} | proc (x: ptr T): Hash{.inline.} | proc (x: T: Ordinal or enum): Hash{.inline.} | proc (x: float): Hash{.inline, noSideEffect, gcsafe, locks: 0.} | proc (x: set[A]): Hash | proc (x: string): Hash{.noSideEffect, gcsafe, locks: <unknown>.} | proc (x: cstring): Hash{.noSideEffect, gcsafe, locks: <unknown>.} | proc (sBuf: string, sPos: int, ePos: int): Hash{.noSideEffect, gcsafe, locks: <unknown>.} | proc (x: T: tuple or object or proc): Hash{.inline.} | proc (aBuf: openArray[A], sPos: int, ePos: int): Hash>
but expected one of:
proc map[T, R](self: Option[T]; callback: proc (input: T): R): Option[R] [proc declared in /Users/timothee/git_clone/nim/Nim_prs/lib/pure/options.nim(244, 6)]
  first type mismatch at position: 2
  required type for callback: proc (input: T): R{.closure.} [proc]
  but expression 'hash' is of type: None [none]
proc map[T](self: Option[T]; callback: proc (input: T)) [proc declared in /Users/timothee/git_clone/nim/Nim_prs/lib/pure/options.nim(225, 6)]
  first type mismatch at position: 2
  required type for callback: proc (input: T){.closure.} [proc]
  but expression 'hash' is of type: None [none]
1 other mismatching symbols have been suppressed; compile with --showAllMismatches:on to see them

expression: map(x[1], hash)
    optz.map(hash).get(0)

EDIT

further simplified:

when true:
  import options, hashes
  proc hash*[T](optz: Option[T]): int =
    map(optz, hash).get(0)
  type Foo = object
    foo: int
  var b: Option[Foo]
  let ci = hash(b)

note, this would fail regardless of {.inline.}:

  var b: Option[int]
  let ci = hash(b)

EDIT

down to 1 single import:

when true:
  import hashes
  type Bar[T] = object
  proc map2*[T, R](self: Bar[T], fn: proc (input: T): R): Bar[R] = discard
  proc hash*[T](optz: Bar[T]): int =
    let b = map2(optz, hash)
    123

  type Foo = object
  var b: Bar[Foo]
  let ci = hash(b)

EDIT

further:

when true:
  # repro
  import hashes
  type Bar[T] = object
  proc map2*[T](self: Bar[T], fn: proc (a: T): int) = discard
  proc hash*[T](optz: Bar[T]): int =
    map2(optz, hash)
    123

  type Foo = object
  var b: Bar[Foo]
  let ci = hash(b)

note

further reduction is still needed to remove stdlib modules, help welcome

@timotheecour
Copy link
Member

timotheecour commented Jun 10, 2021

here's the final reduction:

when true:
  proc map2(a: proc(): int) = discard
  # proc map2(a: proc(): int {.inline.}) = discard # uncommenting would make it work
  proc fn1(): int = 1
  proc fn2(): int {.inline.} = 2
  map2(fn1) # ok
  map2(fn2) # error

conclusion:
a signature like this:

# in options.nim
proc map*[T, R](self: Option[T], callback: proc (input: T): R): Option[R] {.inline.} =

is IMO un-necessarily restrictive and that's what should be fixed instead, either in the signature or in the compiler to ignore the {.inline.} pragma in sigmatch (eg by making the proc implicitly generic on the pragmas being used).

options.map should not care whether the callback is inline or not, this prevents valid optimizations;

  • gcsafe, noSideEffect: these are fine
  • inline, noinline: these prevent sigmatch

The proper fix though is neither patching the compiler to ignore inline/noinline pragmas nor to use old/new concepts (refs #18225). The proper fix is alias + enableif as I've already implemented, see #11992 and #12048; it would allow in particular passing templates (or macros, iterators etc) to generics, instead of being restricted to passing procs, so that for example a hash overload can use a template without breaking options.map in the above example.

examples

import std/options
proc fn(a: int): int = a
proc fn(a: string): string {.noinline.} = a
template fn(a: float): float = a
echo some(1).map(fn) # ok
echo some("a").map(fn) # Error: type mismatch
echo some(1.5).map(fn) # Error: internal error: expr(skTemplate); unknown symbol

@zqqw
Copy link
Author

zqqw commented Jun 10, 2021

Brilliant, thank you for looking at this. Hopefully it might get fixed before 1.6 is released. It would have taken me a lot longer to reduce this even partially I think, dustmite would certainly be helpful so long as it comes with a good manual and I could figure out how to use it!

@Araq
Copy link
Member

Araq commented Jun 10, 2021

The inline pragma was wrong for objects and tuples anyway as you don't know how many object fields would be expanded.

Araq added a commit that referenced this issue Jun 10, 2021
@Araq Araq closed this as completed in 0a4858d Jun 10, 2021
@timotheecour
Copy link
Member

timotheecour commented Jun 10, 2021

you don't know how many object fields would be expanded.

it doesn't matter! the C optimizer is free to inline or not inline a particular generic instantiation marked with {.inline.}; it has more context than std/hashes library code as to whether it should be inlined or not for a particular generic type in a particular program (say objects with a single field).

Without {.inline.} though, it cannot inline it even if it would make sense as soon as client code is in a different module.

here's a simplified example showing a 8X speedup simply by adding {.inline.}:

nim r -d:case1 -d:danger main # 1s
nim r -d:case2 -d:danger main # 0.12s
# main.nim:
import std/times
import t12382b
proc main()=
  let n = 100_000_0000
  var c = 0
  let t = cpuTime()
  for i in 0..<n:
    when defined(case1): c += fn1(i, c)
    when defined(case2): c += fn2(i, c)
  let t2 = cpuTime()
  echo (t2 - t, c)
main()

#  t12382b.nim:
proc fn1*[T](a: T, b: T): T = a + b
proc fn2*[T](a: T, b: T): T {.inline.} = a + b
if false:
  # having these instantiated here or in another module prevents
  # cross-module inlining forr fn1 which is not {.inline.}
  let a1 = fn1(1, 2)
  let a2 = fn2(1, 2)

@timotheecour
Copy link
Member

timotheecour commented Jun 10, 2021

@Araq as I predicted (see previous comment), the hotfix #18227 for closing this issue introduces a performance regression, so the problem should be fixed differently. It affects all code involving hashes, which can be particularly performance sensitive. Surely there must be a better way that neither breaks code nor makes hashes slower than necessary (it's a general problem btw; with the same logic as #18227, we'd end up having to remove {.inline.} from all generics just because someone might be using it, not good).

before #18227:
nim r -d:case2 -d:danger main # 2.2
after #18227:
nim r -d:case2 -d:danger main # 3.5

=> 1.6X slower

# main.nim:
when true:
  import std/times
  import t12382b
  import hashes
  proc main()=
    let n = 100_000_0000
    var c = 0
    let t = cpuTime()
    for i in 0..<n:
      c += hash (i, i)
    let t2 = cpuTime()
    echo (t2 - t, c)
  main()

# t12382b.nim:
when true:
  import std/hashes
  let a2 = hash (1, 2) # instantiates the generic first in this module

@Araq
Copy link
Member

Araq commented Jun 10, 2021

A performance regression that is caught by link time optimizations is not nearly as bad as a regression which prevented valid code from compiling.

@timotheecour
Copy link
Member

timotheecour commented Jun 10, 2021

A performance regression that is caught by link time optimizations is not nearly as bad as a regression which prevented valid code from compiling.

with this logic, we'd have to consider adding/removing {.inline.} a breaking change, preventing fixing performance issues, just because someone may be using code like this in their project:

proc callFun[A, B](a: A, fn: proc(a2: A): B): B = fn(a) # analog to options.map from this issue
import std/strutils
echo callFun("ab", validIdentifier)

(or anything similar that has a proc argument, doesn't have to be generic)

that is caught by link time optimizations

--passc:lto is a blunt all or nothing tool, it's easy to find examples where it results in worse performance (and the compile times is 3-4X slower):

# with LTO:
nim c --passc:-flto -d:release -o:bin/nim.tmp1 -f compiler/nim # 90 seconds
bin/nim.tmp1 c --compileOnly -f compiler/nim # 9.4s

# without LTO:
nim c -d:release -o:bin/nim.tmp2 -f compiler/nim # 24 seconds
bin/nim.tmp2 c --compileOnly -f compiler/nim # 8s

(tested on 1.5.1 0a4858d)

There has got to be a better way.

links

see also nim-lang/RFCs#198 (comment)

--passc:-flto runtime performance is still not on par with having marked procs as {.inline.} even in this simple example (1.35X slower)

@timotheecour timotheecour changed the title Addition of inline pragma in recent hashes.nim commit causes toHashSet build error in Pakku adding/removing pragmas like {.inline.} causes breaking changes Jun 10, 2021
@zqqw
Copy link
Author

zqqw commented Jun 10, 2021

# main.nim:
when true:
  import std/times
  include t12382b
  import hashes
  proc main()=
    let n = 100_000_0000
    var c = 0
    let t = cpuTime()
    for i in 0..<n:
      c += hash (i, i)
    let t2 = cpuTime()
    echo (t2 - t, c)
    echo a2
  main()

$ nim r --passC:-Ofast -d:case2 -d:danger main
-Ofast gives a small improvement in any case, echo a2 removes the a2 unused warning (it has to be let a2* with import) and using include not import gives equal times. (Probably missing the point of course but it does sort of "inline" the module another way :))

@timotheecour
Copy link
Member

-Ofast gives a small improvement in any case

I cannot reproduce this, this probably just reflects measurement noise (take the best-of-5 for example). https://linux.die.net/man/1/gcc says:

If you use multiple -O options, with or without level numbers, the last such option is the one that is effective.

and --passc:-Ofast -d:danger --hint:cc (or with flipped arguments) shows -Ofast -O3 is being used, ie, -O3 is what's used (as of 1.5.1 6b97889). Note that even if that were fixed, it's hardly related to this issue, because it doesn't change how inlining works (note also that -Ofast implies disregarding standards compliance, ie can affect semantics).

include also is hardly related to this issue, besides the know caveats of include, include won't help you your hot-spot is in a 3rd-party library that imports hashes.

@zqqw
Copy link
Author

zqqw commented Jun 10, 2021

proc inlinehash*[T: tuple | object | proc](x: T): Hash {.inline.} =

  when T is "closure":
    result = hash((rawProc(x), rawEnv(x)))
  elif T is (proc):
    result = hash(pointer(x))
  else:
    for f in fields(x):
      result = result !& hash(f)
    result = !$result

Adding this to hashes.nim and calling inlinehash instead of hash in both files works. I suppose ideally you could have inlinehash and notinlinehash, hash would be something like
if (check somehow this is going to work)
inlinehash
else
notinlinehash

@Araq
Copy link
Member

Araq commented Jun 11, 2021

with this logic, we'd have to consider adding/removing {.inline.} a breaking change, preventing fixing performance issues, just because someone may be using code like this

The pre-1.0 Nim had a .procvar pragma so that we were free to add .inline or new additional optional parameters to existing procs. People didn't understand the purpose and complained until I removed it...

But anyway, "with this logic" implies that I intend to always undo inline changes because it would break code, no, I agree with you that the language could handle this better instead. I merely applied a hotfix until the better solution emerges.

@timotheecour
Copy link
Member

The pre-1.0 Nim had a .procvar pragma so that we were free to add .inline or new additional optional parameters to existing procs. People didn't understand the purpose and complained until I removed it...

IIUC procvar avoided some of the issues of #18220 (allowing one to add inline pragma, addoptional params etc, without breaking related APIs), but it required one to annotate the proc you want to pass as param with {.procvar.} (eg #2487), forcing each API to make that decision; in particular, this wouldn't help with 3rd party procs that didn't have that annotation.

It possibly also involved a performance penalty via an extra indirection but I'm not sure, please calrify.

So {.procvar.} didn't solve the general of APIs not marked with {.procvar.}, nor did it help with symbol forwarding (eg iterators, templates, generics).

See also #18241

@Araq
Copy link
Member

Araq commented Jun 12, 2021

.procvar means the procs to use "by address" are explicitly opt-in which is the only sane default for Nim's stdlib. It worked exactly how I envisioned it to work, but I won't bring it back, not important enough to justify the fights.

Araq pushed a commit that referenced this issue Jun 12, 2021
#18241)

* getType now works with tyInferred (concepts); refs #18220

* avoid cast

* add more docs
@timotheecour
Copy link
Member

timotheecour commented Jun 28, 2021

this is related to #8303 (map shouldn't care whether proc arg is cdecl, preventing args.map(quoteShell))

the proper fix (with no runtime cost) is alias (#11992), possibly with alias Callable[R, T] to optionally restrict to an alias with a specific input/output types, refs #18241, and/or alias + {.where: condition.} (aka enableif)

@Varriount
Copy link
Contributor

Varriount commented Jul 1, 2021

Would another fix be for the compiler to automatically generate procvar and closure thunks for procedures of other calling conventions?

@timotheecour
Copy link
Member

Would another fix be for the compiler to automatically generate procvar and closure thunks for procedures of other calling conventions?

Ya I was thinking exactly on those lines as well, see nim-lang/RFCs#396

PMunch pushed a commit to PMunch/Nim that referenced this issue Mar 28, 2022
PMunch pushed a commit to PMunch/Nim that referenced this issue Mar 28, 2022
…ang#18220 (nim-lang#18241)

* getType now works with tyInferred (concepts); refs nim-lang#18220

* avoid cast

* add more docs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants