Smalltalk80LanguageImplementationKor:Chapter 29
- 제 29 장 프리미티브 메서드의 형식적 명세
프리미티브 메서드의 형식적 명세
인터프리터는 메시지가 전송되면 주로 Smalltalk CompiledMethod를 실행하여 응답한다. 이는 CompiledMethod에 대해 새로운 MethodContext를 생성하고, return 바이트코드를 마주칠 때까지 그 바이트코드를 실행하는 과정을 수반한다. 하지만 일부 메시지는 primitively하게 반응할 수 있다. 프리미티브 응답은 새로운 컨텍스트를 생성하거나 다른 바이트코드를 실행하지 않고 인터프리터가 직접 실행한다. 인터프리터가 할 수 있는 각 프리미티브 응답은 "primitive routine"(프리미티브 루틴)에 의해 설명된다. 프리미티브 루틴은 메시지 수신자와 인자를 스택으로부터 제거하여 적절한 결과로 대체한다. 일부 프리미티브 루틴은 객체 메모리 또는 일부 하드웨어 장치에 다른 영향을 미치기도 한다. 프리미티브 응답이 완료되고 나면 인터프리터는 프리미티브의 실행을 야기한 send 바이트코드 이후에 바이트코드의 해석을 진행한다.
프리미티브 루틴은 실행 도중에 언제든 프리미티브 응답을 만들 수 없다는 결정을 내릴 수 있다. 이러한 상황은 올바르지 않은 클래스의 메시지 인자로 인해 발생하는 수도 있다. 이를 "primitive failure"(프리미티브 실패)라고 부른다. 프리미티브가 실패하면 프리미티브 메서드가 존재하지 않는 것처럼 선택자 및 수신자의 클래스와 연관된 Smalltalk 메서드가 실행될 것이다.
아래 표는 각 프리미티브 루틴과 연관된 클래스-선택자 쌍을 보여준다. 이러한 클래스-선택자 쌍 중 일부는 본 서적의 앞부분에서 소개하지 않았는데, 클래스의 private 프로토콜에 해당하기 때문이다. 일부 프리미티브 루틴은 그 명세를 충족해야만 시스템이 올바로 기능한다. 그 외 프리미티브 루틴은 선택적이며, 계속 실패한다면 시스템이 덜 효율적으로 실행될 뿐이다. 선택적 프리미티브에는 별표가 표시되어 있다. 선택적 프리미티브 루틴과 연관된 Smalltalk 메서드는 프리미티브가 하는 일이라면 모두 실행해야 한다. 필요한 프리미티브 루틴과 연관된 Smalltalk 메서드는 프리미티브가 실패하는 경우에만 처리하면 된다.
프리미티브 색인 | 클래스-선택자 쌍(Pairs) |
1 | SmallInteger + |
2 | SmallInteger - |
3 | SmallInteger < |
4 | SmallInteger > |
5* | SmallInteger < = |
6* | SmallInteger > = |
7 | SmallInteger = |
8* | SmallInteger ~= |
9 | SmallInteger * |
10* | SmallInteger / |
11* | SmallInteger \\ |
12 | SmallInteger // |
13* | SmallInteger quo: |
14 | SmallInteger bitAnd: |
15 | SmallInteger bitOr: |
16 | SmallInteger bitXor: |
17 | SmallInteger bitShift: |
18* | Number @ |
19 | |
2O | |
21* | Integer + LargePositiveInteger + |
22* | Integer - LargePositiveInteger - |
23* | Integer < LargePositiveInteger < |
24* | Integer > LargePositiveInteger > |
25* | Integer < = LargePositiveInteger < = |
26* | Integer > = LargePositiveInteger > = |
27* | Integer = LargePositiveInteger = |
28* | Integer ~= LargePositiveInteger ~= |
29* | Integer * LargePositiveInteger * |
30* | Integer / LargePositiveInteger / |
31* | Integer \\ LargePositiveInteger \\ |
32* | Integer // LargePositiveInteger // |
33* | Integer quo: LargePositiveInteger quo: |
34* | Integer bitAnd: LargePositiveInteger bitAnd: |
35* | Integer bitOr: LargePositiveInteger bitOr: |
36* | Integer bitXor: LargePositiveInteger bitXor: |
37* | Integer bitShift: LargePositiveInteger bitShift: |
38 | |
39 | |
4O | SmallInteger asFloat |
41 | Float + |
42 | Float - |
43 | Float < |
44 | Float > |
45* | Float < = |
46* | Float > = |
47 | Float = |
48* | Float ~= |
49 | Float * |
50 | Float / |
51 | Float truncated |
52* | Float fractionPart |
53* | Float exponent |
54* | Float timesTwoPower: |
55 | |
56 | |
57 | |
58 | |
59 | |
60 | LargeNegativeInteger digitAt: LargePositiveInteger digitAt: Object at: Object basicAt: |
61 | LargeNegativeInteger digitAt:put: LargePositiveInteger digitAt:put: Object basicAt:put: Object at:put: |
62 | ArrayedCollection size LargeNegativeInteger digitLength LargePositiveInteger digitLength Object basicSize Object size String size |
63 | String at: String basicAt: |
64 | String basicAt:put: String at:put: |
65* | ReadStream next ReadWriteStream next |
66* | WriteStream nextPut: |
67* | PositionableStream atEnd |
68 | CompiledMethod objectAt: |
69 | CompiledMethod objectAt:put: |
70 | Behavior basicNew Behavior new Interval class new |
71 | Behavior new: Behavior basicNew: |
72 | Object become: |
73 | Object instVarAt: |
74 | Object instVarAt:put: |
75 | Object asOop Object hash Symbol hash |
76 | SmallInteger asObject SmallInteger asObjectNoFail |
77 | Behavior someInstance |
78 | Object nextInstance |
79 | CompiledMethod class newMethod:header: |
80* | ContextPart blockCopy: |
81 | BlockContext value:value:value: BlockContext value: BlockContext value: BlockContext value:value: |
82 | BlockContext valueWithArguments: |
83* | Object perform:with:with:with: Object perform:with: Object perform:with:with: Object perform: |
84 | Object perform:withArguments: |
85 | Semaphore signal |
86 | Semaphore wait |
87 | Process resume |
88 | Process suspend |
89 | Behavior flushCache |
90* | InputSensor primMousePt InputState primMousePt |
91 | InputState primCursorLocPut: InputState primCursorLocPutAgain: |
92 | Cursor class cursorLink: |
93 | InputState primInputSemaphore: |
94 | InputState primSampleInterval: |
95 | InputState primInputWord |
96 | BitBlt copyBitsAgain BitBlt copyBits |
97 | SystemDictionary snapshotPrimitive |
98 | Time class secondClockInto: |
99 | Time class millisecondClockInto: |
100 | ProcessorScheduler signal:atMilliseconds: |
101 | Cursor beCursor |
102 | DisplayScreen beDisplay |
103* | CharacterScanner scanCharactersFrom:to:in: rightX:stopConditions:displaying: |
104* | BitBlt drawLoopX:Y: |
105* | ByteArray primReplaceFrom:to:with:startingAt: ByteArray replaceFrom:to:withString:startingAt: String replaceFrom:to:withByteArray:startingAt: String primReplaceFrom:to:with:startingAt: |
106 | |
107 | |
108 | |
109 | |
110 | Character = Object = = |
111 | Object class |
112 | SystemDictionary coreLeft |
113 | SystemDictionary quitPrimitive |
114 | SystemDictionary exitToDebugger |
115 | SystemDictionary oopsLeft |
116 | SystemDictionary signal:atOopsLeft:wordsLeft: |
117 | |
118 | |
119 | |
120 | |
121 | |
122 | |
123 | |
124 | |
125 | |
126 | |
127 | |
스몰토크 프리미티브 |
프리미티브 메서드의 예로, 선택자가 +인 메시지에 대한 SmallInteger의 인스턴스의 응답을 들 수 있다. 인자 또한 SmallInteger의 인스턴스일 경우 수신자와 인자의 값을 합한 값은 SmallInteger가 표현 가능한 범위에 해당하며, 프리미티브 메서드는 스택으로부터 수신자와 인자를 제거한 후 앞의 합계를 값으로 가진 SmallInteger의 인스턴스로 교체한다. 인자가 SmallInteger가 아니거나 총계가 범위에서 벗어난 경우 프리미티브가 실패하고 SmallInteger에서 선택자 +와 연관된 Smalltalk 메서드가 실행될 것이다.
본 서적에 제공된 인터프리터의 명세에서 사용한 제어 구조체와 인터프리터의 기계어에 사용된 제어 구조체는 프리미티브 루틴의 완료가 실패할 경우 아마 다른 매커니즘을 사용할 것이다. 실패 상황이 발생 시 기계어 프리미티브 루틴은 그 호출자에게 리턴하지 않기로 결정하고 인터프리터의 적절한 장소로 단순히 건너뛸 수 있다 (주로 CompiledMethod를 활성화시킨 장소). 하지만 형식적 명세는 Smalltalk에서 작성되기 때문에 모든 루틴은 그 전송자에게 리턴해야만 하고 Interpreter는 루틴 호출 구조체와 상관없이 프리미티브의 성공과 실패를 추적해야만 한다. 따라서 책의 명세 일부는 프리미티브가 시작될 때 true로 초기화되는 success라는 레지스터로, 루틴이 실패하면 false로 설정될 것이다. 다음 두 개의 루틴은 프리미티브 success 레지스터의 상태를 설정하고 테스트한다.
success: successValue
success ← successValue & success
success
↑success
아래의 루틴은 프리미티브가 실행되기 전에 그리고 그것이 완료될 수 없음을 프리미티브 루틴이 발견하기 이전, 즉 가장 흔한 두 가지 초기화 사례에서 success 플래그의 상태를 설정한다.
initPrimitive
success ← true
primitiveFail
success ← false
프리미티브 다수는 정수 수량을 조작하므로 인터프리터는 공통된 기능을 실행하는 루틴을 여러 개 포함한다. popInteger 루틴은 프리미티브가 스택의 맨 위에 SmallInteger를 예상할 때 사용된다. SmallInteger로 밝혀지면 그 값이 리턴되고, 그렇지 않으면 프리미티브 실패가 시그널링된다.
popInteger
| integerPointer |
integerPointer ← self popStack.
self success: (memory isIntegerObject: integerPointer).
self success
ifTrue: [↑memory integerValueOf: integerPointer]
표시된 필드가 SmallInteger를 포함하지 않을 경우 fetchInteger:ofObject: 루틴이 프리미티브 실패를 시그널링하였음을 상기해보자. pushInteger: 루틴은 값을 SmallInteger로 변환하고 스택 맨 위로 밀어넣는다.
pushInteger: integerValue
self push: (memory integerObjectOf: integerValue)
제일 큰 색인 가능 컬렉션은 65534개의 색인 가능한 요소를 가질 수 있고 SmallIntegers는 16383까지의 값만 표현이 가능하므로 색인이나 크기를 처리하는 프리미티브 루틴은 LargePositiveIntegers를 조작할 수 있어야 한다. 아래의 두 루틴은 16-bit 부호가 없는 값과 객체 포인터를 SmallIntegers 또는 LargePositiveIntegers로, 또 그 반대로 변환한다.
positive 16BitIntegerFor: integerValue
| newLargeInteger |
integerValue < 0
ifTrue: [↑self primitiveFail].
(memory isIntegerValue: integerValue)
ifTrue: [↑memory integerObjectOf: integerValue].
newLargeInteger ← memory instantiateClass:
ClassLargePositiveIntegerPointer
withBytes: 2.
memory storeByte: 0
ofObject: newLargeInteger
withValue: (self lowByteOf: integerValue).
memory storeByte: 1
ofObject: newLargeInteger
withValue: (self highByteOf: integerValue).
↑newLargeInteger
positive 16BitValueOf: integerPointer
| value |
(memory isIntegerObject: integerPointer)
ifTrue: [↑memory integerValueOf: integerPointer].
(memory fetchClassOf: integerPointer) =
ClassLargePositiveIntegerPointer
ifFalse: [↑self primitiveFail].
(memory fetchByteLengthOf: integerPointer) = 2
ifFalse: [↑self primitiveFail].
value ← memory fetchByte: 1
ofObject: integerPointer.
value ← value * 256 + (memory fetchByte: 0
ofObject: integerPointer).
↑value
Send-message 바이트코드를 해석하는 과정에서 프리미티브에 도달할 수 있는 방법이 세 가지 있다.
- 일부 프리미티브 루틴은 수신자의 특정 클래스에 대한 send-special-selector 바이트코드와 연관되어 있다. 이는 메시지 검색 없이 도달할 수 있다.
- (self 또는 인스턴스 변수를 리턴하는 데) 가장 흔히 사용되는 두 가지 프리미티브 루틴은 CompiledMethod의 헤더에 대한 플래그 값에 표시할 수 있다. 이는 메시지 검색이 CompiledMethod를 생성한 후에만 발견되지만 헤더만 검색해야 한다.
- 대부분의 프리미티브 루틴은 CompiledMethod의 헤더 확장 내 숫자로 표시된다. 이 또한 메시지 검색 이후에 발견된다.
프리미티브 루틴에 대한 첫 번째 경로는 sendSpecialSelectorBytecode 루틴 내 specialSelectorPrimitiveResponse의 호출로 표현되었다. specialSelectorPrimitiveResponse 루틴은 적절한 프리미티브 루틴을 선택하고, 프리미티브 응답이 성공적으로 이루어지면 true를, 그 외에는 false를 리턴한다. specialSelectorPrimitiveResponse가 false를 리턴할 경우 sendSpecialSelectorBytecode 루틴은 특수 선택자를 검색한다는 사실을 상기해보자.
specialSelectorPrimitiveResponse
self initPrimitive.
(currentBytecode between: 176 and: 191)
ifTrue: [self arithmeticSelectorPrimitive].
(currentByteCode between: 192 and: 207)
ifTrue: [self commonSelectorPrimitive].
↑self success
프리미티브 루틴은 수신자가 SmallInteger일 때만 특수 산술 선택자에 의해 접근될 것이다. 실제 프리미티브 루틴은 산술 프리미티브를 다루면서 함께 설명하겠다.
arithmeticSelectorPrimitive
self success: (memory isIntegerObject: (self stackValue: 1)).
self success
ifTrue: [currentBytecode = 176 ifTrue: [↑self primitiveAdd].
currentBytecode = 177 ifTrue: [↑self primitiveSubtract].
currentBytecode = 178 ifTrue: [↑self primitiveLessThan].
currentBytecode = 179 ifTrue: [↑self primitiveGreaterThan].
currentBytecode = 180 ifTrue: [↑self primitiveLessOrEqual].
currentBytecode = 181 ifTrue: [↑self primitiveGreaterOrEqual].
currentBytecode = 182 ifTrue: [↑self primitiveEqual].
currentBytecode = 183 ifTrue: [↑self primitiveNotEquat].
currentBytecode = 184 ifTrue: [↑self primitiveMultiply].
currentBytecode = 185 ifTrue: [↑self primitiveDivide].
currentBytecode = 186 ifTrue: [↑self primitiveMod].
currentBytecode = 187 ifTrue: [↑self primitiveMakePoint].
currentBytecode = 188 ifTrue: [↑self primitiveBitShift].
currentBytecode = 189 ifTrue: [↑self primitiveDiv].
currentBytecode = 190 ifTrue: [↑self primitiveBitAnd].
currentBytecode = 191 ifTrue: [↑self primitiveBitOr]]
5개의 비산술 특수 선택자만 메시지 검색 없이 프리미티브를 호출한다 (==, class, blockCopy:, value, value:). ==에 대한 프리미티브 루틴은 시스템 프리미티브에 관한 절에서 찾아볼 수 있으며, class에 대한 루틴은 저장공간 관리 프리미티브에서 찾을 수 있다. 둘 다 수신자의 어떤 클래스에든 호출된다. blockCopy:, value, value:에 대한 루틴은 제어 프리미티브에 관한 절에서 찾을 수 있다. blockCopy:에 대한 루틴은 수신자가 MethodContext이거나 BlockContext일 경우 호출될 것이다. value와 value:에 대한 루틴은 수신자가 BlockContext일 경우에만 호출될 것이다.
commonSelectorPrimitive
| receiverClass |
argumentCount ← self fetchInteger: (currentBytecode - 176) * 2 + 1
ofObject: SpecialSelectorsPointer.
receiverClass ←
memory fetchClassOf: (self stackValue: argumentCount).
currentBytecode = 198 ifTrue: [↑self primitiveEquivalent].
currentBytecode = 199 ifTrue: [↑self primitiveClass].
currentBytecode = 200
ifTrue: [self success:
(receiverClass = ClassMethodContextPointer)
| (receiverClass = ClassBlockContextPointer).
↑self success ifTrue: [self primitiveBlockCopy]].
(currentBytecode = 201) | (currentBytecode = 202)
ifTrue: [self success: receiverClass = ClassBlockContextPointer.
↑self success ifTrue: [self primitiveValue]].
self primitiveFail
위에 열거된 프리미티브 루틴에 대한 두 번째와 세 번째 경로는 메시지에 대한 CompiledMethod가 발견된 이후에 취한다. 프리미티브의 존재는 executeNewMethod에서 호출되는 primitiveResponse 루틴이 감지한다. primitiveResponse 루틴은 프리미티브 응답이 성공적으로 이루어지면 true를 리턴하고 그 외의 경우 false를 리턴한다는 점에서 specialSelectorPrimitiveResponse 루틴과 비슷하다. executeNewMethod 루틴은 primitiveResponse가 false를 리턴할 경우 검색되는 CompiledMethod를 활성화한다.
primitiveresponse
| flagValue thisReceiver offset |
primitiveIndex = 0
ifTrue: [flagValue ← self flagValueOf: newMethod.
flagValue = 5
ifTrue: [self quickReturnSelf.
↑true].
flagValue = 6
ifTrue: [self quickInstanceLoad.
↑true].
↑false]
ifFalse: [self initPrimitive.
self dispatchPrimitives.
↑self success]
플래그 값 5와 6은 가장 흔히 발견되는 프리미티브 두 개, self의 단순한 리턴과 수신자의 인스턴스 변수 중 하나의 단순한 리턴으로 도달한다. self를 리턴하는 것은 인터프리터가 관련되는 한 no-op에 해당하는데, self의 객체 포인터는 스택에서 그것이 메시지 응답으로서 차지해야 하는 메시지 수신자와 동일한 위치를 차지한다.
quickReturnSelf
수신자의 인스턴스 변수를 리턴하는 것도 거의 마찬가지로 쉽게 가능하다.
quickInstanceLoad
| thisReceiver fieldIndex |
thisReceiver ← self popStack.
fieldIndex ← self fieldIndexOf: newMethod.
self push: (memory fetchPointer: fieldIndex
ofObject: thisReceiver)
형식적 명세에 여섯 가지 유형의 프리미티브는 산술, 스크립팅과 스트리밍, 저장공간 관리, 제어 구조체, 입/출력, 일반 시스템 접근을 처리한다. 이는 프리미티브 색인 범위 여섯 개에 해당한다. 프리미티브 색인 범위는 implementation-private 프리미티브 루틴용으로 예약된다. 어떤 의미든 할당될 수 있지만 인터프리터로부터 인터프리터로 의존할 수는 없다. 이는 명세에 속하지 않으므로 본문에서 설명하지 않겠다.
dispatchPrimitives
primitiveIndex < 60
ifTrue: [↑self dispatchArithmeticPrimitives].
primitiveIndex < 68
ifTrue: [↑self dispatchSubscriptAndStreamPrimitives].
primitiveIndex < 80
ifTrue: [↑self dispatchStorageManagementPrimitives].
primitiveIndex < 90
ifTrue: [↑self dispatchControlPrimitives].
primitiveIndex < 110
ifTrue: [↑self dispatchInputOutputPrimitives].
primitiveIndex < 128
ifTrue: [↑self dispatchSystemPrimitives].
primitiveIndex < 256
ifTrue: [↑self dispatchPrivatePrimitives]
산술 프리미티브(Arithmetic Primitives)
산술 프리미티브 루틴에는 세 가지 집합이 있는데 하나는 SmallIntegers용이고 하나는 큰 정수용(LargePositiveIntegers와 LargeNegativeIntegers), 나머지 하나는 Floats용이다. SmallIntegers와 Floats에 대한 프리미티브는 꼭 구현되어야 하고 큰 정수에 대한 프리미티브는 선택적이다.
dispatchArithmeticPrimitives
primitiveIndex < 20
ifTrue: [↑self dispatchIntegerPrimitives].
primitiveIndex < 40
ifTrue: [↑self dispatchLargeIntegerPrimitives].
primitiveIndex < 60
ifTrue: [↑self dispatchFloatPrimitives]
산술 프리미티브 루틴의 첫 번째 집합은 모두 스택으로부터 수신자와 인자를 모두 꺼내고 Smallintegers가 아니라면 실패한다. 이후 루틴은 비교의 Boolean 결과 또는 계산의 정수형 결과를 스택으로 밀어넣는다. 값을 SmallInteger로 표현할 수 없을 경우 정수형 결과를 생성하는 루틴은 실패한다.
dispatchIntegerPrimitives
primitiveIndex = 1 ifTrue: [↑self primitiveAdd].
primitiveIndex = 2 ifTrue: [↑self primitiveSubtract].
primitiveIndex = 3 ifTrue: [↑self primitiveLessThan].
primitiveIndex = 4 ifTrue: [↑self primitiveGreaterThan].
primitiveIndex = 5 ifTrue: [↑self primitiveLessOrEqual].
primitiveIndex = 6 ifTrue: [↑self primitiveGreaterOrEqual].
primitiveIndex = 7 ifTrue: [↑self primitiveEqual].
primitiveIndex = 8 ifTrue: [↑self primitiveNotEqual].
primitiveIndex = 9 ifTrue: [↑self primitiveMultiply].
primitiveIndex = 10 ifTrue: [↑self primitiveDivide].
primitiveIndex = 11 ifTrue: [↑self primitiveMod].
primitiveIndex = 12 ifTrue: [↑self primitiveDiv].
primitiveIndex = 13 ifTrue: [↑self primitiveQuo].
primitiveIndex = 14 ifTrue: [↑self primitiveBitAnd].
primitiveIndex = 15 ifTrue: [↑self primitiveBitOr].
primitiveIndex = 16 ifTrue: [↑self primitiveBitXor].
primitiveIndex = 17 ifTrue: [↑self primitiveBitShift].
primitiveIndex = 18 ifTrue: [↑self primitiveMakePoint]
primitiveAdd, primitiveSubtract, primitiveMultiply 루틴은 사용되는 산술 연산만 제외하고 모두 동일하기 때문에 primitiveAdd 루틴만 표시하겠다.
primitiveAdd
| integerReceiver integerArgument integerResult |
integerArgument ← self popInteger.
integerReceiver ← self popInteger.
self success
ifTrue: [integerResult ← integerReceiver + integerArgument.
self success: (memory isIntegerValue: integerResult)].
self success
ifTrue: [self pushInteger: integerResult]
ifFalse: [self unPop: 2]
나눗셈에 대한 (선택자 /와 연관된) 프리미티브 루틴은 나눗셈이 정확할 때만 결과를 생성하고 그 외의 경우 실패하므로 나머지 세 개의 산술 프리미티브와 차이가 있다. 이 프리미티브를 비롯해 반올림 나눗셈(rounding division)과 연관된 다음 세 개의 프리미티브는 모두 인자가 0일 경우 실패한다.
primitiveDivide
| integerReceiver integerArgument integerResult |
integerArgument ← self popInteger.
integerReceiver ← self popInteger.
self success: integerArgument ~= 0.
self success: integerReceiver \\ integerArgument = 0.
self success
ifTrue: [integerResult ← integerReceiver // integerArgument.
self success: (memory isIntegerValue: integerResult)].
self success
ifTrue: [self push: (memory integerObjectOf: integerResult)]
ifFalse: [self unPop: 2]
모듈로 함수(선택자 \\ 와 연관된)에 대한 프리미티브 루틴은 몫을 항상 내림으로 처리하는 (음의 무한대로) 나눗셈의 나머지를 제공한다.
primitiveMode
| integerReceiver integerArgument integerResult |
integerArgument ← self popInteger.
integerReceiver ← self popInteger.
self success: integerArgument ~= 0.
self success
ifTrue: [integerResult ← integerReceiver \\ integerArgument.
self success: (memory isIntegerValue: integerResult)].
self success
ifTrue: [self pushInteger: integerResult]
ifFalse: [self unPop: 2]
반올림 나눗셈에 (선택자 // 와 quo:에 연관된) 대한 프리미티브가 두 개 있다. // 의 결과는 항상 반내림된다 (음의 무한대로).
primitiveDiv
| integerReceiver integerArgument integerResult |
integerArgument ← self popInteger.
integerReceiver ← self popInteger.
self success: integerArgument ~= 0.
self success
ifTrue: [integerResult ← integerReceiver // integerArgument.
self success: (memory isIntegerValue: integerResult)].
self success
ifTrue: [self pushInteger: integerResult]
ifFalse: [self unPop: 2]
quo:의 결과는 버린다 (0으로 반올림).
primitiveQuo
| integerReceiver integerArgument integerResult |
integerArgument ← self popInteger.
integerReceiver ← self popInteger.
self success: integerArgument ~= 0.
self success
ifTrue: [integerResult ← integerReceiver quo: integerArgument.
self success: (memory isIntegerValue: integerResult)].
self success
ifTrue: [self pushInteger: integerResult]
ifFalse: [self unPop: 2]
primitiveEqual, primitiveNotEqual, primitiveLessThan, primitiveLessOrEqual, primitiveGreaterThan, primitiveGreaterOrEqual 루틴들은 사용된 비교 연산만 제외하고 모두 동일하므로 primitiveEqual 루틴만 본문에 소개하겠다.
primitiveEqual
| integerReceiver integerArgument integerResult |
integerArgument ← self popInteger.
integerReceiver ← self popInteger.
self success
ifTrue: [integerReceiver = integerArgument
ifTrue: [self push: TruePointer]
ifFalse: [self push: FalsePointer]]
ifFalse: [self unPop: 2]
primitiveBitAnd, primitiveBitOr, primitiveBitXor 루틴은 SmallInteger 값의 2의 보수 이항 표현에 논리 연산을 실행한다. 이들은 사용된 논리 연산만 제외하고 모두 동일하므로 primitiveBitAnd 루틴만 본문에 표시하겠다.
primitiveBitAnd
| integerReceiver integerArgument integerResult |
integerArgument ← self popInteger.
integerReceiver ← self popInteger.
self success
ifTrue: [integerResult ← integerReceiver bitAnd: integerArgument].
self success
ifTrue: [self pushInteger: integerResult]
ifFalse: [self unPop: 2]
전환을 위한 (선택자 bitShift:과 연관된) 프리미티브 루틴은 SmallInteger를 리턴하는데, 2의 보수로 표현된 그 값은 인자가 나타내는 비트 개수만큼 왼쪽으로 전환된다. 음의 인자는 오른쪽으로 전환된다. 0은 왼쪽 전환에서 오른쪽으로부터 안으로 전환(shifted in)된다. 부호 비트는 오른쪽 전환에서 연장된다. 올바른 결과를 SmallInteger로 표현할 수 없다면 해당 프리미티브는 실패한다.
primitiveBitShift
| integer Receiver integerArgument integerResult |
integerArgument ← self popInteger.
integerReceiver ← self popInteger.
self success
ifTrue: [integerResult ← integerReceiver bitShift: integerArgument.
self success: (memory isIntegerValue: integerResult)].
self success
ifTrue: [self pushInteger: integerResult]
ifFalse: [self unPop: 2]
선택자 @와 연관된 프리미티브 루틴은 x값이 수신자이고 y 값이 인자인 새로운 Point를 리턴한다.
primitiveMakePoint
| integerReceiver integerArgument pointResult |
integerArgument ← self popStack.
integerReceiver ← self popStack.
self success: (memory isIntegerValue: integerReceiver).
self success: (memory isIntegerValue: integerArgument).
self success
ifTrue: [pointResult ← memory instantiateClass: ClassPointPointer
withPointers: ClassPointSize.
memory storePointer: XIndex
ofObject: pointResult
withValue: integerReceiver.
memory storePointer: YIndex
ofObject: pointResult
withValue: integerArgument.
self push: pointResult]
ifFalse: [self unPop: 2]
initializePointIndices
XIndex ← 0.
YIndex ← 1.
ClassPointSize ← 2
프리미티브 색인 21부터 37까지는 큰 정수에 (LargePositiveInteger와 LargeNegativeInteger의 인스턴스) 그들의 연산을 실행한다는 점만 제외하고 1부터 17까지 프리미티브와 동일하다. 이러한 연산 모두에 적합한 Smalltalk 구현이 있으므로 프리미티브 루틴은 선택적이며, 이번 장에서는 명시하지 않을 것이다. 이를 구현하기 위해서는 해당하는 Smalltalk 메서드가 기계어 루틴으로 해석되어야 한다.
dispatchLargeIntegerPrimitives
self primitiveFail
Float의 인스턴스는 IEEE single-precision(32-bit) 포맷으로 표현된다. 이러한 포맷은 부동소수점 수량을 1과 2 사이의 숫자, 2의 거듭제곱, 부호로 나타낸다. Float은 워드 크기의 비포인터(nonpointer) 객체이다. 첫 번째 필드의 최상위 비트는 숫자의 부호를 나타낸다 (1은 음을 의미한다). 첫 번째 필드에서 그 다음 8개의 최상위 비트는 127이 치우친(biased) 2의 8-bit 지수이다 (0은 -127의 지수를 의미하고 128은 0의 지수를 의미한다). 첫 번째 필드의 최하위 7 비트는 1과 2 사이 숫자의 분수 부분의 최상위 7 비트이다. 분수 부분은 23 비트 길이로, 그 중 최하위 16 비트는 Float의 두 번째 필드의 내용이다. 따라서 필드가 다음과 같은 Float은,
SEEEEEEE EFFFFFFF
FFFFFFFF FFFFFFFF
다음 값을 나타낸다.
0은 '두 필드=0'으로 표현된다. 인자가 Float의 인스턴스가 아니거나 결과를 Float으로 표현할 수 없는 경우 부동소수점 프리미티브는 실패한다. Smalltalk-80 가상 머신의 이러한 명세는 부동소수점 표현 이외의 IEEE 표준 부분들은 구체적으로 포함하지 않는다. 부동소수점 값에 필요한 연산을 실행하는 루틴의 구현은 구현자에게 남겨진다.
primitiveAsFloat 루틴은 그 SmallInteger 수신자를 Float으로 변환한다. 41부터 50까지 프리미티브에 대한 루틴은 Floats에 실행한다는 점을 제외하면 1부터 10까지 또는 21부터 30까지와 동일한 연산을 실행한다. primitiveTruncated 루틴은 분수 부분 없이 수신자의 값과 동일한 SmallInteger를 리턴한다. 자른 값을 SmallInteger로 표현할 수 없을 경우 실패한다. primitiveFractionPart는 수신자와 그 잘린 값의 차이를 리턴한다. primitiveExponent 루틴은 수신자의 지수를 리턴하고 primitiveTimesTwoPower 루틴은 인자가 명시한 양만큼 지수를 증가시킨다.
dispatchFloatPrimitives
primitiveIndex = 40 ifTrue: [↑self primitiveAsFloat].
primitiveIndex = 41 ifTrue: [↑self primitiveFloatAdd].
primitiveIndex = 42 ifTrue: [↑self primitiveFloatSubtract].
primitiveIndex = 43 ifTrue: [↑self primitiveFloatLessThan].
primitiveIndex = 44 ifTrue: [↑self primitiveFloatGreaterThan].
primitiveIndex = 45 ifTrue: [↑self primitiveFloatLessOrEqual].
primitiveIndex = 46 ifTrue: [↑self primitiveFloatGreaterOrEqual].
primitiveIndex = 47 ifTrue: [↑self primitiveFloatEqual].
primitiveIndex = 48 ifTrue: [↑self primitiveFloatNotEqual].
primitiveIndex = 49 ifTrue: [↑self primitiveFloatMultiply].
primitiveIndex = 50 ifTrue: [↑self primitiveFloatDivide].
primitiveIndex = 51 ifTrue: [↑self primitiveTruncated].
primitiveIndex = 52 ifTrue: [↑self primitiveFractionalPart].
primitiveIndex = 53 ifTrue: [↑self primitiveExponent].
primitiveIndex = 54 ifTrue: [↑self primitiveTimesTwoPower]
배열 및 스트림 프리미티브(Array and Stream Primitives)
두 번째 프리미티브 루틴 집합은 객체의 색인 가능한 필드를 스크립팅에 따라 직접적으로, 그리고 스트리밍에 따라 간접적으로 조작하기 위해 제공된다. 이러한 루틴은 16-bit 양의 정수 루틴을 이용하는데, 색인 가능한 필드에 대한 한계값이 65534이기 때문이다.
dispatchSubscriptAndStreamPrimitives
primitiveIndex = 60 ifTrue: [↑self primitiveAt].
primitiveIndex = 61 ifTrue: [↑self primitiveAtPut].
primitiveIndex = 62 ifTrue: [↑self primitiveSize].
primitiveIndex = 63 ifTrue: [↑self primitiveStringAt].
primitiveIndex = 64 ifTrue: [↑self primitiveStringAtPut].
primitiveIndex = 65 ifTrue: [↑self primitiveNext].
primitiveIndex = 66 ifTrue: [↑self primitiveNextPut].
primitiveIndex = 67 ifTrue: [↑self primitiveAtEnd]
다음 루틴은 연산을 첨자지정(subscripting)하는 데 있어 경계(bounds)를 확인하고 첨자지정 접근을 실행하는 데 사용된다. 이들은 색인되는 객체의 색인 가능한 필드에 포인터, 16-bit 정수값, 또는 8-bit 정수값이 포함되는지 여부를 결정한다. checkIndexableBoundsOf:in: 루틴은 one-relative 색인을 취하고, 그것이 객체에 적합한 첨자인지 결정한다. 고정된 필드는 모두 고려해야 한다.
checkIndexableBoundsOf: index in: array
| class |
class ← memory fetchClassOf: array.
self success: index > = 1.
self success: index + (self fixedFieldsOf: class) < = (self lengthOf: array)
lengthOf: array
(self isWords: (memory fetchClassOf: array))
ifTrue: [↑memory fetchWordLengthOf: array]
ifFalse: [↑memory fetchByteLengthOf: array]
subscript:with:과 subscript:with:storing: 루틴은 고정된 필드의 개수가 색인에 추가되었다고 간주하므로 전체적으로 객체 내에서 one-relative 색인으로 사용한다.
subscript: array with: index
| class value |
class ← memory fetchClassOf: array.
(self isWords: class)
ifTrue: [(self isPointers: class)
ifTrue: [↑memory fetchPointer: index - 1
ofObject: array]
ifFalse: [value ← memory fetchWord: index - 1
ofObject: array.
↑self positive 16BitIntegerFor: value]]
ifFalse: [value ← memory fetchByte: index - 1
ofObject: array.
↑memory integerObjectOf: value]
subscript: array with: index storing: value
| class |
class ← memory fetchClassOf: array.
(self isWords: class)
ifTrue: [(self isPointers: class)
ifTrue: [↑memory storePointer: index - 1
ofObject: array
withValue: value]
ifFalse: [self success: (memory isIntegerObject: value).
self success ifTrue:
[↑memory
storeWord: index - 1
ofObject: array
withValue: (self positive 16BitValueOf:
value)]]]
ifFalse: [self success: (memory isIntegerObject: value).
self success ifTrue:
[↑memory storeByte: index - 1
ofObject: array
withValue: (self lowByteOf:
(memory integerValueOf:
value))]]
primitiveAt과 primitiveAtPut 루틴은 수신자의 색인 가능한 필드 중 하나를 단순히 인출하거나 저장한다. 색인이 SmallInteger가 아니거나 범위를 벗어난 경우 실패한다.
primitiveAt
| index array arrayClass result |
index ← self positive 16BitValueOf: self popStack.
array ← self popStack.
arrayClass ← memory fetchClassOf: array.
self checkIndexableBoundsOf: index
in: array.
self success
ifTrue: [index ← index + (self fixedFieldsOf: arrayClass).
result ← self subscript: array
with: index].
self success
ifTrue: [self push: result]
ifFalse: [self unPop: 2]
primitiveAtPut 루틴은 수신자가 포인터 타입이 아니고 두 번째 인자가 8-bti (바이트 색인 가능한 객체) 또는 16-bit(워드 색인 가능한 객체)로 된 양의 정수가 아닌 경우에도 실패한다. 프리미티브 루틴은 저장된 값을 그 값으로 리턴한다.
primitiveAtPut
| array index arrayClass value result |
value ← self popStack.
index ← self positive 16BitValueOf: self popStack.
array ← self popStack.
arrayClass ← memory fetchClassOf: array.
self checkIndexableBoundsOf: index
in: array.
self success
ifTrue: [index ← index + (self fixedFieldsOf: arrayClass).
self subscript: array
with: index
storing: value].
self success
ifTrue: [self push: value]
ifFalse: [self unPop: 3]
primitiveSize 루틴은 수신자가 가진 색인 가능한 필드의 개수를 리턴한다 (예: 적합한 최대 첨자).
primitiveSize
| array class length |
array ← self popStack.
class ← memory fetchClassOf: array
length ← self positive 16BitIntegerFor:
(self lengthOf: array) - (self fixedFieldsOf: class).
self success
ifTrue: [self push: length]
ifFalse: [self unPop: 1]
primitiveStringAt과 primitiveStringAtPut 루틴은 at:과 at:put: 메시지에 대한 String의 인스턴스가 하는 응답이다. String은 사실 바이트 색인 가능한 필드에 8-bit 숫자를 저장하지만 Character의 인스턴스를 이용해 at:과 at:put: 메시지를 통해 통신한다. Character는 SmallInteger를 보유하는 단일 인스턴스 변수를 가진다. at: 메시지로부터 리턴된 SmallInteger의 값은 표시된 String의 필드에 저장된 바이트이다. primitiveStringAt 루틴은 어떠한 값이든 항상 동일한 Character의 인스턴스를 리턴한다. 이는 객체 메모리 내 characterTablePointer라고 불리는 보장된 객체 포인터를 가진 Array로부터 Characters를 얻는다.
primitiveStringAt
| index array ascii character |
index ← self positive 16BitValueOf: self popStack.
array ← self popStack.
self checkIndexableBoundsOf: index
in: array.
self success
ifTrue: [ascii ← memory integerValueOf: (self subscript: array
with: index).
primitiveStringAt
| index array ascii character |
index ← self positive 16BitValueOf: self popStack.
array ← self popStack.
self checkIndexableBoundsOf: index
in: array.
self success
ifTrue: [ascii ← memory integerValueOf: (self subscript: array
with: index).
character ← memory fetchPointer: ascii
ofObject: CharacterTablePointer].
self success
ifTrue: [self push: character]
ifFalse: [self unPop: 2]
initializeCharacterIndex
CharacterValueIndex ← 0
primitiveStringAtPut 루틴은 수신자의 색인 가능한 바이트 중 하나에 Character의 값을 저장한다. at:put: 메시지의 두 번째 인자가 Character가 아닐 경우 실패한다.
primitiveStringAtPut
| index array ascii character |
character ← self popStack.
index ← self positive 16BitValueOf: self popStack.
array ← self popStack.
self checkIndexableBoundsOf: index
in: array.
self success: (memory fetchClassOf: character) = ClassCharacterPointer.
self success
ifTrue: [ascii ← memory fetchPointer: CharacterValueIndex
ofObject: character.
self subscript: array
with: index
storing: ascii].
self success
ifTrue: [self push: character]
ifFalse: [self unPop: 2]
primitiveNext, primitiveNextPut, primitiveAtEnd 루틴은 스트림으로 전송되는 next, nextPut:, atEnd 메시지에 대한 Smalltalk 코드의 선택적인 프리미티브 버전에 해당한다. primitiveNext와 primitiveNextPut 루틴은 스트리밍되는 객체가 Array이거나 String일 경우에만 작동한다.
initializeStreamIndices
StreamArrayIndex ← 0.
StreamIndexIndex ← 1.
StreamReadLimitIndex ← 2.
StreamWriteLimitIndex ← 3
primitiveNext
| stream index limit array arrayClass result ascii |
stream ← self popStack.
array ← memory fetchPointer: StreamArrayIndex
ofObject: stream.
arrayClass ← memory fetchClassOf: array.
index ← self fetchInteger: StreamIndexIndex
ofObject: stream.
limit ← self fetchInteger: StreamReadLimitIndex
ofObject: stream.
self success: index < limit.
self success:
(arrayClass = ClassArrayPointer) | (arrayClass = ClassStringPointer).
self checkIndexableBoundsOf: index + 1
in: array.
self success
ifTrue: [index ← index + 1.
result ← self subscript: array
with: index].
self success
ifTrue [arrayClass = ClassArrayPointer
ifTrue: [self push: result]
ifFalse: [ascii ← memory integerValueOf: result.
self push: (memory fetchPointer: ascii
ofObject:
CharacterTablePointer)]]
ifFalse: [self unPop: 1]
primitiveNextPut
| value stream index limit array arrayClass result ascii |
value ← self popStack.
stream ← self popStack.
array ← memory fetchPointer: StreamArrayIndex
ofObject: stream.
arrayClass ← memory fetchClassOf: array.
index ← self fetchInteger: StreamIndexIndex
ofObject: stream.
limit ← self fetchInteger: StreamWriteLimitIndex
ofObject: stream.
self success: index < limit.
self success:
(arrayClass = ClassArrayPointer) | (arrayClass = ClassStringPointer).
self checkIndexableBoundsOf: index + 1
in: array.
self success
ifTrue: [index ← index + 1.
arrayClass = ClassArrayPointer
ifTrue: [self subscript: array
with: index
storing: value]
ifFalse: [ascii ← memory fetchPointer:
CharacterValueIndex
ofObject: value.
self subscript: array
with: index
storing: ascii]].
self success
ifTrue: [self storeInteger: StreamIndexIndex
ofObject: stream
withValue: index].
self success
ifTrue: [self push: value]
ifFalse: [self unPop: 2]
primitiveAtEnd
| stream array arrayClass length index limit |
stream ← self popStack.
array ← memory fetchPointer: StreamArrayIndex
ofObject: stream.
arrayClass ← memory fetchClassOf: array.
length ← self lengthOf: array.
index ← self fetchInteger: StreamIndexIndex
ofObject: stream.
limit ← self fetchInteger: StreamReadLimitIndex
ofObject: stream.
self success:
(arrayClass = ClassArrayPointer) | (arrayClass = ClassStringPointer).
self success
ifTrue: [(index > = limit) | (index > = length)
ifFalse: [self push: FalsePointer]]
ifFalse: [self unPop: 1]
저장공간 관리 프리미티브(Storage Management Primitives)
저장공간 관리 프리미티브 루틴은 객체의 표현을 조작한다. 이러한 루틴들은 객체 포인터를 조작하고, 필드로 접근하며, 클래스의 새로운 인스턴스를 생성하고, 클래스의 인스턴스를 열거하는 프리미티브를 포함한다.
dispatchStorageManagementPrimitives
primitiveIndex = 68 ifTrue: [↑self primitiveObjectAt].
primitiveIndex = 69 ifTrue: [↑self primitiveObjectAtPut].
primitiveIndex = 70 ifTrue: [↑self primitiveNew].
primitiveIndex = 71 ifTrue: [↑self primitiveNewWithArg].
primitiveIndex = 72 ifTrue: [↑self primitiveBecome].
primitiveIndex = 73 ifTrue: [↑self primitiveInstVarAt].
primitiveIndex = 74 ifTrue: [↑self primitiveInstVarAtPut].
primitiveIndex = 75 ifTrue: [↑self primitiveAsOop].
primitiveIndex = 76 ifTrue: [↑self primitiveAsObject].
primitiveIndex = 77 ifTrue: [↑self primitiveSomeInstance].
primitiveIndex = 78 ifTrue: [↑self primitiveNextInstance].
primitiveIndex = 79 ifTrue: [↑self primitiveNewMethod]
primitiveObjectAt과 primitiveObjectAtPut 루틴은 CompiledMethod 내의 objectAt:과 objectAt:put: 메시지에 연관된다. 이 루틴들은 스몰토크로부터 수신자의 객체 포인터 필드로 (메서드 헤더와 리터럴) 접근을 제공한다. 헤더는 1의 색인으로 접근되고 리터럴은 색인 2부터 리터럴 개수에 1을 더한 값까지를 이용해 접근된다. 이러한 메시지는 주로 컴파일러에 의해 사용된다.
primitiveObjectAt
| thisReceiver index |
index ← self popInteger.
thisReceiver ← self popStack.
self success: index > 0.
self success: index < = (self objectPointerCountOf: thisReceiver).
self success
ifTrue: [self push: (memory fetchPointer: index - 1
ofObject: thisReceiver)]
ifFalse: [self unPop: 2]
primitiveObjectAtPut
| thisReceiver index newValue |
newValue ← self popStack.
index ← self popInteger.
thisReceiver ← self popStack.
self success: index > 0.
self success: index < = (self objectPointerCountOf: thisReceiver).
self success
ifTrue: [memory storePointer: index - 1
ofObject: thisReceiver
withValue: newValue.
self push: newValue]
ifFalse: [self unPop: 3]
primitiveNew 루틴은 색인 가능한 필드 없이 수신자의 (클래스의) 새로운 인스턴스를 생성한다. 클래스가 색인 가능하면 프리미티브는 실패한다.
primitiveNew
| class size |
class ← self popStack.
size ← self fixedFieldsOf: class.
self success: (self isIndexable: class) = = false.
self success
ifTrue: [(self isPointers: class)
ifTrue: [ self push: (memory instantiateClass: class
withPointers: size)]
ifFalse: [self push: (memory instantiateClass: class
withWords: size)]]
ifFalse: [self unPop: 1]
primitiveNewWithArg 루틴은 정수 인자가 명시한 색인 가능한 필드의 개수로 수신자의 (클래스) 새로운 인스턴스를 생성한다. 클래스가 색인 가능할 경우 프리미티브는 실패한다.
primitiveNewWithArg
| size class |
size ← self positive 16BitValueOf: self popStack.
class ← self popStack.
self success: (self isIndexable: class).
self success
ifTrue: [size ← size + (self fixedFieldsOf: class).
(self isPointers: class)
ifTrue: [self push: (memory instantiateClass: class
withPointers: size)]
ifFalse: [(self isWords: class)
ifTrue: [self push: (memory instantiateClass:
class
withWords: size)]
ifFalse: [self push: (memory instantiateClass:
class
withBytes: size)]]]
ifFalse: [self unPop: 2]
primitiveBecome 루틴은 수신자의 인스턴스 포인터와 인자를 바꾼다. 수신자를 가리키는 데 사용된 모든 객체가 이제는 인자를 가리키고 인자를 가리키던 객체들은 이제 수신자를 가리킴을 의미한다.
primitiveBecome
| thisReceiver otherPointer |
otherPointer ← self popStack.
thisReceiver ← self popStack.
self success: (memory isIntegerObject: otherPointer) not.
self success: (memory isIntegerObject: thisReceiver) not.
self success
ifTrue: [memory swapPointersOf: thisReceiver and: otherPointer.
self push: thisReceiver]
ifFalse: [self unpop: 2]
primitiveInstVarAt과 primitiveInstVarAtPut 루틴은 Object 내의 instVarAt:과 instVarAt:put: 메시지와 연관된다. 이들은 필드의 번호매김에서 색인 가능한 필드 대신 고정된 필드(명명된 인스턴스 변수에 상응하는)로 시작된다는 점만 제외하면 primitiveAt과 primitiveAtPut과 비슷하다. 색인 가능한 필드는 고정된 필드 개수보다 1 많은 숫자부터 매겨진다. 이러한 루틴은 첨자의 경계를 확인 시 다른 루틴을 필요로 한다.
checkInstanceVariableBoundsOf: index in: object
| class |
class ← memory fetchClassOf: object.
self success: index > = 1.
self success: index < = (self lengthOf: object)
primitiveInstVarAt
| thisReceiver index value |
index ← self popInteger.
thisReceiver ← self popStack.
self checkInstanceVariableBoundsOf: index
in: thisReceiver.
self success
ifTrue: [value ← self subscript: thisReceiver
with: index].
self success
ifTrue: [self push: value]
ifFalse: [self unPop: 2]
primitiveInstVarAtPut
| thisReceiver index newValue realValue |
newValue ← self popStack.
index ← self popInteger.
thisReceiver ← self popStack.
self checkInstanceVariableBoundsOf: index
in: thisReceiver.
self success
ifTrue: [self subscript: thisReceiver
with: index
storing: newValue].
self success
ifTrue: [self push: newValue]
ifFalse: [self unPop: 3]
primitiveAsOop 루틴은 수신자의 객체 포인터의 (객체 포인터를 16-bit 부호가 있는 수량으로 해석하는) 절반을 값으로 가진 SmallInteger를 생성한다. 프리미티브는 SmallInteger가 아닌 수신자에만 작동한다. SmallInteger가 아닌 객체 포인터는 짝수이므로 객체 포인터 내의 어떤 정보도 손실되지 않는다. SmallIntegers의 인코딩 때문에 수신자의 객체 포인터에서 최하위 비트를 설정하여 halving 연산을 실행할 수 있다.
primitiveAsOop
| thisReceiver |
thisReceiver ← self popStack.
self success: (memory isIntegerObject: thisReceiver) = = false.
self success
ifTrue: [self push: (thisReceiver bitOr: 1)]
ifFalse: [self unPop: 1]
primitiveAsObject 루틴은 primitiveAsOop의 역연산을 실행한다. 이는 SmallInteger 수신자에만 작동한다 (이는 SmallInteger 내의 asObject 메시지와 연관된다). 해당 루틴은 수신자의 값 두 배에 달하는 객체 포인터를 생성한다. 그러한 포인터에 대한 객체가 없다면 프리미티브는 실패한다.
primitiveAsObject
| thisReceiver newOop |
thisReceiver ← self popStack.
newOop ← thisReceiver bitAnd: 16rFFFE.
self success: (memory hasObject: newOop).
self success
ifTrue: [self push: newOop]
ifFalse: [self unPop: 1]
primitiveSomeInstance와 primitiveNextInstance 루틴은 클래스의 인스턴스 열거를 허용한다. 이러한 루틴들은 객체 메모리가 객체 포인터에서 정렬을 정의하고, 해당 정렬에서 클래스의 첫 인스턴스를 찾으며, 객체 포인터가 주어지면 동일한 클래스의 다음 인스턴스를 찾는 능력에 의존한다.
primitiveSomeInstance
| class |
class ← self popStack.
(memory instanceOf: class)
ifTrue: [self push: (memory initialInstanceOf: class)]
ifFalse: [self primitiveFail]
primitiveNextInstance
| object |
object ← self popStack.
(memory isLastInstance: object)
ifTrue: [self primitiveFail]
ifFalse: [self push: (memory instanceAfter: object)]
primitiveNewMethods 루틴은 CompiledMethod 클래스 내 newMethod:header: 메시지와 연관된다. CompiledMethod의 인스턴스는 특수 메시지를 이용해 생성된다. CompiledMethod에서 바이트 대신 포인터를 포함하는 부분은 헤더에 나타나므로 모든 CompiledMethods는 유효한 헤더를 가져야 한다. 따라서 CompiledMethods는 바이트 수를 첫 번째 인자로 취하고 헤더를 두 번째 인자로 취하는 메시지를 (newMethod:header:) 이용해 생성된다. 그리고 헤더는 포인터 필드의 개수를 나타낸다.
primitiveNewMethod
| header bytecodeCount class size |
header ← self popStack.
bytecodeCount ← self popInteger.
class ← self popStack.
size ← (self literalCountOfHeader: header) + 1 * 2 + bytecodeCount.
self push: (memory instantiateClass: class
withBytes: size)
제어 프리미티브(Control Primitives)
제어 프리미티브는 바이트코드가 제공하지 않는 제어 구조체를 제공한다. 이러한 프리미티브는 BlockContexts, Processes, Semaphores의 행위를 지원한다. 매개변수화된 선택자가 있는 메시지를 제공하기도 한다.
dispatchControlPrimitives
primitiveIndex = 80 ifTrue: [↑self primitiveBlockCopy].
primitiveIndex = 81 ifTrue: [↑self primitiveValue].
primitiveIndex = 82 ifTrue: [↑self primitiveValueWithArgs].
primitiveIndex = 83 ifTrue: [↑self primitivePerform].
primitiveIndex = 84 ifTrue: [↑self primitivePerformWithArgs].
primitiveIndex = 85 ifTrue: [↑self primitiveSignal].
primitiveIndex = 86 ifTrue: [↑self primitiveWait].
primitiveIndex = 87 ifTrue: [↑self primitiveResume].
primitiveIndex = 88 ifTrue: [↑self primitiveSuspend].
primitiveIndex = 89 ifTrue: [↑self primitiveFlushCache]
primitiveBlockCopy 루틴은 BlockContext와 MethodContext 모두에서 blockCopy: 메시지와 연관된다. 이 메시지는 컴파일러에 의해서만 생성된다. 새로운 BlockContext가 취하는 블록 인자의 개수는 인자로서 전달된다. primitiveBlockCopy 루틴은 BlockContext의 새로운 인스턴스를 생성한다. 수신자가 MethodContext일 경우 이는 새로운 BlockContext의 홈 컴텍스트가 된다. 수신자가 BlockContext일 경우 그 홈 컨텍스트는 새로운 BlockContext의 홈 컨텍스트에 사용된다.
primitiveBlockCopy
| context methodContext blockArgumentCount newContext initialIP
contextSize |
blockArgumentCount ← self popStack.
context ← self popStack.
(self isBlockContext: context)
ifTrue: [methodContext ← memory fetchPointer: HomeIndex
ofObject: context]
ifFalse: [methodContext ← context].
contextSize ← memory fetchWordLengthOf: methodContext.
newContext ← memory instantiateClass: ClassBlockContextPointer
withPointers: contextSize.
initialIP ← memory integerObjectOf: instructionPointer + 3.
memory storePointer: InitialIPIndex
ofObject: newContext
withValue: initialIP.
memory storePointer: InstructionPointerIndex
ofObject: newContext
withValue: initialIP.
self storeStackPointerValue: 0
inContext: newContext.
memory storePointer: BlockArgumentCountIndex
ofObject: newContext
withValue: blockArgumentCount.
memory storePointer: HomeIndex
ofObject: newContext
withValue: methodContext.
self push: newContext
primitiveValue 루틴은 BlockContext 내의 모든 "value" 메시지와 연관이 있다 (value, value:, value:value: 등). 이 루틴은 "value" 메시지가 취한 블록 인자와 동일한 수를 수신자가 취하는지 확인하고, 이를 활성 컨텍스트의 스택으로부터 수신자의 스택으로 전송한다. 인자의 개수가 일치하지 않으면 프리미티브는 실패한다. primitiveValue 루틴은 활성 컨텍스트를 수신자의 호출자 필드에 저장하고 수신자의 명령 포인터와 스택 포인터를 초기화하기도 한다. 수신자가 초기화되고 나면 그것이 활성 컨텍스트가 된다.
primitiveValue
| blockContext blockArgumentCount initialIP |
blockContext ← self stackValue: argumentCount.
blockArgumentCount ← self argumentCountOfBlock: blockContext.
self success: argumentCount = blockArgumentCount.
self success
ifTrue: [self transfer: argumentCount
fromIndex: stackPointer - argumentCount + 1
ofObject: activeContext
toIndex: TempFrameStart
ofObject: blockContext.
self pop: argumentCount + 1.
initialIP ← memory fetchPointer: InitialIPIndex
ofObject: blockContext.
memory storePointer: InstructionPointerIndex
ofObject: blockContext
withValue: initialIP.
self storeStackPointerValue: argumentCount
inContext: blockContext.
memory storePointer: CallerIndex
ofObject: blockContext
withValue: activeContext.
self newActiveContext: blockContext]
primitiveValueWithArgs 루틴은 BlockContext 내 valueWithArguments: 메시지들과 연관이 있다. 이는 기본적으로 primitiveValue 루틴과 동일한데, 블록 인자가 "value" 메시지에 대한 다수의 인자 대신 valueWithArguments: 메시지에 대한 단일 Array 인자로 온다는 점에서 차이가 있다.
primitiveValueWithArgs
| argumentArray blockContext blockArgumentCount
arrayClass arrayArgumentCount initialIP |
argumentArray ← self popStack.
blockContext ← self popStack.
blockArgumentCount ← self argumentCountOfBlock: blockContext.
arrayClass ← memory fetchClassOf: argumentArray.
self success: (arrayClass = ClassArrayPointer).
self success
ifTrue: [arrayArgumentCount ← memory fetchWordLengthOf:
argumentArray.
self success: arrayArgumentCount = blockArgumentCount].
self success
ifTrue: [self transfer: arrayArgumentCount
fromIndex: 0
ofObject: argumentArray
toIndex: TempFrameStart
ofObject: blockContext.
initialIP ← memory fetchPointer: InitialIPIndex
ofObject: blockContext.
memory storePointer: InstructionPointerIndex
ofObject: blockContext
withValue: initialIP.
self storeStackPointerValue: arrayArgumentCount
inContext: blockContext.
memory storePointer: CallerIndex
ofObject: blockContext
withValue: activeContext.
self newActiveContext: blockContext]
ifFalse: [self unPop: 2]
primitivePerform 루틴은 Object 내의 모든 "perform" 메시지들과 연관이 있다 (perform:, perform:with:, perform:with:with: 등). 이 루틴은 첫 번째 인자를 선택자로 갖고 있고 나머지 인자를 인자로 가진 메시지를 수신자에게 전송하는 것과 동일하다. 따라서 executeNewMethod를 호출하기 전에 스택으로부터 선택자를 제거해야 하고 그것이 발견하는 CompiledMethod가 "perform" 메시지보다 인자를 하나 적게 취하는지 확인해야 한다는 점만 제외하면 sendSelector:argumentCount: 루틴과 유사하다. 인자의 개수가 일치하지 않으면 프리미티브는 실패한다.
primitivePerform
| performSelector newReceiver selectorIndex |
performSelector ← messageSelector.
messageSelector ← self stackValue: argumentCount - 1.
newReceiver ← self stackValue: argumentCount.
self lookupMethodInClass: (memory fetchClassOf: newReceiver).
self success: (self argumentCountOf: newMethod) = (argumentCount - 1).
self success
ifTrue: [selectorIndex ← stackPointer - argumentCount + 1.
self transfer: argumentCount - 1
fromIndex: selectorIndex + 1
ofObject: activeContext
toIndex: selectorIndex
ofObject: activeContext.
self pop: 1.
argumentCount ← argumentCount - 1.
self executeNewMethod]
ifFalse: [messageSelector ← performSelector]
primitivePerformWithArgs 루틴은 Object 내 performWithArguments: 메시지들과 연관된다. 기본적으로 primitivePerform 루틴과 동일하나 메시지 인자가 "perform" 메시지에 대한 다수의 인자 대신 performWithArguments: 메시지에 대한 단일 Array 인자로 온다는 점에서 차이가 있다.
primitivePerformWithArgs
| thisReceiver performSelector argumentArray arrayClass arraySize
index |
argumentArray ← self popStack.
arraySize ← memory fetchWordLengthOf: argumentArray.
arrayClass ← memory fetchClassOf argumentArray.
self success: (stackPointer + arraySize)
< (memory fetchWordLengthOf: activeContext).
self success: (arrayClass = ClassArrayPointer).
self success
ifTrue: [performSelector ← messageSelector.
messageSelector ← self popStack.
thisReceiver ← self stackTop.
argumentCount ← arraySize.
index ← 1.
[index < = argumentCount]
whileTrue: [self push (memory fetchPointer: index - 1
ofObject: argumentArray).
index ← index + 1].
self lookupMethodInClass:
(memory fetchClassOf: thisReceiver).
self success (self argumentCountOf: newMethod)
= argumentCount.
self success
ifTrue: [self executeNewMethod]
ifFalse: [self unPop: argumentCount.
self push: messageSelector.
self push: argumentArray.
argumentCount ← 2.
messageSelector ← performSelector]]
ifFalse: [self unPop: 1]
다음 네 개의 프리미티브 루틴은 (프리미티브 색인 85부터 88까지) 독립 프로세스의 스케줄링과 통신에 사용된다. 아래의 루틴은 Processes, ProcessorSchedulers, Semaphores로 접근하는 데 사용된 색인을 초기화한다.
initializeSchedulerIndices
"Class ProcessorScheduler"
ProcessListsIndex ← 0.
ActiveProcessIndex ← 1.
"Class LinkedList"
FirstLinkIndex ← 0.
LastLinkIndex ← 1.
"Class Semaphore"
ExcessSignalsIndex ← 2.
"Class Link"
NextLinkIndex ← 0.
"Class Process"
SuspendedContextIndex ← 1.
PriorityIndex ← 2.
MyListIndex ← 3
프로세스 교환(process switching)은 바이트코드의 실행과 동기화되어야 한다. 이러한 동기화는 다음 네 개의 인터프리터 레지스터와 네 개의 루틴, checkProcessSwitch, asynchronousSignal:, synchronousSignal:, transferTo:을 이용해 이루어진다.
newProcessWaiting | newProcessWaiting 레지스터는 프로세스 교환이 호출되면 true가, 그 외의 경우 false가 될 것이다. |
newProcess | newProcessWaiting이 true일 경우 newProcess 레지스터는 전송할 Process를 가리킬 것이다. |
semaphoreList | semaphoreList 레지스터는 신호가 전송되어야 하는 Semaphores를 buffer하기 위해 인터프리터가 사용한 Array를 가리킨다. 이는 객체 메모리가 아니라 Interpreter 내의 Array이다. 기계어 인터프리터에서는 테이블이 될 것이다. |
semaphoreIndex | semaphoreIndex 레지스터는 semaphoreList 버퍼에 마지막 Semaphore의 색인을 보유한다. |
인터프리터의 프로세스 관련 레지스터 |
asynchronousSignal: 루틴은 Semaphore를 버퍼로 추가한다.
asynchronousSignal: aSemaphore
semaphoreIndex ← semaphoreIndex +1 .
semaphoreList at: semaphoreIndex put: aSemaphore
Semaphores는 버퍼 내 각 Semaphore마다 synchronousSignal: 루틴을 한 번씩 호출하는 checkProcessSwitch 루틴에서 사실상 신호가 전송될 것이다. Process가 Semaphore를 기다릴 경우 synchronousSignal: 루틴은 프로세스를 재개한다. 대기 중인 Process가 없다면 synchronousSignal: 루틴은 초과 신호의 Semaphore 계수를 증가시킨다. isEmptyList:, resume:, removeFirstLinkOfList: 루틴은 이번 절의 뒷부분에 설명되어 있다.
synchronousSignal: aSemaphore
| excessSignals |
(self isEmptyList: aSemaphore)
ifTrue: [excessSignals ← self fetchInteger: ExcessSignalsIndex
ofObject: aSemaphore.
self storeInteger: ExcessSignalsIndex
ofObject: aSemaphore
withValue: excessSignals + 1]
ifFalse: [self resume: (self removeFirstLinkOfList: aSemaphore)]
transferTo: 루틴은 프로세스를 교환해야 한다는 필요성이 감지될 때마다 사용된다. 이 루틴은 newProcessWaiting과 newProcess 레지스터를 설정한다.
transferTo: aProcess
newProcessWaiting ← true.
newProcess ← aProcess
checkProcessSwitch 루틴은 (interpret 루틴에서) 각 바이트코드의 인출 전에 호출되고, 하나가 호출되었다면 실제 프로세스 교환을 실행한다. 해당 루틴은 오래된 Process에 활성 컨텍스트 포인터를 저장하고, ProcessorScheduler의 활성 프로세스 필드에 새로운 Process를 저장하며, 해당 Process로부터 새로운 활성 컨텍스트를 로드한다.
checkProcessSwitch
| activeProcess |
[semaphoreIndex > 0]
whileTrue:
[self synchronousSignal: (semaphoreList at: semaphoreIndex).
semaphoreIndex ← semaphoreIndex - 1].
newProcessWaiting
ifTrue: [newProcessWaiting ← false.
activeProcess ← self activeProcess.
memory storePointer: SuspendedContextIndex
ofObject: activeProcess
withValue: activeContext.
memory storePointer: ActiveProcessIndex
ofObject: self schedulerPointer
withValue: newProcess.
self newActiveContext:
(memory fetchPointer: SuspendedContextIndex
ofObject: newProcess)]
활성 프로세스가 무엇이 될 것인지 알고자 하는 루틴이라면 newProcessWaiting과 newProcess 레지스터를 고려해야 한다. 따라서 그러한 레지스터는 다음과 같은 루틴을 사용한다.
activeProcess
newProcessWaiting
ifTrue: [↑newProcess]
ifFalse: [↑memory fetchPointer: ActiveProcessIndex
ofObject: self schedulerPointer]
실제 프로세서의 스케줄링을 책임지는 ProcessorScheduler의 인스턴스는 전역적으로 알려져야만 프리미티브가 Processes를 어디서 재개하고 보류하는지 알 것이다. 이러한 ProcessorScheduler는 Processor를 Smalltalk 전역 사전에 명명할 의무가 있다. Processor에 상응하는 연관은 객체 포인터를 갖도록 보장되어 있으므로 적합한 ProcessorScheduler를 찾을 수 있다.
schedulerPointer
↑memory fetchPointer: ValueIndex
ofObject: SchedulerAssociationPointer
Smalltalk가 시작되면 스케줄러의 활성 Process를 통해 초기 활성 컨텍스트가 발견된다.
firstContext
newProcessWaiting ← false
↑memory fetchPointer: SuspendedContextIndex
ofObject: self activeProcess
객체 메모리가 참조 계수를 기반으로 객체를 자동으로 할당해제할 경우, 프로세스 스케줄링 루틴 내 참조 계수를 특별히 고려해야 한다. 그 중 일부를 실행하는 동안에 객체 메모리로부터 일부 객체에 대한 참조가 존재하지 않는 경우가 종종 있다 (예: Process가 Semaphore로부터 제거되었지만 ProcessorScheduler의 LinkedLists 중 하나에 위치하기 전에). 객체 메모리가 쓰레기 수집을 이용할 경우 프리미티브 루틴 중간에 컬렉션을 실행하는 일은 무조건 피해야 한다. 본문에 열거된 루틴에서는 명료성을 위해 참조 계수 문제는 무시하겠다. 참조 계수를 이용한 구현에서 객체의 이른 할당해제(premature deallocation)를 막기 위해서는 이러한 루틴을 수정해야 할 것이다.
LinkedLists를 조작하기 위해 다음 세 개의 루틴이 사용된다.
removeFirstLinkOfList: aLinkedList
| firstLink lastLink nextLink |
firstLink ← memory fetchPointer: FirstLinkIndex
ofObject: aLinkedList.
lastLink ← memory fetchPointer: LastLinkIndex
ofObject: aLinkedList.
lastLink = firstLink
ifTrue: [memory storePointer: FirstLinkIndex
ofObject: aLinkedList
withValue: NilPointer.
memory storePointer: LastLinkIndex
ofObject: aLinkedList
withValue: NilPointer]
ifFalse: [nextLink ← memory fetchPointer: NextLinkIndex
ofObject: firstLink.
memory storePointer: FirstLinkIndex
ofObject: aLinkedList
withValue: nextLink].
memory storePointer: NextLinkIndex
ofObject: firstLink
withValue: NilPointer.
↑firstLink
addLastLink: aLink toList: aLinkedList
| lastLink |
(self isEmptyList: aLinkedList)
ifTrue: [memory storePointer: FirstLinkIndex
ofObject: aLinkedList
withValue: aLink]
ifFalse: [lastLink ← memory fetchPointer: LastLinkIndex
ofObject: aLinkedList.
memory storePointer: NextLinkIndex
ofObject: lastLink
withValue: aLink].
memory storePointer: LastLinkIndex
ofObject: aLinkedList
withValue: aLink.
memory storePointer: MyListIndex
ofObject: aLink
withValue: aLinkedList
isEmptyList: aLinkedList
↑(memory fetchPointer: FirstLinkIndex
ofObject: aLinkedList)
= NilPointer
이 세 개의 LinkedList 루틴은 진행이 중단된 Processes의 ProcessorScheduler의 LinkedLists로 링크를 추가하거나 그로부터 제거하는 아래의 두 루틴을 구현하기 위해 사용된다.
wakeHighestPriority
| priority processLists processList |
processLists ← memory fetchPointer: ProcessListsIndex
ofObject: self schedulerPointer.
priority ← memory fetchWordLengthOf: processLists.
[processList ← memory fetchPointer: priority - 1
ofObject: processLists.
self is EmptyList: processList] whileTrue: [priority ← priority - 1].
↑self removeFirstLinkOfList: processList!
sleep: aProcess
| priority processLists processList |
priority ← self fetchInteger: PriorityIndex
ofObject: aProcess.
processLists ← memory fetchPointer: ProcessListsIndex
ofObject: self schedulerPointer.
processList ← memory fetchPointer: priority - 1
ofObject: processLists.
self addLastLink: aProcess
toList: processList
이 두 가지 루틴은 실제로 Processes를 보류하고 재개하는 아래 두 개의 루틴을 구현하는 데 사용된다.
suspendActive
self transferTo: self wakeHighestPriority
resume: aProcess
| activeProcess activePriority newPriority |
activeProcess ← self activeProcess.
activePriority ← self fetchInteger: PriorityIndex
ofObject: activeProcess.
newPriority ← self fetchInteger: PriorityIndex
ofObject: aProcess.
newPriority > activePriority
ifTrue: [self sleep: activeProcess.
self transferTo: aProcess]
ifFalse: [self sleep: aProcess]
primitiveSignal 루틴은 Semaphore 내에서 signal 메시지와 연관된다. 이는 바이트코드를 해석하는 과정에서 호출되므로 synchronousSignal: 루틴을 사용할 수 있다. 인터프리터가 또 다시 Semaphores를 시그널링하는 경우 (예: 시간만료 또는 키스트로크) asynchronousSignal: 루틴을 사용해야 한다.
primitiveSignal
self synchronousSignal: self stackTop.
primitiveWait 루틴은 Semaphore에서 wait 메시지와 연관된다. 수신자가 0보다 큰 초과 신호 계수를 갖지만 primitiveWait 루틴은 계수를 감소시킨다. 초과 신호 계수가 0이면 primitiveWait은 활성 Process를 보류하고 수신자의 Processes 리스트로 추가한다.
primitiveWait
| thisReceiver excessSignals |
thisReceiver ← self stackTop.
excessSignals ← self fetchInteger: ExcessSignalsIndex
ofObject: thisReceiver.
excessSignals > 0
ifTrue: [self storeInteger: ExcessSignalsIndex
ofObject: thisReceiver
withValue: excessSignals - 1]
ifFalse: [self addLastLink: self activeProcess
toList: thisReceiver.
self suspendActive]
primitiveResume 루틴은 Process 내에서 resume 메시지와 연관된다. 이는 수신자를 인자로 하여 resume: 루틴을 호출한다.
primitiveResume
self resume: self stackTop
primitiveSuspend 루틴은 Process 내에서 suspend 메시지와 연관된다. primitiveSuspend 루틴은 수신자가 활성 Process일 경우 그것을 보류한다. 수신자가 활성 Process가 아닐 경우 프리미티브는 실패한다.
primitiveSuspend
self success: self stackTop = self activeProcess.
self success
ifTrue: [self popStack.
self push: NilPointer.
self suspendActive]
primitiveFlushCache 루틴은 메서드 캐시의 내용을 제거한다. 메서드 캐시를 사용하지 않는 구현은 이를 no-op으로 취급할 수 있다.
primitiveFlushCache
self initializeMethodCache
입/출력 프리미티브(Input/Output Primitives)
입/출력 프리미티브 루틴은 스몰토크가 하드웨어 장치의 상태로 접근하도록 해준다. 이러한 루틴들의 구현은 구현하는 머신의 구조에 따라 좌우되므로 어떠한 루틴도 제공되지 않고 프리미티브의 행위에 대한 명세만 제공될 것이다.
dispatchInputOutputPrimitives
primitiveIndex = 90 ifTrue: [↑self primitiveMousePoint].
primitiveIndex = 91 ifTrue: [↑self primitiveCursorLocPut].
primitiveIndex = 92 ifTrue: [↑self primitiveCursorLink].
primitiveIndex = 93 ifTrue: [↑self primitiveInputSemaphore].
primitiveIndex = 94 ifTrue: [↑self primitiveSampleInterval].
primitiveIndex = 95 ifTrue: [↑self primitiveInputWord].
primitiveIndex = 96 ifTrue: [↑self primitiveCopyBits].
primitiveIndex = 97 ifTrue: [↑self primitiveSnapshot].
primitiveIndex = 98 ifTrue: [↑self primitiveTimeWordsInto].
primitiveIndex = 99 ifTrue: [↑self primitiveTickWordsInto].
primitiveIndex = 100 ifTrue: [↑self primitiveSignalAtTick].
primitiveIndex = 101 ifTrue: [↑self primitiveBeCursor].
primitiveIndex = 102 ifTrue: [↑self primitiveBeDisplay].
primitiveIndex = 103 ifTrue: [↑self primitiveScanCharacters].
primitiveIndex = 104 ifTrue: [↑self primitiveDrawLoop].
primitiveIndex = 105 ifTrue: [↑self primitiveStringReplace]
사용자가 액션을 감지하도록 네 가지 프리미티브 루틴이 사용한다. 시스템이 감지할 수 있는 두 가지 유형의 사용자 액션은 2상태(bi-state) 장치의 상태 변경과 지시 장치의 이동이다. 2상태 장치는 키보드의 키에 해당하고 세 개의 버튼은 지시 장치 및 선택적 five-paddle keyset과 연관된다. 지시 장치와 연관된 버튼은 사실상 물리적 지시 장치에 위치할 수도 있고 그렇지 않을 수도 있다. 네 가지 입력 프리미티브 루틴 중 세 개는 (primitiveInputSemaphore, primitiveInputWord, primitiveSampleInterval) 상태나 움직임을 감지하도록 "active"(활성) 또는 "event-initiated"(이벤트 시작) 매커니즘을 제공한다. 또 다른 프리미티브 루틴(primitiveMousePoint)은 지시 장치 위치를 감지하도록 "passive"(수동적) 또는 "polling"(폴링) 매커니즘을 제공한다.
이벤트 시작 매커니즘은 2상태 장치 또는 지시 장치 위치에 일어난 변화를 인코딩하는 16-bit 워드에 대해 버퍼 스트림(buffered stream)을 제공한다. 워드가 버퍼에 위치할 때마다 Semaphore가 시그널링된다 (asynchronousSignal: 루틴을 이용해). 시그널링할 Semaphore는 primitiveInputSemaphore 루틴에 의해 초기화된다. 이 루틴은 InputState 내의 primInputSemaphore: 메시지와 연관되고, 메시지의 인자는 시그널링될 Semaphore가 된다. primitiveInputWord 루틴(InputState 내의 primInputWord와 연관된)은 버퍼 내 다음 워드를 리턴하고 버퍼로부터 제공한다. Semaphore는 버퍼 내 워드마다 한 번씩 시그널링되므로 버퍼를 비우는 Smalltalk 프로세스는 각 primInputWord 메시지를 전송하기 전에 Semaphore에게 wait 메시지를 전송해야 한다. 버퍼에는 6개 타입의 16-bit 워드가 위치한다. 두 개의 타입은 이벤트의 시간을 명시하고, 또 다른 두 개의 타입은 2상태 장치의 상태 변경을 명시하며, 나머지 두 타입은 지시 장치의 움직임을 명시한다. 워드의 타입은 최상위 4 비트에 저장된다. 최하위 12-bits는 "parameter"(매개변수)라고 부른다.
6개 타입의 코드는 다음과 같은 의미를 지닌다.
타입 코드(type code) | 의미 |
0 | Delta 시간 (어떤 타입의 이벤트이든 매개변수는 마지막 이벤트 이후 경과한 밀리초를 의미한다) |
1 | 지시 장치의 X 위치 |
2 | 지시 장치의 Y 위치 |
3 | 2상태 장치가 켜짐 (매개변수는 어떤 장치인지 나타냄) |
4 | 2상태 장치가 꺼짐 (매개변수는 어떤 장치인지 나타냄) |
5 | 절대 시간 (매개변수가 무시됨, 버퍼 내 다음 두 워드는 밀리초 시간의 절대값, 32-bit의 부호 없는 숫자를 포함한다) |
장치 상태가 변경되거나 지시 장치가 이동하면 시간 워드가 버퍼에 위치한다. 타입 0 워드는 마지막 이벤트 이후 경과 밀리초 수가 12 bits로 표현될 수 있을 경우 사용될 것이다. 그 외에는 타입 5 이벤트가 사용된 다음 절대 시간을 나타내는 2개의 워드가 따라온다. 후자의 경우 Semaphore가 세 번 시그널링된다는 사실을 주목한다. 시간 워드 다음에는 타입 1부터 4까지 하나 또는 그 이상의 워드가 따라올 것이다. 타입 1과 2 워드는 지시 장치가 움직일 때마다 생성될 것이다. Smalltalk는 화면에 대해 이야기할 때 왼손을 기준으로 한 좌표계를 사용함을 기억해야 한다. 원점은 화면의 상단 좌측 모서리이고 x면은 우측으로 갈수록 증가하며 y면은 하단으로 갈수록 증가한다. 이러한 이벤트 간 최소 시간 간격은 InputState 내의 primSampleInterval: 메시지와 연관된 primitiveSampleInterval 루틴에 의해 설정 가능하다. primSampleInterval: 에 대한 인자는 지시 장치가 계속해서 움직일 경우 이동 이벤트 간 밀리 초를 명시한다.
타입 3과 4 워드는 어떤 장치가 상태를 변경하였는지 명시하기 위해 매개변수의 하위 8 비트를 사용한다. 숫자를 매기는 방식은 디코딩(decoded) 및 언디코딩(undecoded) 키보드와 작동하도록 설정된다. 언디코딩 키보드는 위 아랫방향 전환(transition)이 독립된 키로 구성된다. 디코딩 키보드는 몇몇 독립된 키와 몇몇 "meta"(메타) 키(shift와 escape)로 구성되는데, 이러한 키는 스스로는 감지될 수 없으며 다른 키의 값을 변경한다. 디코딩 키보드의 키는 아랫방향의 전환만 나타내고 윗방향 전환은 나타내지 않는다. 언디코딩 키보드에서 표준 키는 shift 또는 control 정보가 없는 keytop에서 문자의 ASCII 코드에 해당하는 매개변수를 생성한다 (예: "A"가 표시된 키는 "a"에 대한 ASCII를 생성하고 "2"와 "@"라고 적힌 키는 "2"에 대한 ASCII를 생성한다). 그 외 표준 키는 다음과 같은 매개변수를 생성한다.
키(key) | 매개변수(parameter) |
backspace | 8 |
tab | 9 |
line feed | 10 |
return | 13 |
escape | 27 |
space | 32 |
delete | 127 |
언디코딩(undecoded) 키보드의 경우 메타 키는 다음과 같은 매개변수를 갖는다.
키(key) | 매개변수(parameter) |
left shift | 136 |
right shift | 137 |
control | 138 |
alpha-lock | 139 |
디코딩(decoded) 키보드의 경우 완전히 전환되고 "controlled"(제어된) ASCII는 매개변수로서 사용되어야 하며 각 키스트로크마다 연속된 타입의 3 또는 4개 워드가 생성되어야 한다.
나머지 2상태 장치에는 다음과 같은 매개변수가 있다.
키(key) | 매개변수(parameter) |
left or top "pointing device" button | 128 |
center "pointing device" button | 129 |
right or bottom "pointing device" button | 130 |
keyset paddles right to left | 131 through 135 |
primitiveMousePoint 루틴은 지시 장치의 위치의 폴링을 허용한다. 이는 새로운 Point를 할당하고 지시 장치의 위치를 x와 y 필드에 저장한다.
디스플레이 화면은 직사각형 픽셀 집합으로, 두 색상 중 하나가 가능하다. 픽셀 색상은 특별히 지정된 DisplayScreen의 인스턴스 내 각 비트로 결정된다. DisplayScreen은 Form의 서브클래스이다. 화면의 업데이트에 사용되어야 하는 DisplayScreen의 인스턴스는 beDisplay 메시지를 전송하여 지정된다. 이 메시지는 primitiveBeDisplay 프리미티브 루틴을 호출한다. 화면은 beDisplay의 마지막 수신자로부터 초당 대략 60회 정도 업데이트될 것이다.
화면이 업데이트될 때마다 "cursor"(커서)는 픽셀로 OR된다. 커서 이미지는 특별히 지정된 Cursor의 인스턴스에 의해 결정된다. Cursor는 인스턴스가 항상 16의 너비와 높이를 가진 Form의 서브클래스이다. 화면으로 OR되어야 하는 Cursor의 인스턴스는 beCursor 메시지를 전송하여 지정된다. 이 메시지는 primitiveBeCursor 프리미티브 루틴을 호출한다.
커서 이미지가 나타나야 하는 위치를 "cursor location"(커서 위치)라고 부른다. 커서 위치는 지시 장치의 위치와 연관되기도 하고 서로 무관할 수도 있다. 두 위치의 연관 여부는 cursorLink:을 선택자로 갖고 true 또는 false를 인자로 한 메시지를 Cursor 클래스로 전송하여 결정된다. 인자가 true일 경우 두 개의 위치는 동일해질 것이고, false일 경우 둘은 무관하다. Cursor의 메타클래스 내 cursorLink: 메시지는 primitiveCursorLink 프리미티브 루틴을 호출한다.
커서는 두 가지 방식으로 이동할 수 있다. 커서와 지시 장치가 연관되어 있다면 지시 장치를 이동 시 커서가 이동한다. 커서는 InputState의 인스턴스로 primCursorLocPut: 메시지를 전송함으로써 이동할 수 있다. 이 메시지는 Point를 인자로 취하고 primitiveCursorLocPut 프리미티브 루틴을 호출한다. 이 루틴은 인자가 명시한 위치로 커서를 이동시킨다. 커서와 지시 장치가 연관되어 있다면 primitiveCursorLocPut 루틴 또한 지시 장치가 나타내는 위치를 변경한다.
primitiveCopyBits 루틴은 BitBIt 내에 copyBits 메시지와 연관이 있고 수신자가 명시한 비트맵에서 연산을 실행한다. 이러한 루틴은 제 18장에서 설명하고 있다.
primitiveSnapshot 루틴은 Smalltalk-80 release 파일과 동일한 포맷으로 된 파일에 객체 메모리의 현재 상태를 기록한다. 이 파일은 release 파일이 애초에 시작된 방식과 정확히 동일한 방식으로 재개될 수 있다. 프리미티브 호출 시 활성 컨텍스트의 포인터는 파일에 활성 Process로 저장되어야 한다.
primitiveTimeWordsInto와 primitiveTickWordsInto 루틴은 Sensor 의 timeWordsInto:과 tickWordsInto: 메시지와 연관이 있다. 이 두 메시지는 최소 4 바이트의 바이트 색인 가능 객체를 인자로 취한다. primitiveTimeWordsInto 루틴은 1901년 1월 1일 이전 자정부터 경과한 초의 수를 부호가 없는 32-bit 정수로 하여 인자의 첫 4 바이트에 저장한다. primitiveTickWordsInto 루틴은 밀리초 시계의 초 수를 (마지막으로 리셋되거나 롤 오버된 이후) 부호가 없는 32-bit 정수로 하여 인자의 첫 4 바이트에 저장한다.
primitiveSignalAtTick 루틴은 ProcessorScheduler 내 signal:atTick: 메시지와 연관되어 있다. 이 메시지는 Semaphore를 첫 번째 인자로 취하고 최소 4 바이트의 바이트 색인 가능 객체를 두 번째 인자로 취한다. 두 번째 인자의 첫 4 바이트는 primitiveTickWordsInto 루틴이 저장한 타입의 부호가 없는 32-bit 정수로 해석된다. 인터프리터는 밀리 초 시계가 두 번째 인자가 명시한 값에 도달하면 Semaphore 인자를 시그널링해야 한다. 명시된 시간이 경과하면 Semaphore는 즉시 시그널링된다. 이러한 프리미티브는 그곳으로 전달될 마지막 Semaphore를 시그널링한다. 여기서 마지막 타이머 값에 도달하기 전에 새로운 호출이 이루어지면 마지막 Semaphore는 시그널링되지 않을 것이다. 첫 번째 인자가 Semaphore가 아닐 경우 현재 대기 중인 Semaphore는 잊혀질 것이다.
primitiveScanCharacters 루틴은 CharacterScanner 내의 scanCharactersFrom:to:in:rightX:stopConditions:displaying 메시지와 연관된 선택적 프리미티브이다. Smalltalk 메서드의 기능이 프리미티브 루틴에서 중복되면 텍스트 표시가 훨씬 빨라질 것이다. primitiveDrawLoop 루틴도 이와 유사하게 선택적 프리미티브로서 BitBIt 내의 drawLoopX:Y 메시지와 연관되어 있다. Smalltalk 메서드의 기능이 프리미티브 루틴에서 중복되면 직선 그리기가 훨씬 빨라질 것이다.
시스템 프리미티브(System Primitives)
마지막으로 7개의 프리미티브가 시스템 프리미티브로 그룹화된다.
dispatchSystemPrimitives
primitiveIndex = 110 ifTrue: [↑self primitiveEquivalent].
primitiveIndex = 111 ifTrue: [↑self primitiveClass].
primitiveIndex = 112 ifTrue: [↑self primitiveCoreLeft].
primitiveIndex = 113 ifTrue: [↑self primitiveQuit].
primitiveIndex = 114 ifTrue: [↑self primitiveExitToDebugger].
primitiveIndex = 115 ifTrue: [↑self primitiveOopsLeft].
primitiveIndex = 116 ifTrue: [↑self primitiveSignalAtOopsLeftWordsLeft]
primitiveEquivalent 루틴은 Object 내에 == 메시지와 연관된다. 수신자와 인자가 동일한 객체일 경우 (동일한 객체 포인터를 가진 경우) true를 리턴하고 그 외는 false를 리턴한다.
primitiveEquivalent
| thisObject otherObject |
otherObject ← self popStack.
thisObject ← self popStack.
thisObject = otherObject
ifTrue: [self push: TruePointer]
ifFalse: [self push: FalsePointer]
primitiveClass 루틴은 Object 내에 class 메시지와 연관된다. 이는 수신자의 클래스에 대한 객체 포인터를 리턴한다.
primitiveClass
| instance |
instance ← self popStack.
self push: (memory fetchClassOf: instance)
primitiveCoreLeft 루틴은 객체 공간에서 할당되지 않은 워드의 개수를 리턴한다. primitiveQuit 루틴은 호스트 기계에 또 다른 운영체제가 존재할 경우 그곳으로 나간다(exit). primitiveExitToDebugger 루틴은 기계어 디버거가 있을 경우 그것을 호출한다.