// Uses object identity to locate the next occurrence of the argument in the receiver from // the specified index to the specified index Oop* __fastcall Interpreter::primitiveStringSearch() { Oop* const sp = m_registers.m_stackPointer; Oop integerPointer = *sp; if (!ObjectMemoryIsIntegerObject(integerPointer)) return primitiveFailure(0); // startingAt not an integer const SMALLINTEGER startingAt = ObjectMemoryIntegerValueOf(integerPointer); Oop oopSubString = *(sp-1); BytesOTE* oteReceiver = reinterpret_cast<BytesOTE*>(*(sp-2)); if (ObjectMemory::fetchClassOf(oopSubString) != oteReceiver->m_oteClass) return primitiveFailure(2); // We know it can't be a SmallInteger because it has the same class as the receiver BytesOTE* oteSubString = reinterpret_cast<BytesOTE*>(oopSubString); VariantByteObject* bytesPattern = oteSubString->m_location; VariantByteObject* bytesReceiver = oteReceiver->m_location; const int M = oteSubString->bytesSize(); const int N = oteReceiver->bytesSize(); // Check 'startingAt' is in range if (startingAt < 1 || startingAt > N) return primitiveFailure(1); // out of bounds int nOffset = M == 0 || ((startingAt + M) - 1 > N) ? -1 : stringSearch(bytesReceiver->m_fields, N, bytesPattern->m_fields, M, startingAt - 1); *(sp-2) = ObjectMemoryIntegerObjectOf(nOffset+1); return sp-2; }
// Uses object identity to locate the next occurrence of the argument in the receiver from // the specified index to the specified index Oop* __fastcall Interpreter::primitiveNextIndexOfFromTo() { Oop integerPointer = stackTop(); if (!ObjectMemoryIsIntegerObject(integerPointer)) return primitiveFailure(0); // to not an integer const SMALLINTEGER to = ObjectMemoryIntegerValueOf(integerPointer); integerPointer = stackValue(1); if (!ObjectMemoryIsIntegerObject(integerPointer)) return primitiveFailure(1); // from not an integer SMALLINTEGER from = ObjectMemoryIntegerValueOf(integerPointer); Oop valuePointer = stackValue(2); OTE* receiverPointer = reinterpret_cast<OTE*>(stackValue(3)); // #ifdef _DEBUG if (ObjectMemoryIsIntegerObject(receiverPointer)) return primitiveFailure(2); // Not valid for SmallIntegers // #endif Oop answer = ZeroPointer; if (to >= from) { if (!receiverPointer->isPointers()) { // Search a byte object BytesOTE* oteBytes = reinterpret_cast<BytesOTE*>(receiverPointer); if (ObjectMemoryIsIntegerObject(valuePointer))// Arg MUST be an Integer to be a member { const MWORD byteValue = ObjectMemoryIntegerValueOf(valuePointer); if (byteValue < 256) // Only worth looking for 0..255 { const SMALLINTEGER length = oteBytes->bytesSize(); // We can only be in here if to>=from, so if to>=1, then => from >= 1 // furthermore if to <= length then => from <= length if (from < 1 || to > length) return primitiveFailure(2); // Search is in bounds, lets do it VariantByteObject* bytes = oteBytes->m_location; from--; while (from < to) if (bytes->m_fields[from++] == byteValue) { answer = ObjectMemoryIntegerObjectOf(from); break; } } } } else { // Search a pointer object - but only the indexable vars PointersOTE* oteReceiver = reinterpret_cast<PointersOTE*>(receiverPointer); VariantObject* receiver = oteReceiver->m_location; Behavior* behavior = receiverPointer->m_oteClass->m_location; const MWORD length = oteReceiver->pointersSize(); const MWORD fixedFields = behavior->m_instanceSpec.m_fixedFields; // Similar reasoning with to/from as for byte objects, but here we need to // take account of the fixed fields. if (from < 1 || (to + fixedFields > length)) return primitiveFailure(2); // Out of bounds Oop* indexedFields = receiver->m_fields + fixedFields; from--; while (from < to) if (indexedFields[from++] == valuePointer) { answer = ObjectMemoryIntegerObjectOf(from); break; } } } else answer = ZeroPointer; // Range is non-inclusive, cannot be there stackValue(3) = answer; return primitiveSuccess(3); }
// This is a double dispatched primitive which knows that the argument is a byte object (though // we still check this to avoid GPFs), and the receiver is guaranteed to be a byte object. e.g. // // aByteObject replaceBytesOf: anOtherByteObject from: start to: stop startingAt: startAt // BOOL __fastcall Interpreter::primitiveReplaceBytes() { Oop integerPointer = stackTop(); if (!ObjectMemoryIsIntegerObject(integerPointer)) return primitiveFailure(0); // startAt is not an integer SMALLINTEGER startAt = ObjectMemoryIntegerValueOf(integerPointer); integerPointer = stackValue(1); if (!ObjectMemoryIsIntegerObject(integerPointer)) return primitiveFailure(1); // stop is not an integer SMALLINTEGER stop = ObjectMemoryIntegerValueOf(integerPointer); integerPointer = stackValue(2); if (!ObjectMemoryIsIntegerObject(integerPointer)) return primitiveFailure(2); // start is not an integer SMALLINTEGER start = ObjectMemoryIntegerValueOf(integerPointer); OTE* argPointer = reinterpret_cast<OTE*>(stackValue(3)); if (ObjectMemoryIsIntegerObject(argPointer) || !argPointer->isBytes()) return primitiveFailure(3); // Argument MUST be a byte object // Empty move if stop before start, is considered valid regardless (strange but true) // this is the convention adopted by most implementations. if (stop >= start) { if (startAt < 1 || start < 1) return primitiveFailure(4); // Out-of-bounds // We still permit the argument to be an address to cut down on the number of primitives // and double dispatch methods we must implement (2 rather than 4) BYTE* pTo; Behavior* behavior = argPointer->m_oteClass->m_location; if (behavior->isIndirect()) { AddressOTE* oteBytes = reinterpret_cast<AddressOTE*>(argPointer); // We don't know how big the object is the argument points at, so cannot check length // against stop point pTo = static_cast<BYTE*>(oteBytes->m_location->m_pointer); } else { // We can test that we're not going to write off the end of the argument int length = argPointer->bytesSize(); // We can only be in here if stop>=start, so => stop-start >= 0 // therefore if startAt >= 1 then => stopAt >= 1, for similar // reasons (since stopAt >= startAt) we don't need to test // that startAt <= length if (stop > length) return primitiveFailure(4); // Bounds error VariantByteObject* argBytes = reinterpret_cast<BytesOTE*>(argPointer)->m_location; pTo = argBytes->m_fields; } BytesOTE* receiverPointer = reinterpret_cast<BytesOTE*>(stackValue(4)); // Now validate that the interval specified for copying from the receiver // is within the bounds of the receiver (we've already tested startAt) { int length = receiverPointer->bytesSize(); // We can only be in here if stop>=start, so if start>=1, then => stop >= 1 // furthermore if stop <= length then => start <= length int stopAt = startAt+stop-start; if (stopAt > length) return primitiveFailure(4); } // Only works for byte objects ASSERT(receiverPointer->isBytes()); VariantByteObject* receiverBytes = receiverPointer->m_location; #ifdef _DEBUG { Behavior* behavior = receiverPointer->m_oteClass->m_location; ASSERT(!behavior->isIndirect()); } #endif BYTE* pFrom = receiverBytes->m_fields; memmove(pTo+start-1, pFrom+startAt-1, stop-start+1); } // Answers the argument by moving it down over the receiver stackValue(4) = reinterpret_cast<Oop>(argPointer); pop(4); return TRUE; }
OTE* ObjectMemory::CopyElements(OTE* oteObj, MWORD startingAt, MWORD count) { // Note that startingAt is expected to be a zero-based index ASSERT(startingAt >= 0); OTE* oteSlice; if (oteObj->isBytes()) { BytesOTE* oteBytes = reinterpret_cast<BytesOTE*>(oteObj); size_t elementSize = ObjectMemory::GetBytesElementSize(oteBytes); if (count == 0 || ((startingAt + count) * elementSize <= oteBytes->bytesSize())) { MWORD objectSize = elementSize * count; if (oteBytes->m_flags.m_weakOrZ) { // TODO: Allocate the correct number of null term bytes based on the encoding auto newBytes = static_cast<VariantByteObject*>(allocObject(objectSize + NULLTERMSIZE, oteSlice)); // When copying strings, the slices has the same string class (oteSlice->m_oteClass = oteBytes->m_oteClass)->countUp(); memcpy(newBytes->m_fields, oteBytes->m_location->m_fields + (startingAt * elementSize), objectSize); *reinterpret_cast<NULLTERMTYPE*>(&newBytes->m_fields[objectSize]) = 0; oteSlice->beNullTerminated(); return oteSlice; } else { VariantByteObject* newBytes = static_cast<VariantByteObject*>(allocObject(objectSize, oteSlice)); // When copying bytes, the slice is always a ByteArray oteSlice->m_oteClass = Pointers.ClassByteArray; oteSlice->beBytes(); memcpy(newBytes->m_fields, oteBytes->m_location->m_fields + (startingAt * elementSize), objectSize); return oteSlice; } } } else { // Pointers PointersOTE* otePointers = reinterpret_cast<PointersOTE*>(oteObj); BehaviorOTE* oteClass = otePointers->m_oteClass; InstanceSpecification instSpec = oteClass->m_location->m_instanceSpec; if (instSpec.m_indexable) { startingAt += instSpec.m_fixedFields; if (count == 0 || (startingAt + count) <= otePointers->pointersSize()) { MWORD objectSize = SizeOfPointers(count); auto pSlice = static_cast<VariantObject*>(allocObject(objectSize, oteSlice)); // When copying pointers, the slice is always an Array oteSlice->m_oteClass = Pointers.ClassArray; VariantObject* pSrc = otePointers->m_location; for (MWORD i = 0; i < count; i++) { countUp(pSlice->m_fields[i] = pSrc->m_fields[startingAt + i]); } return oteSlice; } } } return nullptr; }