* [Caml-list] Freeing dynamically loaded code @ 2003-12-12 19:04 Nuutti Kotivuori 2003-12-12 19:36 ` Alain.Frisch 2003-12-15 9:35 ` Basile Starynkevitch 0 siblings, 2 replies; 16+ messages in thread From: Nuutti Kotivuori @ 2003-12-12 19:04 UTC (permalink / raw) To: caml-list So, I went down the dirty depths of Dynlink and friends, and bytecomp and byterun, and all that - and I think I have a rather good image of the problem. I would wish for Dynlinkable code to be garbage collected. ========================================================== When a file is loaded with Dynlink.loadfile, most of what's read and executed and handled is stored in the normal OCaml heap - and hence garbage collected properly when there are no references anymore. But, the actual executable code isn't. Basically what is done to that is that the buffer is allocated by Meta.static_alloc, the code is read there, and then Meta.reify_bytecode is invoked on it - which merely wraps the pointer in a Closure_tag block it allocates. So, this means that the code is allocated outside Ocaml heap - and as such ignored by the garbage collector. Huge thanks to Olivier Andrieu for helping me out in IRC sorting out this stuff. What could be done to fix the situation then? ============================================= I can think of two solutions, neither sound too easy right now though. The first choice would be to add the statically alloced block to a "special" list of code blocks - and when the garbage collector encounters references to blocks outside Ocaml heap, it would handle these blocks specially and manually free them when there are no references - a bit like custom blocks, but having it all implicit, since there's no block header or anything. The problem is that closure code pointers into the statically allocated block most likely will not point to the beginning of it - but instead somewhere inside the allocated block. Which would mean that the garbage collector would have to check if the pointer is *inside* a certain range, or somehow add all possible pointer places from inside the block with annotations of the start of the block to the list. And both sound rather horrible in many ways. The second choice would be to allocate all of the code actually inside the Ocaml heap somehow, and let the garbage collector handle it like everything else. But right now I don't have too good of an idea how this should be done. And it also has the same problem - what to do with pointers that fall inside the code block, and not at the start of it where the headers are. I see there's some code for Infix tags, which look like they would be tags inside blocks that tell where the actual block header lies - but this was starting to go quite a bit over my comprehension. Ofcourse there's a bunch of "yet another choices" - like making a special area for loaded code, and special-case that once again - and so on. === So, I'd very much like to hear any comments on the matter - I am still very new to Ocaml, so I'm way over my head here. - Naked ------------------- To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/ Beginner's list: http://groups.yahoo.com/group/ocaml_beginners ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [Caml-list] Freeing dynamically loaded code 2003-12-12 19:04 [Caml-list] Freeing dynamically loaded code Nuutti Kotivuori @ 2003-12-12 19:36 ` Alain.Frisch 2003-12-12 20:05 ` Nuutti Kotivuori 2003-12-15 9:35 ` Basile Starynkevitch 1 sibling, 1 reply; 16+ messages in thread From: Alain.Frisch @ 2003-12-12 19:36 UTC (permalink / raw) To: Nuutti Kotivuori; +Cc: Caml list On Fri, 12 Dec 2003, Nuutti Kotivuori wrote: > I would wish for Dynlinkable code to be garbage collected. I submitted a similar feature wish to the caml team a while ago (in french: http://caml.inria.fr/bin/caml-bugs/wish%20not%20granted?id=445 ) The request has been transferred to the "wish not granted" section of the BTS with the comment "Too hard to implement. No can do.", so I'm afraid there is no hope to see unloading/garbage collection of dynlink'ed code in the near future. -- Alain ------------------- To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/ Beginner's list: http://groups.yahoo.com/group/ocaml_beginners ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [Caml-list] Freeing dynamically loaded code 2003-12-12 19:36 ` Alain.Frisch @ 2003-12-12 20:05 ` Nuutti Kotivuori 2003-12-12 21:26 ` Alain.Frisch 0 siblings, 1 reply; 16+ messages in thread From: Nuutti Kotivuori @ 2003-12-12 20:05 UTC (permalink / raw) To: Alain.Frisch; +Cc: Caml list Alain Frisch wrote: > On Fri, 12 Dec 2003, Nuutti Kotivuori wrote: > >> I would wish for Dynlinkable code to be garbage collected. > > I submitted a similar feature wish to the caml team a while ago > (in french: > http://caml.inria.fr/bin/caml-bugs/wish%20not%20granted?id=445) So it seems :-) My french knowledge is close to nil, but with the advances of babelfish and creative reading, I think I was able to catch most of that. > The request has been transferred to the "wish not granted" section > of the BTS with the comment "Too hard to implement. No can do.", so > I'm afraid there is no hope to see unloading/garbage collection of > dynlink'ed code in the near future. Well, like Xavier Leroy said at the end of the mail - *he* probably isn't doing it. That doesn't mean there wouldn't be someone else crazy enough to try :-) And atleast I'm not dropping the investigation just yet. But expect no commitment ofcourse, I might grow disinterested in the matter any moment and just leave it. Or I might just settle for a workaround of manually freeing the blocks after ensuring there are no external references. -- Naked ------------------- To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/ Beginner's list: http://groups.yahoo.com/group/ocaml_beginners ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [Caml-list] Freeing dynamically loaded code 2003-12-12 20:05 ` Nuutti Kotivuori @ 2003-12-12 21:26 ` Alain.Frisch 2003-12-12 21:54 ` Nuutti Kotivuori ` (2 more replies) 0 siblings, 3 replies; 16+ messages in thread From: Alain.Frisch @ 2003-12-12 21:26 UTC (permalink / raw) To: Nuutti Kotivuori; +Cc: Caml list On Fri, 12 Dec 2003, Nuutti Kotivuori wrote: > Well, like Xavier Leroy said at the end of the mail - *he* probably > isn't doing it. That doesn't mean there wouldn't be someone else crazy > enough to try :-) > > And atleast I'm not dropping the investigation just yet. Thinking again about the technical challenge (put the dynlink'ed code under GC control), I think the following approach is worth a try. The question is how to let the GC know that the code block cannot be freed as long as there is some accessible closure pointing into this block. We can think of using something similar to InfixTag, but this is a heavy solution as it requires to modify the GC in many places. I propose to avoid creating "bad" closures that points to the loaded code. We simulate a bad closure by a closure to a generic stub. In addition to the normal environment, we put two additional slots into this closure: a pointer to the code block and an offset. This closure is fully under the control of the GC. The stub is made of a single instruction (or maybe two, see next paragraph), say a new bytecode CALL_DYN, which computes the real destination from the closure and jumps to it. We also need a new CLOSURE_DYN bytecode that behaves as CLOSURE but create a faked closure instead of a bad one. Dynlink changes CLOSURE to CLOSURE_DYN when it loads a module. We need to take care of GRAB, RESTART and CLOSUREREC similarly. We also need to make sure that the "active" code blocks (the ones which have an active stack frame) are accessible by the GC. We have to be careful since the faked closure may become inacessible even if it is currently running (because of an in-place modification). So CALL_DYN should keep its closure on the stack (which contains a pointer to the code block) and call the real function. The bytecode following CALL_DYN would just pop the closure after the function returns. These changes does not affect the GC at all, and are simple additions to the bytecode interpreter (interp.c). The cost of calling closures to dynlink'ed modules is increased, but we don't really care since: 1) this is bytecode, so anyway ... 2) non dynlink'ed code is not affected at all Any comment on this? Alain ------------------- To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/ Beginner's list: http://groups.yahoo.com/group/ocaml_beginners ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [Caml-list] Freeing dynamically loaded code 2003-12-12 21:26 ` Alain.Frisch @ 2003-12-12 21:54 ` Nuutti Kotivuori 2003-12-13 7:25 ` Nuutti Kotivuori 2003-12-13 2:04 ` skaller 2003-12-15 3:11 ` Nuutti Kotivuori 2 siblings, 1 reply; 16+ messages in thread From: Nuutti Kotivuori @ 2003-12-12 21:54 UTC (permalink / raw) To: Alain.Frisch; +Cc: Caml list Alain Frisch wrote: > I propose to avoid creating "bad" closures that points to the loaded > code. We simulate a bad closure by a closure to a generic stub. In > addition to the normal environment, we put two additional slots into > this closure: a pointer to the code block and an offset. This > closure is fully under the control of the GC. The stub is made of a > single instruction (or maybe two, see next paragraph), say a new > bytecode CALL_DYN, which computes the real destination from the > closure and jumps to it. We also need a new CLOSURE_DYN bytecode > that behaves as CLOSURE but create a faked closure instead of a bad > one. Dynlink changes CLOSURE to CLOSURE_DYN when it loads a > module. We need to take care of GRAB, RESTART and CLOSUREREC > similarly. Hmm. Quite ingenious. I read through the CLOSURE and CLOSUREREC calls and considered modifying those when linking, but never came up with a proper solution. That sounds like it. > We also need to make sure that the "active" code blocks (the ones > which have an active stack frame) are accessible by the GC. We have > to be careful since the faked closure may become inacessible even if > it is currently running (because of an in-place modification). So > CALL_DYN should keep its closure on the stack (which contains a > pointer to the code block) and call the real function. The bytecode > following CALL_DYN would just pop the closure after the function > returns. Right. If we are unlucky, there might be some other places around the code where code is expected to be around without registering references to it. > These changes does not affect the GC at all, and are simple > additions to the bytecode interpreter (interp.c). Yeah, sounds good. > The cost of calling closures to dynlink'ed modules is increased, but > we don't really care since: > 1) this is bytecode, so anyway ... > 2) non dynlink'ed code is not affected at all Actually... I think we might care. If I didn't misunderstand something, this does not only change the calls *to* dynlink'ed modules, but for *every* call of a function inside that dynlink'ed module. And since function calls do not seem to be inlined in bytecode at all, this might seriously affect performance. But, this is mere speculation as I don't really know the function call overhead in bytecode, nor how much it would be increased by this. But if it is a problem - I'm not sure how to avoid it. Internal closure calls inside the code-file do not need this hackery, but separating those closures that are to be called from the outside doesn't seem trivial. The external closure pointers held will be either closures stored in the module interface posted with SETGLOBAL, or just closure pointers given as parameters to external closures - or is there still something else? I'm not sure of your needs for this - but for me it would be no problem to for example require special compilation flags for dynlinkable files, and require special syntax or something for declaring exported closures - as long as safety is preserved, that a file is actually incapable of giving out closures that would break in GC. -- Naked ------------------- To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/ Beginner's list: http://groups.yahoo.com/group/ocaml_beginners ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [Caml-list] Freeing dynamically loaded code 2003-12-12 21:54 ` Nuutti Kotivuori @ 2003-12-13 7:25 ` Nuutti Kotivuori 2003-12-13 8:15 ` Alain.Frisch 0 siblings, 1 reply; 16+ messages in thread From: Nuutti Kotivuori @ 2003-12-13 7:25 UTC (permalink / raw) To: Alain.Frisch; +Cc: Caml list Nuutti Kotivuori wrote: > Alain Frisch wrote: >> I propose to avoid creating "bad" closures that points to the >> loaded code. We simulate a bad closure by a closure to a generic >> stub. In addition to the normal environment, we put two additional >> slots into this closure: a pointer to the code block and an >> offset. This closure is fully under the control of the GC. The stub >> is made of a single instruction (or maybe two, see next paragraph), >> say a new bytecode CALL_DYN, which computes the real destination >> from the closure and jumps to it. We also need a new CLOSURE_DYN >> bytecode that behaves as CLOSURE but create a faked closure instead >> of a bad one. Dynlink changes CLOSURE to CLOSURE_DYN when it loads >> a module. We need to take care of GRAB, RESTART and CLOSUREREC >> similarly. > > Hmm. Quite ingenious. I read through the CLOSURE and CLOSUREREC > calls and considered modifying those when linking, but never came up > with a proper solution. That sounds like it. Ha! I got it! There was a problem I was pondering out with that suggestion. And it was basically that the functions from the dynamically linked module must appear as normal closures taking the appropriate number of arguments - and yet would have to be called throug CALL_DYN - which would mean either patching the code of *all* modules, or having an intermediate code block which does the call to CALL_DYN. And this didn't sound nice. But then I got the idea. If we are going to be mangling the bytecode anyway, why not mangle it a bit more? Let's make sure every closure generated from the library has an extra local variable in it's environment, pointing to the head of the block where that closure's code resides. This local variable need not even be used, as long as it doesn't break the other locals in the function. So then every closure will carry an extra local variable, and those are seen by the garbage collector, so the main codefile will exist as long as any of those closures do. The bytecode manipulation might not be entirely trivial though, but certainly seems easier than anything else we've talked about yet. What do you think? Have I got it all wrong? -- Naked ------------------- To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/ Beginner's list: http://groups.yahoo.com/group/ocaml_beginners ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [Caml-list] Freeing dynamically loaded code 2003-12-13 7:25 ` Nuutti Kotivuori @ 2003-12-13 8:15 ` Alain.Frisch 2003-12-13 20:57 ` Nuutti Kotivuori 2003-12-17 7:17 ` Jacques Garrigue 0 siblings, 2 replies; 16+ messages in thread From: Alain.Frisch @ 2003-12-13 8:15 UTC (permalink / raw) To: Nuutti Kotivuori; +Cc: Caml list On Sat, 13 Dec 2003, Nuutti Kotivuori wrote: > There was a problem I was pondering out with that suggestion. And it > was basically that the functions from the dynamically linked module > must appear as normal closures taking the appropriate number of > arguments - and yet would have to be called throug CALL_DYN - which > would mean either patching the code of *all* modules, or having an > intermediate code block which does the call to CALL_DYN. And this > didn't sound nice. I was proposing to have an intermediate code block (made of CALL_DYN). > Let's make sure every closure generated from the library has an extra > local variable in it's environment, pointing to the head of the block > where that closure's code resides. This local variable need not even > be used, as long as it doesn't break the other locals in the function. > > So then every closure will carry an extra local variable, and those > are seen by the garbage collector, so the main codefile will exist as > long as any of those closures do. I have been considering this idea too, but I think it does'nt work: sure, the GC won't free the code block, but it can still move it without updating the code pointer in the closure. Maybe this can be addressed by using a different GC tag to denote "movable" closures, so that the GC knows that the code pointer has to be translated by the same amount as the last slot (which is the pointer to the code block). -- Alain ------------------- To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/ Beginner's list: http://groups.yahoo.com/group/ocaml_beginners ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [Caml-list] Freeing dynamically loaded code 2003-12-13 8:15 ` Alain.Frisch @ 2003-12-13 20:57 ` Nuutti Kotivuori 2003-12-17 7:17 ` Jacques Garrigue 1 sibling, 0 replies; 16+ messages in thread From: Nuutti Kotivuori @ 2003-12-13 20:57 UTC (permalink / raw) To: Alain.Frisch; +Cc: Caml list Alain Frisch wrote: > I was proposing to have an intermediate code block (made of > CALL_DYN). Ah yes, I see it now. I didn't really understand your proposal fully in the beginning. And to be honest, I still am not sure of the actual implementation. I'll read it once more and probably ask some questions - I have yet another idea which might be what you mean as well, or then again it might not be - if you have the patience for me :-) > I have been considering this idea too, but I think it does'nt work: > sure, the GC won't free the code block, but it can still move it > without updating the code pointer in the closure. Maybe this can be > addressed by using a different GC tag to denote "movable" closures, > so that the GC knows that the code pointer has to be translated by > the same amount as the last slot (which is the pointer to the code > block). You are obviously right. I didn't even consider the GC moving the block. Well that's it for the cleanliness of that idea - if there needs to be a new block type or touching the GD, it's not really better than the other suggestions, except in a performance sense. -- Naked ------------------- To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/ Beginner's list: http://groups.yahoo.com/group/ocaml_beginners ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [Caml-list] Freeing dynamically loaded code 2003-12-13 8:15 ` Alain.Frisch 2003-12-13 20:57 ` Nuutti Kotivuori @ 2003-12-17 7:17 ` Jacques Garrigue 2003-12-17 23:48 ` Nuutti Kotivuori 1 sibling, 1 reply; 16+ messages in thread From: Jacques Garrigue @ 2003-12-17 7:17 UTC (permalink / raw) To: Alain.Frisch; +Cc: naked+caml, caml-list From: Alain.Frisch@ens.fr > > Let's make sure every closure generated from the library has an extra > > local variable in it's environment, pointing to the head of the block > > where that closure's code resides. This local variable need not even > > be used, as long as it doesn't break the other locals in the function. > > > > So then every closure will carry an extra local variable, and those > > are seen by the garbage collector, so the main codefile will exist as > > long as any of those closures do. > > I have been considering this idea too, but I think it does'nt work: sure, > the GC won't free the code block, but it can still move it without > updating the code pointer in the closure. Maybe this can be addressed > by using a different GC tag to denote "movable" closures, so that the > GC knows that the code pointer has to be translated by the same amount > as the last slot (which is the pointer to the code block). The variable doesn't need to point directly to the code block: it can point to a finalized stub. The code is still statically allocated, but when the stub is garbage collected the code is freed. So this should work. But I'm not candidate to try it :-) Jacques Garrigue ------------------- To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/ Beginner's list: http://groups.yahoo.com/group/ocaml_beginners ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [Caml-list] Freeing dynamically loaded code 2003-12-17 7:17 ` Jacques Garrigue @ 2003-12-17 23:48 ` Nuutti Kotivuori 0 siblings, 0 replies; 16+ messages in thread From: Nuutti Kotivuori @ 2003-12-17 23:48 UTC (permalink / raw) To: Jacques Garrigue; +Cc: Alain.Frisch, caml-list Jacques Garrigue wrote: > The variable doesn't need to point directly to the code block: it > can point to a finalized stub. The code is still statically > allocated, but when the stub is garbage collected the code is freed. > > So this should work. > But I'm not candidate to try it :-) Hmmmmmmmm... So then the pointers wouldn't ever get invalid since the block is statically allocated. And if every closure referenced the finalized block, when the last reference goes, the block would get freed. Then the block wouldn't be in the normal heap compaction at all - but it would be handled as malloc handles these things. And since it's not like we are mallocing for every local variable, but just for loading new code files, malloc will do a rather good job at it as well. Rather nice. Rather nice indeed. So then, while the code is running - I would expect the current env will always have to be traversed by the GC - and while executing subfunctions, the old env is probably on the stack, so that's handled as well. Hmm. If it all works, then the only thing to ensure is that every closure has the abstract block referenced. Grab and restart are irrelevant, since the closures they create reference the original closure - and the code pointers will stay valid. Closurerec can have the pointer in the local environment as well. That leaves us with objects, which would have to have the pointer in their member variables as well - and that shouldn't be a problem either. So yes, can't find a damn thing wrong with that proposal. Now the problem is just to get the variables there, either by mangling bytecode, or the interpreter. Thanks for the suggestion, I'll look into it! -- Naked ------------------- To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/ Beginner's list: http://groups.yahoo.com/group/ocaml_beginners ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [Caml-list] Freeing dynamically loaded code 2003-12-12 21:26 ` Alain.Frisch 2003-12-12 21:54 ` Nuutti Kotivuori @ 2003-12-13 2:04 ` skaller 2003-12-13 6:50 ` Nuutti Kotivuori 2003-12-15 3:11 ` Nuutti Kotivuori 2 siblings, 1 reply; 16+ messages in thread From: skaller @ 2003-12-13 2:04 UTC (permalink / raw) To: Alain.Frisch; +Cc: Nuutti Kotivuori, Caml list On Sat, 2003-12-13 at 08:26, Alain.Frisch@ens.fr wrote: > On Fri, 12 Dec 2003, Nuutti Kotivuori wrote: > > > Well, like Xavier Leroy said at the end of the mail - *he* probably > > isn't doing it. That doesn't mean there wouldn't be someone else crazy > > enough to try :-) > > > > And atleast I'm not dropping the investigation just yet. > > Thinking again about the technical challenge (put the dynlink'ed code > under GC control), I think the following approach is worth a try. > > The question is how to let the GC know that the code block cannot be freed > as long as there is some accessible closure pointing into this block. That is not enough. It is not necessary for some code to be active in order that it may later *become* active as a result of a subroutine call or jump. It would be necessary to find all code addresses and which blocks they point to, and build a dependency graph, and add the roots of that graph to the gc memory graph as well. Otherwise, after you say #load xxxx the collector would immediately free it :-) ------------------- To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/ Beginner's list: http://groups.yahoo.com/group/ocaml_beginners ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [Caml-list] Freeing dynamically loaded code 2003-12-13 2:04 ` skaller @ 2003-12-13 6:50 ` Nuutti Kotivuori 0 siblings, 0 replies; 16+ messages in thread From: Nuutti Kotivuori @ 2003-12-13 6:50 UTC (permalink / raw) To: skaller; +Cc: Alain.Frisch, Caml list skaller@ozemail.com.au wrote: > That is not enough. It is not necessary for some code to be > active in order that it may later *become* active as > a result of a subroutine call or jump. It would be necessary > to find all code addresses and which blocks they point to, > and build a dependency graph, and add the roots of that graph > to the gc memory graph as well. > > Otherwise, after you say > > #load xxxx > > the collector would immediately free it :-) Actually no. The way to access code that is not "active" as you say, is through global variables. Eg. if you do #load "test.cmo" then there's an entry Test in the global table that holds pointers for all functions defined in that file. And the global table is inside the ocaml heap, so those all will be referenced. The only way for that to become unloaded is that the global Test is replaced or removed by something - either by some unload module or loading a newer source on top of it - and then no references point to Test. And if no references point to the file, it really cannot be accessed anymore by any means, and hence can be removed. Ofcourse, right now there might be also other external references to the code in the heap, I am not sure - but those would have to be either removed or made Weak somehow. But that is to be solved after this immediate problem is resolved. -- Naked ------------------- To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/ Beginner's list: http://groups.yahoo.com/group/ocaml_beginners ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [Caml-list] Freeing dynamically loaded code 2003-12-12 21:26 ` Alain.Frisch 2003-12-12 21:54 ` Nuutti Kotivuori 2003-12-13 2:04 ` skaller @ 2003-12-15 3:11 ` Nuutti Kotivuori 2003-12-17 23:16 ` Nuutti Kotivuori 2 siblings, 1 reply; 16+ messages in thread From: Nuutti Kotivuori @ 2003-12-15 3:11 UTC (permalink / raw) To: Alain.Frisch; +Cc: Caml list Alain Frisch wrote: > I propose to avoid creating "bad" closures that points to the loaded > code. We simulate a bad closure by a closure to a generic stub. In > addition to the normal environment, we put two additional slots into > this closure: a pointer to the code block and an offset. This > closure is fully under the control of the GC. The stub is made of a > single instruction (or maybe two, see next paragraph), say a new > bytecode CALL_DYN, which computes the real destination from the > closure and jumps to it. We also need a new CLOSURE_DYN bytecode > that behaves as CLOSURE but create a faked closure instead of a bad > one. Dynlink changes CLOSURE to CLOSURE_DYN when it loads a > module. We need to take care of GRAB, RESTART and CLOSUREREC > similarly. Okay, now I finally think I know what you mean. I'll try to spell it out in a verbose manner here. So, if a normal closure would be something like this: +-----+--------+-----------------+ static alloc | ... | <code> | ... | +-----+--------+-----------------+ ^ ,---' | +-------------+----------+----------+----------+ | Closure_tag | Code_val | Field(1) | Field(2) | +-------------+----------+----------+----------+ This would be transformed to: +-------------+-----+--------+-----------------+ +--------------+ | String_tag | ... | <code> | ... | | CALLDYN... | +-------------+-----+--------+-----------------+ +--------------+ ^`-------+-------' ^ | | | | `-----------------------------------------|-----. | ,------------------------------------' | `-------------|-------------------------------. <offset> | | | +-------------+----------+----------+----------+----------+----------+ | Closure_tag | Code_val | Field(1) | Field(2) | Field(3) | Field(4) | +-------------+----------+----------+----------+----------+----------+ And this CALLDYN instruction would do something like: pc = Field(env, Wosize_val(env) - 2) + Field(env, Wosize_val(env) - 1); /* env already points correctly */ goto check_stacks; Am I on the right track? Now I have no idea what GRAB and RESTART do, and what would be need to fix them. They look scary. On CLOSUREREC I have an idea what it's supposed to do, but I have no idea how the Infix headers really work there - and what we'd need to do to fix that. Now, to get the file to produce these kind of wacky closures, we would need to do a few things. First of all we'd obviously need the CALLDYN parts allocated somewhere statically. Then, before reifying the bytecode, we'd wish to substitute all calls to CLOSURE with CLOSUREDYN, right? And GRABDYN, RESTARTDYN, CLOSURERECDYN if necessary. This would mean that we would have to go through the bytecode, pretty much like dumpobj.ml does - so we know how many parameters each instruction takes so we can find the correct instructions to modify. If we don't have to touch the parameters on anything, we don't need to recalculate any jump offsets, which is good. One thing which I am pondering though when thinking about CLOSUREDYN calls. All the information it has is the offset of the code to be referenced from the current position. How do we pass the knowledge where the start of the block is where this code resides, so it can place that pointer in the env and calculate the offset there as well? And it can't be a global variable temporarily or anything since every CLOSUREDYN instruction needs to create references to the codeblock that CLOSUREDYN instruction resides in. Well, I hope this can be solved with some hackery. Now the open questions that I am thinking about are that is anything bothered by having two more variables in it's env than it is expecting to have? Does anyone look at the size of the env block? Can we implement all of CLOSUREDYN, CLOSURERECDYN, GRABDYN and RESTARTDYN so that they produce compatible results? What about OFFSETCLOSURE and friends, did they do anything evil? Though sometimes I wonder how nice it could be if *all* code would have this calling convention. No more hacking with these special instructions, APPLY and friends would already do it. Though the cost would be one more word in size for closures everywhere. -- Naked ------------------- To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/ Beginner's list: http://groups.yahoo.com/group/ocaml_beginners ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [Caml-list] Freeing dynamically loaded code 2003-12-15 3:11 ` Nuutti Kotivuori @ 2003-12-17 23:16 ` Nuutti Kotivuori 0 siblings, 0 replies; 16+ messages in thread From: Nuutti Kotivuori @ 2003-12-17 23:16 UTC (permalink / raw) To: Alain.Frisch; +Cc: Caml list Nuutti Kotivuori wrote: [...] > Now I have no idea what GRAB and RESTART do, and what would be need > to fix them. They look scary. On CLOSUREREC I have an idea what it's > supposed to do, but I have no idea how the Infix headers really work > there - and what we'd need to do to fix that. Okay, I think I now know how GRAB and RESTART work, and they should be doable. And I have a better idea how CLOSUREREC works as well - but that seems to be nasty. Considering that it has several code pointers, and none of those have a local environment but the whole closure only has one - every one of those code pointers would have to point to something like CALLRECDYN or something, and the environment of the CLOSUREREC should have offsets for every subfunction and then the code block address. CALLRECDYN would then have to find the correct offset for the correct closure in the CLOSUREREC block, and call that. Eww. But possibly doable. Another thing which struck me. What about objects? All they have is a virtual table as the first pointer, which carries loads of code pointers that aren't embedded in a closure. And if we can't touch the callers, calling any of those would have to still be compatible, so where on earth could the actual offsets be stored then? Mucho problems. [...] > How do we pass the knowledge where the start of the block is where > this code resides, so it can place that pointer in the env and > calculate the offset there as well? One very hackish way to implement this would be to add a semi-global variable to the interpreter, that has this pointer - then the calldyn instruction could update it for the current block always, so any closures created inside a calldyn'd instruction would have the right pointer. Or something. [...] This is starting to get muddier and muddier - and not looking too good. But I will continue thinking about the problems for now - if something comes up that is really impossible to do, then we can just drop this approach since it isn't going to work and move on to something else. -- Naked ------------------- To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/ Beginner's list: http://groups.yahoo.com/group/ocaml_beginners ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [Caml-list] Freeing dynamically loaded code 2003-12-12 19:04 [Caml-list] Freeing dynamically loaded code Nuutti Kotivuori 2003-12-12 19:36 ` Alain.Frisch @ 2003-12-15 9:35 ` Basile Starynkevitch 2003-12-15 11:34 ` Nuutti Kotivuori 1 sibling, 1 reply; 16+ messages in thread From: Basile Starynkevitch @ 2003-12-15 9:35 UTC (permalink / raw) To: caml-list On Fri, Dec 12, 2003 at 09:04:24PM +0200, Nuutti Kotivuori wrote: > So, I went down the dirty depths of Dynlink and friends, and bytecomp > and byterun, and all that - and I think I have a rather good image of > the problem. > > I would wish for Dynlinkable code to be garbage collected. > ========================================================== > > When a file is loaded with Dynlink.loadfile, most of what's read and > executed and handled is stored in the normal OCaml heap - and hence > garbage collected properly when there are no references anymore. > > But, the actual executable code isn't. Basically what is done to that > is that the buffer is allocated by Meta.static_alloc, the code is read > there, and then Meta.reify_bytecode is invoked on it - which merely > wraps the pointer in a Closure_tag block it allocates. > Please note that for code to be GC-ed, the garbage collector has to handle specially every closure, looking into the code pointed by the GC, etc. This might slow down the GC significantly (given that the pointer to code is usually in the body of the code chunk, not to its beginning). Also both the bytecode and the native compiler should share a common behavior on this. Besides, most of the runtime (including the bytecode interpreter) rely upon the fact that code is not moved (ie remains at a fixed address). If it was moved, the GC would have to update the code pointer referenced in closures which would be difficult. (Also, for moving machine code in ocamlopt, you'll have to flush -in a system dependent way- the instruction cache, which is expensive). Not moving code means that you cannot copy or compact it, as the GC does for most values. I understand your wish (and in an ideal world I do share it) but I also think that making code garbage collected would impact a big part of the ocaml runtime system (and would, for example, probably make ocaml's GC slower, even for most of the applications which don't load any code). FWIW, some old versions of SML/NJ (maybe 0.93...) did actually garbage-collect and move machine code, so this is in principle doable, but it is difficult and involve a serie of tradeoffs (different from those of Ocaml3). Also, most of Common Lisp implementations probably collect code (even in machine code form). So I would believe that to make code GC-able would require a big lot of work, and I understand that it is not currently a top priority of the Cristal team. Making code garbageable is a big design decision which should be done when starting to implement a language. It cannot be made afterwards without lot of pains. -- Basile STARYNKEVITCH -- basile dot starynkevitch at inria dot fr Project cristal.inria.fr - INRIA Rocquencourt http://cristal.inria.fr/~starynke --- all opinions are only mine ------------------- To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/ Beginner's list: http://groups.yahoo.com/group/ocaml_beginners ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [Caml-list] Freeing dynamically loaded code 2003-12-15 9:35 ` Basile Starynkevitch @ 2003-12-15 11:34 ` Nuutti Kotivuori 0 siblings, 0 replies; 16+ messages in thread From: Nuutti Kotivuori @ 2003-12-15 11:34 UTC (permalink / raw) To: Basile Starynkevitch; +Cc: caml-list Basile Starynkevitch wrote: > On Fri, Dec 12, 2003 at 09:04:24PM +0200, Nuutti Kotivuori wrote: >> So, I went down the dirty depths of Dynlink and friends, and >> bytecomp and byterun, and all that - and I think I have a rather >> good image of the problem. >> >> I would wish for Dynlinkable code to be garbage collected. >> ========================================================== >> >> When a file is loaded with Dynlink.loadfile, most of what's read >> and executed and handled is stored in the normal OCaml heap - and >> hence garbage collected properly when there are no references >> anymore. >> >> But, the actual executable code isn't. Basically what is done to >> that is that the buffer is allocated by Meta.static_alloc, the code >> is read there, and then Meta.reify_bytecode is invoked on it - >> which merely wraps the pointer in a Closure_tag block it allocates. Thanks for your input on this matter. > Please note that for code to be GC-ed, the garbage collector has to > handle specially every closure, looking into the code pointed by the > GC, etc. This might slow down the GC significantly (given that the > pointer to code is usually in the body of the code chunk, not to its > beginning). Not necessarily at all - if code pointers are handled as pointers to the start of a block and an offset, the GC can work exactly as it did. This ofcourse introduces runtime overhead in calling closures. If the GC is augmented to handle blocks of type Closure_tag specially, but still so that they contain a pointer to the start of the memoryblock as well, then there will be no runtime overhead in calling closures, minimal overhead in garbage collection (update of two pointers instead of one when moving a block), and only an increased storage of one word for each closure. > Also both the bytecode and the native compiler should share a common > behavior on this. Um, I believe this concerns only interpreting bytecode. We are talking about dynamic linking - loading code at runtime. If the code is statically loaded - there hardly is any need to garbage collect it. It could result in small gains in code size for code parts that are executed only once and then never again, but that's minimal. Same thing for the native compiler - there's no need to free actual code pages from the executable if we are running native code. And dynamically linking nativecode - that is, taking in a .o file to a running executable, resolving it's unresolved symbol references at runtime, and being able to access it - doesn't seem feasible at all. And dynamically linking native code to a bytecode executable seems even less feasible. Dynamically loading bytecode from an executable compiled natively with ocamlopt doesn't seem to work directly, but Asmdynlink (part of CDK) seems to do it with some hacking, so it shouldn't be impossible - but this should not break it either. So, for this thing we are *only* talking about dynamically linking bytecode - and being able to free the code when it is no longer required. > Besides, most of the runtime (including the bytecode interpreter) > rely upon the fact that code is not moved (ie remains at a fixed > address). If it was moved, the GC would have to update the code > pointer referenced in closures which would be difficult. (Also, for > moving machine code in ocamlopt, you'll have to flush -in a system > dependent way- the instruction cache, which is expensive). Not > moving code means that you cannot copy or compact it, as the GC does > for most values. GC would only have to update the Code pointer in closures if that's a plain pointer to the actual code address to jump to. If for example _every_ closure would have a pointer to the start of an allocated block, and an offset from it - the GC would not have to be modified at all. The runtime cost in this case example would be two loads and an add compared to one load. And as already mentioned, moving machine code is not meaningful as the code pages are not linked or allocated by us, but by the OS (or libc/ELF runtime). > I understand your wish (and in an ideal world I do share it) but I > also think that making code garbage collected would impact a big > part of the ocaml runtime system (and would, for example, probably > make ocaml's GC slower, even for most of the applications which > don't load any code). Doing it only for dynamically linked bytecode, and accepting a runtime performance cost for them compared to others, I believe it is doable without impacting other performance at all. And the changes could be rather well localized. > FWIW, some old versions of SML/NJ (maybe 0.93...) did actually > garbage-collect and move machine code, so this is in principle > doable, but it is difficult and involve a serie of tradeoffs > (different from those of Ocaml3). Also, most of Common Lisp > implementations probably collect code (even in machine code form). I believe Common Lisp is a bit different in this respect, because it actually generates code at runtime - something that I don't think OCaml does at all. > So I would believe that to make code GC-able would require a big lot > of work, and I understand that it is not currently a top priority of > the Cristal team. I understand. However, even if the Cristal team is not interested in spending effort towards making this happen, for the moment, I am. Alain Frisch and me have been discussing a possibility of implementing this with a rather minimal change to the runtime system, without touching garbage collection at all - or more like Alain has been describing his idea, and I have been trying to understand. If we can figure out the final implementation plan on this - I will probably start implementing this rather soon. > Making code garbageable is a big design decision which should be > done when starting to implement a language. It cannot be made > afterwards without lot of pains. Yes, achieving true garbage collection on all parts of the code without incurring a runtime cost nor making the garbage collector exceedingly inefficient is indeed a hard thing to get proper. However, adding limited garbage collection for some parts of the code might still be doable. Best wishes, -- Naked ------------------- To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/ Beginner's list: http://groups.yahoo.com/group/ocaml_beginners ^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2003-12-17 23:48 UTC | newest] Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2003-12-12 19:04 [Caml-list] Freeing dynamically loaded code Nuutti Kotivuori 2003-12-12 19:36 ` Alain.Frisch 2003-12-12 20:05 ` Nuutti Kotivuori 2003-12-12 21:26 ` Alain.Frisch 2003-12-12 21:54 ` Nuutti Kotivuori 2003-12-13 7:25 ` Nuutti Kotivuori 2003-12-13 8:15 ` Alain.Frisch 2003-12-13 20:57 ` Nuutti Kotivuori 2003-12-17 7:17 ` Jacques Garrigue 2003-12-17 23:48 ` Nuutti Kotivuori 2003-12-13 2:04 ` skaller 2003-12-13 6:50 ` Nuutti Kotivuori 2003-12-15 3:11 ` Nuutti Kotivuori 2003-12-17 23:16 ` Nuutti Kotivuori 2003-12-15 9:35 ` Basile Starynkevitch 2003-12-15 11:34 ` Nuutti Kotivuori
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).