Where can we obtain the code written for a generated method?

When a class containing a method generator is compiled, the code that implements the method at runtime is written according to the instructions in the method definition. 

Suppose I want to obtain the generated code in order to parse and analyze it (e.g. in our Yuzinji tool). The method generator code is available in the Implementation stream of %Dictionary.MethodDefinition, but I want the code that this code generates. I tried looking in %Dictionary.CompiledMethod but was disappointed to find that its Implementation stream also contains the generator code, not the generated code. See below for an example using a method of a class that exists in the SAMPLES namespace:

SAMPLES>s m1=##class(%Dictionary.MethodDefinition).%OpenId("Sample.Installer||setup")
 
SAMPLES>d m1.Implementation.OutputToDevice()
        #; Let our XGL document generate code for this method.
        Quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "MyApp")                                                                              
SAMPLES>
 
SAMPLES>s m2=##class(%Dictionary.CompiledMethod).%OpenId("Sample.Installer||setup")
 
SAMPLES>d m2.Implementation.OutputToDevice()
        #; Let our XGL document generate code for this method.
        Quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "MyApp")
 
SAMPLES>w $zv
Cache for Windows (x86-64) 2017.2 (Build 744U) Fri Sep 29 2017 10:58:27 EDT
SAMPLES>

Does anyone know of a way to get the generated code, other than forcing the compilation to retain INT code and then digging into the INT routine for the relevant procedure?

  • 0
  • 0
  • 165
  • 5
  • 1

Answers

Hi John,

Unfortunately the generated code is only held long enough to build the class routines and then is discarded. The problem is this takes up a lot of space and we want to keep the size of ^oddCOM down to something fairly reasonable so there is no official way to obtain this.

 

Thanks for your reply Mark. Presumably you want to keep ^oddCOM small because it gets referenced at runtime, and smaller means faster.

How about giving us a compile-time choice about this, akin to the "k" option to keep INT source?

Or adding a property to %Dictionary.CompiledMethod called, say, ActualImplementation, whose stream you'd initialize in a property-get method? Perhaps you'd load the stream my running the relevant generator code, like Paul Waterman suggests in his comment on this post.

Comments

Hi John,

I've done this. We have superclasses with a number of generated methods, so I wrote a utility to take a dump of all the generated code before and after any change to one of them, so I could be sure my changes only had desired effects. Basically I throw the code out to temp routines that get compiled, grab the code into an array and then delete the temp routine. It's a bit more complicated than that, you need some dependencies, etc, but has worked very well for me. I'm really tied up this week, but message me next week if you'd like more details.

Mike

Mike, if you're able to post any more details of your approach it might be useful.

Could you pretend to be the compiler and run the generator method? Might be a problem if the method does anything other than generate code.

Class temp.proc
{

ClassMethod showcode(classname, methodname)
{
    n %class,%code,%method,%compiledclass,%compiledmethod,%parameter
    
    s %compiledclass=##class(%Dictionary.CompiledClass).%OpenId(classname)
    s %compiledmethod=##class(%Dictionary.CompiledMethod).%OpenId(classname_"||"_methodname)
    s generatorclassname=%compiledmethod.Origin
    s %class=##class(%Dictionary.ClassDefinition).%OpenId(classname)
    s %method=##class(%Dictionary.MethodDefinition).%OpenId(generatorclassname_"||"_methodname)
        
    s key="" f  { s param=%compiledclass.Parameters.GetNext(.key) q:key=""  s %parameter(param.Name)=param.Default }
    
    s %code=##class(%Stream.MethodGenerator).%New()

    s exec=" d "_methodname_"^"_generatorclassname_".G1(%class,%code,%method,%compiledclass,%compiledmethod,.%parameter)"
    x exec
    
    d %code.Rewind()
    w %code.Read()
}

 

I am not sure if all the variables here are set correctly, or if the generator code always ends up in a G1 or even if this is a good idea, was just thinking.

This is not code I have used before.

That's an interesting suggestion Paul. I might try it out.

Anyone else know if the method generation code always goes into the .G1 routine? Or does it overflow into a .G2 at a certain size?