public inbox for kawa@sourceware.org
 help / color / mirror / Atom feed
* interactive development and code re-loading
@ 2015-10-11 20:20 Per Bothner
  2015-10-12  7:43 ` Helmut Eller
  0 siblings, 1 reply; 10+ messages in thread
From: Per Bothner @ 2015-10-11 20:20 UTC (permalink / raw)
  To: Kawa mailing list

A major benefit of "dynamic" languages is "interactive development":
Use a repl, try some code, change the code as needed - and re-load
the changed code into the existing session.   Kawa is weaker in
this respect than some other languages, especially Lisps, where this
kind of interactivity is expected.  One reason is that Kawa does a
lot of inlining and type propagation: Say f depends on g, and you change
g but not f.  In that case the compiled version of f may have
baked-in assumptions about f that are no longer true.  Another
reason Kawa is weaker is that it tries to be close to the VM,
so a Kawa class is mapped fairly directly to a VM class, which
you can't change after creating the class and instances of it.

There are various mechanisms for improving the situation: The
main ones are indirection (less inlining), as well as automatic
re-compilation (if f depends on g, and g is changed, then
recompile f *and* g).  I won't go into details; getting to
something we're happy with will take a while.  (I do have
some patches that are close to go, which will help a bit.)

What I'd like to decide is *when* Kawa should use "interactive mode"
- i.e. to add indirection and automatic re-compilation.  The answer
is certainly not "always" because of the costs.  Having interactive
mode be controlled by flags is possible, but the more flags people
have to remember, the more confusion.  So having good defaults and
simple rules is better.

Note that "interactive mode" is a session property - in practice
that means it is a global property: Every time a file is imported,
required, loaded, or included - how the code is handled depends
on the "interactive mode" session property.  This property
could be a parameter (in the SRFI-39 sense) but it will normally
be initialized based on the kawa command line.

Starting a REPL (implicitly or using the "--" flag) should
obviously set the interactive mode property.

Running a file in whole-module mode (i.e. '$kawa foo.scm'), or
otherwise running a "script" should IMO *not* set the interactive
mode property.

However, running a file in line-at-a-time mode (with the -f flag)
*should* IMO set the interactive mode property.

I'm unsure about the -e flag, but that should probably also set
the interactive mode property.

PROPOSAL SUMMARY

There is a "global" is-interactive property (actually
stored as a field of the current ModuleManager instance).
It is initially false.

Each module (file or repl command) has an is-interactive,
property which is set from the is-interactive global.  This flag
controls how the module is compiled; how Kawa deels with any
"previous version" of a function/value/class; and whether
Kawa needs to keep track of dependencies.

We may add command-line flags to set the global is-interactive property,
though I'll wait to see if they're actually useful.

Entering a repl, or using the -f, -e, -c, or --script options
sets the is-interactive property.

Comments?
-- 
	--Per Bothner
per@bothner.com   http://per.bothner.com/

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: interactive development and code re-loading
  2015-10-11 20:20 interactive development and code re-loading Per Bothner
@ 2015-10-12  7:43 ` Helmut Eller
  2015-10-12 17:34   ` Per Bothner
  2015-10-13  5:03   ` Per Bothner
  0 siblings, 2 replies; 10+ messages in thread
From: Helmut Eller @ 2015-10-12  7:43 UTC (permalink / raw)
  To: kawa

On Sun, Oct 11 2015, Per Bothner wrote:

> What I'd like to decide is *when* Kawa should use "interactive mode"
> - i.e. to add indirection and automatic re-compilation.  The answer
> is certainly not "always" because of the costs.  Having interactive
> mode be controlled by flags is possible, but the more flags people
> have to remember, the more confusion.  So having good defaults and
> simple rules is better.

I would say that in R7RS mode, "always" is required for code that is
defined in the interaction-environment, i.e. outside of libraries.
Basically (set!  foo ...), if foo was defined in the
interaction-environment, should always work regardless what type/value
foo previously had.

It seems to me that a per-library flag would be quite convenient,
because some libraries tend to be more stable than others.  Usually, I
want to re-load new/unstable libraries but very rarely older or
third-party libraries.  Alternatively we could say that "stable"
libraries should be compiled to jar files and be added to the JVM
classpath while "unstable" libraries should be loaded from source.

I'm still wondering how a re-loadable version of define-simple-class
could possibly work.

Helmut

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: interactive development and code re-loading
  2015-10-12  7:43 ` Helmut Eller
@ 2015-10-12 17:34   ` Per Bothner
  2015-10-13  5:03   ` Per Bothner
  1 sibling, 0 replies; 10+ messages in thread
From: Per Bothner @ 2015-10-12 17:34 UTC (permalink / raw)
  To: kawa



On 10/12/2015 12:42 AM, Helmut Eller wrote:
> I'm still wondering how a re-loadable version of define-simple-class
> could possibly work.

I think you can do a decent job as long as the super-classes (extended
classes and implemented interfaces) don't change.

The initial load creates a "skeleton class" with the specified super-classes.
All instances of the class are compiled to instances of the skeleton class.
The skeleton class doesn't change even if the source code is re-compiled.

Every method defined in the class is compiled to a static method in a helper
class.  The skeleton class has just a stub method that calls the static method
indirectly, for example using a MethodHandle.  If a method is changed, we create
a new static method and update the MethodHandle to point to it.

If the original class inherits a method, that is treated as an actual
method that calls the super method - i.e. we create a static method that
uses invokesuper to call the inherited method.  (There are some
limitations on invokesuper, especially for protected methods.)
That way you can add new overrides after the initial load.

Adding a completely new method after the skeleton class is created
is trickier.  If there are no sub-classes, we can just treat
the method as a static function.  If there are sub-classes, and
the method is overridden in a sub-class, it more complicated.
One solution is to use a switch based on a sub-class index.

Changing the signature (types) of a method is tricky - it may just
have to be treated as deleting a method and adding a method.

Calling a deleted method generally throws an exception.  In simple
cases if there is a new method with the same name and the same
number of parameters perhaps we can try to convert the methods instead.

Instance fields could be handled by translating them into pairs of
getter/setter methods.  Each field (including deleted fields)
has a unique integer index.   Each object has a helper array for the
value of the field.  We use a special unique object NO_SUCH_FIELD.
Reading a field indexes into the field array.  If the index
is out of bounds, or the value is NO_SUCH_FIELD we throw an
exception.  Writing a field updates the element of the field value
array (which is extended as needed).

This design maintains object identity, and handles binary compatibility
acceptably, I think.  It adds non-trivial overhead, but I suspect
less than many other JVM languages :-)

Renaming a method or a field can't be detected by Kawa.  However,
if the rename is done by an IDE, the IDE can notify Kawa of the
rename, and Kawa could automatically translate new names to old names.

Static methods and fields are essentially plain top-level functions and
variable definitions, which we should support before tackling classes.

I don't know when I'll have time to implement these ideas.
Re-loading class definitions should be handled after we have
handle function and variable re-definitions well.  Class re-loading
could be a good project for someone - possibly a Google Summer of
Code project for next year.  Or anyone who wants a non-trivial
but manageable project.
-- 
	--Per Bothner
per@bothner.com   http://per.bothner.com/

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: interactive development and code re-loading
  2015-10-12  7:43 ` Helmut Eller
  2015-10-12 17:34   ` Per Bothner
@ 2015-10-13  5:03   ` Per Bothner
  2015-10-13 13:22     ` Helmut Eller
  2015-10-14  7:08     ` Per Bothner
  1 sibling, 2 replies; 10+ messages in thread
From: Per Bothner @ 2015-10-13  5:03 UTC (permalink / raw)
  To: kawa



On 10/12/2015 12:42 AM, Helmut Eller wrote:
> On Sun, Oct 11 2015, Per Bothner wrote:
>
>> What I'd like to decide is *when* Kawa should use "interactive mode"
>> - i.e. to add indirection and automatic re-compilation.  The answer
>> is certainly not "always" because of the costs.  Having interactive
>> mode be controlled by flags is possible, but the more flags people
>> have to remember, the more confusion.  So having good defaults and
>> simple rules is better.
>
> I would say that in R7RS mode, "always" is required for code that is
> defined in the interaction-environment, i.e. outside of libraries.
> Basically (set!  foo ...), if foo was defined in the
> interaction-environment, should always work regardless what type/value
> foo previously had.

I think that makes sense.

With this change, your testcase in testsuite/lib-test.scm works
(which it didn't before this change, without needing other flag changes,
so that's a plus.

> It seems to me that a per-library flag would be quite convenient,
> because some libraries tend to be more stable than others.  Usually, I
> want to re-load new/unstable libraries but very rarely older or
> third-party libraries.  Alternatively we could say that "stable"
> libraries should be compiled to jar files and be added to the JVM
> classpath while "unstable" libraries should be loaded from source.

That is plausible,  What I'm currently leaning towards:
- If an interactive module (or a repl) requires/imports a source module,
   the then latter is also interactive.
- If a non-interactive module requires/imports a source module,
the then latter is also non-interactive.
-- 
	--Per Bothner
per@bothner.com   http://per.bothner.com/

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: interactive development and code re-loading
  2015-10-13  5:03   ` Per Bothner
@ 2015-10-13 13:22     ` Helmut Eller
  2015-10-13 17:01       ` Per Bothner
  2015-10-14  7:08     ` Per Bothner
  1 sibling, 1 reply; 10+ messages in thread
From: Helmut Eller @ 2015-10-13 13:22 UTC (permalink / raw)
  To: kawa

On Mon, Oct 12 2015, Per Bothner wrote:

> That is plausible,  What I'm currently leaning towards:
> - If an interactive module (or a repl) requires/imports a source module,
>   the then latter is also interactive.
> - If a non-interactive module requires/imports a source module,
> the then latter is also non-interactive.

Out of curiosity: if a macro is re-defined, will users of the macro be
re-compiled?

Helmut


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: interactive development and code re-loading
  2015-10-13 13:22     ` Helmut Eller
@ 2015-10-13 17:01       ` Per Bothner
  0 siblings, 0 replies; 10+ messages in thread
From: Per Bothner @ 2015-10-13 17:01 UTC (permalink / raw)
  To: kawa



On 10/13/2015 06:21 AM, Helmut Eller wrote:
> On Mon, Oct 12 2015, Per Bothner wrote:
>
>> That is plausible,  What I'm currently leaning towards:
>> - If an interactive module (or a repl) requires/imports a source module,
>>    the then latter is also interactive.
>> - If a non-interactive module requires/imports a source module,
>> the then latter is also non-interactive.
>
> Out of curiosity: if a macro is re-defined, will users of the macro be
> re-compiled?

Yes, that is the goal.

I'm considering re-compiling at the granularity of procedures, which means
remembering the definition of a procedure as an S-expression.  Actually two
S-expressions: the formals and the body.  That means a macro usage in the formals or
the body can be recorded as a dependency, and trigger a re-compilation.

However, that would not work for a macro that expands to a lambda or define,
since that macro usage would be "outside" the remembered S-expressions.

An alternative to re-compile and track dependencies at the granularity of
a module (or a "command" in the case of a REPL or eval).  That has some
advantages, including less memory use.  (We don't have to remember old
definitions that come from a file - we just re-parse the file as needed.)

Working at a module level has some disadvantages, too, most obviously
it's harder to avoid excessive re-compilation.  There are also semantics
issues: If you re-compile a module because the user explicitly requests
it, or does an explicit import/require, that is one thing: We execute
the various side-effects of the module.  But when module foo depends
on module bar, and bar is modified and re-imported - do we want to
re-execute the side-effects of foo, even though it hasn't changed
or been explicitly re-imported?

Tricky questions ... Doing the "right" thing in all cases is probably
not doable, so we'll aim for a compromise between what is most useful and
implementation ease.
-- 
	--Per Bothner
per@bothner.com   http://per.bothner.com/

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: interactive development and code re-loading
  2015-10-13  5:03   ` Per Bothner
  2015-10-13 13:22     ` Helmut Eller
@ 2015-10-14  7:08     ` Per Bothner
  2015-10-14  9:21       ` Helmut Eller
  1 sibling, 1 reply; 10+ messages in thread
From: Per Bothner @ 2015-10-14  7:08 UTC (permalink / raw)
  To: kawa

I checked in code that defines "interactive mode" as suggested
in these emails - specifically based on Helmut's suggestion to
define interactive mode "always ... for code that is
defined in the interaction-environment".  In addition,
interactive mode applies to modules/libraries that are
imported/required by interactive mode modules or a repl.

I also checked in some improvements to better handle
re-definitions:

- Before it was the case that re-defining a top-level named
procedure in the interaction-environment would patch the Procedure
object to point to the new definition.  Thus old calls that weren't
inlined would use the new definition.

Now we do the same for a procedure in a module.  For example,
suppose module mod1.scm defines a procedure f.  If in the REPL
you import mod1, you'd get f, and you can define a procedure g
that calls f.  If you modify the definition of f in mod1.scm,
and then again do (import mod1) the existing procedure value of f
will be patched to the new definition.  Thus a call to g will
call the new definition of f.

- A top-level variable definition is now made "indirect", even
if it has a type specifier.  This old references can pick
up the new definitions.

There is no dependency-tracking, smart recompilation, or support for
updating class definitions.  It is likely to be a while before I can
look into those improvements - I have a back-log of other tasks to deal
with first.
-- 
	--Per Bothner
per@bothner.com   http://per.bothner.com/

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: interactive development and code re-loading
  2015-10-14  7:08     ` Per Bothner
@ 2015-10-14  9:21       ` Helmut Eller
  2015-10-15  2:00         ` Per Bothner
  0 siblings, 1 reply; 10+ messages in thread
From: Helmut Eller @ 2015-10-14  9:21 UTC (permalink / raw)
  To: kawa

On Wed, Oct 14 2015, Per Bothner wrote:

> I checked in code that defines "interactive mode" as suggested
> in these emails

Not sure if this was caused by those changes but:

 kawa -e '(eval (call-with-input-string "(define (foo) (foo))" read))'

now gets stuck in an endless loop.

Helmut

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: interactive development and code re-loading
  2015-10-14  9:21       ` Helmut Eller
@ 2015-10-15  2:00         ` Per Bothner
  2015-10-15  5:30           ` Per Bothner
  0 siblings, 1 reply; 10+ messages in thread
From: Per Bothner @ 2015-10-15  2:00 UTC (permalink / raw)
  To: Helmut Eller, kawa



On 10/14/2015 02:20 AM, Helmut Eller wrote:
> On Wed, Oct 14 2015, Per Bothner wrote:
>
>> I checked in code that defines "interactive mode" as suggested
>> in these emails
>
> Not sure if this was caused by those changes but:
>
>   kawa -e '(eval (call-with-input-string "(define (foo) (foo))" read))'
>
> now gets stuck in an endless loop.

It doesn't appear to have anything with the recent changes.

The reason you have to use the convoluted eval+call-with-input-string
in the test-case is because otherwise line number "fixme" are generated
for the bytecode, and those line number notes "disrupt" the bug.  If you
uncomment the code of CodeAttr#putLineNumber then you get the same
infinite compiler loop for plain:

   kawa -e '(define (foo) (foo))'

The loop happens in processFixups when it is trying to optimize:
   TRANSFER L1; ... L1: GOTO L2
to
   TRANSFER L2; ... L1: GOTO L2
where TRANSFER is a GOTO, conditional goto or certain other instructions.

This optimization gets into an infinite loop when L1==L2.
So (you say): check for L1==L2 - but that doesn't handle longer cycles.

So use a counter to limit the loop to finite number of iterations.
Alas, that triggers a VerifyError - for CodeAttr.class.  (I.e.
it's a bug either in javac or HotSpot.)  I'm having trouble
finding a working fix.  For example completely dropping the
"optimization", or only executing the loop at most once causes other problems.
-- 
	--Per Bothner
per@bothner.com   http://per.bothner.com/

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: interactive development and code re-loading
  2015-10-15  2:00         ` Per Bothner
@ 2015-10-15  5:30           ` Per Bothner
  0 siblings, 0 replies; 10+ messages in thread
From: Per Bothner @ 2015-10-15  5:30 UTC (permalink / raw)
  To: Helmut Eller, kawa


> On 10/14/2015 02:20 AM, Helmut Eller wrote:
> Not sure if this was caused by those changes but:
>
>   kawa -e '(eval (call-with-input-string "(define (foo) (foo))" read))'
>
> now gets stuck in an endless loop.

On 10/14/2015 06:59 PM, Per Bothner wrote:
> So use a counter to limit the loop to finite number of iterations.
> Alas, that triggers a VerifyError - for CodeAttr.class.  (I.e.
> it's a bug either in javac or HotSpot.)  I'm having trouble
> finding a working fix.  For example completely dropping the
> "optimization", or only executing the loop at most once causes other problems.

I found a reasonably clean (and conservative) fix.
-- 
	--Per Bothner
per@bothner.com   http://per.bothner.com/

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2015-10-15  5:30 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-10-11 20:20 interactive development and code re-loading Per Bothner
2015-10-12  7:43 ` Helmut Eller
2015-10-12 17:34   ` Per Bothner
2015-10-13  5:03   ` Per Bothner
2015-10-13 13:22     ` Helmut Eller
2015-10-13 17:01       ` Per Bothner
2015-10-14  7:08     ` Per Bothner
2015-10-14  9:21       ` Helmut Eller
2015-10-15  2:00         ` Per Bothner
2015-10-15  5:30           ` Per Bothner

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).