Chris, Joerg:that is exactly the problem: assigning a procedure of a *client* module to a procedure variable of an *imported* module, and then unloading the client module - leaving the procedure variable in the imported module dangling.Assigning a procedure of a client module to a variable of an imported "base" module is in fact the standard way the Oberon system itself employs procedure variables to implement the viewer system in an object-oriented style.For example, module TextFrames (the client) installs a handler (procedure TextFrames.Handle) in the field F.handle of text frame F (see TextFrames.NewText and TextFrames.Open for the details), where the record field F.handle is declared as a procedure variable in the "base" module Viewers (see Viewers.ViewerDesc), which also manages the viewer data structure (see procedure Viewers.Open). Except that of course in the case of the Oberon system, the "client" module TextFrames *never* gets unloaded, so this is not a problem there. Thus, the burden is on the user to make sure that such modules never get unloaded.It is of course possible to implement a "procedure variable" counter - in analogy to the "module counter" employed in Oberon: each time an assignment to a procedure variable is made, the counter gets increased. But that would require changing the code generator compiler for assignments, and the module loader (Modules.Load) and unloader (Modules.Free). But I would refrain from introducing such complexity to the compiler and runtime system.Andreas Chris Burrows chris at cfbsoftware.com
Joerg,
OK - you may well be onto something here but it is incomplete as it is. Something has to> import Square so that the module body is executed, or you need an exported procedure in> Square that is called from the OS. > Note: there are a couple of semicolons missing and Init must be exported.> Chris.
-----Original Message----- From: Jörg Straube [mailto:joerg.straube at iaeth.ch] Sent: Saturday, 1 October 2016 2:22 PM To: chris at cfbsoftware.com; ETH Oberon and related systems Subject: Re: [Oberon] Oberon for a C++ user.
Chris
Here a possble example that might cause issues:
MODULE Figure; TYPE DrawProc = PROCEDURE; Figure = POINTER TO FigureDesc; FigureDesc = RECORD next: Figure; draw: DrawProc END; VAR list: Figure; PROCEDURE Init(d: DrawProc) VAR f: Figure; BEGIN NEW(f); f.next := list; list := f; f.draw := d END Init; BEGIN list:= NIL END Figure.
MODULE Square; IMPORT Figure; PROCEDURE DrawSquare; BEGIN (* do what ever you need to do to draw a square *) END DrawSquare BEGIN Figure.Init(DrawSquare) END Square.
Now you could run Square and unload Square. The list in Figure has a reference to unloaded code.
J rg
Andreas: the problem IMHO is in the operator :=, which is overloaded. The procedure variables are not a variables in the usual sense. The operator := should not apply to them. A regular variable has a value. The value is passive. It gets acted upon by a client. The variable is not doing anything on its own. Somebody is doing something with the variable (somebody is using its value for some goal). IF var1 = var2 THEN... (*passive role of the variables*) The procedure variable has a value (the address to jump to), and also a behavior. The value is passive, but the behavior is active. The value is retained after unloading the implementation, but the behavior is damaged. This is the problem. The value (the address) is not that important. Yes, you can use it in comparisons: IF proc1 = proc2 THEN... (*passive role of the procedure variables*) Note that after unloading the implementations of the proc1 and proc2, you can still make the comparison. Both proc1 and proc2 are perfectly valid in the passive sense. Their code is gone, but their passive values (addresses) are perfectly OK. But this is not why procedure variables are important. We want their behaviors. And this gets damaged upon unloading. In order to cure the problem, the operator :=should not be permitted with procedure variables. There needs to be another, separate operator which will assign procedure variables and also take care of the reference counting. ASSIGN (procvar, Mod.Proc) I think that separating both operators := and ASSIGN will make sense, because regular variables are not posing the problem which procedure variables are posing. The counterpart of ASSIGN would be RETRACT (or some better name). Since you re not allowed to use := with procedure variables, you could not write procvar := NIL; (*not allowed*) You would have to say RETRACT (procvar); (*takes care of the references*) I hope this proposition makes sense. W. ---------------------------- Chris, Joerg: that is exactly the problem: assigning a procedure of a *client* module to a procedure variable of an *imported* module, and then unloading the client module - leaving the procedure variable in the imported module dangling. Assigning a procedure of a client module to a variable of an imported "base" module is in fact the standard way the Oberon system itself employs procedure variables to implement the viewer system in an object-oriented style. For example, module TextFrames (the client) installs a handler (procedure TextFrames.Handle) in the field F.handle of text frame F (see TextFrames.NewText and TextFrames.Open for the details), where the record field F.handle is declared as a procedure variable in the "base" module Viewers (see Viewers.ViewerDesc), which also manages the viewer data structure (see procedure Viewers.Open). Except that of course in the case of the Oberon system, the "client" module TextFrames *never* gets unloaded, so this is not a problem there. Thus, the burden is on the user to make sure that such modules never get unloaded. It is of course possible to implement a "procedure variable" counter - in analogy to the "module counter" employed in Oberon: each time an assignment to a procedure variable is made, the counter gets increased. But that would require changing the code generator compiler for assignments, and the module loader (Modules.Load) and unloader (Modules.Free). But I would refrain from introducing such complexity to the compiler and runtime system. Andreas Chris Burrows chris at cfbsoftware.com <mailto:oberon%40lists.inf.ethz.ch?Subject=Re:%20Re%3A%20%5BOberon%5D%20Oberon%20for%20a%20C%2B%2B%20user.&In-Reply-To=%3C000a01d21ba8%24b97dc9d0%242c795d70%24%40cfbsoftware.com%3E> ________________________________
Joerg,
OK - you may well be onto something here but it is incomplete as it is. Something has to
import Square so that the module body is executed, or you need an exported procedure in
Square that is called from the OS.
Note: there are a couple of semicolons missing and Init must be exported.
Chris.
-----Original Message----- From: Jörg Straube [mailto:joerg.straube at iaeth.ch<https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=UHr5AP3cXC2ZAXacD2dYANUhpxDnR6gWosSpUd2oaRw&s=TWgxIZxS5z7mpZJDUj92ydjRoic3rLYdp0iN4Zb0ngQ&e=>] Sent: Saturday, 1 October 2016 2:22 PM To: chris at cfbsoftware.com<https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=UHr5AP3cXC2ZAXacD2dYANUhpxDnR6gWosSpUd2oaRw&s=TWgxIZxS5z7mpZJDUj92ydjRoic3rLYdp0iN4Zb0ngQ&e=>; ETH Oberon and related systems Subject: Re: [Oberon] Oberon for a C++ user.
Chris
Here a possble example that might cause issues:
MODULE Figure; TYPE DrawProc = PROCEDURE; Figure = POINTER TO FigureDesc; FigureDesc = RECORD next: Figure; draw: DrawProc END; VAR list: Figure; PROCEDURE Init(d: DrawProc) VAR f: Figure; BEGIN NEW(f); f.next := list; list := f; f.draw := d END Init; BEGIN list:= NIL END Figure.
MODULE Square; IMPORT Figure; PROCEDURE DrawSquare; BEGIN (* do what ever you need to do to draw a square *) END DrawSquare BEGIN Figure.Init(DrawSquare) END Square.
Now you could run Square and unload Square. The list in Figure has a reference to unloaded code.
J rg
Hi there, Sorry, I have not done any Oberon work in 13+ years but when I see a suggestion such as ASSIGN and RETRACT to "reference count" procedures, I just shiver. (Let's not mess with the language if we don't have to would be my motto.) I believe what you really want to avoid the "dangling procedure variable" problem is a garbage collector that deals with modules. It would basically be an RCU scheme. When you unload a module, it's removed from the mapping that associates some module name with the actual data/code used behind the scenes. However, that data/code is NOT actually thrown out of memory at that time, instead it sticks around until the last *reference* to it is gone, meaning here until the last procedure variable referring to this module's code has been reassigned. The usual := way. I'd be interested to hear why that wouldn't solve your issues. Regards, Peter On Mon, Oct 3, 2016 at 3:07 AM, Skulski, Wojciech <skulski@pas.rochester.edu> wrote:
Andreas:
the problem IMHO is in the operator :=, which is overloaded. The procedure variables are not a variables in the usual sense. The operator := should not apply to them.
A regular variable has a value. The value is passive. It gets acted upon by a client. The variable is not doing anything on its own. Somebody is doing something with the variable (somebody is using its value for some goal).
IF var1 = var2 THEN... (*passive role of the variables*)
The procedure variable has a value (the address to jump to), and also a behavior. The value is passive, but the behavior is active. The value is retained after unloading the implementation, but the behavior is damaged. This is the problem.
The value (the address) is not that important. Yes, you can use it in comparisons:
IF proc1 = proc2 THEN... (*passive role of the procedure variables*)
Note that after unloading the implementations of the proc1 and proc2, you can still make the comparison. Both proc1 and proc2 are perfectly valid in the passive sense. Their code is gone, but their passive values (addresses) are perfectly OK.
But this is not why procedure variables are important. We want their behaviors. And this gets damaged upon unloading.
In order to cure the problem, the operator :=should not be permitted with procedure variables. There needs to be another, separate operator which will assign procedure variables and also take care of the reference counting.
ASSIGN (procvar, Mod.Proc)
I think that separating both operators := and ASSIGN will make sense, because regular variables are not posing the problem which procedure variables are posing.
The counterpart of ASSIGN would be RETRACT (or some better name). Since you re not allowed to use := with procedure variables, you could not write
procvar := NIL; (*not allowed*)
You would have to say
RETRACT (procvar); (*takes care of the references*)
I hope this proposition makes sense.
W.
----------------------------
Chris, Joerg:
that is exactly the problem: assigning a procedure of a *client* module to a procedure variable of an *imported* module, and then unloading the client module - leaving the procedure variable in the imported module dangling.
Assigning a procedure of a client module to a variable of an imported "base" module is in fact the standard way the Oberon system itself employs procedure variables to implement the viewer system in an object-oriented style.
For example, module TextFrames (the client) installs a handler (procedure TextFrames.Handle) in the field F.handle of text frame F (see TextFrames.NewText and TextFrames.Open for the details), where the record field F.handle is declared as a procedure variable in the "base" module Viewers (see Viewers.ViewerDesc), which also manages the viewer data structure (see procedure Viewers.Open).
Except that of course in the case of the Oberon system, the "client" module TextFrames *never* gets unloaded, so this is not a problem there. Thus, the burden is on the user to make sure that such modules never get unloaded.
It is of course possible to implement a "procedure variable" counter - in analogy to the "module counter" employed in Oberon: each time an assignment to a procedure variable is made, the counter gets increased. But that would require changing the code generator compiler for assignments, and the module loader (Modules.Load) and unloader (Modules.Free). But I would refrain from introducing such complexity to the compiler and runtime system.
Andreas
Chris Burrows chris at cfbsoftware.com <mailto:oberon%40lists.inf.ethz.ch?Subject=Re:%20Re%3A%20%5BOberon%5D%20Oberon%20for%20a%20C%2B%2B%20user.&In-Reply-To=%3C000a01d21ba8%24b97dc9d0%242c795d70%24%40cfbsoftware.com%3E>
________________________________
Joerg,
OK - you may well be onto something here but it is incomplete as it is. Something has to
import Square so that the module body is executed, or you need an exported procedure in
Square that is called from the OS.
Note: there are a couple of semicolons missing and Init must be exported.
Chris.
-----Original Message----- From: Jörg Straube [mailto:joerg.straube at iaeth.ch<https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=UHr5AP3cXC2ZAXacD2dYANUhpxDnR6gWosSpUd2oaRw&s=TWgxIZxS5z7mpZJDUj92ydjRoic3rLYdp0iN4Zb0ngQ&e=>] Sent: Saturday, 1 October 2016 2:22 PM To: chris at cfbsoftware.com<https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=UHr5AP3cXC2ZAXacD2dYANUhpxDnR6gWosSpUd2oaRw&s=TWgxIZxS5z7mpZJDUj92ydjRoic3rLYdp0iN4Zb0ngQ&e=>; ETH Oberon and related systems Subject: Re: [Oberon] Oberon for a C++ user.
Chris
Here a possble example that might cause issues:
MODULE Figure; TYPE DrawProc = PROCEDURE; Figure = POINTER TO FigureDesc; FigureDesc = RECORD next: Figure; draw: DrawProc END; VAR list: Figure; PROCEDURE Init(d: DrawProc) VAR f: Figure; BEGIN NEW(f); f.next := list; list := f; f.draw := d END Init; BEGIN list:= NIL END Figure.
MODULE Square; IMPORT Figure; PROCEDURE DrawSquare; BEGIN (* do what ever you need to do to draw a square *) END DrawSquare BEGIN Figure.Init(DrawSquare) END Square.
Now you could run Square and unload Square. The list in Figure has a reference to unloaded code.
J rg
-- Oberon@lists.inf.ethz.ch mailing list for ETH Oberon and related systems https://lists.inf.ethz.ch/mailman/listinfo/oberon
-- Peter H. Froehlich <http://www.cs.jhu.edu/~phf/> Senior Lecturer | Director, Johns Hopkins Gaming Lab The views stated in this communication are my own and do not express the position or views of The Johns Hopkins University or the Association for Computing Machinery.
Hi all ASSIGN and RETRACT (or whatever) is not something we would add to the language. It's up to the compiler to take care of it if deemed useful, as he knows the type of the variables. For example i := 2 and s := "abc" generate totally different code for :=, as the compiler knows the types of "i" and "s". Principally module unloading is only a problem if you use procedure variables involved in some kind of "inheritance". I mean: you have a lower base module A (IMPORTed by others) taking care of base types and use call back procedures to modules higher in the import chain (IMPORTing A). By using this construct the module reference counting will not help. There are other cases of procedure variables not involving this mechanism and they are completely safe even when unloading modules. E.g. avoiding recurrence (look at VAR Type in ORP.Mod) br Jörg -----Original Message----- From: Oberon [mailto:oberon-bounces@lists.inf.ethz.ch] On Behalf Of Peter Froehlich Sent: Montag, 3. Oktober 2016 09:31 To: ETH Oberon and related systems <oberon@lists.inf.ethz.ch> Subject: Re: [Oberon] Oberon for a C++ user. Hi there, Sorry, I have not done any Oberon work in 13+ years but when I see a suggestion such as ASSIGN and RETRACT to "reference count" procedures, I just shiver. (Let's not mess with the language if we don't have to would be my motto.) I believe what you really want to avoid the "dangling procedure variable" problem is a garbage collector that deals with modules. It would basically be an RCU scheme. When you unload a module, it's removed from the mapping that associates some module name with the actual data/code used behind the scenes. However, that data/code is NOT actually thrown out of memory at that time, instead it sticks around until the last *reference* to it is gone, meaning here until the last procedure variable referring to this module's code has been reassigned. The usual := way. I'd be interested to hear why that wouldn't solve your issues. Regards, Peter On Mon, Oct 3, 2016 at 3:07 AM, Skulski, Wojciech <skulski@pas.rochester.edu> wrote:
Andreas:
the problem IMHO is in the operator :=, which is overloaded. The procedure variables are not a variables in the usual sense. The operator := should not apply to them.
A regular variable has a value. The value is passive. It gets acted upon by a client. The variable is not doing anything on its own. Somebody is doing something with the variable (somebody is using its value for some goal).
IF var1 = var2 THEN... (*passive role of the variables*)
The procedure variable has a value (the address to jump to), and also a behavior. The value is passive, but the behavior is active. The value is retained after unloading the implementation, but the behavior is damaged. This is the problem.
The value (the address) is not that important. Yes, you can use it in comparisons:
IF proc1 = proc2 THEN... (*passive role of the procedure variables*)
Note that after unloading the implementations of the proc1 and proc2, you can still make the comparison. Both proc1 and proc2 are perfectly valid in the passive sense. Their code is gone, but their passive values (addresses) are perfectly OK.
But this is not why procedure variables are important. We want their behaviors. And this gets damaged upon unloading.
In order to cure the problem, the operator :=should not be permitted with procedure variables. There needs to be another, separate operator which will assign procedure variables and also take care of the reference counting.
ASSIGN (procvar, Mod.Proc)
I think that separating both operators := and ASSIGN will make sense, because regular variables are not posing the problem which procedure variables are posing.
The counterpart of ASSIGN would be RETRACT (or some better name). Since you re not allowed to use := with procedure variables, you could not write
procvar := NIL; (*not allowed*)
You would have to say
RETRACT (procvar); (*takes care of the references*)
I hope this proposition makes sense.
W.
----------------------------
Chris, Joerg:
that is exactly the problem: assigning a procedure of a *client* module to a procedure variable of an *imported* module, and then unloading the client module - leaving the procedure variable in the imported module dangling.
Assigning a procedure of a client module to a variable of an imported "base" module is in fact the standard way the Oberon system itself employs procedure variables to implement the viewer system in an object-oriented style.
For example, module TextFrames (the client) installs a handler (procedure TextFrames.Handle) in the field F.handle of text frame F (see TextFrames.NewText and TextFrames.Open for the details), where the record field F.handle is declared as a procedure variable in the "base" module Viewers (see Viewers.ViewerDesc), which also manages the viewer data structure (see procedure Viewers.Open).
Except that of course in the case of the Oberon system, the "client" module TextFrames *never* gets unloaded, so this is not a problem there. Thus, the burden is on the user to make sure that such modules never get unloaded.
It is of course possible to implement a "procedure variable" counter - in analogy to the "module counter" employed in Oberon: each time an assignment to a procedure variable is made, the counter gets increased. But that would require changing the code generator compiler for assignments, and the module loader (Modules.Load) and unloader (Modules.Free). But I would refrain from introducing such complexity to the compiler and runtime system.
Andreas
Chris Burrows chris at cfbsoftware.com <mailto:oberon%40lists.inf.ethz.ch?Subject=Re:%20Re%3A%20%5BOberon%5D%20Oberon%20for%20a%20C%2B%2B%20user.&In-Reply-To=%3C000a01d21ba8%24b97dc9d0%242c795d70%24%40cfbsoftware.com%3E>
________________________________
Joerg,
OK - you may well be onto something here but it is incomplete as it is. Something has to
import Square so that the module body is executed, or you need an exported procedure in
Square that is called from the OS.
Note: there are a couple of semicolons missing and Init must be exported.
Chris.
-----Original Message----- From: Jörg Straube [mailto:joerg.straube at iaeth.ch<https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=UHr5AP3cXC2ZAXacD2dYANUhpxDnR6gWosSpUd2oaRw&s=TWgxIZxS5z7mpZJDUj92ydjRoic3rLYdp0iN4Zb0ngQ&e=>] Sent: Saturday, 1 October 2016 2:22 PM To: chris at cfbsoftware.com<https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=UHr5AP3cXC2ZAXacD2dYANUhpxDnR6gWosSpUd2oaRw&s=TWgxIZxS5z7mpZJDUj92ydjRoic3rLYdp0iN4Zb0ngQ&e=>; ETH Oberon and related systems Subject: Re: [Oberon] Oberon for a C++ user.
Chris
Here a possble example that might cause issues:
MODULE Figure; TYPE DrawProc = PROCEDURE; Figure = POINTER TO FigureDesc; FigureDesc = RECORD next: Figure; draw: DrawProc END; VAR list: Figure; PROCEDURE Init(d: DrawProc) VAR f: Figure; BEGIN NEW(f); f.next := list; list := f; f.draw := d END Init; BEGIN list:= NIL END Figure.
MODULE Square; IMPORT Figure; PROCEDURE DrawSquare; BEGIN (* do what ever you need to do to draw a square *) END DrawSquare BEGIN Figure.Init(DrawSquare) END Square.
Now you could run Square and unload Square. The list in Figure has a reference to unloaded code.
J rg
-- Oberon@lists.inf.ethz.ch mailing list for ETH Oberon and related systems https://lists.inf.ethz.ch/mailman/listinfo/oberon
-- Peter H. Froehlich <http://www.cs.jhu.edu/~phf/> Senior Lecturer | Director, Johns Hopkins Gaming Lab The views stated in this communication are my own and do not express the position or views of The Johns Hopkins University or the Association for Computing Machinery. -- Oberon@lists.inf.ethz.ch mailing list for ETH Oberon and related systems https://lists.inf.ethz.ch/mailman/listinfo/oberon
Peter, You've got the problem backwards. A.proc knows nothing about B, C, ... So unloading B, C, ... doesn't affect A EXCEPT that A.proc points to one of (or all if a list of assignments is kept in A). Unloading B, C, ... has to transmit that fact back to A so that its internal pointer(s) are set to NIL. On 10/3/2016 12:30 AM, Peter Froehlich wrote:
Hi there,
Sorry, I have not done any Oberon work in 13+ years but when I see a suggestion such as ASSIGN and RETRACT to "reference count" procedures, I just shiver. (Let's not mess with the language if we don't have to would be my motto.)
I believe what you really want to avoid the "dangling procedure variable" problem is a garbage collector that deals with modules. It would basically be an RCU scheme. When you unload a module, it's removed from the mapping that associates some module name with the actual data/code used behind the scenes. However, that data/code is NOT actually thrown out of memory at that time, instead it sticks around until the last *reference* to it is gone, meaning here until the last procedure variable referring to this module's code has been reassigned. The usual := way.
I'd be interested to hear why that wouldn't solve your issues.
Regards, Peter
On Mon, Oct 3, 2016 at 3:07 AM, Skulski, Wojciech <skulski@pas.rochester.edu> wrote:
Andreas:
the problem IMHO is in the operator :=, which is overloaded. The procedure variables are not a variables in the usual sense. The operator := should not apply to them.
A regular variable has a value. The value is passive. It gets acted upon by a client. The variable is not doing anything on its own. Somebody is doing something with the variable (somebody is using its value for some goal).
IF var1 = var2 THEN... (*passive role of the variables*)
The procedure variable has a value (the address to jump to), and also a behavior. The value is passive, but the behavior is active. The value is retained after unloading the implementation, but the behavior is damaged. This is the problem.
The value (the address) is not that important. Yes, you can use it in comparisons:
IF proc1 = proc2 THEN... (*passive role of the procedure variables*)
Note that after unloading the implementations of the proc1 and proc2, you can still make the comparison. Both proc1 and proc2 are perfectly valid in the passive sense. Their code is gone, but their passive values (addresses) are perfectly OK.
But this is not why procedure variables are important. We want their behaviors. And this gets damaged upon unloading.
In order to cure the problem, the operator :=should not be permitted with procedure variables. There needs to be another, separate operator which will assign procedure variables and also take care of the reference counting.
ASSIGN (procvar, Mod.Proc)
I think that separating both operators := and ASSIGN will make sense, because regular variables are not posing the problem which procedure variables are posing.
The counterpart of ASSIGN would be RETRACT (or some better name). Since you re not allowed to use := with procedure variables, you could not write
procvar := NIL; (*not allowed*)
You would have to say
RETRACT (procvar); (*takes care of the references*)
I hope this proposition makes sense.
W.
----------------------------
Chris, Joerg:
that is exactly the problem: assigning a procedure of a *client* module to a procedure variable of an *imported* module, and then unloading the client module - leaving the procedure variable in the imported module dangling.
Assigning a procedure of a client module to a variable of an imported "base" module is in fact the standard way the Oberon system itself employs procedure variables to implement the viewer system in an object-oriented style.
For example, module TextFrames (the client) installs a handler (procedure TextFrames.Handle) in the field F.handle of text frame F (see TextFrames.NewText and TextFrames.Open for the details), where the record field F.handle is declared as a procedure variable in the "base" module Viewers (see Viewers.ViewerDesc), which also manages the viewer data structure (see procedure Viewers.Open).
Except that of course in the case of the Oberon system, the "client" module TextFrames *never* gets unloaded, so this is not a problem there. Thus, the burden is on the user to make sure that such modules never get unloaded.
It is of course possible to implement a "procedure variable" counter - in analogy to the "module counter" employed in Oberon: each time an assignment to a procedure variable is made, the counter gets increased. But that would require changing the code generator compiler for assignments, and the module loader (Modules.Load) and unloader (Modules.Free). But I would refrain from introducing such complexity to the compiler and runtime system.
Andreas
Chris Burrows chris at cfbsoftware.com <mailto:oberon%40lists.inf.ethz.ch?Subject=Re:%20Re%3A%20%5BOberon%5D%20Oberon%20for%20a%20C%2B%2B%20user.&In-Reply-To=%3C000a01d21ba8%24b97dc9d0%242c795d70%24%40cfbsoftware.com%3E>
________________________________
Joerg,
OK - you may well be onto something here but it is incomplete as it is. Something has to import Square so that the module body is executed, or you need an exported procedure in Square that is called from the OS. Note: there are a couple of semicolons missing and Init must be exported. Chris. -----Original Message----- From: Jörg Straube [mailto:joerg.straube at iaeth.ch<https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=UHr5AP3cXC2ZAXacD2dYANUhpxDnR6gWosSpUd2oaRw&s=TWgxIZxS5z7mpZJDUj92ydjRoic3rLYdp0iN4Zb0ngQ&e=>] Sent: Saturday, 1 October 2016 2:22 PM To: chris at cfbsoftware.com<https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=UHr5AP3cXC2ZAXacD2dYANUhpxDnR6gWosSpUd2oaRw&s=TWgxIZxS5z7mpZJDUj92ydjRoic3rLYdp0iN4Zb0ngQ&e=>; ETH Oberon and related systems Subject: Re: [Oberon] Oberon for a C++ user.
Chris
Here a possble example that might cause issues:
MODULE Figure; TYPE DrawProc = PROCEDURE; Figure = POINTER TO FigureDesc; FigureDesc = RECORD next: Figure; draw: DrawProc END; VAR list: Figure; PROCEDURE Init(d: DrawProc) VAR f: Figure; BEGIN NEW(f); f.next := list; list := f; f.draw := d END Init; BEGIN list:= NIL END Figure.
MODULE Square; IMPORT Figure; PROCEDURE DrawSquare; BEGIN (* do what ever you need to do to draw a square *) END DrawSquare BEGIN Figure.Init(DrawSquare) END Square.
Now you could run Square and unload Square. The list in Figure has a reference to unloaded code.
J rg
-- Oberon@lists.inf.ethz.ch mailing list for ETH Oberon and related systems https://lists.inf.ethz.ch/mailman/listinfo/oberon
Hi Douglas, On Mon, Oct 3, 2016 at 5:01 PM, Douglas G. Danforth <danforth@greenwoodfarm.com> wrote:
You've got the problem backwards. A.proc knows nothing about B, C, ... So unloading B, C, ... doesn't affect A EXCEPT that A.proc points to one of (or all if a list of assignments is kept in A). Unloading B, C, ... has to transmit that fact back to A so that its internal pointer(s) are set to NIL.
What you suggest means that Oberon will grow the same wart C has had for years: variables that change their value for a reason *other* than an assignment instruction being executed. Wirth always seemed to try hard to use basic axiomatic semantics to justify his language constructs. A variable suddenly becoming NIL without any (visible to the programmer) code having been executed goes against that *very* strongly. I do believe that I understand the scenario. Which is why I suggested doing this in the garbage collector, the only place that could *possibly* make the decision that freeing the memory for a module is "safe" because no (outside) references to it exist anymore. Anything else you do will either lead to "dangling procedure variables" or to "magic NIL" approaches and the like. Of course one doesn't have to care whether some operation is safe or not, that's another technique C has been successful with for years. I just always looked at Oberon a bit differently I guess. Cheers, Peter
Overloading is not the problem here. It would be straightforward to modify the code generator for the assignment operator := to care of reference counting when it is applied to a procedure variable, i.e. implement what you call ASSIGN and RETRACT. In fact, I have implemented just that on Ceres a long time ago as part of (the original version of) Experimental Oberon. But I do not recommend to use reference counting in general. Reason: it introduces a hidden mechanism (not desired) and a non-negligible overhead in assignments (ok, one may argue that this is “only" done for procedure variables which are typically assigned only once, but still - it is in the spirit of Oberon to avoid hidden mechanisms). There is an alternative solution without reference counting: When unloading a module M using Modules.Free(M), one *could* traverse the heap object for object - just like the garbage collector does - and check whether the encountered objects contain procedure variables, and if so see whether the (addresses of) such procedure variables belong to M's code section (i.e. whether M.code <= address < M.imports). If there is at least one such procedure variable, simply don't unload M. It's practically the same code as the garbage collector (so it could in fact be be combined with it), except that is it merely used to *check* whether the addresses of procedure variables in heap records belong to (the code section of) module M. That too I have implemented on Ceres (in NS32032 assembly code) a long time ago, and now that the garbage collector is itself implemented in Oberon (see procedures Kernel.Mark and Kernel.Scan), it would be straightforward to bring the code forward to Oberon 2013 - one really just needs to create a modified version of Kernel.Mark and call it from within procedure Modules.Free. But again, this would create a non-negligible overhead during unloading of a module - every time a module M needs to be unloaded, the *entire* heap would need to be traversed, and this just to find out whether one or more of its elements contain a procedure variable whose memory addresses belong to module M's code (this inefficiency can be remedied though in various ways, e.g. by setting a flag in module M when an assignment of M.proc to a procedure variable is made, but still, this would make both assignments and unloading rather cumbersome operations). But it is definitely doable. Note that performance is not really an issue, because unloading of modules should be rare - and could be preceded by a garbage collection run as a matter of course (in which case one just needs to slightly adapt *its* code). Thus, it is doable and has in fact been implemented. But I have yet to see a truly satisfactory and elegant solution to this problem - and this is why I haven’t brought the code forward to Oberon 2013 (and Experimental Oberon 2013). Andreas From: "Skulski, Wojciech" <skulski@pas.rochester.edu> To: Andreas Pirklbauer <andreas_pirklbauer@yahoo.com>; ETH Oberon and related systems <oberon@lists.inf.ethz.ch> Sent: Monday, October 3, 2016 9:07 AM Subject: RE: [Oberon] Oberon for a C++ user. Andreas: the problem IMHO is in the operator :=, which is overloaded. The procedure variables are not a variables in the usual sense. The operator := should not apply to them. A regular variable has a value. The value is passive. It gets acted upon by a client. The variable is not doing anything on its own. Somebody is doing something with the variable (somebody is using its value for some goal). IF var1 = var2 THEN... (*passive role of the variables*) The procedure variable has a value (the address to jump to), and also a behavior. The value is passive, but the behavior is active. The value is retained after unloading the implementation, but the behavior is damaged. This is the problem. The value (the address) is not that important. Yes, you can use it in comparisons: IF proc1 = proc2 THEN... (*passive role of the procedure variables*) Note that after unloading the implementations of the proc1 and proc2, you can still make the comparison. Both proc1 and proc2 are perfectly valid in the passive sense. Their code is gone, but their passive values (addresses) are perfectly OK. But this is not why procedure variables are important. We want their behaviors. And this gets damaged upon unloading. In order to cure the problem, the operator :=should not be permitted with procedure variables. There needs to be another, separate operator which will assign procedure variables and also take care of the reference counting. ASSIGN (procvar, Mod.Proc) I think that separating both operators := and ASSIGN will make sense, because regular variables are not posing the problem which procedure variables are posing. The counterpart of ASSIGN would be RETRACT (or some better name). Since you re not allowed to use := with procedure variables, you could not write procvar := NIL; (*not allowed*) You would have to say RETRACT (procvar); (*takes care of the references*) I hope this proposition makes sense. W. ---------------------------- Chris, Joerg: that is exactly the problem: assigning a procedure of a *client* module to a procedure variable of an *imported* module, and then unloading the client module - leaving the procedure variable in the imported module dangling. Assigning a procedure of a client module to a variable of an imported "base" module is in fact the standard way the Oberon system itself employs procedure variables to implement the viewer system in an object-oriented style. For example, module TextFrames (the client) installs a handler (procedure TextFrames.Handle) in the field F.handle of text frame F (see TextFrames.NewText and TextFrames.Open for the details), where the record field F.handle is declared as a procedure variable in the "base" module Viewers (see Viewers.ViewerDesc), which also manages the viewer data structure (see procedure Viewers.Open). Except that of course in the case of the Oberon system, the "client" module TextFrames *never* gets unloaded, so this is not a problem there. Thus, the burden is on the user to make sure that such modules never get unloaded. It is of course possible to implement a "procedure variable" counter - in analogy to the "module counter" employed in Oberon: each time an assignment to a procedure variable is made, the counter gets increased. But that would require changing the code generator compiler for assignments, and the module loader (Modules.Load) and unloader (Modules.Free). But I would refrain from introducing such complexity to the compiler and runtime system. Andreas Chris Burrows chris at cfbsoftware.com <mailto:oberon%40lists.inf.ethz.ch?Subject=Re:%20Re%3A%20%5BOberon%5D%20Oberon%20for%20a%20C%2B%2B%20user.&In-Reply-To=%3C000a01d21ba8%24b97dc9d0%242c795d70%24%40cfbsoftware.com%3E> ________________________________
Joerg,
OK - you may well be onto something here but it is incomplete as it is. Something has to
import Square so that the module body is executed, or you need an exported procedure in
Square that is called from the OS.
Note: there are a couple of semicolons missing and Init must be exported.
Chris.
-----Original Message----- From: Jörg Straube [mailto:joerg.straube at iaeth.ch<https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=UHr5AP3cXC2ZAXacD2dYANUhpxDnR6gWosSpUd2oaRw&s=TWgxIZxS5z7mpZJDUj92ydjRoic3rLYdp0iN4Zb0ngQ&e= <https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=UHr5AP3cXC2ZAXacD2dYANUhpxDnR6gWosSpUd2oaRw&s=TWgxIZxS5z7mpZJDUj92ydjRoic3rLYdp0iN4Zb0ngQ&e=>>] Sent: Saturday, 1 October 2016 2:22 PM To: chris at cfbsoftware.com<https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=UHr5AP3cXC2ZAXacD2dYANUhpxDnR6gWosSpUd2oaRw&s=TWgxIZxS5z7mpZJDUj92ydjRoic3rLYdp0iN4Zb0ngQ&e= <https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=UHr5AP3cXC2ZAXacD2dYANUhpxDnR6gWosSpUd2oaRw&s=TWgxIZxS5z7mpZJDUj92ydjRoic3rLYdp0iN4Zb0ngQ&e=>>; ETH Oberon and related systems
Subject: Re: [Oberon] Oberon for a C++ user.
Chris
Here a possble example that might cause issues:
MODULE Figure; TYPE DrawProc = PROCEDURE; Figure = POINTER TO FigureDesc; FigureDesc = RECORD next: Figure; draw: DrawProc END; VAR list: Figure; PROCEDURE Init(d: DrawProc) VAR f: Figure; BEGIN NEW(f); f.next := list; list := f; f.draw := d END Init; BEGIN list:= NIL END Figure.
MODULE Square; IMPORT Figure; PROCEDURE DrawSquare; BEGIN (* do what ever you need to do to draw a square *) END DrawSquare BEGIN Figure.Init(DrawSquare) END Square.
Now you could run Square and unload Square. The list in Figure has a reference to unloaded code.
J rg
Andreas: I like your post. Preventing run time crashes is worth a bit of performance while unloading. Imagine, that unloading a module takes one second due to extra overhead. (A whole second is unlikely.) Imagine, that cleaning after a crash takes 30 seconds: closing the traps, saving files just in case, perhaps rebooting the system. The gain is then 29 seconds, not counting the nerves. I think it is worth implementing in a system whose main claim to fame is robustness. Forget my ASSIGN/RETRACT if there is a simpler solution. Thank you, Wojtek ________________________________________ From: Andreas Pirklbauer [andreas_pirklbauer@yahoo.com] Sent: Monday, October 3, 2016 4:01 AM To: Skulski, Wojciech; Oberon List Subject: Re: [Oberon] Oberon for a C++ user. Overloading is not the problem here. It would be straightforward to modify the code generator for the assignment operator := to care of reference counting when it is applied to a procedure variable, i.e. implement what you call ASSIGN and RETRACT. In fact, I have implemented just that on Ceres a long time ago as part of (the original version of) Experimental Oberon. But I do not recommend to use reference counting in general. Reason: it introduces a hidden mechanism (not desired) and a non-negligible overhead in assignments (ok, one may argue that this is “only" done for procedure variables which are typically assigned only once, but still - it is in the spirit of Oberon to avoid hidden mechanisms). There is an alternative solution without reference counting: When unloading a module M using Modules.Free(M), one *could* traverse the heap object for object - just like the garbage collector does - and check whether the encountered objects contain procedure variables, and if so see whether the (addresses of) such procedure variables belong to M's code section (i.e. whether M.code <= address < M.imports). If there is at least one such procedure variable, simply don't unload M. It's practically the same code as the garbage collector (so it could in fact be be combined with it), except that is it merely used to *check* whether the addresses of procedure variables in heap records belong to (the code section of) module M. That too I have implemented on Ceres (in NS32032 assembly code) a long time ago, and now that the garbage collector is itself implemented in Oberon (see procedures Kernel.Mark and Kernel.Scan), it would be straightforward to bring the code forward to Oberon 2013 - one really just needs to create a modified version of Kernel.Mark and call it from within procedure Modules.Free. But again, this would create a non-negligible overhead during unloading of a module - every time a module M needs to be unloaded, the *entire* heap would need to be traversed, and this just to find out whether one or more of its elements contain a procedure variable whose memory addresses belong to module M's code (this inefficiency can be remedied though in various ways, e.g. by setting a flag in module M when an assignment of M.proc to a procedure variable is made, but still, this would make both assignments and unloading rather cumbersome operations). But it is definitely doable. Note that performance is not really an issue, because unloading of modules should be rare - and could be preceded by a garbage collection run as a matter of course (in which case one just needs to slightly adapt *its* code). Thus, it is doable and has in fact been implemented. But I have yet to see a truly satisfactory and elegant solution to this problem - and this is why I haven’t brought the code forward to Oberon 2013 (and Experimental Oberon 2013). Andreas ________________________________ From: "Skulski, Wojciech" <skulski@pas.rochester.edu<mailto:skulski@pas.rochester.edu>> To: Andreas Pirklbauer <andreas_pirklbauer@yahoo.com<mailto:andreas_pirklbauer@yahoo.com>>; ETH Oberon and related systems <oberon@lists.inf.ethz.ch<mailto:oberon@lists.inf.ethz.ch>> Sent: Monday, October 3, 2016 9:07 AM Subject: RE: [Oberon] Oberon for a C++ user. Andreas: the problem IMHO is in the operator :=, which is overloaded. The procedure variables are not a variables in the usual sense. The operator := should not apply to them. A regular variable has a value. The value is passive. It gets acted upon by a client. The variable is not doing anything on its own. Somebody is doing something with the variable (somebody is using its value for some goal). IF var1 = var2 THEN... (*passive role of the variables*) The procedure variable has a value (the address to jump to), and also a behavior. The value is passive, but the behavior is active. The value is retained after unloading the implementation, but the behavior is damaged. This is the problem. The value (the address) is not that important. Yes, you can use it in comparisons: IF proc1 = proc2 THEN... (*passive role of the procedure variables*) Note that after unloading the implementations of the proc1 and proc2, you can still make the comparison. Both proc1 and proc2 are perfectly valid in the passive sense. Their code is gone, but their passive values (addresses) are perfectly OK. But this is not why procedure variables are important. We want their behaviors. And this gets damaged upon unloading. In order to cure the problem, the operator :=should not be permitted with procedure variables. There needs to be another, separate operator which will assign procedure variables and also take care of the reference counting. ASSIGN (procvar, Mod.Proc) I think that separating both operators := and ASSIGN will make sense, because regular variables are not posing the problem which procedure variables are posing. The counterpart of ASSIGN would be RETRACT (or some better name). Since you re not allowed to use := with procedure variables, you could not write procvar := NIL; (*not allowed*) You would have to say RETRACT (procvar); (*takes care of the references*) I hope this proposition makes sense. W. ---------------------------- Chris, Joerg: that is exactly the problem: assigning a procedure of a *client* module to a procedure variable of an *imported* module, and then unloading the client module - leaving the procedure variable in the imported module dangling. Assigning a procedure of a client module to a variable of an imported "base" module is in fact the standard way the Oberon system itself employs procedure variables to implement the viewer system in an object-oriented style. For example, module TextFrames (the client) installs a handler (procedure TextFrames.Handle) in the field F.handle of text frame F (see TextFrames.NewText and TextFrames.Open for the details), where the record field F.handle is declared as a procedure variable in the "base" module Viewers (see Viewers.ViewerDesc), which also manages the viewer data structure (see procedure Viewers.Open). Except that of course in the case of the Oberon system, the "client" module TextFrames *never* gets unloaded, so this is not a problem there. Thus, the burden is on the user to make sure that such modules never get unloaded. It is of course possible to implement a "procedure variable" counter - in analogy to the "module counter" employed in Oberon: each time an assignment to a procedure variable is made, the counter gets increased. But that would require changing the code generator compiler for assignments, and the module loader (Modules.Load) and unloader (Modules.Free). But I would refrain from introducing such complexity to the compiler and runtime system. Andreas Chris Burrows chris at cfbsoftware.com<https://urldefense.proofpoint.com/v2/url?u=http-3A__cfbsoftware.com&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=uCo5T7FsqFpABSbe4qNj_9wO71q0cOpBhZcNZ79F_28&s=QDMllTxM_47yu_wVGCaG1gvwnYwA6fnPwzkt16mBJsc&e=> <mailto:oberon%40lists.inf.ethz.ch<https://urldefense.proofpoint.com/v2/url?u=http-3A__40lists.inf.ethz.ch&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=uCo5T7FsqFpABSbe4qNj_9wO71q0cOpBhZcNZ79F_28&s=JjFlvNvGq14Jc57YnjqQ1ua9Yx-tBWQ69_Y7UalxAa0&e=>?Subject=Re:%20Re%3A%20%5BOberon%5D%20Oberon%20for%20a%20C%2B%2B%20user.&In-Reply-To=%3C000a01d21ba8%24b97dc9d0%242c795d70%24%40cfbsoftware.com<https://urldefense.proofpoint.com/v2/url?u=http-3A__40cfbsoftware.com&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=uCo5T7FsqFpABSbe4qNj_9wO71q0cOpBhZcNZ79F_28&s=UF_1Pj7G3IOmGznnb4-LwXOfO9BuhZiedJF_e_vdyQw&e=>%3E> ________________________________
Joerg,
OK - you may well be onto something here but it is incomplete as it is. Something has to
import Square so that the module body is executed, or you need an exported procedure in
Square that is called from the OS.
Note: there are a couple of semicolons missing and Init must be exported.
Chris.
-----Original Message----- From: Jörg Straube [mailto:joerg.straube at iaeth.ch<https://urldefense.proofpoint.com/v2/url?u=http-3A__iaeth.ch&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=uCo5T7FsqFpABSbe4qNj_9wO71q0cOpBhZcNZ79F_28&s=FN1J5KaD-V4B2ndfhuj-8OpXXPH4thm38kn7TNpwkBw&e=><https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=UHr5AP3cXC2ZAXacD2dYANUhpxDnR6gWosSpUd2oaRw&s=TWgxIZxS5z7mpZJDUj92ydjRoic3rLYdp0iN4Zb0ngQ&e=>] Sent: Saturday, 1 October 2016 2:22 PM To: chris at cfbsoftware.com<https://urldefense.proofpoint.com/v2/url?u=http-3A__cfbsoftware.com&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=uCo5T7FsqFpABSbe4qNj_9wO71q0cOpBhZcNZ79F_28&s=QDMllTxM_47yu_wVGCaG1gvwnYwA6fnPwzkt16mBJsc&e=><https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=UHr5AP3cXC2ZAXacD2dYANUhpxDnR6gWosSpUd2oaRw&s=TWgxIZxS5z7mpZJDUj92ydjRoic3rLYdp0iN4Zb0ngQ&e=>; ETH Oberon and related systems
Subject: Re: [Oberon] Oberon for a C++ user.
Chris
Here a possble example that might cause issues:
MODULE Figure; TYPE DrawProc = PROCEDURE; Figure = POINTER TO FigureDesc; FigureDesc = RECORD next: Figure; draw: DrawProc END; VAR list: Figure; PROCEDURE Init(d: DrawProc) VAR f: Figure; BEGIN NEW(f); f.next := list; list := f; f.draw := d END Init; BEGIN list:= NIL END Figure.
MODULE Square; IMPORT Figure; PROCEDURE DrawSquare; BEGIN (* do what ever you need to do to draw a square *) END DrawSquare BEGIN Figure.Init(DrawSquare) END Square.
Now you could run Square and unload Square. The list in Figure has a reference to unloaded code.
J rg
I agree. So it could be a candidate for another small “experiment” to be added to Experimental Oberon (https://github.com/andreaspirklbauer/Oberon-experimental <https://github.com/andreaspirklbauer/Oberon-experimental>). Any volunteers? I’d be interested in hearing how much overhead is generated by adding the necessary code the Modules.Free. The main question is of course how does one find out whether a particular record found on the heap does indeed contain a procedure variable or not - given that the structure of the Oberon-1 type descriptor only contains offsets (but no "method table” as is typically used in implementations of Object Oberon or Oberon-2 with its type-bound procedures). PS: I’m not convinced that this problem should in fact be solved at the module level. This seems to be a software deployment and/or management issue. If there was a separat abstraction level “apps” (which one could simply define to be module groups) in Oberon, serving as the basic unit of software distribution (like in modern mobile devices), then one could simply define a “top-level” module within an given “app” (bundle) and be done at that moment. But I agree, in the absence of such a software deployment mechanism in Oberon (and lack of app store), it’s probably worth exploring the idea at the module level first and do an implementation of that. Note that in the Ceres implementation, the overhead in the modified Modules.Free was practically not noticeable (which is not surprising given that the garbage collector itself was already very fast on a 25MHz Ceres machine. However, I believe this is only true for memory sizes up to a few MegaBytes, as is used in Oberon. If one tried to use Oberon’s mark-and-scan method with today’s typical memory sizes (measured in GigaBytes, not MegaBytes), the system would probably run into scalability issues very quickly, although I haven’t checked myself (I just believe to vaguely remember that the mark-and-scan method employed in Oberon-1 does not scale very well to large memory sizes). Andreas
On 3 Oct 2016, at 17:42, Skulski, Wojciech <skulski@pas.rochester.edu> wrote:
Andreas:
I like your post. Preventing run time crashes is worth a bit of performance while unloading. Imagine, that unloading a module takes one second due to extra overhead. (A whole second is unlikely.) Imagine, that cleaning after a crash takes 30 seconds: closing the traps, saving files just in case, perhaps rebooting the system. The gain is then 29 seconds, not counting the nerves.
I think it is worth implementing in a system whose main claim to fame is robustness.
Forget my ASSIGN/RETRACT if there is a simpler solution.
Thank you, Wojtek ________________________________________ From: Andreas Pirklbauer [andreas_pirklbauer@yahoo.com <mailto:andreas_pirklbauer@yahoo.com>] Sent: Monday, October 3, 2016 4:01 AM To: Skulski, Wojciech; Oberon List Subject: Re: [Oberon] Oberon for a C++ user.
Overloading is not the problem here. It would be straightforward to modify the code generator for the assignment operator := to take care of reference counting when it is applied to a procedure variable, i.e. implement what you call ASSIGN and RETRACT. In fact, I have implemented just that on Ceres a long time ago as part of (the original version of) Experimental Oberon. But I do not recommend to use reference counting in general. Reason: It introduces a hidden mechanism (not desired) and a non-negligible overhead in assignments (ok, one may argue that this is “only" done for procedure variables which are typically assigned only once, but still - it is in the spirit of Oberon to avoid hidden mechanisms).
There is an alternative solution without reference counting: When unloading a module M using Modules.Free(M), one *could* traverse the heap object for object - just like the garbage collector does - and check whether the encountered objects contain procedure variables, and if so see whether the (addresses of) such procedure variables belong to M's code section (i.e. whether M.code <= address < M.imports). If there is at least one such procedure variable, simply don't unload M. It's practically the same code as the garbage collector (so it could in fact be be combined with it), except that is it merely used to *check* whether the addresses of procedure variables in heap records belong to (the code section of) module M. That too I have implemented on Ceres (in NS32032 assembly code) a long time ago, and now that the garbage collector is itself implemented in Oberon (see procedures Kernel.Mark and Kernel.Scan), it would be straightforward to bring the code forward to Oberon 2013 - one really just needs to create a modified version of Kernel.Mark and call it from within procedure Modules.Free. But again, this would create a non-negligible overhead during unloading of a module - every time a module M needs to be unloaded, the *entire* heap would need to be traversed, and this just to find out whether one or more of its elements contain a procedure variable whose memory addresses belong to module M's code (this inefficiency can be remedied though in various ways, e.g. by setting a flag in module M when an assignment of M.proc to a procedure variable is made, but still, this would make both assignments and unloading rather cumbersome operations). But it is definitely doable. Note that performance is not really an issue, because unloading of modules should be rare - and could be preceded by a garbage collection run as a matter of course (in which case one just needs to slightly adapt *its* code).
Thus, it is doable and has in fact been implemented. But I have yet to see a truly satisfactory and elegant solution to this problem - and this is why I haven’t brought the code forward to Oberon 2013 (and Experimental Oberon 2013).
Andreas
________________________________ From: "Skulski, Wojciech" <skulski@pas.rochester.edu <mailto:skulski@pas.rochester.edu><mailto:skulski@pas.rochester.edu <mailto:skulski@pas.rochester.edu>>> To: Andreas Pirklbauer <andreas_pirklbauer@yahoo.com <mailto:andreas_pirklbauer@yahoo.com><mailto:andreas_pirklbauer@yahoo.com <mailto:andreas_pirklbauer@yahoo.com>>>; ETH Oberon and related systems <oberon@lists.inf.ethz.ch <mailto:oberon@lists.inf.ethz.ch><mailto:oberon@lists.inf.ethz.ch <mailto:oberon@lists.inf.ethz.ch>>> Sent: Monday, October 3, 2016 9:07 AM Subject: RE: [Oberon] Oberon for a C++ user.
Andreas:
the problem IMHO is in the operator :=, which is overloaded. The procedure variables are not a variables in the usual sense. The operator := should not apply to them.
A regular variable has a value. The value is passive. It gets acted upon by a client. The variable is not doing anything on its own. Somebody is doing something with the variable (somebody is using its value for some goal).
IF var1 = var2 THEN... (*passive role of the variables*)
The procedure variable has a value (the address to jump to), and also a behavior. The value is passive, but the behavior is active. The value is retained after unloading the implementation, but the behavior is damaged. This is the problem.
The value (the address) is not that important. Yes, you can use it in comparisons:
IF proc1 = proc2 THEN... (*passive role of the procedure variables*)
Note that after unloading the implementations of the proc1 and proc2, you can still make the comparison. Both proc1 and proc2 are perfectly valid in the passive sense. Their code is gone, but their passive values (addresses) are perfectly OK.
But this is not why procedure variables are important. We want their behaviors. And this gets damaged upon unloading.
In order to cure the problem, the operator :=should not be permitted with procedure variables. There needs to be another, separate operator which will assign procedure variables and also take care of the reference counting.
ASSIGN (procvar, Mod.Proc)
I think that separating both operators := and ASSIGN will make sense, because regular variables are not posing the problem which procedure variables are posing.
The counterpart of ASSIGN would be RETRACT (or some better name). Since you re not allowed to use := with procedure variables, you could not write
procvar := NIL; (*not allowed*)
You would have to say
RETRACT (procvar); (*takes care of the references*)
I hope this proposition makes sense.
W.
----------------------------
Chris, Joerg:
that is exactly the problem: assigning a procedure of a *client* module to a procedure variable of an *imported* module, and then unloading the client module - leaving the procedure variable in the imported module dangling.
Assigning a procedure of a client module to a variable of an imported "base" module is in fact the standard way the Oberon system itself employs procedure variables to implement the viewer system in an object-oriented style.
For example, module TextFrames (the client) installs a handler (procedure TextFrames.Handle) in the field F.handle of text frame F (see TextFrames.NewText and TextFrames.Open for the details), where the record field F.handle is declared as a procedure variable in the "base" module Viewers (see Viewers.ViewerDesc), which also manages the viewer data structure (see procedure Viewers.Open).
Except that of course in the case of the Oberon system, the "client" module TextFrames *never* gets unloaded, so this is not a problem there. Thus, the burden is on the user to make sure that such modules never get unloaded.
It is of course possible to implement a "procedure variable" counter - in analogy to the "module counter" employed in Oberon: each time an assignment to a procedure variable is made, the counter gets increased. But that would require changing the code generator compiler for assignments, and the module loader (Modules.Load) and unloader (Modules.Free). But I would refrain from introducing such complexity to the compiler and runtime system.
Andreas
Chris Burrows chris at cfbsoftware.com <http://cfbsoftware.com/><https://urldefense.proofpoint.com/v2/url?u=http-3A__cfbsoftware.com&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=uCo5T7FsqFpABSbe4qNj_9wO71q0cOpBhZcNZ79F_28&s=QDMllTxM_47yu_wVGCaG1gvwnYwA6fnPwzkt16mBJsc&e= <https://urldefense.proofpoint.com/v2/url?u=http-3A__cfbsoftware.com&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=uCo5T7FsqFpABSbe4qNj_9wO71q0cOpBhZcNZ79F_28&s=QDMllTxM_47yu_wVGCaG1gvwnYwA6fnPwzkt16mBJsc&e=>> <mailto:oberon%40lists.inf.ethz.ch <http://40lists.inf.ethz.ch/><https://urldefense.proofpoint.com/v2/url?u=http-3A__40lists.inf.ethz.ch&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=uCo5T7FsqFpABSbe4qNj_9wO71q0cOpBhZcNZ79F_28&s=JjFlvNvGq14Jc57YnjqQ1ua9Yx-tBWQ69_Y7UalxAa0&e= <https://urldefense.proofpoint.com/v2/url?u=http-3A__40lists.inf.ethz.ch&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=uCo5T7FsqFpABSbe4qNj_9wO71q0cOpBhZcNZ79F_28&s=JjFlvNvGq14Jc57YnjqQ1ua9Yx-tBWQ69_Y7UalxAa0&e=>>?Subject=Re:%20Re%3A%20%5BOberon%5D%20Oberon%20for%20a%20C%2B%2B%20user.&In-Reply-To=%3C000a01d21ba8%24b97dc9d0%242c795d70%24%40cfbsoftware.com <http://40cfbsoftware.com/><https://urldefense.proofpoint.com/v2/url?u=http-3A__40cfbsoftware.com&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=uCo5T7FsqFpABSbe4qNj_9wO71q0cOpBhZcNZ79F_28&s=UF_1Pj7G3IOmGznnb4-LwXOfO9BuhZiedJF_e_vdyQw&e= <https://urldefense.proofpoint.com/v2/url?u=http-3A__40cfbsoftware.com&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=uCo5T7FsqFpABSbe4qNj_9wO71q0cOpBhZcNZ79F_28&s=UF_1Pj7G3IOmGznnb4-LwXOfO9BuhZiedJF_e_vdyQw&e=>>%3E>
________________________________
Joerg,
OK - you may well be onto something here but it is incomplete as it is. Something has to
import Square so that the module body is executed, or you need an exported procedure in
Square that is called from the OS.
Note: there are a couple of semicolons missing and Init must be exported.
Chris.
-----Original Message----- From: Jörg Straube [mailto:joerg.straube at iaeth.ch <http://iaeth.ch/><https://urldefense.proofpoint.com/v2/url?u=http-3A__iaeth.ch&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=uCo5T7FsqFpABSbe4qNj_9wO71q0cOpBhZcNZ79F_28&s=FN1J5KaD-V4B2ndfhuj-8OpXXPH4thm38kn7TNpwkBw&e= <https://urldefense.proofpoint.com/v2/url?u=http-3A__iaeth.ch&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=uCo5T7FsqFpABSbe4qNj_9wO71q0cOpBhZcNZ79F_28&s=FN1J5KaD-V4B2ndfhuj-8OpXXPH4thm38kn7TNpwkBw&e=>><https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=UHr5AP3cXC2ZAXacD2dYANUhpxDnR6gWosSpUd2oaRw&s=TWgxIZxS5z7mpZJDUj92ydjRoic3rLYdp0iN4Zb0ngQ&e= <https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=UHr5AP3cXC2ZAXacD2dYANUhpxDnR6gWosSpUd2oaRw&s=TWgxIZxS5z7mpZJDUj92ydjRoic3rLYdp0iN4Zb0ngQ&e=>>] Sent: Saturday, 1 October 2016 2:22 PM To: chris at cfbsoftware.com <http://cfbsoftware.com/><https://urldefense.proofpoint.com/v2/url?u=http-3A__cfbsoftware.com&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=uCo5T7FsqFpABSbe4qNj_9wO71q0cOpBhZcNZ79F_28&s=QDMllTxM_47yu_wVGCaG1gvwnYwA6fnPwzkt16mBJsc&e= <https://urldefense.proofpoint.com/v2/url?u=http-3A__cfbsoftware.com&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=uCo5T7FsqFpABSbe4qNj_9wO71q0cOpBhZcNZ79F_28&s=QDMllTxM_47yu_wVGCaG1gvwnYwA6fnPwzkt16mBJsc&e=>><https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=UHr5AP3cXC2ZAXacD2dYANUhpxDnR6gWosSpUd2oaRw&s=TWgxIZxS5z7mpZJDUj92ydjRoic3rLYdp0iN4Zb0ngQ&e= <https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=UHr5AP3cXC2ZAXacD2dYANUhpxDnR6gWosSpUd2oaRw&s=TWgxIZxS5z7mpZJDUj92ydjRoic3rLYdp0iN4Zb0ngQ&e=>>; ETH Oberon and related systems
Subject: Re: [Oberon] Oberon for a C++ user.
Chris
Here a possble example that might cause issues:
MODULE Figure; TYPE DrawProc = PROCEDURE; Figure = POINTER TO FigureDesc; FigureDesc = RECORD next: Figure; draw: DrawProc END; VAR list: Figure; PROCEDURE Init(d: DrawProc) VAR f: Figure; BEGIN NEW(f); f.next := list; list := f; f.draw := d END Init; BEGIN list:= NIL END Figure.
MODULE Square; IMPORT Figure; PROCEDURE DrawSquare; BEGIN (* do what ever you need to do to draw a square *) END DrawSquare BEGIN Figure.Init(DrawSquare) END Square.
Now you could run Square and unload Square. The list in Figure has a reference to unloaded code.
J rg
On 10/3/2016 9:38 AM, Andreas Pirklbauer wrote:
PS: I’m not convinced that this problem should in fact be solved at the module level. This seems to be a software deployment and/or management issue. If there was a separat abstraction level “apps” (which one could simply define to be module groups) in Oberon, serving as the basic unit of software distribution (like in modern mobile devices), then one could simply define a “top-level” module within an given “app” (bundle) and be done at that moment.
I disagree. In BlackBox there is the module Views which contains a procedure variable that is set by a client of Views. But ANY module can be a client of Views (IMPORT Views) so there is no "group" that can be used to somehow be used to bundle an app. -Doug
Hi Andreas, On Mon, Oct 3, 2016 at 12:38 PM, Andreas Pirklbauer <andreas_pirklbauer@yahoo.com> wrote:
The main question is of course how does one find out whether a particular record found on the heap does indeed contain a procedure variable or not - given that the structure of the Oberon-1 type descriptor only contains offsets (but no "method table” as is typically used in implementations of Object Oberon or Oberon-2 with its type-bound procedures).
The problem is not (in my humble opinion) that you only have offsets. The offsets are to POINTER fields of the RECORD, and (whether one uses the word or not) procedure variables are "pointers" as well, just to a piece of code instead of a RECORD. The problem is more that this is *all* they are. With a POINTER to a RECORD, I can find another type descriptor at the "destination" and that's how the garbage collector's mark phase proceeds through the heap. From what I can tell, adding a "procedure descriptor" and a "module descriptor" of some sort would allow extending the garbage collection approach to those constructs as well (with detailed semantics obviously needing to be settled). I consider something else "problematic" namely that you'd have to know if a procedure is currently running; then it should obviously not be "garbage collected" from under the CPU, regardless whether the last "pointer" to it has been reassigned. But *if* the current Oberon still follows the old approach of running the GC only *between* commands and not at any other time, and *if* the current Oberon is still single-threaded, then that "problem" actually disappears. (Sorry, it has been too long since I seriously looked at Oberon System variants. I demoed one for my students last year as part of the OS course, but I didn't actually study it in any detail. It certainly *looked* like from 1985, so maybe my knowledge of the system *is* still applicable?) Best, Peter
Peter: procedure variables are "pointers" as well, just to a piece of code instead of a RECORD.
This is very much true from GC's point of view... Thanks for proposing GC as an alternative solution. The idea behind using GC is that, GC can keep the unloaded module in memory till referrers vanish. However, while using GC, we don't *explicitly* delete the object. We just pretend that old object doesn't exist. We reuse the pointer with a new object. That means both objects exist in memory, till GC comes. But we unload modules *explicitly*. And often we load a new *updated* module with same name. In such cases, can both old module and new module co-exist in memory, till GC comes to play? The biggest problem is, object identity is just an address, But module identity is its name.
Wojtek: You can also look at it from the point of view of the client/ server. At the point of assignment procvar := Mod.Proc, the client gets the asset and the server Mod makes the promise. When the server is unloaded, it breaks the promise without telling the client "I am gone, do not rely on my assets". The client thinks it still has a good asset, tries to use it, and it gets duped. The client is perfectly innocent. It was the server who broke the contract.
Say, we unloaded the old server module and loaded a new updated server module (interface remaining intact), does procvar := Mod.Proc points to new updated server Proc? If answer is Yes, that means old module and new module doesn't co-exist in memory. Don't know if GC can help in such cases. Then, I think, as long as interface remains same, no problem. When interface differs, client or base or imported module has to be compiled and reloaded. So no problem. If the answer is No, that means module when loaded, have to always assign procvar := Mod.Proc automatically, may be inside module BEGIN...END. [Do we generally make procvar := Mod.Proc inside module BEGIN...END?] True that server broke the promise. So, I think, he should make procvar := NIL; Where and when is the problem... If procvar := Mod.Proc is done automatically while module loading, (through code sitting inside module BEGIN...END) we should have an automatic means of making it NIL while unloading. But...we don't have a place like BEGIN...END to execute when module gets unloaded! Can we have something like this... A procedure UnloadMe() which a module will implement. And when the module is unloaded, UnloadMe() will be called. Hope that could be simpler to implement. No language change! Only implementation correction. With thanks and best regards, Yours sincerely, Srinivas Nayak Home: http://www.mathmeth.com/sn/ Blog: http://srinivas-nayak.blogspot.in/ On 10/05/2016 05:06 AM, Peter Fröhlich wrote:
Hi Andreas,
On Mon, Oct 3, 2016 at 12:38 PM, Andreas Pirklbauer <andreas_pirklbauer@yahoo.com> wrote:
The main question is of course how does one find out whether a particular record found on the heap does indeed contain a procedure variable or not - given that the structure of the Oberon-1 type descriptor only contains offsets (but no "method table” as is typically used in implementations of Object Oberon or Oberon-2 with its type-bound procedures).
The problem is not (in my humble opinion) that you only have offsets. The offsets are to POINTER fields of the RECORD, and (whether one uses the word or not) procedure variables are "pointers" as well, just to a piece of code instead of a RECORD. The problem is more that this is *all* they are.
With a POINTER to a RECORD, I can find another type descriptor at the "destination" and that's how the garbage collector's mark phase proceeds through the heap. From what I can tell, adding a "procedure descriptor" and a "module descriptor" of some sort would allow extending the garbage collection approach to those constructs as well (with detailed semantics obviously needing to be settled).
I consider something else "problematic" namely that you'd have to know if a procedure is currently running; then it should obviously not be "garbage collected" from under the CPU, regardless whether the last "pointer" to it has been reassigned. But *if* the current Oberon still follows the old approach of running the GC only *between* commands and not at any other time, and *if* the current Oberon is still single-threaded, then that "problem" actually disappears.
(Sorry, it has been too long since I seriously looked at Oberon System variants. I demoed one for my students last year as part of the OS course, but I didn't actually study it in any detail. It certainly *looked* like from 1985, so maybe my knowledge of the system *is* still applicable?)
Best, Peter -- Oberon@lists.inf.ethz.ch mailing list for ETH Oberon and related systems https://lists.inf.ethz.ch/mailman/listinfo/oberon
If procvar := Mod.Proc is done automatically while module loading, (through code sitting inside module BEGIN...END) we should have an automatic means of making it NIL while unloading. But...we don't have a place like BEGIN...END to execute when module gets unloaded!
Can we have something like this... A procedure UnloadMe() which a module will implement. And when the module is unloaded, UnloadMe() will be called.
Clemens Szypeski in his thesis "Insight ETHOS" discusses on "Finalization" in section 2.1.4. "Another aspect closely related to garbage collection is object finalization. Object finalization refers to the possibility for an object to take some final actions just before it gets removed by the garbage collector." With thanks and best regards, Yours sincerely, Srinivas Nayak Home: http://www.mathmeth.com/sn/ Blog: http://srinivas-nayak.blogspot.in/ On 10/05/2016 01:38 PM, Srinivas Nayak wrote:
Peter: procedure variables are "pointers" as well, just to a piece of code instead of a RECORD.
This is very much true from GC's point of view... Thanks for proposing GC as an alternative solution.
The idea behind using GC is that, GC can keep the unloaded module in memory till referrers vanish.
However, while using GC, we don't *explicitly* delete the object. We just pretend that old object doesn't exist. We reuse the pointer with a new object. That means both objects exist in memory, till GC comes. But we unload modules *explicitly*.
And often we load a new *updated* module with same name. In such cases, can both old module and new module co-exist in memory, till GC comes to play?
The biggest problem is, object identity is just an address, But module identity is its name.
Wojtek: You can also look at it from the point of view of the client/ server. At the point of assignment procvar := Mod.Proc, the client gets the asset and the server Mod makes the promise. When the server is unloaded, it breaks the promise without telling the client "I am gone, do not rely on my assets". The client thinks it still has a good asset, tries to use it, and it gets duped. The client is perfectly innocent. It was the server who broke the contract.
Say, we unloaded the old server module and loaded a new updated server module (interface remaining intact), does procvar := Mod.Proc points to new updated server Proc?
If answer is Yes, that means old module and new module doesn't co-exist in memory. Don't know if GC can help in such cases. Then, I think, as long as interface remains same, no problem. When interface differs, client or base or imported module has to be compiled and reloaded. So no problem.
If the answer is No, that means module when loaded, have to always assign procvar := Mod.Proc automatically, may be inside module BEGIN...END. [Do we generally make procvar := Mod.Proc inside module BEGIN...END?]
True that server broke the promise. So, I think, he should make procvar := NIL; Where and when is the problem...
If procvar := Mod.Proc is done automatically while module loading, (through code sitting inside module BEGIN...END) we should have an automatic means of making it NIL while unloading. But...we don't have a place like BEGIN...END to execute when module gets unloaded!
Can we have something like this... A procedure UnloadMe() which a module will implement. And when the module is unloaded, UnloadMe() will be called.
Hope that could be simpler to implement. No language change! Only implementation correction.
With thanks and best regards,
Yours sincerely, Srinivas Nayak
Home: http://www.mathmeth.com/sn/ Blog: http://srinivas-nayak.blogspot.in/
On Fri, November 11, 2016 11:34 am, Skulski, Wojciech wrote:
Clemens Szypeski in his thesis "Insight ETHOS" discusses on "Finalization" in section 2.1.4.
Finalization was implemented in BlackBox. Other Oberons may consider it as well.
Were there any catch 22's involved, such as mentioned in my other email to the list. How did they decide on order of finalization in a multi module project? Obviously they would usually base it on order you pull in the modules... but AFAIR there are gotchyas/catch22's here... What if finalization calls code, from a module that's already unloaded? What if multiple modules use the same module (call it in).... how do you figure out which module will be unloaded in what order, if multiple modules are using the module in different orders of the module "uses" section (sorry that's a delphi term).
Lars:
How did they decide on order of finalization in a multi module project? What if finalization calls code, from a module that's already unloaded?
The modules can be unloaded in the reverse import order. The idea is that the lower level (imported) modules are servers. If a server is imported then it cannot be unloaded. It is closely related to the other thread "Safe unloading". Your concerns were addressed by Andreas. I guess you can perhaps devise a devious example where a top level module installs a procedure variable (a "hook") in the lower level module, and this causes chaos upon finalization. I am guessing that Andreas has fixed that. Barring such deviousness, the finalization is intended to deal with freeing resources such as files or communication channels. Its use should be restricted to such straightforward cases. I am not sure if we need to worry about hundreds of modules at this point. Wojtek
I guess you can perhaps devise a devious example where a top level module installs a procedure variable (a "hook") in the lower level module, and this causes chaos upon finalization. I am guessing that Andreas has fixed that. Barring such deviousness, the finalization is intended to deal with freeing resources such as files or communication channels. Its use should be restricted to such straightforward cases.
Yes, Andreas wrote... "The case of inheritance (type extension in Oberon) is now fully covered in the latest version of safe module unloading in Experimental Oberon. ..." Can anybody please summarize how he mitigated the problem? I was thinking, Finalization can help us here. But, interested to know the approach taken by Andreas to barring such deviousness. With thanks and best regards, Yours sincerely, Srinivas Nayak Home: http://www.mathmeth.com/sn/ Blog: http://srinivas-nayak.blogspot.in/ On 11/12/2016 05:59 AM, Skulski, Wojciech wrote:
Lars:
How did they decide on order of finalization in a multi module project? What if finalization calls code, from a module that's already unloaded?
The modules can be unloaded in the reverse import order. The idea is that the lower level (imported) modules are servers. If a server is imported then it cannot be unloaded. It is closely related to the other thread "Safe unloading". Your concerns were addressed by Andreas.
I guess you can perhaps devise a devious example where a top level module installs a procedure variable (a "hook") in the lower level module, and this causes chaos upon finalization. I am guessing that Andreas has fixed that. Barring such deviousness, the finalization is intended to deal with freeing resources such as files or communication channels. Its use should be restricted to such straightforward cases.
I am not sure if we need to worry about hundreds of modules at this point.
Wojtek
-- Oberon@lists.inf.ethz.ch mailing list for ETH Oberon and related systems https://lists.inf.ethz.ch/mailman/listinfo/oberon
I am of the opinion that Component Pacal *should* just become the official Oberon of the future.
Wojtek
Oberon is just Oberon and Component Pascal is just Component Pascal :-) I don't like all those extravagant keywords and it's not true that *a pure client programmer never needs to write any of above attributes* as the What's New document states. For example: I just want a final record, that extends no record, with some type-bound procedures, for a very simple use-case. TYPE MyFinalRec = RECORD [...] END; PROCEDURE (VAR m : MyFinalRec) Method(), NEW; BEGIN [...] END Method; I'm forced to use the NEW keyword, due to the fact that Method() is newly introduced. But it is meaningless for a final record that does not extend a record. To make it disappear, I have to make an interface for that record like this: TYPE IMyFinalRec = ABSTRACT RECORD END; PROCEDURE (VAR m : IMyFinalRec) Method(), NEW, ABSTRACT; Extending this record (or in an alternative form: implementing this interface) the NEW keyword will disappear (of course). Now I'm a pure client programmer, but before I had to be a software designer :-) Except for some minor points like the above one, I have to say that I like Component Pascal and it's a pity that they did not include Szyperski's work on (a lightweight) Parametric Polymorphism. Anyway I tend to like more Oberon/Oberon-2 than CP, software design is a very important discipline but it is not everything. -- Diego Sardina
Hello!
I don't like all those extravagant keywords and it's not true that *a pure client programmer never needs to write any of above attributes* as the What's New document states.
...
I'm forced to use the NEW keyword, due to the fact that Method() is newly introduced. But it is meaningless for a final record that does not extend a record.
But you are not being a client in this case, are you? If you were a client in the sense of the word that document uses, you would only extend existing records and override existing methods. When you create new methods and records, you are designing a new framework, you are not being a *pure client* of the BlackBox Framework. ---=====--- Александр
Hello!
Hi :-)
But you are not being a client in this case, are you? If you were a client in the sense of the word that document uses, you would only extend existing records and override existing methods.
Well, we have to define what a client programmer is :-) In general I mean any use of concrete records, also if they don't extend or use any framework. In particular he's not going to make an interface for a service module. But of course the What's New document means *an user of the framework*. I put the term in a more general context.
When you create new methods and records, you are designing a new framework, you are not being a *pure client* of the BlackBox Framework.
With a final record that extends no record I'm not creating or using a framework. I've a fully concrete record that neither requires dynamic dispatch for type-bound procedures. The NEW keyword is totally meaningless here, it makes sense in TBPs of EXTENSIBLE records and for those final ones that extends a record. Or am I missing something? I used CP only for a very short period. Thanks for further clarification. -- Diego Sardina
With a final record that extends no record I'm not creating or using a framework. I've a fully concrete record that neither requires dynamic dispatch for type-bound procedures.
The NEW keyword is totally meaningless here, it makes sense in TBPs of EXTENSIBLE records and for those final ones that extends a record.
Here's the code you are describing, and it has no extra keywords: MODULE MyTest; TYPE MyRecord = RECORD data1, data2: INTEGER END; PROCEDURE Do* (VAR rec: MyRecord); BEGIN rec.data1 := 0; rec.data2 := 1 END Do; END MyTest. ---=====--- Александр
Here's the code you are describing, and it has no extra keywords:
I don't understand your reply and I find it somewhat provocative, like you are talking with a new comer. I hope it's not. Why did you reply me to not use TBPs? And what if I really want to use them? Classical procedures, with or without Method Suite, are more convenient when you have one object (or record) per module. With this style having more is really troublesome. It is easy to name clash when you have common names like Write() or Print(). You have to append or prepend the type name, like WriteClient() or PrintList(), and one is often redundant with the module's name. The situation is worse when you have to implement the methods of extensions in the same module. So the default code style is to append the type name to all procedures. The solution is to put the extended record and/or other related records in separate modules, it seems more natural with this style. Type-bound procedures on the contrary are more convenient when you have more objects/records per module. The problem of name clash simply doesn't exist, every TBP is related to its record. Also they visually help to group all related procedures to its record. With Method Suite you have to look at procedure's name or module's name, looking at the receiver is often not helpful because it's contravariant in plain Oberon. Anyway, I've no problem with both styles, but I don't like to mix the two. If I'm using Oberon-2 or Component Pascal I prefer to see the module as a container of (cooperative) classes. Especially when CP treats TBPs of final records that extend no record like ordinary procedures. -- Diego Sardina
From the Component Pascal documentation "10.4 Finalization A predeclared method named FINALIZE is associated with each record type as if it were declared to be bound to the type ANYREC: PROCEDURE (a: ANYPTR) FINALIZE-, NEW, EMPTY; The FINALIZE procedure can be implemented for any pointer type. The method is called at some unspecified time after an object of that type (or a base type of it) has become unreachable via other pointers (not globally anchored anymore) and before the object is deallocated. It is not recommended to re-anchor an object in its finalizer and the finalizer is not called again when the object repeatedly becomes unreachable. Multiple unreachable objects are finalized in an unspecified order. " On 11/13/2016 11:41 AM, Diego Sardina wrote:
I am of the opinion that Component Pacal *should* just become the official Oberon of the future.
Wojtek
Oberon is just Oberon and Component Pascal is just Component Pascal :-)
I don't like all those extravagant keywords and it's not true that *a pure client programmer never needs to write any of above attributes* as the What's New document states.
For example: I just want a final record, that extends no record, with some type-bound procedures, for a very simple use-case.
TYPE MyFinalRec = RECORD [...] END;
PROCEDURE (VAR m : MyFinalRec) Method(), NEW; BEGIN [...] END Method;
I'm forced to use the NEW keyword, due to the fact that Method() is newly introduced. But it is meaningless for a final record that does not extend a record.
To make it disappear, I have to make an interface for that record like this:
TYPE IMyFinalRec = ABSTRACT RECORD END;
PROCEDURE (VAR m : IMyFinalRec) Method(), NEW, ABSTRACT;
Extending this record (or in an alternative form: implementing this interface) the NEW keyword will disappear (of course).
Now I'm a pure client programmer, but before I had to be a software designer :-)
Except for some minor points like the above one, I have to say that I like Component Pascal and it's a pity that they did not include Szyperski's work on (a lightweight) Parametric Polymorphism.
Anyway I tend to like more Oberon/Oberon-2 than CP, software design is a very important discipline but it is not everything.
-- Diego Sardina
-- Oberon@lists.inf.ethz.ch mailing list for ETH Oberon and related systems https://lists.inf.ethz.ch/mailman/listinfo/oberon
Hello! 13.11.2016, 22:41, "Diego Sardina" <dsar@eml.cc>:
TYPE MyFinalRec = RECORD [...] END;
PROCEDURE (VAR m : MyFinalRec) Method(), NEW; BEGIN [...] END Method;
I'm forced to use the NEW keyword, due to the fact that Method() is newly introduced. But it is meaningless for a final record that does not extend a record.
It is not meaningless, it stresses the fact that you are not extending an existing method, and affirmatively states that in the source text. If at a later point you change the RECORD definition to become an extension of a more generic object, the compiler will help you resolve the method name conflicts. I find it very thoughtful on part of CP language designers to make this word mandatory. Without it in the situation I described all the methods with clashing names would silently be converted into overrides. ---=====--- Александр
It is not meaningless, it stresses the fact that you are not extending an existing method, and affirmatively states that in the source text.
Again, in a final record that *extends no record* OF COURSE I'm not extending an existing method and being final it is not going to be a base. The purpose of NEW is meaningless here. I'm not stating that in a type hierarchy it is useless. It is just that particular case that I find the NEW keyword invasive. But I understand that removing it only for this special case would complicate language semantics.
If at a later point you change the RECORD definition to become an extension of a more generic object, the compiler will help you resolve the method name conflicts.
Why I have to change record definition to be an extension of a more generic object? That means there was something wrong in my design decisions. There is also a lot of work, i.e. implementing all the mandatory part of the interface. NEW keyword of course helps here in the case of clashing names, but putting an independent type into a type hierarchy is easier to state than to do. In most cases, it requires a re-design -- Diego Sardina
Hello!
If at a later point you change the RECORD definition to become an extension of a more generic object, the compiler will help you resolve the method name conflicts.
Why I have to change record definition to be an extension of a more generic object? That means there was something wrong in my design decisions.
Are you saying you're never wrong the first time 'round?
There is also a lot of work, i.e. implementing all the mandatory part of the interface. NEW keyword of course helps here in the case of clashing names, but putting an independent type into a type hierarchy is easier to state than to do. In most cases, it requires a re-design
A redesign? Why not gradual evolution of a design? POINT 1: Initial design. TYPE MyData = RECORD ... END; PROCEDURE (VAR this: MyData) Store (VAR file: Streams.Stream), NEW; (* Oh, no! A useless keyword! *) BEGIN ... END Store; POINT 2: New object introduced. TYPE MyData = RECORD ... END; MyData2 = RECORD ... END; PROCEDURE (VAR this: MyData) Store (VAR file: Streams.Stream), NEW; BEGIN ... END Store; PROCEDURE (VAR this: MyData2) Store (VAR file: Streams.Stream), NEW; (* Shucks! Now we've got two of those! *) BEGIN ... END Store; POINT 3: Generalization is in order. TYPE Storable = RECORD ... END; MyData = RECORD (Storable) ... END; MyData2 = RECORD (Storable) ... END; PROCEDURE (VAR this: Storable) Store (VAR file: Streams.Stream), NEW; BEGIN ... END Store; PROCEDURE (VAR this: MyData) Store (VAR file: Streams.Stream); (* That's better! *) BEGIN ... END Store; PROCEDURE (VAR this: MyData2) Store (VAR file: Streams.Stream); (* Got rid of the buggers! *) BEGIN ... END Store; At this point the compiler demands removal of the two "NEW" qualifiers, thus bringing to the programmer's attention all the places where code needs to be updated in the face of the new object hierarchy. It's the long-term evolution of the codebase that's being helped by the compiler here, which is a rare feat even these days. ---=====--- Александр
On Fri, November 11, 2016 9:10 am, Srinivas Nayak wrote:
If procvar := Mod.Proc is done automatically while module loading, (through code sitting inside module BEGIN...END) we should have an automatic means of making it NIL while unloading. But...we don't have a place like BEGIN...END to execute when module gets unloaded!
Can we have something like this... A procedure UnloadMe() which a module will implement. And when the module is unloaded, UnloadMe() will be called.
Clemens Szypeski in his thesis "Insight ETHOS" discusses on "Finalization" in section 2.1.4.
Just a heads up to implementers and designers: Finalization, although seems trivial at first sight, has actually caused some nightmares in freepascal. It seems simple: you just call a finalization section before the module is unloaded. However the order of finalization is tricky... which module do you finalize first? You're going to have hundreds of modules, in large programs, so how do you decide the order of finalization? Looking at it from just one module perspective, it's simple. A one module program is easy. But a large program with hundreds of modules: care must be taken to ensure order of unloading is done right, if there is even a "right" way. Unfortunately my work with freepascal has seen some serious bugs in finalization for some reason I cannot remember, I think it was finalization/initialization sections in a DLL not occurring at the right times. Since modules all communicate with each other or pull in code from each other you can get into tricky conflicts, that were not originally anticipated. For example if finalization accesses some code in another module directly or indirectly, what if that module is already freed? Will the compiler warn you that you are calling code that has already been unloaded? Or will run time warn you?
In BlackBox one can do Info->Loaded Modules which yields a list of all modules currently in memory. One can then select a subset of that list and do Dev->Unload Module List and all of those modules will be unloaded IF every module in the list has clients in the list or a module has no clients. Said another way, the selected modules comprise a tree (or forest of trees) whose root (roots) have no clients. On 11/11/2016 2:20 PM, Lars wrote:
Since modules all communicate with each other or pull in code from each other you can get into tricky conflicts, that were not originally anticipated. For example if finalization accesses some code in another module directly or indirectly, what if that module is already freed? Will the compiler warn you that you are calling code that has already been unloaded? Or will run time warn you?
On Tue, 4 Oct 2016 19:36:05 -0400, Peter Fröhlich wrote:
With a POINTER to a RECORD, I can find another type descriptor at the "destination" and that's how the garbage collector's mark phase proceeds through the heap. From what I can tell, adding a "procedure descriptor" and a "module descriptor" of some sort would allow extending the garbage collection approach to those constructs as well (with detailed semantics obviously needing to be settled).
I think it is no big problem to extend the garbage collector.
I consider something else "problematic" namely that you'd have to know if a procedure is currently running; then it should obviously not be "garbage collected" from under the CPU, regardless whether the last "pointer" to it has been reassigned. But *if* the current Oberon still follows the old approach of running the GC only *between* commands and not at any other time, and *if* the current Oberon is still single-threaded, then that "problem" actually disappears.
No real problem either. If you do garbage collection while commands are running you have to do a conservative scan of the stack anyway. There you will find a pointer into the procedure's code as a return address. Just treat them like all the other (heap) pointers.
(Sorry, it has been too long since I seriously looked at Oberon System variants. I demoed one for my students last year as part of the OS course, but I didn't actually study it in any detail. It certainly *looked* like from 1985, so maybe my knowledge of the system *is* still applicable?)
More or less. Several Oberon systems used different approaches, but Oberon 2013 is close to the original. I see one problem with the pure garbage collection strategy. If there are still references to procedures and after unloading and reloading a module like in a typical development cycle there are two (or more) versions of the same procedure in the system. That can lead to some confusion for example for a symbolic debugger or any other reflection service. Therefore I am not totally convinced that writing NIL to proc references is such a bad idea if it is done when the user explicitly requested to unload a module. That I would not consider magic behind the back. Greetings, Chris P.S. Nice to hear from you again, did not see you here since the Lagoona days if you still remember ;-)
Chris:
I see one problem with the pure garbage collection strategy. If there are still references to procedures and after unloading and reloading a module like in a typical development cycle there are two (or more) versions of the same procedure in the system. That can lead to some confusion for example for a symbolic debugger or any other reflection service.
A development cycle is driven by a human. A human can wait 0.1 second while the GC is working. Therefore, calling the GC should be an automatic part of the module unloading action. Even a full second waiting time is a fair deal for a system that is not crashing. A crash is costing more grief than the short wait time which will be pretty much unnoticeable in a the Oberon 2013 system with a few megabyte memory. I also seem to remember that the procedure code is in the 2nd heap which is typically much smaller than the heap devoted to records. Traversing the procedure heap should be lightning fast even if the machine has a gigabyte on board. You do not need to scan the entire gigabyte, but rather the code hep only. Correct me if I am mistaken. Wojtek
I think, it would not make sense to invest a lot of effort just to eliminate the unsafety of explicit module unloading. After all, an end user of the Oberon System will just use applications with automatic on-demand module loading and never have to unload older versions of these modules. Only programmers have to do that during the development of a module. And they can already use unsafe features of the programming language simply by importing the System module, provoking similar effects e.g. via unsafe type conversions. Thus, eliminating the module unloading unsafety would not significantly improve the stability of the system. Richard
Richard: with all due respect, I kindly disagree. First, I am not sure if there are many end users at this point. Second, the problem is common to all implementations, including BlackBox if I understand correctly. It would be good to pinpoint the origin of the problem and to find an elegant solution, if at all possible. It would benefit the broader community. Third, the Oberon and BlackBox claim to fame is rapid and safe development without the dreaded "segmentation violation" which is plaguing the C family. But here we have exactly that. I do not know if you are a teacher, but if I was one then I could not teach good and safe programming techniques with Oberon only to say to students "you know what, it is crashing like everyone else". It is either one way or another. Of course we can live with the crashing implementation, but then we can as well live with C like everyone is doing. At the end, there is a "reset" button to the rescue. Let's just use it and forget the elegant design. Just my opinion. W. ________________________________________ From: Oberon [oberon-bounces@lists.inf.ethz.ch] on behalf of Richard Hable [informujo@aon.at] Sent: Wednesday, October 5, 2016 2:16 PM To: oberon@lists.inf.ethz.ch Subject: Re: [Oberon] Oberon for a C++ user. I think, it would not make sense to invest a lot of effort just to eliminate the unsafety of explicit module unloading. After all, an end user of the Oberon System will just use applications with automatic on-demand module loading and never have to unload older versions of these modules. Only programmers have to do that during the development of a module. And they can already use unsafe features of the programming language simply by importing the System module, provoking similar effects e.g. via unsafe type conversions. Thus, eliminating the module unloading unsafety would not significantly improve the stability of the system. Richard -- Oberon@lists.inf.ethz.ch mailing list for ETH Oberon and related systems https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwICAg&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=v2frDBmDgacPcJ9pLUt1od0xyJpTopbtLR1by0RdZY0&s=GG38i_88fgK7hahQLGgXz0mcLg70OLrjrxkb8aFbzwk&e=
On Wed, 5 Oct 2016 13:23:52 +0000, Skulski, Wojciech wrote:
I also seem to remember that the procedure code is in the 2nd heap which is typically much smaller than the heap devoted to records. Traversing the procedure heap should be lightning fast even if the machine has a gigabyte on board. You do not need to scan the entire gigabyte, but rather the code hep only. Correct me if I am mistaken.
Yes, but that was not my point. I don't expect any performance problems. What I don' like about this is, what I would consider an inconsistent state of the system. There can be different versions of code for the same procedure around at the same time after an unload / reload cycle of a module. I am not sure if this situation is a better solution than writing NIL to all references to unloaded procedures so that calling an unloaded procedure's code would trap instead of running outdated code which does not reflect the actal code of the module as loaded. Greetings, chris
Chris: now I feel lost. The whole point of unloading is to get rid of the old code. Conceptually speaking unloading should work something like this: 1. Find all the references to the old (still loaded) code. 2. Get rid of the references. 3. Remove the code from the memory. Now you are ready to load the fresh code. At any given time there is only one copy of the code loaded to RAM. Since all is being done by the GC, and the machine is a single threaded machine with no preemption, it is not possible to interrupt the process. The above is a non-expert view of how this could (should?) work. With my non expert eyes I do not see any potential for different versions of code for the same procedure . What am I missing? Cheers, W. ________________________________________ From: Oberon [oberon-bounces@lists.inf.ethz.ch] on behalf of chris [chris@gcjd.org] Sent: Thursday, October 6, 2016 11:45 AM To: ETH Oberon and related systems Subject: Re: [Oberon] Oberon for a C++ user. On Wed, 5 Oct 2016 13:23:52 +0000, Skulski, Wojciech wrote:
I also seem to remember that the procedure code is in the 2nd heap which is typically much smaller than the heap devoted to records. Traversing the procedure heap should be lightning fast even if the machine has a gigabyte on board. You do not need to scan the entire gigabyte, but rather the code hep only. Correct me if I am mistaken.
Yes, but that was not my point. I don't expect any performance problems. What I don' like about this is, what I would consider an inconsistent state of the system. There can be different versions of code for the same procedure around at the same time after an unload / reload cycle of a module. I am not sure if this situation is a better solution than writing NIL to all references to unloaded procedures so that calling an unloaded procedure's code would trap instead of running outdated code which does not reflect the actal code of the module as loaded. Greetings, chris
On Thu, 6 Oct 2016 16:15:46 +0000, Skulski, Wojciech wrote:
1. Find all the references to the old (still loaded) code. 2. Get rid of the references.
That's the problem. How do you get rid of the references in procedure variables? Normal garbage collection does not deallocate the memory until all references to the memory block have vanished by the usual progress of the program. Until that has happened the old code is still available unless you do anything to eliminate the references.
Now you are ready to load the fresh code. At any given time there is only one copy of the code loaded to RAM. Since all is being done by the GC, and the machine is a single threaded machine with no preemption, it is not possible to interrupt the process.
That's not what I meant. The garbage collector in it's standard way does not remove any references; it just checks them and deallocates only heap blocks that don't have any references anymore. If there are still references and you reload a modified module again you have two incarnations of the module in the system. In the end I see these possibilities. 1. Don't care, just unload like it currently is. 2. Check for references and don't unload if there are any. 3. Unload only removes the module from the module list. Check for references and unload the code later from the garbage collector when no references exist anymore. 4. Check for references and set them to NIL then unload. 5. Even more complicated ideas: like reload and unload in one step to replace the references to the same procedures in the new module, a kind of dynamic relinking. I don't really suggest this, because this causes new problems if the new module does not have the same procedures as the old one and binding is done by name things get really messy. I hope I have explained it better now. And obviously the problem is not so easy to solve not because of it's implementation but because it's not obvious how it should work correct. Greetings, chris
Chris: I think that the most conservative solution is #2: 2. Check for references and don't unload if there are any. The system should then complain in the Log "Cannot unload module <Name> because its procedures are still in use" or something to that effect. The programmer, who is developing the code, will be able to figure out that s/he installed these procedures somewhere. Here is another thought: BlackBox has the FINALIZE section of the module. Perhaps it is the place where the checks could be performed? If the use of the exported procedures could be traced back in the FINALIZE section, then the finalizer can raise a trap. Trap in the finalizer would abort unloading the module. I am not sure how it relates to Andreas' work. Any comments? W. ________________________________________ From: Oberon [oberon-bounces@lists.inf.ethz.ch] on behalf of chris [chris@gcjd.org] Sent: Thursday, October 6, 2016 2:31 PM To: ETH Oberon and related systems Subject: Re: [Oberon] Oberon for a C++ user. On Thu, 6 Oct 2016 16:15:46 +0000, Skulski, Wojciech wrote:
1. Find all the references to the old (still loaded) code. 2. Get rid of the references.
That's the problem. How do you get rid of the references in procedure variables? Normal garbage collection does not deallocate the memory until all references to the memory block have vanished by the usual progress of the program. Until that has happened the old code is still available unless you do anything to eliminate the references.
Now you are ready to load the fresh code. At any given time there is only one copy of the code loaded to RAM. Since all is being done by the GC, and the machine is a single threaded machine with no preemption, it is not possible to interrupt the process.
That's not what I meant. The garbage collector in it's standard way does not remove any references; it just checks them and deallocates only heap blocks that don't have any references anymore. If there are still references and you reload a modified module again you have two incarnations of the module in the system. In the end I see these possibilities. 1. Don't care, just unload like it currently is. 2. Check for references and don't unload if there are any. 3. Unload only removes the module from the module list. Check for references and unload the code later from the garbage collector when no references exist anymore. 4. Check for references and set them to NIL then unload. 5. Even more complicated ideas: like reload and unload in one step to replace the references to the same procedures in the new module, a kind of dynamic relinking. I don't really suggest this, because this causes new problems if the new module does not have the same procedures as the old one and binding is done by name things get really messy. I hope I have explained it better now. And obviously the problem is not so easy to solve not because of it's implementation but because it's not obvious how it should work correct. Greetings, chris -- Oberon@lists.inf.ethz.ch mailing list for ETH Oberon and related systems https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwICAg&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=x1NIqMPi2VYn-rP6tryogccwOa36_ecIpb2Xzbmq1zI&s=2-9Tq1InWQ8qoQ4qwmLjNPxvgjhhnQLt-qm9KKYbgRI&e=
Wojtek: I think that the most conservative solution is #2: 2. Check for references and don't unload if there are any.
I liked this idea very much. Can this be of any help? http://lists.inf.ethz.ch/pipermail/oberon/2016/009916.html With thanks and best regards, Yours sincerely, Srinivas Nayak Home: http://www.mathmeth.com/sn/ Blog: http://srinivas-nayak.blogspot.in/ On 10/07/2016 12:52 AM, Skulski, Wojciech wrote:
Chris:
I think that the most conservative solution is #2:
2. Check for references and don't unload if there are any.
The system should then complain in the Log "Cannot unload module <Name> because its procedures are still in use" or something to that effect. The programmer, who is developing the code, will be able to figure out that s/he installed these procedures somewhere.
Here is another thought: BlackBox has the FINALIZE section of the module. Perhaps it is the place where the checks could be performed? If the use of the exported procedures could be traced back in the FINALIZE section, then the finalizer can raise a trap. Trap in the finalizer would abort unloading the module.
I am not sure how it relates to Andreas' work. Any comments?
W. ________________________________________ From: Oberon [oberon-bounces@lists.inf.ethz.ch] on behalf of chris [chris@gcjd.org] Sent: Thursday, October 6, 2016 2:31 PM To: ETH Oberon and related systems Subject: Re: [Oberon] Oberon for a C++ user.
On Thu, 6 Oct 2016 16:15:46 +0000, Skulski, Wojciech wrote:
1. Find all the references to the old (still loaded) code. 2. Get rid of the references.
That's the problem. How do you get rid of the references in procedure variables?
Normal garbage collection does not deallocate the memory until all references to the memory block have vanished by the usual progress of the program. Until that has happened the old code is still available unless you do anything to eliminate the references.
Now you are ready to load the fresh code. At any given time there is only one copy of the code loaded to RAM. Since all is being done by the GC, and the machine is a single threaded machine with no preemption, it is not possible to interrupt the process.
That's not what I meant. The garbage collector in it's standard way does not remove any references; it just checks them and deallocates only heap blocks that don't have any references anymore.
If there are still references and you reload a modified module again you have two incarnations of the module in the system.
In the end I see these possibilities.
1. Don't care, just unload like it currently is.
2. Check for references and don't unload if there are any.
3. Unload only removes the module from the module list. Check for references and unload the code later from the garbage collector when no references exist anymore.
4. Check for references and set them to NIL then unload.
5. Even more complicated ideas: like reload and unload in one step to replace the references to the same procedures in the new module, a kind of dynamic relinking. I don't really suggest this, because this causes new problems if the new module does not have the same procedures as the old one and binding is done by name things get really messy.
I hope I have explained it better now. And obviously the problem is not so easy to solve not because of it's implementation but because it's not obvious how it should work correct.
Greetings, chris -- Oberon@lists.inf.ethz.ch mailing list for ETH Oberon and related systems https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwICAg&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=x1NIqMPi2VYn-rP6tryogccwOa36_ecIpb2Xzbmq1zI&s=2-9Tq1InWQ8qoQ4qwmLjNPxvgjhhnQLt-qm9KKYbgRI&e= -- Oberon@lists.inf.ethz.ch mailing list for ETH Oberon and related systems https://lists.inf.ethz.ch/mailman/listinfo/oberon
Dear All, Regarding the dangling procedure variable, Is it a language issue? or its implementation issue? [I would like to understand this for my knowledge sake only.] I feel it is not language issue... Language said, modules can be imported. Imported modules can export procedure variables. Importers can assign Procedures to them. That way, imported module can also call importer module's procedures. Done! I feel, language did its own job, saying this much. It shouldn't go on to dictate when to load imported module, when to load importer module, When to unload which one, whether to allow or disallow loading/unloading, and what is the consequence of loading/unloading etc... These are just my thoughts... Or it should dictate all such things? (to guide implementation...) Where do we generally draw the lines? Is it that C++ guys go on dictating all such things and that is why their Standard is longer than Oberon's? Since this dangling procvar is a nice case at hand. So I thought to get expert's opinion on, where to limit language liabilities and what all are then left as implementation issue? And yes, by 'implementation' I mean, mainly the OS here (including linker+loader+libraries). Not the compiler. I think compiler is the realization of the language. So I feel whatever the language says or are the language liabilities, must be supported by compiler. Some other stuff that are not part of the language may go into compiler, but lesser is better. Is there any misunderstanding from me here? Kindly rectify me. Hope to see some lights on this from language/implementation experts. With thanks and best regards, Yours sincerely, Srinivas Nayak Home: http://www.mathmeth.com/sn/ Blog: http://srinivas-nayak.blogspot.in/
Dear Srinivas,
Regarding the dangling procedure variable, Is it a language issue?...or its implementation issue? ...Language said, modules can be imported. Imported modules can export procedure variables. Importers can assign Procedures to them. ...I feel, language did its own job, saying this much. It shouldn't go on to dictate when to load imported module, when to load importer module, When to unload which one, whether to allow or disallow loading/unloading, and what is the consequence of loading/unloading etc...
Or it should dictate all such things? (to guide implementation...) Where do we generally draw the lines?
I think you make a great point. The language usually implies some abstractions which are simply impossible for implementers, e.g. infinite stack size, infinite heap size, infinite module space. One thing which Prof. Wirth has repeatedly pointed out is how delicate the language definition process is. He tends deliberately to leave things unsaid in order to leave freedom for implementers. I think this is informed by his experience as a language implementer, not just a language designer. (Sometimes this goes a bit wrong - like the well-known lack of "short-circuit" boolean operators in Pascal - and in the light of experience is corrected.) In the case of procedure variables pointing to module code which has been unloaded, you could argue that an implementation should *re-load* the module, maintaining the abstraction of an infinite module space (as it happens, a kind of higher-level version of the virtual memory idea), since this is how the system automatically loads missing code in the first place: on demand. This could be done using thunks/trampolines as others have suggested - in one implementation where I was particularly worried about this, I went to some effort to implement exactly that - and yet, it was never needed in practice. So the main point I think is that it's necessary to be pragmatic, implement what's reasonable. This will be different for different people, and at different times. In at least one version of the original Ceres Oberon system, the book points out that the available and otherwise-unused hardware (the NS32032 processor's memory- management unit) was used to detect these dangling procedure pointers, because that was relatively easy to do. The book also admits it's not an ideal or a general solution. Cheers, Paul
Dear Paul, Many thanks for your nice explanation. This also shown me a new perspective with which we should consider languages; that is of being an abstraction for infinite stack space, infinite module space... With thanks and best regards, Yours sincerely, Srinivas Nayak Home: http://www.mathmeth.com/sn/ Blog: http://srinivas-nayak.blogspot.in/ On 10/11/2016 02:24 PM, Paul Reed wrote:
Dear Srinivas,
Regarding the dangling procedure variable, Is it a language issue?...or its implementation issue? ...Language said, modules can be imported. Imported modules can export procedure variables. Importers can assign Procedures to them. ...I feel, language did its own job, saying this much. It shouldn't go on to dictate when to load imported module, when to load importer module, When to unload which one, whether to allow or disallow loading/unloading, and what is the consequence of loading/unloading etc...
Or it should dictate all such things? (to guide implementation...) Where do we generally draw the lines?
I think you make a great point.
The language usually implies some abstractions which are simply impossible for implementers, e.g. infinite stack size, infinite heap size, infinite module space.
One thing which Prof. Wirth has repeatedly pointed out is how delicate the language definition process is. He tends deliberately to leave things unsaid in order to leave freedom for implementers. I think this is informed by his experience as a language implementer, not just a language designer.
(Sometimes this goes a bit wrong - like the well-known lack of "short-circuit" boolean operators in Pascal - and in the light of experience is corrected.)
In the case of procedure variables pointing to module code which has been unloaded, you could argue that an implementation should *re-load* the module, maintaining the abstraction of an infinite module space (as it happens, a kind of higher-level version of the virtual memory idea), since this is how the system automatically loads missing code in the first place: on demand.
This could be done using thunks/trampolines as others have suggested - in one implementation where I was particularly worried about this, I went to some effort to implement exactly that - and yet, it was never needed in practice.
So the main point I think is that it's necessary to be pragmatic, implement what's reasonable. This will be different for different people, and at different times. In at least one version of the original Ceres Oberon system, the book points out that the available and otherwise-unused hardware (the NS32032 processor's memory- management unit) was used to detect these dangling procedure pointers, because that was relatively easy to do. The book also admits it's not an ideal or a general solution.
Cheers, Paul
-- Oberon@lists.inf.ethz.ch mailing list for ETH Oberon and related systems https://lists.inf.ethz.ch/mailman/listinfo/oberon
On Thu, Oct 6, 2016 at 11:45 AM, chris <chris@gcjd.org> wrote:
There can be different versions of code for the same procedure around at the same time after an unload / reload cycle of a module.
The question is what exactly "around" means. In my original proposal I mentioned "RCU" as an example. The idea is that whoever *has* a procedure variable reference to the "unloaded" module at the time unloading was requested will keep that reference. However, when they create a new reference, for example by executing myVar := nameOfModule.nameOfProcedure; the will use the newly loaded "replacement" module. I am *assuming* that some part of Oberon keeps a list that maps module names to loaded "module objects" as it were. When you "unload" you remove that entry. This may or may not also unload the module data structure underneath. But the next time someone wants to access that module, it get loaded "fresh" and is entered into that list of "currently loaded modules" and so on. As references to the old code get changed, eventually the GC decides that it's safe to really remove the old stuff. I do, however, see a new problem now. Say module X has a procedure variable holding a reference to Y.P; however, module Y as part of it's "startup" also requested exclusive access to some resource, maybe Y is a device driver of sorts. Now we "unload" Y but since there's still a reference to it, we cannot *really* remove it. For Y.P to keep working, Y needs to still "own" the hardware it controls. When we try to load Y again, the above mechanism will work fine UNTIL the new Y tries to get control of the piece of hardware it's supposed to manage. Oops. So there may be a more complex "resource management story" that needs taking care of. I had not considered that yet. -- Peter H. Froehlich <http://www.cs.jhu.edu/~phf/> Senior Lecturer | Director, Johns Hopkins Gaming Lab The views stated in this communication are my own and do not express the position or views of The Johns Hopkins University.
On Thu, 6 Oct 2016 16:06:12 -0400, Peter Fröhlich wrote:
create a new reference, for example by executing
myVar := nameOfModule.nameOfProcedure;
the will use the newly loaded "replacement" module. I am *assuming* that some part of Oberon keeps a list that maps module names to loaded "module objects" as it were. When you "unload" you remove that entry. This may or may not also unload the module data structure underneath. But the next time someone wants to access that module, it get loaded "fresh" and is entered into that list of "currently loaded modules" and so on. As references to the old code get changed, eventually the GC decides that it's safe to really remove the old stuff.
I agree. That's the way a garbage collector should handle this. But there are problems like a set of global variables which must live until the last procedure reference vanishes. So we have several sets of globals for one module name in memory at some time and then you mention global external resources, which is a related problem I think:
I do, however, see a new problem now. Say module X has a procedure variable holding a reference to Y.P; however, module Y as part of it's "startup" also requested exclusive access to some resource, maybe Y is a device driver of sorts. Now we "unload" Y but since there's still a reference to it, we cannot *really* remove it. For Y.P to keep working, Y needs to still "own" the hardware it controls. When we try to load Y again, the above mechanism will work fine UNTIL the new Y tries to get control of the piece of hardware it's supposed to manage. Oops. So there may be a more complex "resource management story" that needs taking care of. I had not considered that yet.
I believe it is accepted that lower level modules of the Oberon system cannot be unloaded. What do you think about this for unloading: 1. Remove the module from the list of loaded modules. 2. Add the module to a (new) list of unloaded modules which only has weak pointers to the modules resources. 3. Wait for the garbage collect to do it's work. If the last reference vanishes remove the module from the list of unloaded modules. Loading is changed so that the system refuses to load a module that is in the unloaded list. I hope that the user can usually fix this situation by unloading other related modules so that garbage collection can take place to have a safe and usable way of operation. As an action of last resort a forced unload function could be implemented that writes NIL to all remaining references. Greetings, chris
On 10/6/2016 8:45 AM, chris wrote:
I am not sure if this situation is a better solution than writing NIL to all references to unloaded procedures so that calling an unloaded procedure's code would trap instead of running outdated code which does not reflect the actal code of the module as loaded. The initial state of a procedure variable (I believe) is NIL. The module which exports the procedure variable should (in my opinion) handle the case of NIL without trapping. The NIL case is exactly the case when no module uses (assigns) the procedure variable. Hence any module which exports a procedure variable must handle the NIL case quietly.
A thought, which just occurred to me, which is not fully baked, not even half-baked. This may have been discussed previously, and I missed it at the time. Are we hurting ourselves in this discussion by assuming a particular implementation or code generation strategy for a procedure variable? When we load a module, we define a number of externally-visible symbols. Some of those may become the right-hand-sides of assignments to procedure variables. The naïve implementation just loads the actual code address into the procedure variable, so that an indirect jump goes directly to the newly-loaded code. Can we finesse the problem by, instead, as part of the module-loading process, loading a set of thunks (for lack of a better term: perhaps you prefer trampolines), one for each externally-visible procedure, that exist to redirect a procedure variable call? Then, when the module is unloaded, instead of reclaiming the thunks, we make them branch to trap routines instead. If we then load a new version of the module, the thunks are still around, and can be reconnected to the newly-loaded code. --John R. Strohm --- This email has been checked for viruses by Avast antivirus software. https://www.avast.com/antivirus
On Thu, 6 Oct 2016 17:05:39 -0500, John R. Strohm wrote:
Are we hurting ourselves in this discussion by assuming a particular implementation or code generation strategy for a procedure variable?
I don't think so. The problem is independent of the implementation details.
When we load a module, we define a number of externally-visible symbols. Some of those may become the right-hand-sides of assignments to procedure variables.
You can also assign not exported procedures to procedure variables. The pointers to procedures can be located in any heap object and get passed around. You need a full garbage collection scan to find all references.
Can we finesse the problem by, instead, as part of the module-loading process, loading a set of thunks (for lack of a better term: perhaps you prefer trampolines), one for each externally-visible procedure, that exist to redirect a procedure variable call? Then, when the module is unloaded, instead of reclaiming the thunks, we make them branch to trap routines instead. If we then load a new version of the module, the thunks are still around, and can be reconnected to the newly-loaded code.
Of course this can be done, but there is a price. Every procedure call via procedure variable now needs an double indirection. That means you slow down the common case to make the unloading easier which does not happen often and is not time critical. Greetings, chris
Chris: you wrote:
That means you slow down the common case to make the unloading easier which does not happen often and is not time critical.
I do agree that code unloading is infrequent and it needs not be time critical. At the same time, I insist that code unloading needs be robust. It should not lead to system instability. Also note, that the user needs not be a developer to unload the code. A regular user can unload by accident while interclicking the mouse. Or the user may want to unload code after being done with an application, in order to make space for another application. Unloading code from memory is the most common thing under any operating system. For example, after I finish typing this message, I may want to close Firefox. I would be very disappointed if Firefox stayed locked in RAM alongside with Word, PowerPoint, and a dozen other apps which I used today. I rather expect these apps to go away, so I can open other apps. I would be even more disappointed if Windows became unstable after closing Firefox. I would call it "an egregious bug" in either Windows or Firefox. In fact, in both, because neither Firefox is expected to cause Windows instability, nor Windows should allow it to happen. The only practical difference between the Oberon System and other OS's is that unloading the Oberon application happens on demand. Otherwise it is just the same. Under Windows/Linux, the user clicks the "Exit" button and the app gets unloaded. Under Oberon, the user clicks on System.Free. (Which can be hidden under a button, as Markus Knasmueller did in his Dialogs). Is there any difference between unloading Firefox and unloading an Oberon module? I would say "no". Unloading code is one of the most fundamental actions with any computer. The only difference seems to be that we call system instability a bug under Windows, and a feature under Oberon. And this is backwards in my opinion. We need to make unloading robust and safe, rather than trying to convince ourselves that it is an infrequent nuisance that does not need to be robust. Just my 2 cents. W.
On Fri, 7 Oct 2016 17:17:22 +0000, Skulski, Wojciech wrote:
I do agree that code unloading is infrequent and it needs not be time critical. At the same time, I insist that code unloading needs be robust. It should not lead to system instability. Also note, that the user needs not be a developer to unload the code. A regular user can unload by accident while interclicking the mouse. Or the user may want to unload code after being done with an application, in order to make space for another application.
There are no applications in Oberon. To the other points I agree.
The only practical difference between the Oberon System and other OS's is that unloading the Oberon application happens on demand.
The major difference is, that a module is dynamically linked into the Oberon system like an object file into the Firefox process for example. Try unloading parts of Firefox that where in different object files during (static) link time of Firefox while Firefox is running and replace them with a different version. Good luck!
The only difference seems to be that we call system instability a bug under Windows, and a feature under Oberon. And this is backwards in my opinion. We need to make unloading robust and safe, rather than trying to convince ourselves that it is an infrequent nuisance that does not need to be robust.
I have not seen any evidence yet that a double indirection scheme is more robust than a garbage collector that operates on direct pointers. The common garbage collector for the Oberon heap works this way and I have not heard of any problems related to this. I think that is an implementation detail, that does not matter until there is some understanding how module unloading should work in detail. Greetings, chris
On 10/7/2016 11:46 AM, chris wrote:
There are no applications in Oberon. Not true. See WinBugs <http://www.mrc-bsu.cam.ac.uk/software/bugs/the-bugs-project-contact-usbugs-list/> which is written in Component Pascal a super set of Oberon-2.
Am 2016-10-07 um 19:17 schrieb Skulski, Wojciech:
I do agree that code unloading is infrequent and it needs not be time critical. At the same time, I insist that code unloading needs be robust. It should not lead to system instability. Also note, that the user needs not be a developer to unload the code. A regular user can unload by accident while interclicking the mouse. Or the user may want to unload code after being done with an application, in order to make space for another application.
I have never felt the need to unload any module other than one currently under development. And during development all sorts of bad things happen: NIL accesses, array bounds violations, endless loops, even stack overflows due to unlimited recursion, which can lead to memory corruption and crash the system. Trap viewers all over the place, without even having to use any unsafe language feature from the System module... Outdated procedure references belong to the least common problems. Let's not blow them out of proportion. Interesting discussion, though. :) Richard
Hi Andreas, If you are planning on implementing such a scheme then make sure that the user still has the ability to force modules to be unloaded if he understands what he was doing. E.g. in the problematic scenarios described here where two modules are mutually dependent it is not a problem to unload them as long as both of them are unloaded. Otherwise in the process of developing / testing new modules he would be forced to reboot every time he made a change to one of the modules. I suspect you had already thought of that as you are proposing a new (for Project Oberon) command Modules.Free, I assume that the less restrictive System.Free could still be used as it is currently? Regards, Chris
________________________________________ From: Andreas Pirklbauer [andreas_pirklbauer@yahoo.com] Sent: Monday, October 3, 2016 4:01 AM To: Skulski, Wojciech; Oberon List Subject: Re: [Oberon] Oberon for a C++ user.
There is an alternative solution without reference counting: When unloading a module M using Modules.Free(M), one *could* traverse the heap object for object - just like the garbage collector does - and check whether the encountered objects contain procedure variables, and if so see whether the (addresses of) such procedure variables belong to M's code section (i.e. whether M.code <= address < M.imports). If there is at least one such procedure variable, simply don't unload M. It's practically the same code as the garbage collector (so it could in fact be be combined with it), except that is it merely used to *check* whether the addresses of procedure variables in heap records belong to (the code section of) module M. That too I have implemented on Ceres (in NS32032 assembly code) a long time ago, and now that the garbage collector is itself implemented in Oberon (see procedures Kernel.Mark and Kernel.Scan), it would be straightforward to bring the code forward to Oberon 2013 - one really just needs to create a modified version of Kernel.Mark and call it from within procedure Modules.Free. But again, this would create a non-negligible overhead during unloading of a module - every time a module M needs to be unloaded, the *entire* heap would need to be traversed, and this just to find out whether one or more of its elements contain a procedure variable whose memory addresses belong to module M's code (this inefficiency can be remedied though in various ways, e.g. by setting a flag in module M when an assignment of M.proc to a procedure variable is made, but still, this would make both assignments and unloading rather cumbersome operations). But it is definitely doable. Note that performance is not really an issue, because unloading of modules should be rare - and could be preceded by a garbage collection run as a matter of course (in which case one just needs to slightly adapt *its* code).
Thus, it is doable and has in fact been implemented. But I have yet to see a truly satisfactory and elegant solution to this problem - and this is why I havent brought the code forward to Oberon 2013 (and Experimental Oberon 2013).
Andreas
________________________________ From: "Skulski, Wojciech" <skulski@pas.rochester.edu<mailto:skulski@pas.rochester.edu>> To: Andreas Pirklbauer <andreas_pirklbauer@yahoo.com<mailto:andreas_pirklbauer@yahoo.com>>; ETH Oberon and related systems <oberon@lists.inf.ethz.ch<mailto:oberon@lists.inf.ethz.ch>> Sent: Monday, October 3, 2016 9:07 AM Subject: RE: [Oberon] Oberon for a C++ user.
Andreas:
the problem IMHO is in the operator :=, which is overloaded. The procedure variables are not a variables in the usual sense. The operator := should not apply to them.
A regular variable has a value. The value is passive. It gets acted upon by a client. The variable is not doing anything on its own. Somebody is doing something with the variable (somebody is using its value for some goal).
IF var1 = var2 THEN... (*passive role of the variables*)
The procedure variable has a value (the address to jump to), and also a behavior. The value is passive, but the behavior is active. The value is retained after unloading the implementation, but the behavior is damaged. This is the problem.
The value (the address) is not that important. Yes, you can use it in comparisons:
IF proc1 = proc2 THEN... (*passive role of the procedure variables*)
Note that after unloading the implementations of the proc1 and proc2, you can still make the comparison. Both proc1 and proc2 are perfectly valid in the passive sense. Their code is gone, but their passive values (addresses) are perfectly OK.
But this is not why procedure variables are important. We want their behaviors. And this gets damaged upon unloading.
In order to cure the problem, the operator :=should not be permitted with procedure variables. There needs to be another, separate operator which will assign procedure variables and also take care of the reference counting.
ASSIGN (procvar, Mod.Proc)
I think that separating both operators := and ASSIGN will make sense, because regular variables are not posing the problem which procedure variables are posing.
The counterpart of ASSIGN would be RETRACT (or some better name). Since you re not allowed to use := with procedure variables, you could not write
procvar := NIL; (*not allowed*)
You would have to say
RETRACT (procvar); (*takes care of the references*)
I hope this proposition makes sense.
W.
----------------------------
Chris, Joerg:
that is exactly the problem: assigning a procedure of a *client* module to a procedure variable of an *imported* module, and then unloading the client module - leaving the procedure variable in the imported module dangling.
Assigning a procedure of a client module to a variable of an imported "base" module is in fact the standard way the Oberon system itself employs procedure variables to implement the viewer system in an object-oriented style.
For example, module TextFrames (the client) installs a handler (procedure TextFrames.Handle) in the field F.handle of text frame F (see TextFrames.NewText and TextFrames.Open for the details), where the record field F.handle is declared as a procedure variable in the "base" module Viewers (see Viewers.ViewerDesc), which also manages the viewer data structure (see procedure Viewers.Open).
Except that of course in the case of the Oberon system, the "client" module TextFrames *never* gets unloaded, so this is not a problem there. Thus, the burden is on the user to make sure that such modules never get unloaded.
It is of course possible to implement a "procedure variable" counter - in analogy to the "module counter" employed in Oberon: each time an assignment to a procedure variable is made, the counter gets increased. But that would require changing the code generator compiler for assignments, and the module loader (Modules.Load) and unloader (Modules.Free). But I would refrain from introducing such complexity to the compiler and runtime system.
Andreas
Chris Burrows chris at cfbsoftware.com<https://urldefense.proofpoint.com/v2/url?u=http- 3A__cfbsoftware.com&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZ hHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa- ybV0&m=uCo5T7FsqFpABSbe4qNj_9wO71q0cOpBhZcNZ79F_28&s=QDMllTxM_47yu_wV GCaG1gvwnYwA6fnPwzkt16mBJsc&e=> <mailto:oberon%40lists.inf.ethz.ch<https://urldefense.proofpoint.com/ v2/url?u=http- 3A__40lists.inf.ethz.ch&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2e aqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa- ybV0&m=uCo5T7FsqFpABSbe4qNj_9wO71q0cOpBhZcNZ79F_28&s=JjFlvNvGq14Jc57Y njqQ1ua9Yx- tBWQ69_Y7UalxAa0&e=>?Subject=Re:%20Re%3A%20%5BOberon%5D%20Oberon%20fo r%20a%20C%2B%2B%20user.&In-Reply- To=%3C000a01d21ba8%24b97dc9d0%242c795d70%24%40cfbsoftware.com<https:/ /urldefense.proofpoint.com/v2/url?u=http- 3A__40cfbsoftware.com&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaq QZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa- ybV0&m=uCo5T7FsqFpABSbe4qNj_9wO71q0cOpBhZcNZ79F_28&s=UF_1Pj7G3IOmGznn b4-LwXOfO9BuhZiedJF_e_vdyQw&e=>%3E>
________________________________
Joerg,
OK - you may well be onto something here but it is incomplete as it is. Something has to
import Square so that the module body is executed, or you need an exported procedure in
Square that is called from the OS.
Note: there are a couple of semicolons missing and Init must be
exported.
Chris.
-----Original Message----- From: Jörg Straube [mailto:joerg.straube at iaeth.ch<https://urldefense.proofpoint.com/v2/url?u=http- 3A__iaeth.ch&
d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJ I
lq-_BM9w1wVOuyqPwHi3XzJRa- ybV0&m=uCo5T7FsqFpABSbe4qNj_9wO71q0cOpBhZcNZ 79F_28&s=FN1J5KaD-V4B2ndfhuj- 8OpXXPH4thm38kn7TNpwkBw&e=><https://urlde fense.proofpoint.com/v2/url?u=https- 3A__lists.inf.ethz.ch_mailman_list
info_oberon&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r= u
UiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa- ybV0&m=UHr5AP3cXC2ZAXacD2dYANUhp
xDnR6gWosSpUd2oaRw&s=TWgxIZxS5z7mpZJDUj92ydjRoic3rLYdp0iN4Zb0ngQ&e=>]
Sent: Saturday, 1 October 2016 2:22 PM To: chris at cfbsoftware.com<https://urldefense.proofpoint.com/v2/url?u=http- 3A__cf
bsoftware.com&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU& r
=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa- ybV0&m=uCo5T7FsqFpABSbe4qNj_9w
O71q0cOpBhZcNZ79F_28&s=QDMllTxM_47yu_wVGCaG1gvwnYwA6fnPwzkt16mBJsc&e=
3A__lists.inf.ethz.c
h_mailman_listinfo_oberon&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI 2
eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa- ybV0&m=UHr5AP3cXC2
ZAXacD2dYANUhpxDnR6gWosSpUd2oaRw&s=TWgxIZxS5z7mpZJDUj92ydjRoic3rLYdp0 i
N4Zb0ngQ&e=>; ETH Oberon and related systems
Subject: Re: [Oberon] Oberon for a C++ user.
Chris
Here a possble example that might cause issues:
MODULE Figure; TYPE DrawProc = PROCEDURE; Figure = POINTER TO FigureDesc; FigureDesc = RECORD next: Figure; draw: DrawProc END; VAR list: Figure; PROCEDURE Init(d: DrawProc) VAR f: Figure; BEGIN NEW(f); f.next := list; list := f; f.draw := d END Init; BEGIN list:= NIL END Figure.
MODULE Square; IMPORT Figure; PROCEDURE DrawSquare; BEGIN (* do what ever you need to do to draw a square *) END DrawSquare BEGIN Figure.Init(DrawSquare) END Square.
Now you could run Square and unload Square. The list in Figure has a reference to unloaded code.
J rg
-- Oberon@lists.inf.ethz.ch mailing list for ETH Oberon and related systems https://lists.inf.ethz.ch/mailman/listinfo/oberon
W, I like it. The reference counting can be quite complex. IF module A is imported by B, C, ... and A exports a procedure variable (or routine that sets a procedure variable) then only if B, C, ... actually assign a value to the procedure variable of A does the problem arise. -Doug On 10/3/2016 12:07 AM, Skulski, Wojciech wrote:
Andreas:
the problem IMHO is in the operator :=, which is overloaded. The procedure variables are not a variables in the usual sense. The operator := should not apply to them.
A regular variable has a value. The value is passive. It gets acted upon by a client. The variable is not doing anything on its own. Somebody is doing something with the variable (somebody is using its value for some goal).
IF var1 = var2 THEN... (*passive role of the variables*)
The procedure variable has a value (the address to jump to), and also a behavior. The value is passive, but the behavior is active. The value is retained after unloading the implementation, but the behavior is damaged. This is the problem.
The value (the address) is not that important. Yes, you can use it in comparisons:
IF proc1 = proc2 THEN... (*passive role of the procedure variables*)
Note that after unloading the implementations of the proc1 and proc2, you can still make the comparison. Both proc1 and proc2 are perfectly valid in the passive sense. Their code is gone, but their passive values (addresses) are perfectly OK.
But this is not why procedure variables are important. We want their behaviors. And this gets damaged upon unloading.
In order to cure the problem, the operator :=should not be permitted with procedure variables. There needs to be another, separate operator which will assign procedure variables and also take care of the reference counting.
ASSIGN (procvar, Mod.Proc)
I think that separating both operators := and ASSIGN will make sense, because regular variables are not posing the problem which procedure variables are posing.
The counterpart of ASSIGN would be RETRACT (or some better name). Since you re not allowed to use := with procedure variables, you could not write
procvar := NIL; (*not allowed*)
You would have to say
RETRACT (procvar); (*takes care of the references*)
I hope this proposition makes sense.
W.
----------------------------
Chris, Joerg:
that is exactly the problem: assigning a procedure of a *client* module to a procedure variable of an *imported* module, and then unloading the client module - leaving the procedure variable in the imported module dangling.
Assigning a procedure of a client module to a variable of an imported "base" module is in fact the standard way the Oberon system itself employs procedure variables to implement the viewer system in an object-oriented style.
For example, module TextFrames (the client) installs a handler (procedure TextFrames.Handle) in the field F.handle of text frame F (see TextFrames.NewText and TextFrames.Open for the details), where the record field F.handle is declared as a procedure variable in the "base" module Viewers (see Viewers.ViewerDesc), which also manages the viewer data structure (see procedure Viewers.Open).
Except that of course in the case of the Oberon system, the "client" module TextFrames *never* gets unloaded, so this is not a problem there. Thus, the burden is on the user to make sure that such modules never get unloaded.
It is of course possible to implement a "procedure variable" counter - in analogy to the "module counter" employed in Oberon: each time an assignment to a procedure variable is made, the counter gets increased. But that would require changing the code generator compiler for assignments, and the module loader (Modules.Load) and unloader (Modules.Free). But I would refrain from introducing such complexity to the compiler and runtime system.
Andreas
Chris Burrows chris at cfbsoftware.com <mailto:oberon%40lists.inf.ethz.ch?Subject=Re:%20Re%3A%20%5BOberon%5D%20Oberon%20for%20a%20C%2B%2B%20user.&In-Reply-To=%3C000a01d21ba8%24b97dc9d0%242c795d70%24%40cfbsoftware.com%3E>
________________________________
Joerg,
OK - you may well be onto something here but it is incomplete as it is. Something has to import Square so that the module body is executed, or you need an exported procedure in Square that is called from the OS. Note: there are a couple of semicolons missing and Init must be exported. Chris. -----Original Message----- From: Jörg Straube [mailto:joerg.straube at iaeth.ch<https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=UHr5AP3cXC2ZAXacD2dYANUhpxDnR6gWosSpUd2oaRw&s=TWgxIZxS5z7mpZJDUj92ydjRoic3rLYdp0iN4Zb0ngQ&e=>] Sent: Saturday, 1 October 2016 2:22 PM To: chris at cfbsoftware.com<https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=UHr5AP3cXC2ZAXacD2dYANUhpxDnR6gWosSpUd2oaRw&s=TWgxIZxS5z7mpZJDUj92ydjRoic3rLYdp0iN4Zb0ngQ&e=>; ETH Oberon and related systems Subject: Re: [Oberon] Oberon for a C++ user.
Chris
Here a possble example that might cause issues:
MODULE Figure; TYPE DrawProc = PROCEDURE; Figure = POINTER TO FigureDesc; FigureDesc = RECORD next: Figure; draw: DrawProc END; VAR list: Figure; PROCEDURE Init(d: DrawProc) VAR f: Figure; BEGIN NEW(f); f.next := list; list := f; f.draw := d END Init; BEGIN list:= NIL END Figure.
MODULE Square; IMPORT Figure; PROCEDURE DrawSquare; BEGIN (* do what ever you need to do to draw a square *) END DrawSquare BEGIN Figure.Init(DrawSquare) END Square.
Now you could run Square and unload Square. The list in Figure has a reference to unloaded code.
J rg
-- Oberon@lists.inf.ethz.ch mailing list for ETH Oberon and related systems https://lists.inf.ethz.ch/mailman/listinfo/oberon
Doug: this discussion let me recognize that procedure variables are fundamentally different from regular variables in the following sense. Procedures variables have two attribute: value and validity. These two are independent in case of procedures variables, because they are not variables, but handlers. Regular variables have one attribute: value. The value is trivially valid. (With a minor exception of floating point variables where some values are not-a-number, which however is well understood and often useful.) The validity of a regular variable is trivial, because the regular variable is passive. The procedure variable is different. It has both a value (an address to jump to) and validity. The procedure variable's value is a perfectly valid address, unless it points at the non-existing memory segment. This is well known and often taken care of by the memory protection unit (MPU). Oberon does not stipulate using the MPU because Oberon makes the promise that the language is safe enough to get away without the MPU. At the point of assignment procvar := Mod.Proc, the procvar gets both the value and the positive validity. Later on, someone (the programmer) makes the validity to become invalid, which however does not propagate back to the procvar's value. The Oberon System makes the promise that once valid, always valid. However, the Oberon System breaks this promise by allowing the validity to disappear behind the procvar's back. You can also look at it from the point of view of the client/ server. At the point of assignment procvar := Mod.Proc, the client gets the asset and the server Mod makes the promise. When the server is unloaded, it breaks the promise without telling the client "I am gone, do not rely on my assets". The client thinks it still has a good asset, tries to use it, and it gets duped. The client is perfectly innocent. It was the server who broke the contract. I am proposing that we make a clear case. The procedure variable are not a variable, because it is characterized by a pair of attributes (value, validity). Both attributes need be handled, because they are independent from each other. The regular variable has only one attribute, namely a value. The other attribute is trivially fulfilled and it thus does not require any handling. Based on this consideration, a physicist would conclude: saying that a procedure variable is the same as a regular variable is akin to saying that a vector is the same as a scalar. No, it is not. A vector is distinct from a scalar because the number of dimensions is different. Here we have a similar case: two independent attributes versus one. If this sounds scholarly, then I would say it is not. We need to understand the fundamental reason why there is a potential for system corruption and get rid of it in some elegant but effective way. IMO it can only help if we make a clearcut distinction between two classes of objects which are fundamentally different from each other. W. ________________________________________ From: Oberon [oberon-bounces@lists.inf.ethz.ch] on behalf of Douglas G. Danforth [danforth@greenwoodfarm.com] Sent: Monday, October 3, 2016 4:55 PM To: ETH Oberon and related systems Subject: Re: [Oberon] Oberon for a C++ user. W, I like it. The reference counting can be quite complex. IF module A is imported by B, C, ... and A exports a procedure variable (or routine that sets a procedure variable) then only if B, C, ... actually assign a value to the procedure variable of A does the problem arise. -Doug On 10/3/2016 12:07 AM, Skulski, Wojciech wrote:
Andreas:
the problem IMHO is in the operator :=, which is overloaded. The procedure variables are not a variables in the usual sense. The operator := should not apply to them.
A regular variable has a value. The value is passive. It gets acted upon by a client. The variable is not doing anything on its own. Somebody is doing something with the variable (somebody is using its value for some goal).
IF var1 = var2 THEN... (*passive role of the variables*)
The procedure variable has a value (the address to jump to), and also a behavior. The value is passive, but the behavior is active. The value is retained after unloading the implementation, but the behavior is damaged. This is the problem.
The value (the address) is not that important. Yes, you can use it in comparisons:
IF proc1 = proc2 THEN... (*passive role of the procedure variables*)
Note that after unloading the implementations of the proc1 and proc2, you can still make the comparison. Both proc1 and proc2 are perfectly valid in the passive sense. Their code is gone, but their passive values (addresses) are perfectly OK.
But this is not why procedure variables are important. We want their behaviors. And this gets damaged upon unloading.
In order to cure the problem, the operator :=should not be permitted with procedure variables. There needs to be another, separate operator which will assign procedure variables and also take care of the reference counting.
ASSIGN (procvar, Mod.Proc)
I think that separating both operators := and ASSIGN will make sense, because regular variables are not posing the problem which procedure variables are posing.
The counterpart of ASSIGN would be RETRACT (or some better name). Since you re not allowed to use := with procedure variables, you could not write
procvar := NIL; (*not allowed*)
You would have to say
RETRACT (procvar); (*takes care of the references*)
I hope this proposition makes sense.
W.
----------------------------
Chris, Joerg:
that is exactly the problem: assigning a procedure of a *client* module to a procedure variable of an *imported* module, and then unloading the client module - leaving the procedure variable in the imported module dangling.
Assigning a procedure of a client module to a variable of an imported "base" module is in fact the standard way the Oberon system itself employs procedure variables to implement the viewer system in an object-oriented style.
For example, module TextFrames (the client) installs a handler (procedure TextFrames.Handle) in the field F.handle of text frame F (see TextFrames.NewText and TextFrames.Open for the details), where the record field F.handle is declared as a procedure variable in the "base" module Viewers (see Viewers.ViewerDesc), which also manages the viewer data structure (see procedure Viewers.Open).
Except that of course in the case of the Oberon system, the "client" module TextFrames *never* gets unloaded, so this is not a problem there. Thus, the burden is on the user to make sure that such modules never get unloaded.
It is of course possible to implement a "procedure variable" counter - in analogy to the "module counter" employed in Oberon: each time an assignment to a procedure variable is made, the counter gets increased. But that would require changing the code generator compiler for assignments, and the module loader (Modules.Load) and unloader (Modules.Free). But I would refrain from introducing such complexity to the compiler and runtime system.
Andreas
Chris Burrows chris at cfbsoftware.com <mailto:oberon%40lists.inf.ethz.ch?Subject=Re:%20Re%3A%20%5BOberon%5D%20Oberon%20for%20a%20C%2B%2B%20user.&In-Reply-To=%3C000a01d21ba8%24b97dc9d0%242c795d70%24%40cfbsoftware.com%3E>
________________________________
Joerg,
OK - you may well be onto something here but it is incomplete as it is. Something has to import Square so that the module body is executed, or you need an exported procedure in Square that is called from the OS. Note: there are a couple of semicolons missing and Init must be exported. Chris. -----Original Message----- From: Jörg Straube [mailto:joerg.straube at iaeth.ch<https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=UHr5AP3cXC2ZAXacD2dYANUhpxDnR6gWosSpUd2oaRw&s=TWgxIZxS5z7mpZJDUj92ydjRoic3rLYdp0iN4Zb0ngQ&e=>] Sent: Saturday, 1 October 2016 2:22 PM To: chris at cfbsoftware.com<https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=UHr5AP3cXC2ZAXacD2dYANUhpxDnR6gWosSpUd2oaRw&s=TWgxIZxS5z7mpZJDUj92ydjRoic3rLYdp0iN4Zb0ngQ&e=>; ETH Oberon and related systems Subject: Re: [Oberon] Oberon for a C++ user.
Chris
Here a possble example that might cause issues:
MODULE Figure; TYPE DrawProc = PROCEDURE; Figure = POINTER TO FigureDesc; FigureDesc = RECORD next: Figure; draw: DrawProc END; VAR list: Figure; PROCEDURE Init(d: DrawProc) VAR f: Figure; BEGIN NEW(f); f.next := list; list := f; f.draw := d END Init; BEGIN list:= NIL END Figure.
MODULE Square; IMPORT Figure; PROCEDURE DrawSquare; BEGIN (* do what ever you need to do to draw a square *) END DrawSquare BEGIN Figure.Init(DrawSquare) END Square.
Now you could run Square and unload Square. The list in Figure has a reference to unloaded code.
J rg
-- Oberon@lists.inf.ethz.ch mailing list for ETH Oberon and related systems https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwIF-g&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=2hCB-kYQ6nAwqgBQyA2wJiz_nEJx2nZM1lRobiKp7aA&s=R4cllNBWvXw007qPPb3yL6ptNSRN7MT7SK5adx7jGag&e=
-- Oberon@lists.inf.ethz.ch mailing list for ETH Oberon and related systems https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwIF-g&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=2hCB-kYQ6nAwqgBQyA2wJiz_nEJx2nZM1lRobiKp7aA&s=R4cllNBWvXw007qPPb3yL6ptNSRN7MT7SK5adx7jGag&e=
W, Agreed. -Doug On 10/3/2016 3:09 PM, Skulski, Wojciech wrote:
Doug:
this discussion let me recognize that procedure variables are fundamentally different from regular variables in the following sense.
Procedures variables have two attribute: value and validity. These two are independent in case of procedures variables, because they are not variables, but handlers.
Regular variables have one attribute: value. The value is trivially valid. (With a minor exception of floating point variables where some values are not-a-number, which however is well understood and often useful.) The validity of a regular variable is trivial, because the regular variable is passive.
The procedure variable is different. It has both a value (an address to jump to) and validity. The procedure variable's value is a perfectly valid address, unless it points at the non-existing memory segment. This is well known and often taken care of by the memory protection unit (MPU). Oberon does not stipulate using the MPU because Oberon makes the promise that the language is safe enough to get away without the MPU.
At the point of assignment procvar := Mod.Proc, the procvar gets both the value and the positive validity. Later on, someone (the programmer) makes the validity to become invalid, which however does not propagate back to the procvar's value.
The Oberon System makes the promise that once valid, always valid. However, the Oberon System breaks this promise by allowing the validity to disappear behind the procvar's back.
You can also look at it from the point of view of the client/ server. At the point of assignment procvar := Mod.Proc, the client gets the asset and the server Mod makes the promise. When the server is unloaded, it breaks the promise without telling the client "I am gone, do not rely on my assets". The client thinks it still has a good asset, tries to use it, and it gets duped. The client is perfectly innocent. It was the server who broke the contract.
I am proposing that we make a clear case. The procedure variable are not a variable, because it is characterized by a pair of attributes (value, validity). Both attributes need be handled, because they are independent from each other. The regular variable has only one attribute, namely a value. The other attribute is trivially fulfilled and it thus does not require any handling.
Based on this consideration, a physicist would conclude: saying that a procedure variable is the same as a regular variable is akin to saying that a vector is the same as a scalar. No, it is not. A vector is distinct from a scalar because the number of dimensions is different. Here we have a similar case: two independent attributes versus one.
If this sounds scholarly, then I would say it is not. We need to understand the fundamental reason why there is a potential for system corruption and get rid of it in some elegant but effective way. IMO it can only help if we make a clearcut distinction between two classes of objects which are fundamentally different from each other.
W.
________________________________________ From: Oberon [oberon-bounces@lists.inf.ethz.ch] on behalf of Douglas G. Danforth [danforth@greenwoodfarm.com] Sent: Monday, October 3, 2016 4:55 PM To: ETH Oberon and related systems Subject: Re: [Oberon] Oberon for a C++ user.
W, I like it. The reference counting can be quite complex. IF module A is imported by B, C, ... and A exports a procedure variable (or routine that sets a procedure variable) then only if B, C, ... actually assign a value to the procedure variable of A does the problem arise. -Doug
On 10/3/2016 12:07 AM, Skulski, Wojciech wrote:
Andreas:
the problem IMHO is in the operator :=, which is overloaded. The procedure variables are not a variables in the usual sense. The operator := should not apply to them.
A regular variable has a value. The value is passive. It gets acted upon by a client. The variable is not doing anything on its own. Somebody is doing something with the variable (somebody is using its value for some goal).
IF var1 = var2 THEN... (*passive role of the variables*)
The procedure variable has a value (the address to jump to), and also a behavior. The value is passive, but the behavior is active. The value is retained after unloading the implementation, but the behavior is damaged. This is the problem.
The value (the address) is not that important. Yes, you can use it in comparisons:
IF proc1 = proc2 THEN... (*passive role of the procedure variables*)
Note that after unloading the implementations of the proc1 and proc2, you can still make the comparison. Both proc1 and proc2 are perfectly valid in the passive sense. Their code is gone, but their passive values (addresses) are perfectly OK.
But this is not why procedure variables are important. We want their behaviors. And this gets damaged upon unloading.
In order to cure the problem, the operator :=should not be permitted with procedure variables. There needs to be another, separate operator which will assign procedure variables and also take care of the reference counting.
ASSIGN (procvar, Mod.Proc)
I think that separating both operators := and ASSIGN will make sense, because regular variables are not posing the problem which procedure variables are posing.
The counterpart of ASSIGN would be RETRACT (or some better name). Since you re not allowed to use := with procedure variables, you could not write
procvar := NIL; (*not allowed*)
You would have to say
RETRACT (procvar); (*takes care of the references*)
I hope this proposition makes sense.
W.
----------------------------
Chris, Joerg:
that is exactly the problem: assigning a procedure of a *client* module to a procedure variable of an *imported* module, and then unloading the client module - leaving the procedure variable in the imported module dangling.
Assigning a procedure of a client module to a variable of an imported "base" module is in fact the standard way the Oberon system itself employs procedure variables to implement the viewer system in an object-oriented style.
For example, module TextFrames (the client) installs a handler (procedure TextFrames.Handle) in the field F.handle of text frame F (see TextFrames.NewText and TextFrames.Open for the details), where the record field F.handle is declared as a procedure variable in the "base" module Viewers (see Viewers.ViewerDesc), which also manages the viewer data structure (see procedure Viewers.Open).
Except that of course in the case of the Oberon system, the "client" module TextFrames *never* gets unloaded, so this is not a problem there. Thus, the burden is on the user to make sure that such modules never get unloaded.
It is of course possible to implement a "procedure variable" counter - in analogy to the "module counter" employed in Oberon: each time an assignment to a procedure variable is made, the counter gets increased. But that would require changing the code generator compiler for assignments, and the module loader (Modules.Load) and unloader (Modules.Free). But I would refrain from introducing such complexity to the compiler and runtime system.
Andreas
Chris Burrows chris at cfbsoftware.com <mailto:oberon%40lists.inf.ethz.ch?Subject=Re:%20Re%3A%20%5BOberon%5D%20Oberon%20for%20a%20C%2B%2B%20user.&In-Reply-To=%3C000a01d21ba8%24b97dc9d0%242c795d70%24%40cfbsoftware.com%3E>
________________________________
Joerg,
OK - you may well be onto something here but it is incomplete as it is. Something has to import Square so that the module body is executed, or you need an exported procedure in Square that is called from the OS. Note: there are a couple of semicolons missing and Init must be exported. Chris. -----Original Message----- From: Jörg Straube [mailto:joerg.straube at iaeth.ch<https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=UHr5AP3cXC2ZAXacD2dYANUhpxDnR6gWosSpUd2oaRw&s=TWgxIZxS5z7mpZJDUj92ydjRoic3rLYdp0iN4Zb0ngQ&e=>] Sent: Saturday, 1 October 2016 2:22 PM To: chris at cfbsoftware.com<https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwMFaQ&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=UHr5AP3cXC2ZAXacD2dYANUhpxDnR6gWosSpUd2oaRw&s=TWgxIZxS5z7mpZJDUj92ydjRoic3rLYdp0iN4Zb0ngQ&e=>; ETH Oberon and related systems Subject: Re: [Oberon] Oberon for a C++ user.
Chris
Here a possble example that might cause issues:
MODULE Figure; TYPE DrawProc = PROCEDURE; Figure = POINTER TO FigureDesc; FigureDesc = RECORD next: Figure; draw: DrawProc END; VAR list: Figure; PROCEDURE Init(d: DrawProc) VAR f: Figure; BEGIN NEW(f); f.next := list; list := f; f.draw := d END Init; BEGIN list:= NIL END Figure.
MODULE Square; IMPORT Figure; PROCEDURE DrawSquare; BEGIN (* do what ever you need to do to draw a square *) END DrawSquare BEGIN Figure.Init(DrawSquare) END Square.
Now you could run Square and unload Square. The list in Figure has a reference to unloaded code.
J rg
-- Oberon@lists.inf.ethz.ch mailing list for ETH Oberon and related systems https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwIF-g&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=2hCB-kYQ6nAwqgBQyA2wJiz_nEJx2nZM1lRobiKp7aA&s=R4cllNBWvXw007qPPb3yL6ptNSRN7MT7SK5adx7jGag&e=
-- Oberon@lists.inf.ethz.ch mailing list for ETH Oberon and related systems https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.inf.ethz.ch_mailman_listinfo_oberon&d=CwIF-g&c=kbmfwr1Yojg42sGEpaQh5ofMHBeTl9EI2eaqQZhHbOU&r=uUiA_zLpwaGJIlq-_BM9w1wVOuyqPwHi3XzJRa-ybV0&m=2hCB-kYQ6nAwqgBQyA2wJiz_nEJx2nZM1lRobiKp7aA&s=R4cllNBWvXw007qPPb3yL6ptNSRN7MT7SK5adx7jGag&e= -- Oberon@lists.inf.ethz.ch mailing list for ETH Oberon and related systems https://lists.inf.ethz.ch/mailman/listinfo/oberon
Many Thanks Wojtek.
The procedure variable has a value (the address to jump to), and also a behavior. The value is passive, but the behavior is active. The value is retained after unloading the implementation, but the behavior is damaged. This is the problem.
This gave me a new way of looking at procedure variables. Indeed this kind of thoughts are really important and most welcome. With thanks and best regards, Yours sincerely, Srinivas Nayak Home: http://www.mathmeth.com/sn/ Blog: http://srinivas-nayak.blogspot.in/
that is exactly the problem: assigning a procedure of a *client* module to a procedure variable of an *imported* module, and then unloading the client module - leaving the procedure variable in the imported module dangling.
Assigning a procedure of a client module to a variable of an imported "base" module is in fact the standard way the Oberon system itself employs procedure variables to implement the viewer system in an object-oriented style.
For example, module TextFrames (the client) installs a handler (procedure TextFrames.Handle) in the field F.handle of text frame F (see TextFrames.NewText and TextFrames.Open for the details), where the record field F.handle is declared as a procedure variable in the "base" module Viewers (see Viewers.ViewerDesc), which also manages the viewer data structure (see procedure Viewers.Open).
Except that of course in the case of the Oberon system, the "client" module TextFrames *never* gets unloaded, so this is not a problem there. Thus, the burden is on the user to make sure that such modules never get unloaded.
It is of course possible to implement a "procedure variable" counter - in analogy to the "module counter" employed in Oberon: each time an assignment to a procedure variable is made, the counter gets increased. But that would require changing the code generator compiler for assignments, and the module loader (Modules.Load) and unloader (Modules.Free). But I would refrain from introducing such complexity to the compiler and runtime system.
Andreas
Many thanks Andreas. Now I see a possibility of the problem. With thanks and best regards, Yours sincerely, Srinivas Nayak Home: http://www.mathmeth.com/sn/ Blog: http://srinivas-nayak.blogspot.in/
participants (16)
-
Alexander Ilin -
Andreas Pirklbauer -
chris -
Chris Burrows -
Diego Sardina -
Douglas G Danforth -
Douglas G. Danforth -
John R. Strohm -
Jörg -
Lars -
Paul Reed -
Peter Froehlich -
Peter Fröhlich -
Richard Hable -
Skulski, Wojciech -
Srinivas Nayak