Codegen may allocate and then never use slots. Free up those slots
and rewrite bytecode to be smaller. The resulting bytecode will be
slightly faster, result in better inlining, and be somewhat normalized
for further optimizations.
Currently very simple hueristic, and not supported
on many functions that instatiate closures or load/set upvalues.
We also now, more than ever, need a slot-compaction routine to reduce
the number of slots used by functions.
This exposes the optimization level for a few reasons.
First, Local value numbering exposed some bad codegen. Playing with
optimiaztion level exposes this more easily.
When compiling dead code in a known unused scope, we
would generate code that tried to load invalid slots.
Make sure that unused code will still pass `janet_verify()` as
invalid code could trigger bad behavior in our optimizer or garbage
collector.
Since the code was proven dead, this would not trigger any runtime
behavior issues though.
Second, even trivial optimizations tend to break debug information. For
example, constant propogation will eliminated symbol mappings which can
be useful for debugging. By default, don't eliminate any symbol map
debug info.
However, we will likely need to gate some of these optimizations
behind a switch as they are beginning to destruct debug info. For
example, local value number maybe should be off by default or at the
very least turned off for named loads.
How about we resolve through the special host name "localhost." instead of assuming that the loopback interface is bound to 127.0.0.1 (which might not be the case, especially in FreeBSD jails)?
See RFC6761 for how "localhost." is treated, especially:
Name resolution APIs and libraries SHOULD recognize localhost
names as special and SHOULD always return the IP loopback address
for address queries and negative responses for all other query
types. Name resolution APIs SHOULD NOT send queries for
localhost names to their configured caching DNS server(s).
* Fix a couple of imprecisions in the file/ docs
The `+` flag to `(file/open)` was incorrectly documented as being for
appending. Instead, this allows to read to files opened with `:w` or
`:a`, and to write to files opened with `:r`. This is the same as the
`+` flag in `fopen(3)`.
The `(file/seek)` function was documented as having both `whence` and
`n` as optional arguments. It also didn't say what the default for `n`
was. Anyway, this doesn't make sense, as a default of `:cur` and `0`
would effectively make `(file/seek f)` a no-op. I also amended a test so
both `(file/seek f :set 0)` and `(file/seek f :set)` are covered and
checked to move to the beginning of the file.
* Remove useless check for argc >= 2
We already determined it is between 2 and 3 with `janet_arity(argc, 2, 3)`.
* Dump args in assertion
* Unroll loop in test
* Differentiate failure messages
Add testcase for os/shell to test/suite-ev.janet
While we may not want to completely deprecate os/shell, we should at
least try to redirect users to better and safer interfaces like
os/execute.
Flag collisions was causing some unexpected behavior in some situations.
Removing the source of collisions resolve odd cancelation behavior in
the ev/ module where multiple calls to ev/cancel on the same fiber would
mess with the internal fiber flags and cause bad behavior and the wrong
error message return (but still otherwise behave correctly, so unless
you were parsing the returned error messages, things likely worked).
Also leave comments and test to avoid regressions.
Passing in the wrong number of keyword arguments could be problematic
and cause compialtion issues. Handle that more gracefully and at least
be clean in valgrind.
This also improves the error behavior of malformed automatic parameters
so that the error conditions are better defined. Previously, large
indices like 88888888888888 would actually simply be converted to
symbols and fail later. This change puts strict limitations on this
conversion process - If a symbol is of the form "$<str>" and
(scan-number str) returns non-nil, the result must be a valid index. In
this way, symbols like `$1.1` will result in an immediate macro expansion error
rather than (usually) and unknown binding error.
Add some test cases and asserts for new pretty printing - we were not
properly handing the case when print formatter was not at the start of
the buffer.
A simple fix is to add a lookback_barrier, which is an index into the
buffer that backtracking print formatter can't go past. This ensures
that, whatever newlines are replace with spaces, we don't mess with any
explicit newlines that should be printed.
Also some tweaks to logic to try an make invariants more obvious and
simplify looping and indexing.
kqueue-based filewatch is more limited than inotify, but is still
useful in many cases. Notably absent is any ability to monitor
directories automatically without wasting a lot of file descriptors.
Prevent redefining bindings by accident. There are
a few cases where we want to allow this, such as the `default` macro, so
we allow a keyword :shadow to be included in the `def` expression to
turn off this lint.
TODO:
* Clean up test suite to remove binding shadowing
* Make sure that we don't get lints with *redef* turned on
* Add positive and negative tests for lint messages.
* Add location of shadowed binding in message
Indentation levels are not sufficiently clear for deeply nested
data structures. Instead, align items in a collection
with its opening bracket, also known as hanging indentation.
To avoid guessing the length of a "short" collection to print it
on one line, items are now always printed on separate lines.
Still fallback to blocking connect with WSAConnect when ConnectEx is not
available or applicable, but ConnectEx is preferred and recommended by
Microsoft.
Also make some changes to our use of OVERLAPPED in various places in the
ev code, replacing all uses with JanetOverlapped. This also let's us
avoid reusing internal fields for OVERLAPPED which may or may not be
used in various places.
For unclosable files, no need to dup, but for closable files we can get
a resource leak. Valgrind and similar tools won't catch this but IO will
unexpectedly start going to the wrong descriptor if a file was
transferred to a new thread, closed, and then a new file was created.
janet_collect() marks janet_vm.root_fiber but not janet_vm.fiber.
When janet_pcall (or janet_continue) is called from a C function,
the inner fiber becomes janet_vm.fiber while root_fiber still points
to the outer fiber. If GC triggers inside the inner fiber's execution,
the inner fiber is not in any GC root set and can be collected —
including its stack memory — while actively running.
This also affects deeply nested cases: F1 -> C func -> janet_pcall ->
F2 -> C func -> janet_pcall -> F3, where F2 is saved only in a C
stack local (tstate.vm_fiber), invisible to GC.
Fix: in janet_continue_no_check, root the fiber with janet_gcroot
when this is a nested call (root_fiber already set). Each nesting
level roots its own fiber, handling arbitrary depth. Top-level calls
(event loop, REPL) skip the root/unroot entirely since root_fiber
is NULL.
Add test/test-gc-pcall.c: standalone C test covering both single
and deep nesting cases.
Co-authored-by: Brett Adams <brett@bletia-9.local>