GithubHelp home page GithubHelp logo

Comments (5)

leafi avatar leafi commented on September 14, 2024 1

Oh, that's brilliant! Thank you so much!

Also, speaking of docs, thanks for having generally so robust documentation. It's been essential during my journey learning Nelua. (I like the language a lot, too, obviously.)

from nelua-lang.

edubart avatar edubart commented on September 14, 2024

You example is valid, priv_recs is not in the stack memory, but in static memory (top scope variables are always in static memory). So in your example using the values returned from get_recs will always be valid.

But this new example wouldn't be valid:

require 'span'
global Rec = @record{a: int32}
global function get_recs(x: integer): span(Rec)
  local priv_recs: []Rec = {{x}, {x}}
  return priv_recs
end
print(get_recs(1)[0].a)

And indeed the above prints junk data when compiled with clang, because after returning priv_recs the address inside span isn't valid anymore. To catch such mistakes I usually warn users to run with --sanitize, sadly even it could not catch such mistake. But well, the programmer should know that function is returning an address, and is responsible for checking the liveness of what he returns, but indeed would be handy if we could get a compile error in this case.

The motivation for having span.__convert accepting array by value and getting is reference instead is this example:

require 'span'
global Rec = @record{a: int32}
global function use_recs(x: span(Rec))
  print(x[0].a)
end
 -- without syntax sugar (what span.__convert bellow is really doing does behind the scenes)
use_recs(&(@[]Rec){{a=1}, {a=2}})
-- syntax sugar relying in span.__convert
use_recs{{a=1}, {a=2}}

And that is a valid and useful feature to pass list of values, disallowing spanT.__convert to accept array by values would mean dropping that feature, and I use that feature in my personal projects a lot already as it makes code more readable, without this feature the syntax is much more boilerplate.
Note: You could argue that using & on a temporary constructed argument is invalid, but it's not for aggregate types, even in standard C, the compiler guarantees to keep its address valid until the call ends.

A raw array (not ptr to array) could be detected in spanT.__convert and the array could be automatically copied to the heap. This wouldn't cause crashes, but it would still be somewhat unexpected.

This workaround would be unexpected, implicit conversions to spans should never allocate memory (a semantic of the language), so this is not ideal.

Possible enhacement

The liveness of priv_recs could be tracked by the compiler to then disallow the implicit conversion to a span in the return, but this would just work just for misuse of return, span and array values, there are many other possible ways to bypass such checks and return addresses of variables in the stack, so I am not sure if having a check for that very specific case while having in mind there will be still holes is that motivating. Maybe if it becomes a common mistake it should.

from nelua-lang.

edubart avatar edubart commented on September 14, 2024

After some thought and experiments I've added a compile check for this specific misuse in a7b5173 , your example triggers now this compile error:

play/draft3.nelua:8:10: error: in call of function 'span(Rec).__convert' at argument 1: cannot perform implicit conversion from 'array(Rec, 2)' to 'span(Rec)' while inside a return statement
  return priv_recs

Rationale: I have noticed nowhere using implicit conversions to spans in return statements, and is usually dangerous (this issue is an example), So I have disallowed implicit conversions to spans inside returns, that means the compiler is not really tracking the variable liveness, this still up to the user to be cautious and use --sanitize to double check.

from nelua-lang.

leafi avatar leafi commented on September 14, 2024

Thanks for adding the check! It should help.

One thing I wanted to pick up on, to make sure I understand correctly:

You example is valid, priv_recs is not in the stack memory, but in static memory (top scope variables are always in static memory). So in your example using the values returned from get_recs will always be valid.

Please consider this example, where a static variable is picked directly:

require 'span'
global Rec = @record{
  a: int32
}
local spanRec: type = @span(Rec)

local priv_recs: []Rec = {{1}, {2}}
global function get_recs(): span(Rec)
  return spanRec.__convert(priv_recs)
end

print(&priv_recs[0])
print(get_recs().data)

local b: int32
print('A random static address for comparison: ', &b)

global function showstackaddr()
  local z = 0
  print('A random stack address for comparison: ', &z)
end
showstackaddr()

Output:

[leaf@alicorn tael]$ nelua nelua_span_stack.nelua
0x55f5a1538050
0x7fff2c585010
A random static address for comparison: 	0x55f5a1538064
A random stack address for comparison: 	0x7fff2c585020

i.e. Static arrays are copied to the stack during conversion.

I tried to make an example that would die with automatic scope variables e.g. use_recs{{1}, {2}}, but without success. I could only cause a crash by passing e.g. use_recs((@[67108864]Rec){}) but that doesn't demonstrate anything, really.

from nelua-lang.

edubart avatar edubart commented on September 14, 2024

Please consider this example, where a static variable is picked directly

Thanks for pointing that out, and you were correct. I was assuming span.__convert was taking the array value by reference implicitly (as it does when you use use_recs{} syntax sugar), but reading the code it was actually doing an extra temporary copy, and I forgot to change that after I've implemented the mentioned syntax sugar (which recently added feature, even not in docs). Doing an extra copy is always a wrong thing to do on implicit conversions for spans, so I have changed conversion semantics to what I actually assumed in mind, so please reread my previous response only after commit 9840cc8 .

After that commit, this prints what you expected:

require 'span'
global Rec = @record{
  a: int32
}
local spanRec: type = @span(Rec)

local priv_recs: []Rec = {{1}, {2}}
global function get_recs(): span(Rec)
  local spn = spanRec.__convert(priv_recs)
  return spn
end

print(&priv_recs[0])
print(get_recs().data)
assert(&priv_recs[0] == get_recs().data)

Output:

0x55d5a340f050
0x55d5a340f050

from nelua-lang.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.