The VM used to not detect this case. It meant you could get into a
situation where another fiber's caller had completed. Then, when it
tried to resume that fiber, the VM would crash because there was nothing
to resume to.
This is part of thinking through all the cases around re-entrancy. Added
some notes for that too.
A statement like:
for (i in 1..2)
When run in the REPL declares a local variable ("i"), but not inside
a function or method body. This hit a corner case in the compiler
where it didn't have the correct slot indexes set up.
That corner case is because sometimes when you compile a chunk, local
slot zero is pre-allocated -- either to refer to "this" or to hold the
closure for a function so that it doesn't get GCed while running. But
if you're compiling top-level code, that slot isn't allocated. But top
level code for the REPL *should* be, because that gets invoked like a
function.
To simplify things, *every* compiled chunk now pre-allocates slot zero.
That way, there are fewer cases to keep in mind.
Also fixed an issue where a GC during an import could collected the
imported module body's closure.
Fix#456.
If the function the fiber is created from takes a parameter, the value
passed to the first call() or transfer() gets bound to that parameter.
Also, this now correctly handles fibers with functions that take
parameters. It used to leave the stack in a busted state. Now, it's a
runtime error to create a fiber with a function that takes any more
than one parameter.
- Not sure what was going on, but fix#456. It makes more sense for the
REPL to invoke the compiled code as a fiber than a function anyway.
- Flush stdout before reading from stdin since System.print() no longer
does that automatically.
- Remove List.new(_). I was convinced by the issue discussion that
using it is probably a bad idea. We don't want to encourage more nulls
in the world than there are already are. So let's see if we can live
without it and just have List.filled(). That way users think about
what they're creating a list *of*.
- Added some more tests.
- Correctly handle being given a negative size.
This has a couple of pros:
- It simplifies some code that used to have to check whether a called
thing is a bare function or a closure.
- It's faster because we don't need to do the above checks every time
something is called.
- It lets us more precisely type some fields that used to be Obj*
because they could hold an ObjClosure* or ObjFn*.
The cost is that we allocate a closure every time a function is
declared, even if it has no upvalues. Since functions are called way
more often than they are declared, this is still a net win.
On my Mac laptop:
api_call - wren 0.06s 0.0020 104.73%
api_foreign_method - wren 0.32s 0.0040 101.89%
binary_trees - wren 0.23s 0.0057 98.82%
binary_trees_gc - wren 0.79s 0.0170 98.46%
delta_blue - wren 0.13s 0.0031 101.36%
fib - wren 0.23s 0.0038 103.15%
fibers - wren 0.04s 0.0017 98.97%
for - wren 0.08s 0.0017 107.81%
method_call - wren 0.12s 0.0024 98.60%
map_numeric - wren 0.31s 0.0052 103.93%
map_string - wren 0.11s 0.0113 97.97%
string_equals - wren 0.20s 0.0023 107.75%
This is simpler than having special opcodes for them. It's also a bit
faster, and gets some non-critical code out of the interpreter loop.
Also, this is good prep work for being able to write some of the module
loading process in Wren to make things more flexible for embedders and
the CLI.
Now, you call wrenEnsureSlots() and then wrenSetSlot___() to set up the
receiver and arguments before the call. Then wrenCall() is passed a
handle to the stub function that makes the call. After that, you can
get the result using wrenGetSlot___().
This is a little more verbose to use, but it's more flexible, simpler,
and much faster in the VM. The call benchmark is 185% of the previous
speed.
This allows "%(...)" inside a string literal to interpolate the
stringified result of an expression.
It doesn't support custom interpolators or format strings, but we can
consider extending that later.
Primitives still have a return value to indicate normal value returning
versus runtime errors or fiber switching since it minimizes branches
in the common case of a normal value result.
Instead, Fn.call(...) is a special *method* type that has the same
special sauce. The goal is eventually to get rid of the primitive
result type entirely.
- Add Fiber.transferError(_).
- Primitives place runtime errors directly in the fiber instead of on
the stack.
- Primitives that change fibers set it directly in the VM.
- Allow a fiber's error to be any object (except null).
Get rid of the separate opt-in IO class and replace it with a core
System class.
- Remove wren_io.c, wren_io.h, and io.wren.
- Remove the flags that disable it.
- Remove the overloads for print() with different arity. (It was an
experiment, but I don't think it's that useful.)
- Remove IO.read(). That will reappear using libuv in the CLI at some
point.
- Remove IO.time. Doesn't seem to have been used.
- Update all of the tests, docs, etc.
I'm sorry for all the breakage this causes, but I think "System" is a
better name for this class (it makes it natural to add things like
"System.gc()") and frees up "IO" for referring to the CLI's IO module.
The .count getter on string returns the number of code points. That's
O(n), but it's consistent with the rest of the main string API.
If you want the number of bytes, it's "string".bytes.count.
Updated the docs.
Fixes 68. Woo!