Nim: VM and js backend require `result` for lent or var annotations to work

Created on 21 May 2020  路  17Comments  路  Source: nim-lang/Nim

Most of the standard library is working at compile-time and there are checks to ensure that.

Unfortunately from https://github.com/nim-lang/Nim/pull/14417#issuecomment-632125417, lent is not supported in the VM.

For example the JSON test will fail with

Error: limited VM support for 'addr'

This would prevent using lent to optimize arrays, sequences, options, "result" types, object variants. All of which have a typical no-copy use-case.

The easy fallback would be to make lent a no-op in the VM.

VM

Most helpful comment

IMO, this issue is not VM specific. All backends introduce a temporary variable in this case. IMO, solution is to introduce lowering in transf phase:
Transform:

x = block:
  stmt1
  stmt2
  expr

Into
```nim
block:
stmt1
stmt2
x = expr
````
Similar lowering for if, block, case, stmtList expressions.
injectdestuctors already does this lowering. It needs to be moved from injectdestructors to transf.

All 17 comments

Please provide a self contained test so I can have a look

You can revert this commit: https://github.com/nim-lang/Nim/pull/14417/commits/0964462d8d6849723c8facb433f2b17eed311759

i.e. in the option module change

proc get*[T](self: Option[T]): T {.inline.} =

to

proc get*[T](self: Option[T]): lent T {.inline.} =

And the test suite will crash in many places like Json.

still, a minimal example would've been nice;
which of these since you've already ran this?

lib/pure/json.nim
lib/pure/parsejson.nim
tests/stdlib/mjsonexternproc.nim
tests/stdlib/tjson_unmarshall.nim
tests/stdlib/tjsonexternproc.nim
tests/stdlib/tjsonmacro.nim
tests/stdlib/tjsonmacro_reject.nim
tests/stdlib/tjsontestsuite.nim

I'll cook up one this evening it was tjsonmacro.nim

Here is an example

type MyOption[T] = object
  case has: bool
  of true:
    value: T
  of false:
    discard

func some[T](val: T): MyOption[T] =
  result = MyOption[T](has: true, value: val)

func get[T](opt: MyOption[T]): lent T =
  assert opt.has
  opt.value

const x = some(10)
echo x.get()

static:
  echo x.get()

As a potential impact, I tried to use options in my RayTracer, but they were 5x slower (not just a couple dozen percents like the iterator issue in #14421) than passing mutable parameter + bool

Well nobody claimed Option[T] is a lightweight abstraction.

Well nobody claimed Option[T] is a lightweight abstraction.

I was misled by Rust code :/

Issue turned out completely different. Can use stmtListExpr to assign lent T/var T or you get addr of temporary variable. Just changed get to use stmtListExpr to assign to result

indeed: reduced case:

when true: # D20200525T015650
  type Foo = object
    x: float

  proc fn(a: var Foo): var float =
    ## discard <- turn this into a comment (or a `discard`) and error disappears
    # result = a.x # this would work
    a.x #  Error: limited VM support for 'addr'

  template main =
    var a = Foo()
    discard fn(a)
  main() # works
  static: main()  # fails

So using a return statement or result work.

type MyOption[T] = object
  case has: bool
  of true:
    value: T
  of false:
    discard

func some[T](val: T): MyOption[T] =
  result = MyOption[T](has: true, value: val)

func get[T](opt: MyOption[T]): lent T =
  assert opt.has
  return opt.value

const x = some(10)
echo x.get()

static:
  echo x.get()

Can we change the implicit return _expression_ (not sure how to call that) to be the same as implicit result or explicit return statement?

At the very least the error message should be changed to "Maybe try an explicit return or the implicit result variable".

Furthermore this is a pattern that we favor in nim-beacon-chain. We are already fighting stack issues and genericResets and if return expression introduced non-optimized away temporaries, they might contribute to that.

At the very least the error message should be changed to "Maybe try an explicit return or the implicit result variable".

that's fine as an improvement, but obviously this issue should be kept open until this bug is fixed

Maybe try an explicit return or the implicit result variable

er, why should that be a recommendation? ie they're supposed to be equivalent, so it seems counterproductive to implement a feature that suggests a workaround instead of focusing on the actual bug?

we should reopen this issue (refs https://github.com/nim-lang/Nim/pull/14442#issuecomment-633576965)

Maybe try an explicit return or the implicit result variable

er, why should that be a recommendation? ie they're supposed to be equivalent, so it seems counterproductive to implement a feature that suggests a workaround instead of focusing on the actual bug?

If it's possible to fix this in a timely manner sure, if not we need to tell users how to workaround that.

IMO, this issue is not VM specific. All backends introduce a temporary variable in this case. IMO, solution is to introduce lowering in transf phase:
Transform:

x = block:
  stmt1
  stmt2
  expr

Into
```nim
block:
stmt1
stmt2
x = expr
````
Similar lowering for if, block, case, stmtList expressions.
injectdestuctors already does this lowering. It needs to be moved from injectdestructors to transf.

solution is to introduce lowering in transf phase:

@cooldome do you think this could fix https://github.com/nim-lang/Nim/issues/9230 as well?

see also:
https://github.com/nim-lang/Nim/blob/9502e39b634eea8e04f07ddc110b466387f42322/compiler/lambdalifting.nim#L917

proc liftForLoop*(g: ModuleGraph; body: PNode; owner: PSym): PNode =
if not (body.kind == nkForStmt and body[^2].kind in nkCallKinds):

IMO, this issue is not VM specific

indeed, just got hit by this in https://github.com/nim-lang/Nim/pull/14875

nim c -r main # ok
nim js -r main # bug

proc byLent2[T](a: T): lent T =
  runnableExamples: discard
  a

proc byLent3[T](a: T): lent T =
  runnableExamples: discard
  result = a

block:
  var a = 10
  # let x = byLent3(a) # works
  let x = byLent2(a) # Error: internal error: genAddr: nkStmtListExpr
Was this page helpful?
0 / 5 - 0 ratings