<div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr">Hi Nicolas,<br><div><br></div><div> on reviewing this code first I really like transformInAssignmentTo: ; it's a much nicer design than my hack. Now, a specific issue...</div><div><br></div><div>I see </div><div><div>TReturnNode>>transformInAssignmentTo: aTVariableNode</div><div><span class="gmail-Apple-tab-span" style="white-space:pre"> </span>"a return shall not be assigned:</div><div><span class="gmail-Apple-tab-span" style="white-space:pre"> </span>x := condition ifTrue: [^nil] ifFalse: [2]</div><div><span class="gmail-Apple-tab-span" style="white-space:pre"> </span>shall be transformed into:</div><div><span class="gmail-Apple-tab-span" style="white-space:pre"> </span>condition ifTrue: [^nil] ifFalse: [x := 2]"</div><div><span class="gmail-Apple-tab-span" style="white-space:pre"> </span></div><div><span class="gmail-Apple-tab-span" style="white-space:pre"> </span>^self</div></div><div><br></div><div>The issue here is that if x is a global variable then a necessary side-effect will be lost. Hence I think we need to refactor transformInAssignmentTo: to transformInAssignmentTo:codeGen: so that we can query whether the variable is in fact global in the current scope.</div></div></div></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Sat, Jun 20, 2020 at 11:11 AM <<a href="mailto:commits@source.squeak.org">commits@source.squeak.org</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"> <br>
Nicolas Cellier uploaded a new version of VMMaker to project VM Maker:<br>
<a href="http://source.squeak.org/VMMaker/VMMaker.oscog-nice.2761.mcz" rel="noreferrer" target="_blank">http://source.squeak.org/VMMaker/VMMaker.oscog-nice.2761.mcz</a><br>
<br>
==================== Summary ====================<br>
<br>
Name: VMMaker.oscog-nice.2761<br>
Author: nice<br>
Time: 20 June 2020, 8:11:09.897426 pm<br>
UUID: e8105e2b-a95e-4698-9a5f-939360e251b8<br>
Ancestors: VMMaker.oscog-eem.2760<br>
<br>
1) Revise a bit the tranformation of assignment/returns for C code generation (see below)<br>
<br>
2) Do not try to generate SHA256Plugin, it's obsolete and absent from latest cryptography packages.<br>
<br>
In Smalltalk, every statement is an expression that can be used in other expression, assigned to variables, etc...<br>
<br>
In C, not all statements are valid expressions, or IOW, not all statements have a value.<br>
In C parlance, rvalues are the ones which can be used at the right of an assignment, lvalues the ones which can be used on the left of an assignment.<br>
<br>
While working on FFI, I had invalid code generated looking like:<br>
<br>
err = switch(atomicType) ...<br>
<br>
This is because there is brittle code for transforming some expressions, which are not generic enough.<br>
<br>
For example, we have similar TSwitchStmtListNode and TCaseStmtNode for handling #dispatchOn:in:, but not exactly same handling of both.<br>
<br>
Before this change, an overview can be given by reviewing senders of:<br>
#isSwitch #isCaseStmt<br>
<br>
This commit is an attempt to enhance/generalize handling of such non-rvalues by distributing the handling of those constructs in TParseNode hierarchy.<br>
<br>
A slight change, is that assigning/returning result of a caseOf: without otherwise clause did create a default branch with an error message and a default value for the returned expression/assigned variable.<br>
See #emitCCodeOn:addToEndOfCases:level:generator:<br>
or #emitCCodeOn:prependToEndOfCases:level:generator:<br>
<br>
The new #transformInAssignmentTo: only create the error message, but does not provide the default value.<br>
This is because type analysis might have been not yet taken place at time of transformation.<br>
Is this really needed? I don't think so.<br>
<br>
Please review!<br>
We might want to further extend the mechanism.<br>
<br>
=============== Diff against VMMaker.oscog-eem.2760 ===============<br>
<br>
Item was changed:<br>
----- Method: TAssignmentNode>>emitStatementListExpansion:on:level:generator: (in category 'C code generation') -----<br>
emitStatementListExpansion: stmtList on: aStream level: level generator: aCodeGen<br>
stmtList statements last = variable ifTrue:<br>
[^expression emitCCodeOn: aStream level: level generator: aCodeGen].<br>
+ (stmtList copy transformInAssignmentTo: variable)<br>
- stmtList copy<br>
- assignLastExpressionTo: variable;<br>
emitCCodeOn: aStream level: level generator: aCodeGen!<br>
<br>
Item was changed:<br>
----- Method: TAssignmentNode>>emitStatementListExpansionAsExpression:on:level:generator: (in category 'C code generation') -----<br>
emitStatementListExpansionAsExpression: stmtList on: aStream level: level generator: aCodeGen<br>
stmtList statements last = variable ifTrue:<br>
[^expression emitCCodeAsExpressionOn: aStream level: level generator: aCodeGen].<br>
+ (stmtList copy transformInAssignmentTo: variable)<br>
- stmtList copy<br>
- assignLastExpressionTo: variable;<br>
emitCCodeAsExpressionOn: aStream level: level generator: aCodeGen!<br>
<br>
Item was changed:<br>
----- Method: TAssignmentNode>>emitValueExpansionOn:level:generator: (in category 'C code generation') -----<br>
emitValueExpansionOn: aStream level: level generator: aCodeGen<br>
| stmtList lastStmt copiedStatements |<br>
self assert: (expression isSend and: [expression isValueExpansion]).<br>
stmtList := expression receiver.<br>
lastStmt := stmtList statements last.<br>
(lastStmt = variable or: [lastStmt isReturn]) ifTrue:<br>
[^expression emitCCodeOn: aStream level: level generator: aCodeGen].<br>
+ copiedStatements := stmtList copy transformInAssignmentTo: variable.<br>
- copiedStatements := stmtList copy.<br>
- copiedStatements statements<br>
- at: stmtList statements size<br>
- put: (TAssignmentNode new<br>
- setVariable: variable<br>
- expression: lastStmt).<br>
expression copy<br>
receiver: copiedStatements;<br>
emitCCodeOn: aStream level: level generator: aCodeGen!<br>
<br>
Item was added:<br>
+ ----- Method: TAssignmentNode>>transformInAssignmentTo: (in category 'transformations') -----<br>
+ transformInAssignmentTo: aTVariableNode<br>
+ "Avoid transforming:<br>
+ x := expression<br>
+ into:<br>
+ x := x := expression"<br>
+ <br>
+ aTVariableNode = variable ifTrue: [^self].<br>
+ ^super transformInAssignmentTo: aTVariableNode!<br>
<br>
Item was changed:<br>
TParseNode subclass: #TCaseStmtNode<br>
+ instanceVariableNames: 'expression firsts lasts cases otherwiseOrNil'<br>
- instanceVariableNames: 'expression firsts lasts cases'<br>
classVariableNames: ''<br>
poolDictionaries: ''<br>
category: 'VMMaker-Translation to C'!<br>
<br>
!TCaseStmtNode commentStamp: '<historical>' prior: 0!<br>
I implement the main dispatch case statements for bytecode and primitive dispatch. See TMethod classPool associationAt: #CaseStatements!<br>
<br>
Item was changed:<br>
----- Method: TCaseStmtNode>>emitCCodeOn:level:generator: (in category 'C code generation') -----<br>
emitCCodeOn: aStream level: level generator: aCodeGen<br>
| printMod expansions duplicates |<br>
printMod := false.<br>
(expression isVariable<br>
and: [expression name = 'currentBytecode']) ifTrue:<br>
[printMod := true.<br>
aStream nextPutAll: 'bytecodeDispatchDebugHook();'; cr; crtab: level.<br>
aCodeGen outputAsmLabel: 'bytecodeDispatch' on: aStream.<br>
aStream crtab: level].<br>
aStream nextPutAll: 'switch ('.<br>
expression emitCCodeOn: aStream level: level generator: aCodeGen.<br>
aStream nextPutAll: ') {'; cr.<br>
expansions := aCodeGen suppressAsmLabelsWhile:<br>
[cases collect:<br>
[:case|<br>
self filterCommentsFrom:<br>
(String streamContents:<br>
[:s|<br>
case emitCCodeOn: s level: 0 generator: aCodeGen])]].<br>
duplicates := Set new.<br>
1 to: cases size do:<br>
[:i|<br>
(duplicates includes: i) ifFalse:<br>
[(duplicates addAll: ((i to: cases size) select: [:j| (expansions at: i) = (expansions at: j)])) do:<br>
[:k|<br>
(firsts at: k) to: (lasts at: k) do:<br>
[:caseIndex|<br>
aStream tab: level; nextPutAll: 'case '; print: caseIndex; nextPut: $:.<br>
(caseIndex > 255 and: [printMod]) ifTrue:<br>
[aStream nextPutAll: ' /*'; print: (caseIndex bitAnd: 255); nextPutAll: '*/'].<br>
aStream cr]].<br>
(cases at: i) emitCCodeOn: aStream level: level + 1 generator: aCodeGen.<br>
aStream tab: level + 1; nextPutAll: 'break;'; cr]].<br>
+ otherwiseOrNil<br>
+ ifNotNil:<br>
+ [aStream<br>
+ crtab: level;<br>
+ nextPutAll: 'default:';<br>
+ cr.<br>
+ otherwiseOrNil emitCCodeOn: aStream level: level + 1 generator: aCodeGen].<br>
aStream tab: level; nextPut: $}!<br>
<br>
Item was added:<br>
+ ----- Method: TCaseStmtNode>>isAnRValueIn: (in category 'testing') -----<br>
+ isAnRValueIn: aCodeGen<br>
+ "A switch is not an rvalue"<br>
+ <br>
+ ^false!<br>
<br>
Item was added:<br>
+ ----- Method: TCaseStmtNode>>transformInAssignmentTo: (in category 'transformations') -----<br>
+ transformInAssignmentTo: aTVariableNode<br>
+ "Destructively transform the receiver so that each case is transformed into an assignment."<br>
+ cases := cases collect: [:node | node copy transformInAssignmentTo: aTVariableNode].<br>
+ otherwiseOrNil := otherwiseOrNil isNil<br>
+ ifTrue: [TStmtListNode new setArguments: #() statements:<br>
+ {TSendNode new setSelector: #error<br>
+ receiver: (TConstantNode new setValue: 'Case not found')<br>
+ arguments: #()}]<br>
+ ifFalse: [otherwiseOrNil copy transformInAssignmentTo: aTVariableNode].<br>
+ ^self!<br>
<br>
Item was added:<br>
+ ----- Method: TGoToNode>>isAnRValueIn: (in category 'testing') -----<br>
+ isAnRValueIn: aCodeGen<br>
+ "A goto is not an rvalue"<br>
+ <br>
+ ^false!<br>
<br>
Item was removed:<br>
- ----- Method: TMethod>>isConditionalToBeTransformedForAssignment:in: (in category 'inlining') -----<br>
- isConditionalToBeTransformedForAssignment: aSend in: aCodeGen<br>
- "Answer if a send is of the form<br>
- e1<br>
- ifTrue: [e2 ifTrue: [self m1] ifFalse: [self m2]]<br>
- ifFalse: [self m3]<br>
- such that at least one of the sends mN may be inlined.."<br>
- <br>
- ^(#(ifTrue:ifFalse: ifFalse:ifTrue:) includes: aSend selector)<br>
- and: [aSend args anySatisfy:<br>
- [:arg| | stmt |<br>
- self assert: arg isStmtList.<br>
- arg statements size > 1<br>
- or: [(stmt := arg statements first) isSwitch<br>
- or: [stmt isSend<br>
- and: [(aCodeGen mayInline: stmt selector)<br>
- or: [self isConditionalToBeTransformedForAssignment: stmt in: aCodeGen]]]]]]!<br>
<br>
Item was changed:<br>
----- Method: TMethod>>transformConditionalAssignment:in: (in category 'inlining') -----<br>
transformConditionalAssignment: node in: aCodeGen<br>
"If possible answer the transformation of code of the form<br>
var := e1<br>
ifTrue: [e2 ifTrue: [self m1] ifFalse: [self m2]]<br>
ifFalse: [self m3]<br>
into<br>
e1<br>
ifTrue: [e2 ifTrue: [var := self m1] ifFalse: [var := self m2]]<br>
ifFalse: [var := self m3]<br>
+ to allow inlining of m1, m2, et al. Otherwise answer nil.<br>
+ Also apply to various C constructs like switch"<br>
- to allow inlining of m1, m2, et al. Otherwise answer nil."<br>
<br>
- | expr |<br>
^(node isAssignment<br>
+ and: [node expression mustTransformWhenAssignedIn: aCodeGen]) ifTrue:<br>
+ [node expression copy transformInAssignmentTo: node variable]!<br>
- and: [(expr := node expression) isSend<br>
- and: [(#(ifTrue:ifFalse: ifFalse:ifTrue:) includes: expr selector)<br>
- and: [self isConditionalToBeTransformedForAssignment: expr in: aCodeGen]]]) ifTrue:<br>
- [expr copy<br>
- arguments:<br>
- (expr args collect:<br>
- [:stmtList| stmtList copy assignLastExpressionTo: node variable]);<br>
- yourself]!<br>
<br>
Item was changed:<br>
----- Method: TMethod>>transformReturnSubExpression:toAssignmentOf:andGoto:unless:into: (in category 'inlining') -----<br>
transformReturnSubExpression: node toAssignmentOf: exitVar andGoto: exitLabel unless: eliminateReturnSelfs into: aBinaryBlock<br>
| expr replacement |<br>
expr := node isReturn ifTrue: [node expression] ifFalse: [node].<br>
replacement := (expr isVariable "Eliminate ^self's"<br>
and: [expr name = 'self'<br>
and: [eliminateReturnSelfs]])<br>
ifTrue: [nil]<br>
ifFalse:<br>
[exitVar<br>
ifNil: [expr]<br>
+ ifNotNil: [expr transformInAssignmentTo: (TVariableNode new setName: exitVar)]].<br>
- ifNotNil: [TAssignmentNode new<br>
- setVariable: (TVariableNode new setName: exitVar)<br>
- expression: expr]].<br>
node == parseTree statements last<br>
ifTrue:<br>
[aBinaryBlock value: replacement value: false]<br>
ifFalse:<br>
[replacement := replacement<br>
ifNil: [TGoToNode new setLabel: exitLabel; yourself]<br>
ifNotNil:<br>
[TStmtListNode new<br>
setArguments: #()<br>
statements: {replacement.<br>
TGoToNode new setLabel: exitLabel; yourself};<br>
yourself].<br>
aBinaryBlock value: replacement value: true]!<br>
<br>
Item was changed:<br>
----- Method: TMethod>>transformReturns (in category 'type inference') -----<br>
transformReturns<br>
+ "Once the return type has been found or inferred, returns may need to be modified.<br>
- "Once the return type has been found or inferred, returns may bneed to be modified.<br>
If the return type is #void, any occurrences of ^expr must be replaced with expr. ^self.<br>
If the type is #sqInt any any occurrences of ^self are replaced with ^0."<br>
(returnType == #void or: [returnType == #sqInt]) ifFalse:<br>
[^self].<br>
parseTree nodesWithParentsDo:<br>
[:node :parent|<br>
node isReturn ifTrue:<br>
[(node expression isVariable and: [node expression name = 'self'])<br>
ifTrue:<br>
[returnType = #sqInt ifTrue:<br>
[node setExpression: (TConstantNode new setValue: 0)]]<br>
ifFalse:<br>
[returnType = #void ifTrue:<br>
[parent<br>
replaceChild: node<br>
with: (TStmtListNode new<br>
setArguments: #()<br>
statements: {node expression.<br>
TReturnNode new <br>
setExpression: (TVariableNode new setName: 'self')<br>
yourself})]]]]!<br>
<br>
Item was added:<br>
+ ----- Method: TParseNode>>isAnRValueIn: (in category 'testing') -----<br>
+ isAnRValueIn: aCodeGen<br>
+ "Answer false if this node is potentially not a rvalue.<br>
+ a rvalue is an expression that can occur to the right of an assignement.<br>
+ In C code, not all expression can be a rvalue.<br>
+ For example if() {} else {} are not rvalues, unless they can be transformed into a ()?: construct.<br>
+ Likewise, while and for loops, switch statements are not possible rvalues.<br>
+ This method has to take inlining into account, because a simple message send would be transformed into a function call which is an rvalue.<br>
+ But if the method is inlined rather than called, this may not be the case.<br>
+ Default behavior is to answer true, only notorious non-rvalues have to refine this"<br>
+ <br>
+ ^true<br>
+ !<br>
<br>
Item was added:<br>
+ ----- Method: TParseNode>>mustTransformWhenAssignedIn: (in category 'testing') -----<br>
+ mustTransformWhenAssignedIn: aCodeGen<br>
+ "Answer whether this node must be transformed when assigned to a variable<br>
+ var := expr.<br>
+ Not all statements can be used at the right of assignment in C (rvalues)."<br>
+ <br>
+ ^(self isAnRValueIn: aCodeGen) not<br>
+ !<br>
<br>
Item was added:<br>
+ ----- Method: TParseNode>>transformInAssignmentTo: (in category 'transformations') -----<br>
+ transformInAssignmentTo: aTVariableNode<br>
+ "Default behavior is to transform an expression into an assignement<br>
+ var := expression.<br>
+ This message has to be redefined in subclasses which are not rvalues."<br>
+ ^TAssignmentNode new<br>
+ setVariable: aTVariableNode<br>
+ expression: self!<br>
<br>
Item was added:<br>
+ ----- Method: TReturnNode>>isAnRValueIn: (in category 'testing') -----<br>
+ isAnRValueIn: aCodeGen<br>
+ "This is not an rvalue, we cannot write:<br>
+ x = return y"<br>
+ <br>
+ ^false!<br>
<br>
Item was added:<br>
+ ----- Method: TReturnNode>>transformInAssignmentTo: (in category 'transformations') -----<br>
+ transformInAssignmentTo: aTVariableNode<br>
+ "a return shall not be assigned:<br>
+ x := condition ifTrue: [^nil] ifFalse: [2]<br>
+ shall be transformed into:<br>
+ condition ifTrue: [^nil] ifFalse: [x := 2]"<br>
+ <br>
+ ^self!<br>
<br>
Item was added:<br>
+ ----- Method: TSendNode>>isAnRValueIn: (in category 'testing') -----<br>
+ isAnRValueIn: aCodeGen <br>
+ self isConditionalSend<br>
+ ifTrue: ["If all expressions of a conditional are rvalue, then the<br>
+ conditional can be transformed into a ()?: construct and is<br>
+ thus an rvalue"<br>
+ ^ (receiver isAnRValueIn: aCodeGen)<br>
+ and: [self args<br>
+ allSatisfy: [:arg | arg isAnRValueIn: aCodeGen]]].<br>
+ "inlined message sends are potentially not rvalues"<br>
+ ^ (aCodeGen mayInline: self selector) not!<br>
<br>
Item was added:<br>
+ ----- Method: TSendNode>>mustTransformWhenAssignedIn: (in category 'transformations') -----<br>
+ mustTransformWhenAssignedIn: aCodeGen<br>
+ "Answer whether this node must be transformed.<br>
+ Avoid infinite transformation loops caused by unchanged nodes."<br>
+ <br>
+ (aCodeGen mayInline: self selector) ifTrue: [^false].<br>
+ ^super mustTransformWhenAssignedIn: aCodeGen!<br>
<br>
Item was added:<br>
+ ----- Method: TSendNode>>transformInAssignmentTo: (in category 'transformations') -----<br>
+ transformInAssignmentTo: aTVariableNode<br>
+ "transform a conditional:<br>
+ condition ifTrue: [stmt1. stmt2] ifFalse: [stmt3. stmt4].<br>
+ into:<br>
+ condition ifTrue: [stmt1. var := stmt2] ifFalse: [stmt3. var := stmt4].<br>
+ If the last expression is itself not an rvalue, it will be transformed recursively"<br>
+ <br>
+ self isConditionalSend ifTrue: [^self copy<br>
+ arguments:<br>
+ (self args collect:<br>
+ [:stmtList| stmtList copy transformInAssignmentTo: aTVariableNode]);<br>
+ yourself].<br>
+ "don't attempt to assign aTVariableNode with the error condition (like default switch missing)"<br>
+ selector = #error ifTrue: [^self].<br>
+ ^super transformInAssignmentTo: aTVariableNode!<br>
<br>
Item was removed:<br>
- ----- Method: TStmtListNode>>assignLastExpressionTo: (in category 'transformations') -----<br>
- assignLastExpressionTo: variableNode<br>
- "Destructively transform the receiver so that its last expression is assigned to the argument."<br>
- | index |<br>
- index := statements findLast: [:expr| (expr isGoTo or: [expr isLabel]) not].<br>
- statements<br>
- at: index<br>
- put: (TAssignmentNode new<br>
- setVariable: variableNode<br>
- expression: (statements at: index))!<br>
<br>
Item was added:<br>
+ ----- Method: TStmtListNode>>isAnRValueIn: (in category 'testing') -----<br>
+ isAnRValueIn: aCodeGen<br>
+ "A list of statements is not an rvalue...<br>
+ Well, in simple cases we could use comma operator (,), but don't bother here."<br>
+ <br>
+ ^statements size = 1 and: [statements first isAnRValueIn: aCodeGen]!<br>
<br>
Item was added:<br>
+ ----- Method: TStmtListNode>>transformInAssignmentTo: (in category 'transformations') -----<br>
+ transformInAssignmentTo: aTVariableNode<br>
+ "Destructively transform the receiver so that its last expression is assigned to the argument."<br>
+ | index |<br>
+ index := statements findLast: [:expr| (expr isGoTo or: [expr isLabel]) not].<br>
+ statements<br>
+ at: index<br>
+ put: ((statements at: index) copy transformInAssignmentTo: aTVariableNode).<br>
+ ^self!<br>
<br>
Item was added:<br>
+ ----- Method: TSwitchStmtNode>>isAnRValueIn: (in category 'testing') -----<br>
+ isAnRValueIn: aCodeGen<br>
+ "A switch is not an rvalue"<br>
+ <br>
+ ^false!<br>
<br>
Item was added:<br>
+ ----- Method: TSwitchStmtNode>>transformInAssignmentTo: (in category 'transformations') -----<br>
+ transformInAssignmentTo: aTVariableNode<br>
+ "Destructively transform the receiver so that each case is transformed into an assignment."<br>
+ cases := cases collect: [:pair | {pair first. pair last copy transformInAssignmentTo: aTVariableNode}].<br>
+ otherwiseOrNil := otherwiseOrNil isNil<br>
+ ifTrue: [TStmtListNode new setArguments: #() statements:<br>
+ {TSendNode new setSelector: #error<br>
+ receiver: (TConstantNode new setValue: 'Case not found and no otherwise clause')<br>
+ arguments: #()}]<br>
+ ifFalse: [otherwiseOrNil copy transformInAssignmentTo: aTVariableNode].<br>
+ ^self!<br>
<br>
Item was added:<br>
+ ----- Method: TVariableNode>>transformInAssignmentTo: (in category 'transformations') -----<br>
+ transformInAssignmentTo: aTVariableNode<br>
+ "Avoid transforming:<br>
+ x<br>
+ into:<br>
+ x := x"<br>
+ <br>
+ aTVariableNode = self ifTrue: [^self].<br>
+ ^super transformInAssignmentTo: aTVariableNode!<br>
<br>
Item was changed:<br>
----- Method: VMMaker class>>generateVMPlugins (in category 'configurations') -----<br>
generateVMPlugins<br>
^VMMaker<br>
generatePluginsTo: self sourceTree, '/src'<br>
options: #()<br>
platformDir: self sourceTree, '/platforms'<br>
including:#(ADPCMCodecPlugin AsynchFilePlugin<br>
BalloonEnginePlugin B3DAcceleratorPlugin B3DEnginePlugin BMPReadWriterPlugin BitBltSimulation<br>
BochsIA32Plugin BochsX64Plugin GdbARMv6Plugin GdbARMv8Plugin<br>
CameraPlugin CroquetPlugin DeflatePlugin DropPlugin<br>
+ "Cryptography Plugins:" DESPlugin DSAPlugin MD5Plugin<br>
- "Cryptography Plugins:" DESPlugin DSAPlugin MD5Plugin SHA256Plugin<br>
"FT2Plugin" FFTPlugin FileCopyPlugin FilePlugin FileAttributesPlugin Float64ArrayPlugin FloatArrayPlugin FloatMathPlugin<br>
GeniePlugin HostWindowPlugin IA32ABIPlugin ImmX11Plugin InternetConfigPlugin<br>
JPEGReadWriter2Plugin JPEGReaderPlugin JoystickTabletPlugin KlattSynthesizerPlugin<br>
LargeIntegersPlugin LocalePlugin MIDIPlugin MacMenubarPlugin Matrix2x3Plugin<br>
MiscPrimitivePlugin Mpeg3Plugin QuicktimePlugin RePlugin<br>
ScratchPlugin SecurityPlugin SerialPlugin SocketPlugin<br>
SoundCodecPlugin SoundGenerationPlugin SoundPlugin SqueakSSLPlugin StarSqueakPlugin<br>
ThreadedFFIPlugin ThreadedARM32FFIPlugin ThreadedARM64FFIPlugin ThreadedIA32FFIPlugin<br>
ThreadedX64SysVFFIPlugin ThreadedX64Win64FFIPlugin<br>
UnicodePlugin UnixAioPlugin UUIDPlugin UnixOSProcessPlugin<br>
Win32OSProcessPlugin VMProfileLinuxSupportPlugin VMProfileMacSupportPlugin WeDoPlugin<br>
XDisplayControlPlugin)!<br>
<br>
</blockquote></div><br clear="all"><div><br></div>-- <br><div dir="ltr" class="gmail_signature"><div dir="ltr"><div><span style="font-size:small;border-collapse:separate"><div>_,,,^..^,,,_<br></div><div>best, Eliot</div></span></div></div></div>