[squeak-dev] sends of #class and #== considered harmful, may we please stop?

Marcus Denker marcus.denker at inria.fr
Fri Nov 30 12:21:02 UTC 2018


>> 
>> +1.  Again see Marcus’ super cool reification of inlined blocks in Pharo, even if it is slooooow :-)
> 
> Yes, it is very slow. The goal was more these two:
> 
> 1) do not loose the clever student in the first lecture. They do the “lets implement 3 value logic” and are *very* upset when they realise that the system is not as nice as they thought :-)
> 2) understand the problem better
> 
> and of course, it is not much code. This is the full code (and it can be turned off by a setting):
> 

….

> So this means we do the compilation for every execution. I wonder if this could not be speed up by caching… maybe even one could do something similar at the JIT level?
> 

I now implemented caching of the compiled DoIt. It adds a method property #mustBeBooleanCache which is a dictionary that stores the doit per pc.
The cached method itself stores the pc after the jump. (#mustBeBooleanJump):


mustBeBooleanDeOptimizeIn: context
	"Permits to redefine methods inlined by compiler.
	Take the ast node corresponding to the mustBeBoolean error, compile it on the fly and executes it as a DoIt. Then resume the execution of the context.
	the generated DoIts are cached in the calling method"

	| ret cache method |

	cache := context method propertyAt: #mustBeBooleanCache ifAbsentPut: [ IdentityDictionary new ].
	"compile a doit method for the unoptimized expression"
	method := cache at: (context pc - 1) ifAbsent: [self mustBeBooleanCompileExpression: context andCache: cache ].
	"Execute the generated method with the pc still at the optimzized block so that the lookUp can read variables defined in the optimized block"
	ret := context receiver withArgs: {context} executeMethod: method.
   	"resume the context at the instruction following the send when returning from deoptimized code."
         context pc: (method propertyAt: #mustBeBooleanJump).
	^ret.


with the compilation done in this method:

mustBeBooleanCompileExpression: context andCache: cache
	"Permits to redefine methods inlined by compiler.
	Take the ast node corresponding to the mustBeBoolean error, compile it on the fly and executes it as a DoIt. Then resume the execution of the context."

	| sendNode methodNode pc method pcAfterJump |

	"get the message send node that triggered mustBeBoolean"
	pc := context pc - 1.
	sendNode := context sourceNode sourceNodeForPC: pc.
	"Rewrite non-local returns to return to the correct context from send"
	RBParseTreeRewriter new 
		replace: '^ ``@value' with: 'ThisContext home return: ``@value';
		executeTree: sendNode.
	"Build doit node to perform send unoptimized"
	methodNode := sendNode copy asDoitForContext: context.
	"Keep same compilation context as the sender node's"
	methodNode compilationContext: sendNode methodNode compilationContext copy.
	"Disable inlining so the message send will be unoptimized"
	methodNode compilationContext compilerOptions: #(- optionInlineIf optionInlineAndOr optionInlineWhile).
	"Generate the method"	
	OCASTSemanticCleaner clean: methodNode.
	method := methodNode generate.
	"store the pc of the instruction following the send when returning from deoptimized code."
	pcAfterJump := sendNode irInstruction nextBytecodeOffsetAfterJump.
	method propertyAt: #mustBeBooleanJump put: pcAfterJump.
	"cache the method we just created"
	cache at: pc put: method.
	^method
	

seems lead to a factor 200 speedup for trivial examples. The “hot” code-path now just gets the method from the cache and executes it,
so all the overhead of compilation.  And the mapping pc-AST is not needed at runtime.

	Marcus






More information about the Squeak-dev mailing list