Oop* __fastcall Interpreter::primitivePerformMethod(CompiledMethod& , unsigned) { Oop * sp = m_registers.m_stackPointer; ArrayOTE* oteArg = reinterpret_cast<ArrayOTE*>(*(sp)); if (ObjectMemory::fetchClassOf(Oop(oteArg)) != Pointers.ClassArray) return primitiveFailure(0); // Arguments not an Array Array* arguments = oteArg->m_location; Oop receiverPointer = *(sp-1); MethodOTE* oteMethod = reinterpret_cast<MethodOTE*>(*(sp-2)); // Adjust sp to point at slot where receiver will be moved sp -= 2; //ASSERT(ObjectMemory::isKindOf(oteMethod, Pointers.ClassCompiledMethod)); CompiledMethod* method = oteMethod->m_location; if (!ObjectMemory::isKindOf(receiverPointer, method->m_methodClass)) return primitiveFailure(1); // Wrong class of receiver const unsigned argCount = oteArg->pointersSize(); const unsigned methodArgCount = method->m_header.argumentCount; if (methodArgCount != argCount) return primitiveFailure(2); // Wrong number of arguments // Push receiver and arguments on stack (over the top of array and receiver) sp[0] = receiverPointer; // Write receiver over the top of the method for (MWORD i = 0; i < argCount; i++) { Oop pushee = arguments->m_elements[i]; // Don't count up because we are adding a stack ref. sp[i+1] = pushee; } m_registers.m_stackPointer = sp+argCount; // Don't count down any args executeNewMethod(oteMethod, argCount); return primitiveSuccess(0); }
// Value with args takes an array of arguments Oop* __fastcall Interpreter::primitiveValueWithArgs() { Oop* bp = m_registers.m_stackPointer; ArrayOTE* argumentArray = reinterpret_cast<ArrayOTE*>(*(bp)); BlockOTE* oteBlock = reinterpret_cast<BlockOTE*>(*(bp-1)); ASSERT(ObjectMemory::fetchClassOf(Oop(oteBlock)) == Pointers.ClassBlockClosure); BlockClosure* block = oteBlock->m_location; const MWORD blockArgumentCount = block->m_info.argumentCount; BehaviorOTE* arrayClass = ObjectMemory::fetchClassOf(Oop(argumentArray)); if (arrayClass != Pointers.ClassArray) return primitiveFailure(1); const MWORD arrayArgumentCount = argumentArray->pointersSize(); if (arrayArgumentCount != blockArgumentCount) return primitiveFailure(0); pop(2); // N.B. ref count of Block will be assumed by storing into frame // Store old context details from interpreter registers m_registers.StoreContextRegisters(); // Overwrite receiver block with receiver at time of closure. Oop closureReceiver = block->m_receiver; *(bp-1) = closureReceiver; // No need to count up the receiver since we've written it into a stack slot Array* args = argumentArray->m_location; // Code this carefully so compiler generates optimal code (it makes a poor job on its own) Oop* sp = bp; // Push the args from the array { for (unsigned i=0;i<arrayArgumentCount;i++) { Oop pushee = args->m_elements[i]; *sp++ = pushee; // No need to count up since pushing on the stack } } const unsigned copiedValues = block->copiedValuesCount(oteBlock); { for (unsigned i=0;i<copiedValues;i++) { Oop oopCopied = block->m_copiedValues[i]; *sp++ = oopCopied; // No need to count up since pushing on the stack } } // Nil out any extra stack temp slots we need const unsigned extraTemps = block->stackTempsCount(); { const Oop nilPointer = Oop(Pointers.Nil); for (unsigned i=0;i<extraTemps;i++) *sp++ = nilPointer; } // Stack frame follows args... StackFrame* pFrame = reinterpret_cast<StackFrame*>(sp); pFrame->m_bp = reinterpret_cast<Oop>(bp)+1; m_registers.m_basePointer = reinterpret_cast<Oop*>(bp); // stack ref. removed so don't need to count down pFrame->m_caller = m_registers.activeFrameOop(); // Having set caller can update the active frame Oop m_registers.m_pActiveFrame = pFrame; // Note that ref. count remains the same due dto overwritten receiver slot const unsigned envTemps = block->envTempsCount(); if (envTemps > 0) { ContextOTE* oteContext = Context::New(envTemps, reinterpret_cast<Oop>(block->m_outer)); pFrame->m_environment = reinterpret_cast<Oop>(oteContext); Context* context = oteContext->m_location; context->m_block = oteBlock; // Block has been written into a heap object slot, so must count up oteBlock->countUp(); } else pFrame->m_environment = reinterpret_cast<Oop>(oteBlock); // We don't need to store down the IP and SP into the frame until it is suspended pFrame->m_ip = ZeroPointer; pFrame->m_sp = ZeroPointer; MethodOTE* oteMethod = block->m_method; pFrame->m_method = oteMethod; // Don't need to inc ref count for stack frame ref to method CompiledMethod* method = oteMethod->m_location; m_registers.m_pMethod = method; m_registers.m_instructionPointer = ObjectMemory::ByteAddressOfObjectContents(method->m_byteCodes) + block->initialIP() - 1; // New stack pointer points at last field of stack frame m_registers.m_stackPointer = reinterpret_cast<Oop*>(reinterpret_cast<BYTE*>(pFrame)+sizeof(StackFrame)) - 1; ASSERT(m_registers.m_stackPointer == &pFrame->m_bp); return primitiveSuccess(0); }
Oop* __fastcall Interpreter::primitivePerformWithArgs() { Oop* const sp = m_registers.m_stackPointer; ArrayOTE* argumentArray = reinterpret_cast<ArrayOTE*>(*(sp)); BehaviorOTE* arrayClass = ObjectMemory::fetchClassOf(Oop(argumentArray)); if (arrayClass != Pointers.ClassArray) return primitiveFailure(0); // N.B. We're using a large stack, so don't bother checking for overflow // (standard stack overflow mechanism should catch it) // We must not get the length outside, in case small integer arg const unsigned argCount = argumentArray->pointersSize(); // Save old message selector in case of prim failure (need to reinstate) SymbolOTE* performSelector = m_oopMessageSelector; // To ensure the argumentArray doesn't go away when we push its contents // onto the stack, in case we need it for recovery from an argument // count mismatch we leave its ref. count elevated SymbolOTE* selectorToPerform = reinterpret_cast<SymbolOTE*>(*(sp-1)); if (ObjectMemoryIsIntegerObject(selectorToPerform)) return primitiveFailure(1); m_oopMessageSelector = selectorToPerform; // Get selector from stack // Don't need to count down the stack ref. ASSERT(!selectorToPerform->isFree()); Oop newReceiver = *(sp-2); // receiver is under selector and arg array // Push the args from the array onto the stack. We must do this before // looking up the method, because if the receiver does not understand // the method then the lookup routines copy the arguments off the stack // into a Message object Array* args = argumentArray->m_location; for (MWORD i=0; i<argCount; i++) { Oop pushee = args->m_elements[i]; // Note no need to inc the ref. count when pushing on the stack sp[i-1] = pushee; } // Args written over top of selector and argument array (hence -2) m_registers.m_stackPointer = sp+argCount-2; // There is a subtle complication here when the receiver does not // understand the message, by which lookupMethodInClass() converts // the message we're trying to perform to a #doesNotUnderstand: with // all arguments moved to a Message. We still want to execute this // does not understand, so we also execute the method if the argument // counts do not match, but it was not understood. Note that it is // possible for a doesNotUnderstand: to be executed thru the first // test if the argumentArray contained only one argument. We allow // this to happen to avoid testing for not understood in the normal // case - just be aware of this anomaly. MethodOTE* methodPointer = findNewMethodInClass(ObjectMemory::fetchClassOf(newReceiver), argCount); CompiledMethod& method = *methodPointer->m_location; const unsigned methodArgCount = method.m_header.argumentCount; if (methodArgCount == argCount || m_oopMessageSelector == Pointers.DoesNotUnderstandSelector) { // WE no longer need the argument array, but don't count it down since we only have a stack ref. executeNewMethod(methodPointer, methodArgCount); return primitiveSuccess(0); } else { // Receiver must have understood the message, but we had wrong // number of arguments, so reinstate the stack and fail the primitive pop(argCount); pushObject((OTE*)m_oopMessageSelector); // Argument array already has artificially increased ref. count push(Oop(argumentArray)); m_oopMessageSelector = performSelector; return primitiveFailure(1); } }
// This primitive handles PositionableStream>>next, but only for Arrays, Strings and ByteArrays // Unary message, so does not modify stack pointer, and is therefore called directly from the ASM // primitive table without indirection through an ASM thunk. BOOL __fastcall Interpreter::primitiveNext() { PosStreamOTE* streamPointer = reinterpret_cast<PosStreamOTE*>(stackTop()); // Access receiver // Only works for subclasses of PositionableStream (or look alikes) //ASSERT(!ObjectMemoryIsIntegerObject(streamPointer) && ObjectMemory::isKindOf(streamPointer, Pointers.ClassPositionableStream)); PositionableStream* readStream = streamPointer->m_location; // Ensure valid stream - unusually this validity check is included in the Blue Book spec // and appears to be implemented in most Smalltalks, so we implement here too. if (!ObjectMemoryIsIntegerObject(readStream->m_index) || !ObjectMemoryIsIntegerObject(readStream->m_readLimit)) return primitiveFailure(0); // Receiver fails invariant check SMALLINTEGER index = ObjectMemoryIntegerValueOf(readStream->m_index); SMALLINTEGER limit = ObjectMemoryIntegerValueOf(readStream->m_readLimit); // Is the current index within the limits of the collection? // Remember that the index is 1 based (it's a Smalltalk index), and we're 0 based, // so we don't need to increment it until after we've got the next object if (index < 0 || index >= limit) return primitiveFailure(2); // No, fail it OTE* oteBuf = readStream->m_array; BehaviorOTE* bufClass = oteBuf->m_oteClass; if (bufClass == Pointers.ClassString) { StringOTE* oteString = reinterpret_cast<StringOTE*>(oteBuf); // A sanity check - ensure within bounds of object too (again in Blue Book spec) if (MWORD(index) >= oteString->bytesSize()) return primitiveFailure(3); String* buf = oteString->m_location; stackTop() = reinterpret_cast<Oop>(Character::New(buf->m_characters[index])); } // We also support ByteArrays in our primitiveNext (unlike BB). else if (bufClass == Pointers.ClassByteArray) { ByteArrayOTE* oteBytes = reinterpret_cast<ByteArrayOTE*>(oteBuf); if (MWORD(index) >= oteBytes->bytesSize()) return primitiveFailure(3); ByteArray* buf = oteBytes->m_location; stackTop() = ObjectMemoryIntegerObjectOf(buf->m_elements[index]); } else if (bufClass == Pointers.ClassArray) { ArrayOTE* oteArray = reinterpret_cast<ArrayOTE*>(oteBuf); if (MWORD(index) >= oteArray->pointersSize()) return primitiveFailure(3); Array* buf = oteArray->m_location; stackTop() = buf->m_elements[index]; } else return primitiveFailure(1); // Collection cannot be handled by primitive, rely on Smalltalk code // When incrementing the index we must allow for it overflowing a SmallInteger, even though // this is extremely unlikely in practice readStream->m_index = Integer::NewSigned32WithRef(index+1); return primitiveSuccess(); // Succeed }
// Non-standard, but has very beneficial effect on performance BOOL __fastcall Interpreter::primitiveNextPutAll() { Oop* sp = m_registers.m_stackPointer; WriteStreamOTE* streamPointer = reinterpret_cast<WriteStreamOTE*>(*(sp-1)); // Access receiver under argument WriteStream* writeStream = streamPointer->m_location; // Ensure valid stream - checks from Blue Book if (!ObjectMemoryIsIntegerObject(writeStream->m_index) || !ObjectMemoryIsIntegerObject(writeStream->m_writeLimit)) return primitiveFailure(0); // Fails invariant check SMALLINTEGER index = ObjectMemoryIntegerValueOf(writeStream->m_index); SMALLINTEGER limit = ObjectMemoryIntegerValueOf(writeStream->m_writeLimit); if (index < 0) return primitiveFailure(2); Oop value = *(sp); OTE* oteBuf = writeStream->m_array; BehaviorOTE* bufClass = oteBuf->m_oteClass; MWORD newIndex; if (bufClass == Pointers.ClassString) { BehaviorOTE* oteClass = ObjectMemory::fetchClassOf(value); if (oteClass != Pointers.ClassString && oteClass != Pointers.ClassSymbol) return primitiveFailure(4); // Attempt to put non-string StringOTE* oteString = reinterpret_cast<StringOTE*>(value); String* str = oteString->m_location; MWORD valueSize = oteString->bytesSize(); newIndex = MWORD(index)+valueSize; if (newIndex >= static_cast<MWORD>(limit)) // Beyond write limit return primitiveFailure(2); if (static_cast<int>(newIndex) >= oteBuf->bytesSizeForUpdate()) return primitiveFailure(3); // Attempt to write off end of buffer String* buf = static_cast<String*>(oteBuf->m_location); memcpy(buf->m_characters+index, str->m_characters, valueSize); } else if (bufClass == Pointers.ClassByteArray) { if (ObjectMemory::fetchClassOf(value) != bufClass) return primitiveFailure(4); // Attempt to put non-ByteArray ByteArrayOTE* oteBytes = reinterpret_cast<ByteArrayOTE*>(value); ByteArray* bytes = oteBytes->m_location; MWORD valueSize = oteBytes->bytesSize(); newIndex = MWORD(index)+valueSize; if (newIndex >= (MWORD)limit) // Beyond write limit return primitiveFailure(2); if (static_cast<int>(newIndex) >= oteBuf->bytesSizeForUpdate()) return primitiveFailure(3); // Attempt to write off end of buffer ByteArray* buf = static_cast<ByteArray*>(oteBuf->m_location); memcpy(buf->m_elements+index, bytes->m_elements, valueSize); } else if (bufClass == Pointers.ClassArray) { if (ObjectMemory::fetchClassOf(value) != Pointers.ClassArray) return primitiveFailure(4); // Attempt to put non-Array ArrayOTE* oteArray = reinterpret_cast<ArrayOTE*>(value); Array* array = oteArray->m_location; MWORD valueSize = oteArray->pointersSize(); newIndex = MWORD(index) + valueSize; if (newIndex >= (MWORD)limit) // Beyond write limit return primitiveFailure(2); if (static_cast<int>(newIndex) >= oteBuf->pointersSizeForUpdate()) return primitiveFailure(3); // Attempt to write off end of buffer Array* buf = static_cast<Array*>(oteBuf->m_location); for (MWORD i = 0; i < valueSize; i++) { ObjectMemory::storePointerWithValue(buf->m_elements[index + i], array->m_elements[i]); } } else return primitiveFailure(1); writeStream->m_index = Integer::NewUnsigned32WithRef(newIndex); // Increment the stream index // As we no longer pop stack here, the receiver is still under the argument *(sp-1) = value; return sizeof(Oop); // Pop 4 bytes }
// This primitive handles WriteStream>>NextPut:, but only for Arrays, Strings & ByteArrays // Uses but does not modify stack pointer, instead returns the number of bytes to // pop from the Smalltalk stack. BOOL __fastcall Interpreter::primitiveNextPut() { Oop* sp = m_registers.m_stackPointer; WriteStreamOTE* streamPointer = reinterpret_cast<WriteStreamOTE*>(*(sp-1)); // Access receiver under argument //ASSERT(!ObjectMemoryIsIntegerObject(streamPointer) && ObjectMemory::isKindOf(streamPointer, Pointers.ClassPositionableStream)); WriteStream* writeStream = streamPointer->m_location; // Ensure valid stream - checks from Blue Book if (!ObjectMemoryIsIntegerObject(writeStream->m_index) || !ObjectMemoryIsIntegerObject(writeStream->m_writeLimit)) return primitiveFailure(0); // Fails invariant check SMALLINTEGER index = ObjectMemoryIntegerValueOf(writeStream->m_index); SMALLINTEGER limit = ObjectMemoryIntegerValueOf(writeStream->m_writeLimit); // Within the bounds of the limit if (index < 0 || index >= limit) return primitiveFailure(2); Oop value = *(sp); OTE* oteBuf = writeStream->m_array; BehaviorOTE* bufClass = oteBuf->m_oteClass; if (bufClass == Pointers.ClassString) { if (ObjectMemory::fetchClassOf(value) != Pointers.ClassCharacter) return primitiveFailure(4); // Attempt to put non-character StringOTE* oteString = reinterpret_cast<StringOTE*>(oteBuf); if (index >= oteString->bytesSizeForUpdate()) return primitiveFailure(3); // Attempt to put non-character or off end of String String* buf = oteString->m_location; CharOTE* oteChar = reinterpret_cast<CharOTE*>(value); buf->m_characters[index] = static_cast<char>(oteChar->getIndex() - ObjectMemory::FirstCharacterIdx); } else if (bufClass == Pointers.ClassArray) { ArrayOTE* oteArray = reinterpret_cast<ArrayOTE*>(oteBuf); // In bounds of Array? if (index >= oteArray->pointersSizeForUpdate()) return primitiveFailure(3); Array* buf = oteArray->m_location; // We must ref. count value here as we're storing into a heap object slot ObjectMemory::storePointerWithValue(buf->m_elements[index], value); } else if (bufClass == Pointers.ClassByteArray) { if (!ObjectMemoryIsIntegerObject(value)) return primitiveFailure(4); // Attempt to put non-SmallInteger SMALLINTEGER intValue = ObjectMemoryIntegerValueOf(value); if (intValue < 0 || intValue > 255) return primitiveFailure(4); // Can only store 0..255 ByteArrayOTE* oteByteArray = reinterpret_cast<ByteArrayOTE*>(oteBuf); if (index >= oteByteArray->bytesSizeForUpdate()) return primitiveFailure(3); // Attempt to put non-character or off end of String oteByteArray->m_location->m_elements[index] = static_cast<BYTE>(intValue); } else return primitiveFailure(1); writeStream->m_index = Integer::NewSigned32WithRef(index + 1); // Increment the stream index // As we no longer pop stack here, the receiver is still under the argument *(sp-1) = value; return sizeof(Oop); // Pop 4 bytes }