runtime/runtime.cpp (4,002 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
#include "runtime.h"
#include <unistd.h>
#include <cinttypes>
#include <climits>
#include <csignal>
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cwchar>
#include <fstream>
#include <memory>
#include "array-module.h"
#include "attributedict.h"
#include "builtins-module.h"
#include "bytearray-builtins.h"
#include "bytecode.h"
#include "bytes-builtins.h"
#include "byteslike.h"
#include "capi.h"
#include "code-builtins.h"
#include "complex-builtins.h"
#include "descriptor-builtins.h"
#include "dict-builtins.h"
#include "event.h"
#include "exception-builtins.h"
#include "file.h"
#include "float-builtins.h"
#include "formatter-utils.h"
#include "frame-proxy-builtins.h"
#include "frame.h"
#include "function-builtins.h"
#include "generator-builtins.h"
#include "globals.h"
#include "handles.h"
#include "heap.h"
#include "int-builtins.h"
#include "interpreter.h"
#include "iterator-builtins.h"
#include "layout-builtins.h"
#include "layout.h"
#include "list-builtins.h"
#include "mappingproxy-builtins.h"
#include "memoryview-builtins.h"
#include "mmap-module.h"
#include "module-builtins.h"
#include "module-proxy-builtins.h"
#include "modules.h"
#include "mutex.h"
#include "object-builtins.h"
#include "os.h"
#include "range-builtins.h"
#include "ref-builtins.h"
#include "scavenger.h"
#include "set-builtins.h"
#include "siphash.h"
#include "slice-builtins.h"
#include "str-builtins.h"
#include "str-intern.h"
#include "strarray-builtins.h"
#include "super-builtins.h"
#include "sys-module.h"
#include "thread.h"
#include "traceback-builtins.h"
#include "tuple-builtins.h"
#include "type-builtins.h"
#include "under-collections-module.h"
#include "under-contextvars-module.h"
#include "under-io-module.h"
#include "under-signal-module.h"
#include "unicode.h"
#include "utils.h"
#include "valuecell-builtins.h"
#include "visitor.h"
namespace py {
static const SymbolId kRequiredModules[] = {
ID(_builtins), ID(builtins), ID(operator),
ID(_codecs), ID(_signal), ID(_frozen_importlib_external)};
word Runtime::next_module_index_ = 0;
wchar_t Runtime::exec_prefix_[PATH_MAX + 1] = L"";
wchar_t Runtime::module_search_path_[PATH_MAX + 1] = L"";
wchar_t Runtime::prefix_[PATH_MAX + 1] = L"";
wchar_t Runtime::program_name_[NAME_MAX + 1] = L"python3";
wchar_t* Runtime::programName() { return program_name_; }
void Runtime::setExecPrefix(const wchar_t* exec_prefix) {
DCHECK(wcslen(exec_prefix) < ARRAYSIZE(exec_prefix_),
"exec_prefix is too long");
std::wcsncpy(exec_prefix_, exec_prefix, ARRAYSIZE(exec_prefix_));
exec_prefix_[ARRAYSIZE(exec_prefix_) - 1] = L'\0';
}
void Runtime::setModuleSearchPath(const wchar_t* module_search_path) {
DCHECK(wcslen(module_search_path) < ARRAYSIZE(module_search_path_),
"module_search_path is too long");
std::wcsncpy(module_search_path_, module_search_path,
ARRAYSIZE(module_search_path_));
module_search_path_[ARRAYSIZE(module_search_path_) - 1] = L'\0';
}
void Runtime::setPrefix(const wchar_t* prefix) {
DCHECK(wcslen(prefix) < ARRAYSIZE(module_search_path_), "prefix is too long");
std::wcsncpy(prefix_, prefix, ARRAYSIZE(prefix_));
prefix_[ARRAYSIZE(prefix_) - 1] = L'\0';
}
void Runtime::setProgramName(const wchar_t* program_name) {
DCHECK(wcslen(program_name) < ARRAYSIZE(program_name_),
"program_name is too long");
std::wcsncpy(program_name_, program_name, ARRAYSIZE(program_name_));
prefix_[ARRAYSIZE(program_name_) - 1] = L'\0';
}
RandomState randomState() {
RandomState result;
OS::secureRandom(reinterpret_cast<byte*>(&result), sizeof(result));
return result;
}
RandomState randomStateFromSeed(uint64_t seed) {
// Splitmix64 as suggested by http://xoshiro.di.unimi.it.
auto next = [&seed]() {
uint64_t z = (seed += 0x9e3779b97f4a7c15);
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
return z ^ (z >> 31);
};
RandomState result;
for (size_t i = 0; i < ARRAYSIZE(result.state); i++) {
result.state[i] = next();
}
result.siphash24_secret = next();
for (size_t i = 0; i < ARRAYSIZE(result.extra_secret); i++) {
result.extra_secret[i] = next();
}
return result;
}
Runtime::Runtime(word heap_size, Interpreter* interpreter,
RandomState random_seed)
: heap_(heap_size), interpreter_(interpreter), random_state_(random_seed) {
Thread* thread = newThread();
thread->begin();
// This must be called before initializeTypes is called. Methods in
// initializeTypes rely on instances that are created in this method.
initializePrimitiveInstances();
initializeInterned(thread);
initializeSymbols(thread);
initializeLayouts();
initializeTypes(thread);
initializeCAPIState(this);
initializeModules(thread);
initializeCAPIModules();
initializeJITState();
// This creates a reference that prevents the linker from garbage collecting
// all of the symbols in debugging.cpp. This is a temporary workaround until
// we can fix the build to prevent symbols in debugging.cpp from being GCed.
extern void initializeDebugging();
initializeDebugging();
}
Runtime::~Runtime() {
is_finalizing_ = true;
// TODO(T30392425): This is an ugly and fragile workaround for having multiple
// runtimes created and destroyed by a single thread.
if (Thread::current() == nullptr) {
CHECK(main_thread_ != nullptr, "the runtime does not have any threads");
Thread::setCurrentThread(main_thread_);
}
callAtExit();
flushStdFiles();
finalizeSignals(Thread::current());
clearHandleScopes();
finalizeCAPIModules();
freeApiHandles();
finalizeCAPIState(this);
{
MutexGuard lock(&threads_mutex_);
Thread* thread = main_thread_;
Thread::setCurrentThread(nullptr);
main_thread_ = nullptr;
for (Thread* next; thread != nullptr; thread = next) {
next = thread->next();
delete thread;
}
}
delete symbols_;
delete machine_code_;
}
bool Runtime::allocateForMachineCode(word size, uword* address_out) {
DCHECK(Utils::isAligned(size, kPointerSize), "request %ld not aligned", size);
if (UNLIKELY(!machine_code_->allocate(size, address_out))) {
UNIMPLEMENTED("out of fixed memory");
}
return true;
}
RawObject Runtime::newBoundMethod(const Object& function, const Object& self) {
HandleScope scope(Thread::current());
BoundMethod bound_method(
&scope, newInstanceWithSize(LayoutId::kBoundMethod, BoundMethod::kSize));
bound_method.setFunction(*function);
bound_method.setSelf(*self);
return *bound_method;
}
RawObject Runtime::newLayout(LayoutId id) {
HandleScope scope(Thread::current());
Layout layout(&scope, newInstanceWithSize(LayoutId::kLayout, Layout::kSize));
layout.setId(id);
layout.setInObjectAttributes(empty_tuple_);
layout.setAdditions(newList());
layout.setDeletions(newList());
layout.setNumInObjectAttributes(0);
return *layout;
}
RawObject Runtime::layoutAtSafe(LayoutId layout_id) {
word id = static_cast<word>(layout_id);
if (id < 0 || id >= num_layouts_) {
return Error::notFound();
}
RawObject result = Tuple::cast(layouts_).at(id);
if (result == SmallInt::fromWord(0)) return Error::notFound();
return result;
}
// Sanity check that a subclass that has fixed attributes does inherit from a
// superclass with attributes that are fixed.
static void checkInObjectAttributesWithFixedOffset(const Tuple& attributes) {
for (word i = 0; i < attributes.length(); i++) {
AttributeInfo info(Tuple::cast(attributes.at(i)).at(1));
CHECK(info.isInObject() && info.isFixedOffset(),
"all superclass attributes must be in-object and fixed");
}
}
RawObject Runtime::layoutCreateSubclassWithBuiltins(
Thread* thread, LayoutId subclass_id, LayoutId superclass_id,
View<BuiltinAttribute> attributes, word size) {
HandleScope scope(thread);
// A builtin class is special since it contains attributes that must be
// located at fixed offsets from the start of an instance. These attributes
// are packed at the beginning of the layout starting at offset 0.
Layout super_layout(&scope, layoutAt(superclass_id));
Tuple super_attributes(&scope, super_layout.inObjectAttributes());
checkInObjectAttributesWithFixedOffset(super_attributes);
word super_attributes_len = super_attributes.length();
word attributes_length = attributes.length();
CHECK((attributes_length + super_attributes_len) * kPointerSize == size,
"Object size does not match attribute count");
for (word i = 0; i < attributes_length; i++) {
CHECK(attributes.get(i).offset == (i + super_attributes_len) * kPointerSize,
"unexpected attribute offset (not sorted or duplicated?)");
}
// Create an empty layout for the subclass
Layout result(&scope, newLayout(subclass_id));
// Copy down all of the superclass attributes into the subclass layout
result.setOverflowAttributes(super_layout.overflowAttributes());
word in_object_len = super_attributes_len + attributes_length;
if (in_object_len == 0) {
result.setInObjectAttributes(emptyTuple());
result.setNumInObjectAttributes(0);
} else {
MutableTuple in_object(&scope, newMutableTuple(in_object_len));
in_object.replaceFromWith(0, *super_attributes, super_attributes_len);
appendBuiltinAttributes(thread, attributes, in_object,
super_attributes_len);
// Install the in-object attributes
result.setInObjectAttributes(in_object.becomeImmutable());
result.setNumInObjectAttributes(in_object_len);
}
return *result;
}
void Runtime::appendBuiltinAttributes(Thread* thread,
View<BuiltinAttribute> attributes,
const MutableTuple& dst,
word start_index) {
if (attributes.length() == 0) {
return;
}
HandleScope scope(thread);
Object name(&scope, NoneType::object());
for (word i = 0; i < attributes.length(); i++) {
DCHECK((attributes.get(i).flags &
(AttributeFlags::kInObject | AttributeFlags::kDeleted |
AttributeFlags::kFixedOffset)) == 0,
"flag not allowed");
AttributeInfo info(attributes.get(i).offset,
attributes.get(i).flags | AttributeFlags::kInObject |
AttributeFlags::kFixedOffset);
SymbolId symbol_id = attributes.get(i).name;
name = symbol_id == SymbolId::kInvalid
? NoneType::object()
: static_cast<RawObject>(symbols()->at(symbol_id));
dst.atPut(start_index + i, layoutNewAttribute(name, info));
}
}
RawObject Runtime::newBytearray() {
HandleScope scope(Thread::current());
Bytearray result(&scope,
newInstanceWithSize(LayoutId::kBytearray, Bytearray::kSize));
result.setItems(empty_mutable_bytes_);
result.setNumItems(0);
return *result;
}
RawObject Runtime::newBytearrayIterator(Thread* thread,
const Bytearray& bytearray) {
HandleScope scope(thread);
BytearrayIterator result(&scope,
newInstanceWithSize(LayoutId::kBytearrayIterator,
BytearrayIterator::kSize));
result.setIterable(*bytearray);
result.setIndex(0);
return *result;
}
RawObject Runtime::createLargeBytes(word length) {
DCHECK(length > SmallBytes::kMaxLength, "fits into a SmallBytes");
word size = LargeBytes::allocationSize(length);
uword address;
CHECK(heap()->allocate(size, &address), "out of memory");
return LargeBytes::cast(
DataArray::initialize(address, length, LayoutId::kLargeBytes));
}
RawObject Runtime::createLargeInt(word num_digits) {
DCHECK(num_digits > 0, "num_digits must be positive");
word size = LargeInt::allocationSize(num_digits);
uword address;
CHECK(heap()->allocate(size, &address), "out of memory");
return LargeInt::cast(LargeInt::initialize(address, num_digits));
}
RawObject Runtime::createLargeStr(word length) {
DCHECK(length > RawSmallStr::kMaxLength,
"string len %ld is too small to be a large string", length);
word size = LargeStr::allocationSize(length);
uword address;
CHECK(heap()->allocate(size, &address), "out of memory");
return LargeStr::cast(
DataArray::initialize(address, length, LayoutId::kLargeStr));
}
RawObject Runtime::createMutableBytes(word length) {
DCHECK(length >= 0, "cannot allocate negative size");
word size = MutableBytes::allocationSize(length);
uword address;
CHECK(heap()->allocate(size, &address), "out of memory");
return MutableBytes::cast(
DataArray::initialize(address, length, LayoutId::kMutableBytes));
}
RawObject Runtime::newBytes(word length, byte fill) {
DCHECK(length >= 0, "invalid length %ld", length);
if (length <= SmallBytes::kMaxLength) {
byte buffer[SmallBytes::kMaxLength];
for (word i = 0; i < SmallBytes::kMaxLength; i++) {
buffer[i] = fill;
}
return SmallBytes::fromBytes({buffer, length});
}
HandleScope scope(Thread::current());
LargeBytes result(&scope, createLargeBytes(length));
std::memset(reinterpret_cast<byte*>(result.address()), fill, length);
return *result;
}
RawObject Runtime::newBytesWithAll(View<byte> array) {
word length = array.length();
if (length <= SmallBytes::kMaxLength) {
return SmallBytes::fromBytes(array);
}
HandleScope scope(Thread::current());
LargeBytes result(&scope, createLargeBytes(length));
std::memcpy(reinterpret_cast<byte*>(result.address()), array.data(), length);
return *result;
}
RawObject Runtime::newBytesIterator(Thread* thread, const Bytes& bytes) {
HandleScope scope(thread);
BytesIterator result(&scope, newInstanceWithSize(LayoutId::kBytesIterator,
BytesIterator::kSize));
result.setIndex(0);
result.setIterable(*bytes);
return *result;
}
RawObject Runtime::newTraceback() {
return newInstanceWithSize(LayoutId::kTraceback, Traceback::kSize);
}
RawObject Runtime::newType() { return newTypeWithMetaclass(LayoutId::kType); }
RawObject Runtime::newTypeWithMetaclass(LayoutId metaclass_id) {
Thread* thread = Thread::current();
HandleScope scope(thread);
Type result(&scope, newInstanceWithSize(metaclass_id, Type::kSize));
attributeDictInit(thread, result);
result.setFlagsAndBuiltinBase(Type::Flag::kNone, LayoutId::kObject);
result.setDoc(NoneType::object());
result.setAbstractMethods(Unbound::object());
return *result;
}
RawObject Runtime::newTypeProxy(const Type& type) {
Thread* thread = Thread::current();
HandleScope scope(thread);
TypeProxy result(&scope,
newInstanceWithSize(LayoutId::kTypeProxy, TypeProxy::kSize));
result.setType(*type);
return *result;
}
bool Runtime::isCallable(Thread* thread, const Object& obj) {
HandleScope scope(thread);
if (obj.isFunction() || obj.isBoundMethod() || obj.isType()) {
return true;
}
Type type(&scope, typeOf(*obj));
return !typeLookupInMroById(thread, *type, ID(__call__)).isError();
}
bool Runtime::isDeleteDescriptor(Thread* thread, const Object& object) {
HandleScope scope(thread);
Type type(&scope, typeOf(*object));
return type.hasFlag(Type::Flag::kHasDunderDelete);
}
bool Runtime::isIterator(Thread* thread, const Object& obj) {
HandleScope scope(thread);
Type type(&scope, typeOf(*obj));
return !typeLookupInMroById(thread, *type, ID(__next__)).isError();
}
bool Runtime::isMapping(Thread* thread, const Object& obj) {
if (obj.isDict()) return true;
HandleScope scope(thread);
Type type(&scope, typeOf(*obj));
return !typeLookupInMroById(thread, *type, ID(__getitem__)).isError();
}
bool Runtime::isSequence(Thread* thread, const Object& obj) {
if (isInstanceOfDict(*obj)) {
return false;
}
HandleScope scope(thread);
Type type(&scope, typeOf(*obj));
return !typeLookupInMroById(thread, *type, ID(__getitem__)).isError();
}
RawObject Runtime::newCode(word argcount, word posonlyargcount,
word kwonlyargcount, word nlocals, word stacksize,
word flags, const Object& code, const Object& consts,
const Object& names, const Object& varnames,
const Object& freevars, const Object& cellvars,
const Object& filename, const Object& name,
word firstlineno, const Object& lnotab) {
DCHECK(code.isInt() || isInstanceOfBytes(*code), "code must be bytes or int");
DCHECK(isInstanceOfTuple(*consts), "expected tuple");
DCHECK(isInstanceOfTuple(*names), "expected tuple");
DCHECK(isInstanceOfTuple(*varnames), "expected tuple");
DCHECK(isInstanceOfTuple(*freevars), "expected tuple");
DCHECK(isInstanceOfTuple(*cellvars), "expected tuple");
DCHECK(isInstanceOfStr(*filename), "expected str");
DCHECK(isInstanceOfStr(*name), "expected str");
DCHECK(isInstanceOfBytes(*lnotab), "expected bytes");
DCHECK(argcount >= 0, "argcount must not be negative");
DCHECK(posonlyargcount >= 0, "posonlyargcount must not be negative");
DCHECK(kwonlyargcount >= 0, "kwonlyargcount must not be negative");
DCHECK(nlocals >= 0, "nlocals must not be negative");
Thread* thread = Thread::current();
HandleScope scope(thread);
Tuple cellvars_tuple(&scope, tupleUnderlying(*cellvars));
Tuple freevars_tuple(&scope, tupleUnderlying(*freevars));
if (cellvars_tuple.length() == 0 && freevars_tuple.length() == 0) {
flags |= Code::Flags::kNofree;
} else {
flags &= ~Code::Flags::kNofree;
}
if (!code.isInt()) {
Bytes code_bytes(&scope, bytesUnderlying(*code));
CHECK(code_bytes.length() <= kMaxUint32, "code objects must fit in 4GB");
}
Code result(&scope, newInstanceWithSize(LayoutId::kCode, Code::kSize));
result.setArgcount(argcount);
result.setPosonlyargcount(posonlyargcount);
result.setKwonlyargcount(kwonlyargcount);
result.setNlocals(nlocals);
result.setStacksize(stacksize);
result.setFlags(flags);
result.setCode(*code);
result.setConsts(*consts);
result.setNames(*names);
result.setVarnames(*varnames);
result.setFreevars(*freevars);
result.setCellvars(*cellvars);
result.setFilename(*filename);
result.setName(*name);
result.setFirstlineno(firstlineno);
result.setLnotab(*lnotab);
result.setIntrinsic(nullptr);
Tuple varnames_tuple(&scope, tupleUnderlying(*varnames));
if (argcount > varnames_tuple.length() ||
kwonlyargcount > varnames_tuple.length() ||
result.totalArgs() > varnames_tuple.length()) {
return thread->raiseWithFmt(LayoutId::kValueError,
"code: varnames is too small");
}
strInternInTuple(thread, names);
strInternInTuple(thread, varnames);
strInternInTuple(thread, freevars);
strInternInTuple(thread, cellvars);
strInternConstants(thread, consts);
// Create mapping between cells and arguments if needed
if (result.numCellvars() > 0) {
MutableTuple cell2arg(&scope, newMutableTuple(result.numCellvars()));
cell2arg.fill(NoneType::object());
bool value_set = false;
for (word i = 0; i < result.numCellvars(); i++) {
for (word j = 0; j < result.totalArgs(); j++) {
if (Tuple::cast(*cellvars).at(i) == Tuple::cast(*varnames).at(j)) {
cell2arg.atPut(i, newInt(j));
value_set = true;
}
}
}
if (value_set) result.setCell2arg(cell2arg.becomeImmutable());
}
DCHECK(result.totalArgs() <= result.nlocals(), "invalid nlocals count");
return *result;
}
RawObject Runtime::newBuiltinCode(word argcount, word posonlyargcount,
word kwonlyargcount, word flags,
BuiltinFunction function,
const Object& parameter_names,
const Object& name_str) {
void* function_ptr = bit_cast<void*>(function);
DCHECK((reinterpret_cast<uword>(function_ptr) &
(1 << Object::kSmallIntTagBits)) == 0,
"function must be aligned");
Thread* thread = Thread::current();
HandleScope scope(thread);
Tuple empty_tuple(&scope, emptyTuple());
Object empty_string(&scope, Str::empty());
Object lnotab(&scope, Bytes::empty());
word nlocals = argcount + kwonlyargcount +
((flags & Code::Flags::kVarargs) != 0) +
((flags & Code::Flags::kVarkeyargs) != 0);
flags |= Code::Flags::kOptimized | Code::Flags::kNewlocals;
Object function_int(&scope, SmallInt::fromAlignedCPtr(function_ptr));
return newCode(argcount, posonlyargcount, kwonlyargcount, nlocals,
/*stacksize=*/0, flags, /*code=*/function_int,
/*consts=*/empty_tuple, /*names=*/empty_tuple,
/*varnames=*/parameter_names, /*freevars=*/empty_tuple,
/*cellvars=*/empty_tuple, /*filename=*/empty_string, name_str,
/*firstlineno=*/0, lnotab);
}
RawObject Runtime::newFunction(Thread* thread, const Object& name,
const Object& code, word flags, word argcount,
word total_args, word total_vars,
const Object& stacksize_or_builtin,
Function::Entry entry, Function::Entry entry_kw,
Function::Entry entry_ex) {
DCHECK(isInstanceOfStr(*name), "expected str");
HandleScope scope(thread);
Function function(&scope,
newInstanceWithSize(LayoutId::kFunction, Function::kSize));
function.setCode(*code);
function.setFlags(flags);
function.setArgcount(argcount);
function.setTotalArgs(total_args);
function.setTotalVars(total_vars);
function.setStacksizeOrBuiltin(*stacksize_or_builtin);
function.setName(*name);
function.setQualname(*name);
function.setEntry(entry);
function.setEntryKw(entry_kw);
function.setEntryEx(entry_ex);
function.setIntrinsic(nullptr);
populateEntryAsm(function);
return *function;
}
RawObject Runtime::newFunctionWithCode(Thread* thread, const Object& qualname,
const Code& code,
const Object& module_obj) {
DCHECK(module_obj.isNoneType() ||
thread->runtime()->isInstanceOfModule(*module_obj),
"module_obj should be either None or a Module");
HandleScope scope(thread);
Function::Entry entry;
Function::Entry entry_kw;
Function::Entry entry_ex;
word flags = code.flags();
if (code.kwonlyargcount() == 0 && (flags & Code::Flags::kNofree) &&
!(flags & (Code::Flags::kVarargs | Code::Flags::kVarkeyargs))) {
flags |= Function::Flags::kSimpleCall;
}
word stacksize = code.stacksize();
Object stacksize_or_builtin(&scope, NoneType::object());
if (!code.hasOptimizedAndNewlocals()) {
// We do not support calling non-optimized functions directly. We only allow
// them in Thread::exec() and Thread::runClassFunction().
entry = unimplementedTrampoline;
entry_kw = unimplementedTrampoline;
entry_ex = unimplementedTrampoline;
stacksize_or_builtin = SmallInt::fromWord(stacksize);
} else if (code.isNative()) {
entry = builtinTrampoline;
entry_kw = builtinTrampolineKw;
entry_ex = builtinTrampolineEx;
stacksize_or_builtin = code.code();
DCHECK(stacksize == 0, "expected zero stacksize");
} else if (code.isGeneratorLike()) {
entry = generatorTrampoline;
entry_kw = generatorTrampolineKw;
entry_ex = generatorTrampolineEx;
// HACK: Reserve one extra stack slot for the case where we need to unwrap a
// bound method.
stacksize++;
stacksize_or_builtin = SmallInt::fromWord(stacksize);
} else {
entry = interpreterTrampoline;
entry_kw = interpreterTrampolineKw;
entry_ex = interpreterTrampolineEx;
flags |= Function::Flags::kInterpreted;
// HACK: Reserve one extra stack slot for the case where we need to unwrap a
// bound method.
stacksize++;
stacksize_or_builtin = SmallInt::fromWord(stacksize);
}
Object name(&scope, code.name());
word total_args = code.totalArgs();
word total_vars =
code.nlocals() - total_args + code.numCellvars() + code.numFreevars();
Function function(
&scope,
newFunction(thread, name, code, flags, code.argcount(), total_args,
total_vars, stacksize_or_builtin, entry, entry_kw, entry_ex));
function.setIntrinsic(code.intrinsic());
populateEntryAsm(function);
if (isInstanceOfStr(*qualname)) {
function.setQualname(*qualname);
} else {
DCHECK(qualname.isNoneType(), "expected str or none type");
}
if (!module_obj.isNoneType()) {
Module module(&scope, *module_obj);
function.setModuleObject(*module_obj);
Object module_name(&scope, moduleAtById(thread, module, ID(__name__)));
if (!module_name.isErrorNotFound() && isInstanceOfStr(*module_name)) {
function.setModuleName(*module_name);
}
} else {
DCHECK(code.isNative(), "Only native code may have no globals");
}
Object consts_obj(&scope, code.consts());
if (consts_obj.isTuple()) {
Tuple consts(&scope, *consts_obj);
if (consts.length() >= 1 && consts.at(0).isStr()) {
function.setDoc(consts.at(0));
}
}
if (!code.isNative()) {
Bytes bytecode(&scope, code.code());
function.setRewrittenBytecode(expandBytecode(thread, bytecode));
// TODO(T45382423): Move this into a separate function to be called by a
// relevant opcode during opcode execution.
rewriteBytecode(thread, function);
}
return *function;
}
RawObject Runtime::newExceptionState() {
return newInstanceWithSize(LayoutId::kExceptionState, ExceptionState::kSize);
}
RawObject Runtime::newCoroutine() {
return newInstanceWithSize(LayoutId::kCoroutine, Coroutine::kSize);
}
RawObject Runtime::newFrameProxy(Thread* thread, const Object& function,
const Object& lasti) {
HandleScope scope(thread);
FrameProxy result(
&scope, newInstanceWithSize(LayoutId::kFrameProxy, FrameProxy::kSize));
result.setFunction(*function);
result.setLasti(*lasti);
return *result;
}
RawObject Runtime::newGenerator() {
return newInstanceWithSize(LayoutId::kGenerator, Generator::kSize);
}
RawObject Runtime::newGeneratorFrame(const Function& function) {
DCHECK(function.isGeneratorLike(), "expected a generator-like code object");
HandleScope scope(Thread::current());
word num_args = function.totalArgs();
word num_vars = function.totalVars();
word stacksize = SmallInt::cast(function.stacksizeOrBuiltin()).value();
// +1 for the function pointer.
word extra_words = num_args + num_vars + stacksize + 1;
GeneratorFrame frame(
&scope, newInstanceWithSize(
LayoutId::kGeneratorFrame,
GeneratorFrame::numAttributes(extra_words) * kPointerSize));
frame.setMaxStackSize(stacksize);
return *frame;
}
RawObject Runtime::newInstance(const Layout& layout) {
// This takes into account the potential overflow pointer.
RawInstance instance =
Instance::cast(newInstanceWithSize(layout.id(), layout.instanceSize()));
// Set the overflow array
if (layout.hasTupleOverflow()) {
instance.instanceVariableAtPut(layout.overflowOffset(), empty_tuple_);
}
return instance;
}
ALWAYS_INLINE USED RawObject Runtime::newInstanceWithSize(LayoutId layout_id,
word object_size) {
word num_attributes = object_size / kPointerSize;
word allocation_size = Instance::allocationSize(num_attributes);
uword address;
CHECK(heap()->allocate(allocation_size, &address), "out of memory");
return Instance::initializeWithNone(address, num_attributes, layout_id);
}
RawObject Runtime::newQualname(Thread* thread, const Type& type,
SymbolId name) {
HandleScope scope(thread);
Str type_name(&scope, type.name());
return newStrFromFmt("%S.%Y", &type_name, name);
}
RawObject Runtime::newDeque() {
HandleScope scope(Thread::current());
Deque deque(&scope, newInstanceWithSize(LayoutId::kDeque, Deque::kSize));
deque.setItems(SmallInt::fromWord(0));
deque.setLeft(0);
deque.setNumItems(0);
deque.setState(0);
return *deque;
}
RawObject Runtime::newDequeIterator(const Deque& deque, word index) {
HandleScope scope(Thread::current());
DequeIterator iter(&scope, newInstanceWithSize(LayoutId::kDequeIterator,
DequeIterator::kSize));
iter.setIndex(index);
iter.setIterable(*deque);
iter.setState(deque.state());
return *iter;
}
RawObject Runtime::newDequeReverseIterator(const Deque& deque, word index) {
HandleScope scope(Thread::current());
DequeReverseIterator iter(&scope,
newInstanceWithSize(LayoutId::kDequeReverseIterator,
DequeReverseIterator::kSize));
iter.setIndex(index);
iter.setIterable(*deque);
iter.setState(deque.state());
return *iter;
}
RawObject Runtime::newList() {
HandleScope scope(Thread::current());
List result(&scope, newInstanceWithSize(LayoutId::kList, List::kSize));
result.setNumItems(0);
result.setItems(empty_tuple_);
return *result;
}
RawObject Runtime::newListIterator(const Object& list) {
HandleScope scope(Thread::current());
ListIterator list_iterator(
&scope,
newInstanceWithSize(LayoutId::kListIterator, ListIterator::kSize));
list_iterator.setIndex(0);
list_iterator.setIterable(*list);
return *list_iterator;
}
RawObject Runtime::newSeqIterator(const Object& sequence) {
HandleScope scope(Thread::current());
SeqIterator iter(
&scope, newInstanceWithSize(LayoutId::kSeqIterator, SeqIterator::kSize));
iter.setIndex(0);
iter.setIterable(*sequence);
return *iter;
}
RawObject Runtime::newModule(const Object& name) {
Thread* thread = Thread::current();
HandleScope scope(thread);
Module result(&scope, newInstanceWithSize(LayoutId::kModule, Module::kSize));
attributeDictInit(thread, result);
result.setDef(newIntFromCPtr(nullptr));
result.setId(reserveModuleId());
Object init_result(&scope, moduleInit(thread, result, name));
if (init_result.isErrorException()) return *init_result;
return *result;
}
RawObject Runtime::newModuleProxy(const Module& module) {
Thread* thread = Thread::current();
HandleScope scope(thread);
ModuleProxy result(
&scope, newInstanceWithSize(LayoutId::kModuleProxy, ModuleProxy::kSize));
result.setModule(*module);
return *result;
}
RawObject Runtime::newSlotDescriptor(const Type& type, const Object& name) {
Thread* thread = Thread::current();
HandleScope scope(thread);
SlotDescriptor result(&scope, newInstanceWithSize(LayoutId::kSlotDescriptor,
SlotDescriptor::kSize));
result.setType(*type);
result.setName(*name);
return *result;
}
RawObject Runtime::newMemoryView(Thread* thread, const Object& obj,
const Object& buffer, word length,
ReadOnly read_only) {
HandleScope scope(thread);
MemoryView result(
&scope, newInstanceWithSize(LayoutId::kMemoryView, MemoryView::kSize));
result.setBuffer(*buffer);
result.setObject(*obj);
result.setLength(length);
result.setFormat(RawSmallStr::fromCodePoint('B'));
result.setReadOnly(read_only == ReadOnly::ReadOnly);
result.setStart(0);
Object length_obj(&scope, SmallInt::fromWord(length));
result.setShape(newTupleWith1(length_obj));
Object one(&scope, SmallInt::fromWord(1));
result.setNdim(*one);
result.setStrides(newTupleWith1(one));
return *result;
}
RawObject Runtime::newMemoryViewFromCPtr(Thread* thread, const Object& obj,
void* ptr, word length,
ReadOnly read_only) {
HandleScope scope(thread);
Object pointer(&scope, newPointer(ptr, length));
return newMemoryView(thread, obj, pointer, length, read_only);
}
RawObject Runtime::newMmap() {
HandleScope scope(Thread::current());
Mmap result(&scope, newInstanceWithSize(LayoutId::kMmap, Mmap::kSize));
result.setAccess(0);
result.setData(NoneType::object());
result.setFd(NoneType::object());
return *result;
}
RawObject Runtime::newMutableBytesZeroed(word size) {
if (size == 0) {
return empty_mutable_bytes_;
}
return createMutableBytes(size);
}
RawObject Runtime::mutableBytesFromBytes(Thread* thread, const Bytes& bytes) {
HandleScope scope(thread);
word len = bytes.length();
MutableBytes mb(&scope, createMutableBytes(len));
bytes.copyTo(reinterpret_cast<byte*>(mb.address()), len);
return *mb;
}
RawObject Runtime::mutableBytesWith(word length, byte value) {
if (length == 0) return empty_mutable_bytes_;
DCHECK(length > 0, "invalid length %ld", length);
HandleScope scope(Thread::current());
MutableBytes result(&scope, createMutableBytes(length));
std::memset(reinterpret_cast<byte*>(result.address()), value, length);
return *result;
}
RawObject Runtime::newIntFromCPtr(void* ptr) {
return newInt(reinterpret_cast<word>(ptr));
}
RawObject Runtime::newMutableTuple(word length) {
DCHECK(length > 0, "use emptyTuple() for MutableTuple with length 0");
word size = MutableTuple::allocationSize(length);
uword address;
CHECK(heap()->allocate(size, &address), "out of memory");
return MutableTuple::cast(MutableTuple::initialize(address, length));
}
RawObject Runtime::newTupleWith1(const Object& item1) {
RawMutableTuple result = MutableTuple::cast(newMutableTuple(1));
result.atPut(0, *item1);
return result.becomeImmutable();
}
RawObject Runtime::newTupleWith2(const Object& item1, const Object& item2) {
RawMutableTuple result = MutableTuple::cast(newMutableTuple(2));
result.atPut(0, *item1);
result.atPut(1, *item2);
return result.becomeImmutable();
}
RawObject Runtime::newTupleWith3(const Object& item1, const Object& item2,
const Object& item3) {
RawMutableTuple result = MutableTuple::cast(newMutableTuple(3));
result.atPut(0, *item1);
result.atPut(1, *item2);
result.atPut(2, *item3);
return result.becomeImmutable();
}
RawObject Runtime::newTupleWith4(const Object& item1, const Object& item2,
const Object& item3, const Object& item4) {
RawMutableTuple result = MutableTuple::cast(newMutableTuple(4));
result.atPut(0, *item1);
result.atPut(1, *item2);
result.atPut(2, *item3);
result.atPut(3, *item4);
return result.becomeImmutable();
}
RawObject Runtime::newTupleWithN(word num_items, const Object* item1, ...) {
RawMutableTuple result = MutableTuple::cast(newMutableTuple(num_items));
result.atPut(0, **item1);
va_list args;
va_start(args, item1);
for (word i = 1; i < num_items; i++) {
const Object* item = va_arg(args, const Object*);
result.atPut(i, **item);
}
va_end(args);
return result.becomeImmutable();
}
NEVER_INLINE
RawObject Runtime::newLargeIntFromWord(word value) {
DCHECK(!SmallInt::isValid(value), "value must not fit into SmallInt");
uword digit[1] = {static_cast<uword>(value)};
return newLargeIntWithDigits(digit);
}
RawObject Runtime::newIntFromUnsigned(uword value) {
if (static_cast<word>(value) >= 0 && SmallInt::isValid(value)) {
return SmallInt::fromWord(value);
}
uword digits[] = {value, 0};
View<uword> view(digits, digits[0] >> (kBitsPerWord - 1) ? 2 : 1);
return newLargeIntWithDigits(view);
}
RawObject Runtime::newFloat(double value) {
uword address;
CHECK(heap()->allocate(Float::allocationSize(), &address), "out of memory");
return Float::cast(Float::initialize(address, value));
}
RawObject Runtime::newComplex(double real, double imag) {
uword address;
CHECK(heap()->allocate(Complex::allocationSize(), &address), "out of memory");
return Complex::cast(Complex::initialize(address, real, imag));
}
RawObject Runtime::newLargeIntWithDigits(View<uword> digits) {
word length = digits.length();
DCHECK(length > 0, "must have at least 1 digit");
DCHECK(length > 1 || !SmallInt::isValid(digits.get(0)),
"must not fit into a SmallInt");
HandleScope scope(Thread::current());
LargeInt result(&scope, createLargeInt(digits.length()));
for (word i = 0; i < digits.length(); i++) {
result.digitAtPut(i, digits.get(i));
}
DCHECK(result.isValid(), "Invalid digits");
return *result;
}
RawObject Runtime::newPointer(void* cptr, word length) {
uword address;
CHECK(heap()->allocate(Pointer::allocationSize(), &address), "out of memory");
return Pointer::cast(Pointer::initialize(address, cptr, length));
}
RawObject Runtime::newProperty(const Object& getter, const Object& setter,
const Object& deleter) {
HandleScope scope(Thread::current());
Property new_prop(&scope,
newInstanceWithSize(LayoutId::kProperty, Property::kSize));
new_prop.setGetter(*getter);
new_prop.setSetter(*setter);
new_prop.setDeleter(*deleter);
return *new_prop;
}
RawObject Runtime::newRange(const Object& start, const Object& stop,
const Object& step) {
HandleScope scope(Thread::current());
Range result(&scope, newInstanceWithSize(LayoutId::kRange, Range::kSize));
result.setStart(*start);
result.setStop(*stop);
result.setStep(*step);
return *result;
}
RawObject Runtime::newLongRangeIterator(const Int& start, const Int& stop,
const Int& step) {
HandleScope scope(Thread::current());
LongRangeIterator result(&scope,
newInstanceWithSize(LayoutId::kLongRangeIterator,
LongRangeIterator::kSize));
result.setNext(*start);
result.setStop(*stop);
result.setStep(*step);
return *result;
}
RawObject Runtime::newRangeIterator(word start, word step, word length) {
HandleScope scope(Thread::current());
RangeIterator result(&scope, newInstanceWithSize(LayoutId::kRangeIterator,
RangeIterator::kSize));
result.setNext(start);
result.setStep(step);
result.setLength(length);
return *result;
}
RawObject Runtime::newSetIterator(const Object& set) {
HandleScope scope(Thread::current());
SetIterator result(
&scope, newInstanceWithSize(LayoutId::kSetIterator, SetIterator::kSize));
result.setIterable(*set);
result.setIndex(0);
result.setConsumedCount(0);
return *result;
}
RawObject Runtime::newSlice(const Object& start, const Object& stop,
const Object& step) {
if (start.isNoneType() && stop.isNoneType() && step.isNoneType()) {
return emptySlice();
}
HandleScope scope(Thread::current());
Slice slice(&scope, newInstanceWithSize(LayoutId::kSlice, Slice::kSize));
slice.setStart(*start);
slice.setStop(*stop);
slice.setStep(*step);
return *slice;
}
RawObject Runtime::newStaticMethod() {
return newInstanceWithSize(LayoutId::kStaticMethod, StaticMethod::kSize);
}
RawObject Runtime::newStrArray() {
HandleScope scope(Thread::current());
StrArray result(&scope,
newInstanceWithSize(LayoutId::kStrArray, StrArray::kSize));
result.setItems(empty_mutable_bytes_);
result.setNumItems(0);
return *result;
}
RawObject Runtime::newStrFromCStr(const char* c_str) {
word length = std::strlen(c_str);
auto data = reinterpret_cast<const byte*>(c_str);
return newStrWithAll(View<byte>(data, length));
}
RawObject Runtime::strFromStrArray(const StrArray& array) {
word length = array.numItems();
if (length <= SmallStr::kMaxLength) {
byte buffer[SmallStr::kMaxLength];
array.copyTo(buffer, length);
return SmallStr::fromBytes({buffer, length});
}
HandleScope scope(Thread::current());
LargeStr result(&scope, createLargeStr(length));
array.copyTo(reinterpret_cast<byte*>(result.address()), length);
return *result;
}
static word strFormat(Thread* thread, const MutableBytes& dst,
bool determine_size, const char* fmt, va_list args) {
HandleScope scope(thread);
word fragment_begin = 0;
word fmt_index = 0;
word dst_index = 0;
word size = determine_size ? -1 : dst.length();
for (; fmt[fmt_index] != '\0'; fmt_index++) {
if (fmt[fmt_index] != '%') {
continue;
}
word fragment_length = fmt_index - fragment_begin;
if (!determine_size) {
std::memcpy(reinterpret_cast<void*>(dst.address() + dst_index),
fmt + fragment_begin, fragment_length);
}
dst_index += fragment_length;
fmt_index++;
fragment_begin = fmt_index + 1;
switch (fmt[fmt_index]) {
case 'c': {
int value_int = va_arg(args, int); // Note that C promotes char to int.
if (value_int < 0 || value_int > kMaxASCII) {
// Replace non-ASCII characters.
RawSmallStr value = SmallStr::fromCodePoint(kReplacementCharacter);
word length = value.length();
if (!determine_size) {
dst.replaceFromWithStr(dst_index, Str::cast(value), length);
}
dst_index += length;
break;
}
if (!determine_size) {
dst.byteAtPut(dst_index, static_cast<char>(value_int));
}
dst_index++;
} break;
case 'd': {
int value = va_arg(args, int);
char* print_dst =
determine_size ? nullptr
: reinterpret_cast<char*>(dst.address() + dst_index);
size_t print_len = determine_size ? 0 : size - dst_index + 1;
dst_index += std::snprintf(print_dst, print_len, "%d", value);
} break;
case 'g': {
double value = va_arg(args, double);
char* print_dst =
determine_size ? nullptr
: reinterpret_cast<char*>(dst.address() + dst_index);
size_t print_len = determine_size ? 0 : size - dst_index + 1;
dst_index += std::snprintf(print_dst, print_len, "%g", value);
} break;
case 's': {
const char* value = va_arg(args, char*);
word length = std::strlen(value);
if (!determine_size) {
std::memcpy(reinterpret_cast<byte*>(dst.address() + dst_index), value,
length);
}
dst_index += length;
} break;
case 'w': {
word value = va_arg(args, word);
char* print_dst =
determine_size ? nullptr
: reinterpret_cast<char*>(dst.address() + dst_index);
size_t print_len = determine_size ? 0 : size - dst_index + 1;
dst_index += std::snprintf(print_dst, print_len, "%" PRIdPTR, value);
} break;
case 'x': {
unsigned value = va_arg(args, unsigned);
char* print_dst =
determine_size ? nullptr
: reinterpret_cast<char*>(dst.address() + dst_index);
size_t print_len = determine_size ? 0 : size - dst_index + 1;
dst_index += std::snprintf(print_dst, print_len, "%x", value);
} break;
case 'C': {
int32_t value_int = va_arg(args, int32_t);
if (value_int < 0 || value_int > kMaxUnicode) {
value_int = kReplacementCharacter;
}
RawSmallStr value = SmallStr::fromCodePoint(value_int);
word length = value.length();
if (!determine_size) {
dst.replaceFromWithStr(dst_index, Str::cast(value), length);
}
dst_index += length;
} break;
case 'S': {
Object value_obj(&scope, **va_arg(args, Object*));
Str value(&scope, strUnderlying(*value_obj));
word length = value.length();
if (!determine_size) {
dst.replaceFromWithStr(dst_index, *value, length);
}
dst_index += length;
} break;
case 'F': {
Object obj(&scope, **va_arg(args, Object*));
Function function(&scope, *obj);
Str value(&scope, function.qualname());
word length = value.length();
if (!determine_size) {
dst.replaceFromWithStr(dst_index, *value, length);
}
dst_index += length;
} break;
case 'T': {
Object obj(&scope, **va_arg(args, Object*));
Type type(&scope, thread->runtime()->typeOf(*obj));
Str value(&scope, type.name());
word length = value.length();
if (!determine_size) {
dst.replaceFromWithStr(dst_index, *value, length);
}
dst_index += length;
} break;
case 'Y': {
SymbolId symbol = va_arg(args, SymbolId);
RawStr value = Str::cast(thread->runtime()->symbols()->at(symbol));
word length = value.length();
if (!determine_size) {
dst.replaceFromWithStr(dst_index, value, length);
}
dst_index += length;
} break;
case '%':
if (!determine_size) {
dst.byteAtPut(dst_index, '%');
}
dst_index++;
break;
default:
UNIMPLEMENTED("Unsupported format specifier");
}
DCHECK(determine_size || dst_index <= size, "dst buffer overflow");
}
word fragment_length = fmt_index - fragment_begin;
if (!determine_size) {
std::memcpy(reinterpret_cast<void*>(dst.address() + dst_index),
fmt + fragment_begin, fragment_length);
}
dst_index += fragment_length;
DCHECK(determine_size || dst_index == size, "dst buffer underflow");
return dst_index;
}
RawObject Runtime::newStrFromFmtV(Thread* thread, const char* fmt,
va_list args) {
va_list args_copy;
va_copy(args_copy, args);
HandleScope scope(thread);
MutableBytes result(&scope, emptyMutableBytes());
word length = strFormat(thread, result, /*determine_size=*/true, fmt, args);
result = newMutableBytesUninitialized(length);
strFormat(thread, result, /*determine_size=*/false, fmt, args_copy);
va_end(args_copy);
return result.becomeStr();
}
RawObject Runtime::newStrFromFmt(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
Thread* thread = Thread::current();
HandleScope scope(thread);
Object result(&scope, newStrFromFmtV(thread, fmt, args));
va_end(args);
return *result;
}
RawObject Runtime::newStrFromUTF32(View<int32_t> code_units) {
word size = 0;
for (word i = 0; i < code_units.length(); ++i) {
int32_t cp = code_units.get(i);
if (cp <= kMaxASCII) {
size += 1;
} else if (cp < 0x0800) {
size += 2;
} else if (cp < 0x010000) {
size += 3;
} else {
DCHECK(cp <= kMaxUnicode, "invalid codepoint");
size += 4;
}
}
if (size <= RawSmallStr::kMaxLength) {
byte dst[SmallStr::kMaxLength];
for (word i = 0, j = 0; i < code_units.length(); ++i) {
RawSmallStr src = SmallStr::fromCodePoint(code_units.get(i));
word num_bytes = src.length();
src.copyTo(&dst[j], num_bytes);
j += num_bytes;
}
return SmallStr::fromBytes(View<byte>(dst, size));
}
RawObject result = createLargeStr(size);
DCHECK(!result.isError(), "failed to create large string");
byte* dst = reinterpret_cast<byte*>(LargeStr::cast(result).address());
if (code_units.length() == size) {
// ASCII fastpath
for (word i = 0; i < size; ++i) {
dst[i] = code_units.get(i);
}
return result;
}
for (word i = 0, j = 0; i < code_units.length(); ++i) {
RawSmallStr src = SmallStr::fromCodePoint(code_units.get(i));
word num_bytes = src.length();
src.copyTo(&dst[j], num_bytes);
j += num_bytes;
}
return result;
}
RawObject Runtime::newStrWithAll(View<byte> code_units) {
word length = code_units.length();
if (length <= RawSmallStr::kMaxLength) {
return SmallStr::fromBytes(code_units);
}
RawObject result = createLargeStr(length);
DCHECK(!result.isError(), "failed to create large string");
byte* dst = reinterpret_cast<byte*>(LargeStr::cast(result).address());
const byte* src = code_units.data();
memcpy(dst, src, length);
return result;
}
void NEVER_INLINE Runtime::internSetGrow(Thread* thread) {
interned_ = py::internSetGrow(thread, MutableTuple::cast(interned_),
&interned_remaining_);
}
RawObject Runtime::internLargeStr(Thread* thread, const Object& str) {
Runtime* runtime = thread->runtime();
RawObject result = NoneType::object();
if (internSetAdd(thread, MutableTuple::cast(runtime->interned_), str,
&result)) {
DCHECK(result == str, "expected str");
if (--runtime->interned_remaining_ == 0) {
runtime->internSetGrow(thread);
return *str;
}
}
return result;
}
RawObject Runtime::internStrFromAll(Thread* thread, View<byte> bytes) {
if (bytes.length() <= SmallStr::kMaxLength) {
return SmallStr::fromBytes(bytes);
}
Runtime* runtime = thread->runtime();
RawObject result = NoneType::object();
if (internSetAddFromAll(thread, MutableTuple::cast(runtime->interned_), bytes,
&result)) {
if (--runtime->interned_remaining_ == 0) {
HandleScope scope(thread);
Object result_obj(&scope, result);
runtime->internSetGrow(thread);
return *result_obj;
}
}
return result;
}
RawObject Runtime::internStrFromCStr(Thread* thread, const char* c_str) {
View<byte> bytes(reinterpret_cast<const byte*>(c_str), std::strlen(c_str));
return internStrFromAll(thread, bytes);
}
bool Runtime::isInternedStr(Thread* thread, const Object& str) {
DCHECK(str.isStr(), "expected str");
if (str.isSmallStr()) {
return true;
}
return internSetContains(MutableTuple::cast(thread->runtime()->interned_),
LargeStr::cast(*str));
}
word Runtime::hash(RawObject object) {
if (!object.isHeapObject()) {
return immediateHash(object);
}
if (object.isLargeBytes() || object.isLargeStr()) {
return valueHash(object);
}
return identityHash(object);
}
word Runtime::immediateHash(RawObject object) {
if (object.isSmallStr()) {
return SmallStr::cast(object).hash();
}
if (object.isSmallInt()) {
return SmallInt::cast(object).hash();
}
if (object.isBool()) {
return Bool::cast(object).hash();
}
if (object.isSmallBytes()) {
return SmallBytes::cast(object).hash();
}
return static_cast<word>(object.raw());
}
// Xoroshiro128+
// http://xoroshiro.di.unimi.it/
uword Runtime::random() {
const uint64_t s0 = random_state_.state[0];
uint64_t s1 = random_state_.state[1];
const uint64_t result = s0 + s1;
s1 ^= s0;
random_state_.state[0] = Utils::rotateLeft(s0, 55) ^ s1 ^ (s1 << 14);
random_state_.state[1] = Utils::rotateLeft(s1, 36);
return result;
}
RawObject* Runtime::finalizableReferences() { return &finalizable_references_; }
word Runtime::identityHash(RawObject object) {
RawHeapObject src = HeapObject::cast(object);
word code = src.header().hashCode();
if (code == RawHeader::kUninitializedHash) {
code = random() & RawHeader::kHashCodeMask;
code = (code == RawHeader::kUninitializedHash) ? code + 1 : code;
src.setHeader(src.header().withHashCode(code));
}
return code;
}
word Runtime::siphash24(View<byte> array) {
word result = 0;
::halfsiphash(
array.data(), array.length(),
reinterpret_cast<const uint8_t*>(&random_state_.siphash24_secret),
reinterpret_cast<uint8_t*>(&result), sizeof(result));
return result;
}
uint64_t Runtime::hashWithKey(const Bytes& bytes, uint64_t key) {
uint64_t result = 0;
word length = bytes.length();
byte small_buffer[SmallBytes::kMaxLength];
byte* data;
if (bytes.isSmallBytes()) {
bytes.copyTo(small_buffer, length);
data = small_buffer;
} else {
data = reinterpret_cast<byte*>(LargeBytes::cast(*bytes).address());
}
::halfsiphash(data, length, reinterpret_cast<const uint8_t*>(&key),
reinterpret_cast<uint8_t*>(&result), sizeof(result));
return result;
}
word Runtime::bytesHash(View<byte> array) {
word result = siphash24(array);
result &= RawHeader::kHashCodeMask;
return (result == RawHeader::kUninitializedHash) ? result + 1 : result;
}
word Runtime::valueHash(RawObject object) {
RawHeapObject src = HeapObject::cast(object);
RawHeader header = src.header();
word code = header.hashCode();
if (code == RawHeader::kUninitializedHash) {
word size = src.headerCountOrOverflow();
code = bytesHash(View<byte>(reinterpret_cast<byte*>(src.address()), size));
src.setHeader(header.withHashCode(code));
DCHECK(code == src.header().hashCode(), "hash failure");
}
return code;
}
const byte* Runtime::hashSecret(size_t size) {
CHECK(size <= sizeof(random_state_.extra_secret),
"hash secret request too big");
return reinterpret_cast<const byte*>(&random_state_.extra_secret);
}
RawObject Runtime::handlePendingSignals(Thread* thread) {
thread->clearInterrupt(Thread::kSignal);
if (!is_signal_pending_ || !thread->isMainThread()) {
return NoneType::object();
}
is_signal_pending_ = false;
HandleScope scope(thread);
for (word i = 1; i < OS::kNumSignals; i++) {
if (OS::pending_signals_[i]) {
OS::pending_signals_[i] = false;
// NOTE: we do not expose frame objects to the user
Object callback(&scope, signalCallback(i));
Object signum(&scope, SmallInt::fromWord(i));
Object frame(&scope, NoneType::object());
Object result(&scope,
Interpreter::call2(thread, callback, signum, frame));
if (result.isErrorException()) {
is_signal_pending_ = true;
return *result;
}
}
}
return NoneType::object();
}
void Runtime::initializeSignals(Thread* thread, const Module& under_signal) {
if (!signal_callbacks_.isNoneType()) {
return; // already initialized
}
signal_stack_ = std::malloc(SIGSTKSZ);
CHECK(signal_stack_ != nullptr, "out of memory");
stack_t altstack;
altstack.ss_sp = signal_stack_;
altstack.ss_size = SIGSTKSZ;
altstack.ss_flags = 0;
CHECK(::sigaltstack(&altstack, nullptr) == 0,
"unable to create signal-handling stack");
HandleScope scope(thread);
MutableTuple callbacks(&scope, newMutableTuple(OS::kNumSignals));
is_signal_pending_ = false;
signal_callbacks_ = *callbacks;
OS::pending_signals_[0] = false;
for (int i = 1; i < OS::kNumSignals; i++) {
OS::pending_signals_[i] = false;
SignalHandler handler = OS::signalHandler(i);
if (handler == SIG_DFL) {
callbacks.atPut(i, kDefaultHandler);
} else if (handler == SIG_IGN) {
callbacks.atPut(i, kIgnoreHandler);
}
}
if (callbacks.at(SIGINT) == kDefaultHandler) {
callbacks.atPut(
SIGINT, moduleAtById(thread, under_signal, ID(default_int_handler)));
OS::setSignalHandler(SIGINT, handleSignal);
}
}
void Runtime::finalizeSignals(Thread* thread) {
if (signal_callbacks_.isNoneType()) return;
stack_t altstack = {};
altstack.ss_size = SIGSTKSZ;
altstack.ss_flags = SS_DISABLE;
CHECK(::sigaltstack(&altstack, nullptr) == 0,
"unable to disable signal-handling stack");
std::free(signal_stack_);
HandleScope scope(thread);
MutableTuple callbacks(&scope, signal_callbacks_);
Object callback(&scope, NoneType::object());
for (int i = 1; i < OS::kNumSignals; i++) {
callback = callbacks.at(i);
if (callback != kDefaultHandler && callback != kIgnoreHandler) {
OS::setSignalHandler(i, SIG_DFL);
}
}
}
void Runtime::setPendingSignal(Thread* thread, int signum) {
if (thread != main_thread_) return;
OS::pending_signals_[signum] = true;
is_signal_pending_ = true;
thread->interrupt(Thread::kSignal);
if (wakeup_fd_ < 0) return;
byte b = signum;
File::write(wakeup_fd_, &b, 1);
// TODO(wmeehan): add pending call to report wakeup error
}
RawObject Runtime::setSignalCallback(word signum, const Object& callback) {
RawMutableTuple callbacks = MutableTuple::cast(signal_callbacks_);
RawObject result = callbacks.at(signum);
callbacks.atPut(signum, *callback);
return result;
}
RawObject Runtime::signalCallback(word signum) {
return Tuple::cast(signal_callbacks_).at(signum);
}
void Runtime::populateEntryAsm(const Function& function) {
function.setEntryAsm(interpreter_->entryAsm(function));
}
static const word kFixedSpaceSize = 1 * kGiB;
void Runtime::initializeJITState() {
machine_code_ = new Space(kFixedSpaceSize);
// TODO(T89276586): Only set X bit per-page after code is written and
// finalized.
OS::protectMemory(reinterpret_cast<byte*>(machine_code_->start()),
machine_code_->size(), OS::kReadWriteExecute);
}
void Runtime::initializeLayouts() {
layouts_ = newMutableTuple(kInitialLayoutTupleCapacity);
static_assert(
static_cast<word>(LayoutId::kLastBuiltinId) < kInitialLayoutTupleCapacity,
"initial layout tuple size too small");
num_layouts_ = static_cast<word>(LayoutId::kLastBuiltinId) + 1;
layout_type_transitions_ =
newMutableTuple(LayoutTypeTransition::kTransitionSize);
}
static const BuiltinAttribute kExceptionStateAttributes[] = {
{ID(_exception_state__type), ExceptionState::kTypeOffset,
AttributeFlags::kHidden},
{ID(_exception_state__value), ExceptionState::kValueOffset,
AttributeFlags::kHidden},
{ID(_exception_state__traceback), ExceptionState::kTracebackOffset,
AttributeFlags::kHidden},
{ID(_exception_state__previous), ExceptionState::kPreviousOffset,
AttributeFlags::kHidden},
};
static const BuiltinAttribute kPointerAttributes[] = {
{ID(_pointer__cptr), Pointer::kCPtrOffset, AttributeFlags::kHidden},
{ID(_pointer__length), Pointer::kLengthOffset, AttributeFlags::kHidden},
};
void Runtime::initializeTypes(Thread* thread) {
initializeObjectTypes(thread);
initializeArrayType(thread);
initializeBytearrayTypes(thread);
initializeBytesTypes(thread);
initializeCodeType(thread);
initializeComplexType(thread);
initializeDescriptorTypes(thread);
initializeDictTypes(thread);
initializeExceptionTypes(thread);
initializeFloatType(thread);
initializeFrameProxyType(thread);
initializeFunctionTypes(thread);
initializeGeneratorTypes(thread);
initializeIntTypes(thread);
initializeIteratorType(thread);
initializeLayoutType(thread);
initializeListTypes(thread);
initializeMappingProxyType(thread);
initializeMemoryViewType(thread);
initializeMmapType(thread);
initializeModuleProxyType(thread);
initializeModuleType(thread);
initializeRangeTypes(thread);
initializeRefTypes(thread);
initializeSetTypes(thread);
initializeSliceType(thread);
initializeStrArrayType(thread);
initializeStrTypes(thread);
initializeSuperType(thread);
initializeTracebackType(thread);
initializeTupleTypes(thread);
initializeTypeTypes(thread);
initializeUnderCollectionsTypes(thread);
initializeUnderContextvarsTypes(thread);
initializeUnderIOTypes(thread);
initializeValueCellTypes(thread);
addBuiltinType(thread, ID(ExceptionState), LayoutId::kExceptionState,
LayoutId::kObject, kExceptionStateAttributes,
ExceptionState::kSize, /*basetype=*/false);
addBuiltinType(thread, ID(_mutablebytes), LayoutId::kMutableBytes,
LayoutId::kObject, kNoAttributes, MutableBytes::kSize,
/*basetype=*/false);
addBuiltinType(thread, ID(_mutabletuple), LayoutId::kMutableTuple,
LayoutId::kObject, kNoAttributes, MutableTuple::kSize,
/*basetype=*/false);
addBuiltinType(thread, ID(_pointer), LayoutId::kPointer, LayoutId::kObject,
kPointerAttributes, Pointer::kSize, /*basetype=*/false);
addBuiltinType(thread, ID(ellipsis), LayoutId::kEllipsis, LayoutId::kObject,
kNoAttributes, Ellipsis::kSize, /*basetype=*/false);
}
void Runtime::collectGarbageInto(CompactionDestination destination) {
EVENT(CollectGarbage);
bool run_callback = callbacks_ == NoneType::object();
RawObject cb = (destination == CompactionDestination::kImmortalPartition)
? scavengeImmortalize(this)
: scavenge(this);
callbacks_ = WeakRef::spliceQueue(callbacks_, cb);
if (run_callback) {
processCallbacks();
}
if (finalizable_references_ != NoneType::object()) {
processFinalizers();
}
}
Thread* Runtime::newThread() {
Thread* thread = new Thread(this, Thread::kDefaultStackSize);
{
MutexGuard lock(&threads_mutex_);
if (main_thread_ == nullptr) {
main_thread_ = thread;
} else {
Thread* next = main_thread_->next();
main_thread_->setNext(thread);
thread->setNext(next);
thread->setPrev(main_thread_);
if (next != nullptr) {
next->setPrev(thread);
}
}
}
return thread;
}
void Runtime::deleteThread(Thread* thread) {
CHECK(thread != main_thread_, "cannot delete main thread");
MutexGuard lock(&threads_mutex_);
Thread* prev = thread->prev();
Thread* next = thread->next();
prev->setNext(next);
if (next != nullptr) {
next->setPrev(prev);
}
delete thread;
}
void Runtime::processCallbacks() {
Thread* thread = Thread::current();
HandleScope scope(thread);
Object saved_type(&scope, thread->pendingExceptionType());
Object saved_value(&scope, thread->pendingExceptionValue());
Object saved_traceback(&scope, thread->pendingExceptionTraceback());
thread->clearPendingException();
while (callbacks_ != NoneType::object()) {
Object weak(&scope, WeakRef::dequeue(&callbacks_));
BoundMethod callback(&scope, WeakRef::cast(*weak).callback());
Interpreter::call0(thread, callback);
thread->ignorePendingException();
WeakRef::cast(*weak).setCallback(NoneType::object());
}
thread->setPendingExceptionType(*saved_type);
thread->setPendingExceptionValue(*saved_value);
thread->setPendingExceptionTraceback(*saved_traceback);
}
void Runtime::processFinalizers() {
Thread* thread = Thread::current();
HandleScope scope(thread);
Object saved_type(&scope, thread->pendingExceptionType());
Object saved_value(&scope, thread->pendingExceptionValue());
Object saved_traceback(&scope, thread->pendingExceptionTraceback());
thread->clearPendingException();
while (finalizable_references_ != NoneType::object()) {
finalizeExtensionObject(thread,
RawNativeProxy::dequeue(&finalizable_references_));
}
thread->setPendingExceptionType(*saved_type);
thread->setPendingExceptionValue(*saved_value);
thread->setPendingExceptionTraceback(*saved_traceback);
}
static void writeCStr(word fd, const char* str) {
File::write(fd, str, std::strlen(str));
}
static void writeStr(word fd, RawStr str) {
static const word buffer_length = 128;
byte buffer[buffer_length];
word start = 0;
word length = str.length();
while (length > buffer_length) {
LargeStr::cast(str).copyToStartAt(buffer, buffer_length, start);
File::write(fd, buffer, buffer_length);
start += buffer_length;
length -= buffer_length;
}
str.copyToStartAt(buffer, length, start);
File::write(fd, buffer, length);
}
RawObject Runtime::printTraceback(Thread* thread, word fd) {
// NOTE: all operations in this function must be async-signal-safe.
// See http://man7.org/linux/man-pages/man7/signal-safety.7.html for details.
static const char* in = " in ";
static const char* line = ", line ";
static const char* unknown = "???";
writeCStr(fd, "Stack (most recent call first):\n");
Frame* frame = thread->currentFrame();
while (!frame->isSentinel()) {
writeCStr(fd, " File ");
RawFunction function = frame->function();
RawObject code_obj = function.code();
if (code_obj.isCode()) {
RawCode code = Code::cast(code_obj);
RawObject filename = code.filename();
if (filename.isStr()) {
writeCStr(fd, "\"");
writeStr(fd, RawStr::cast(filename));
writeCStr(fd, "\"");
} else {
writeCStr(fd, unknown);
}
writeCStr(fd, line);
if (!code.isNative() && code.lnotab().isBytes()) {
byte buf[kUwordDigits10];
byte* end = buf + kUwordDigits10;
byte* start = end;
word pc = Utils::maximum(frame->virtualPC() - kCodeUnitSize, word{0});
word linenum = code.offsetToLineNum(pc);
DCHECK(linenum > 0, "expected non-negative line number");
start = uwordToDecimal(static_cast<uword>(linenum), start);
File::write(fd, start, end - start);
} else {
writeCStr(fd, unknown);
}
writeCStr(fd, in);
RawObject name = function.name();
if (name.isStr()) {
writeStr(fd, RawStr::cast(name));
} else {
writeCStr(fd, unknown);
}
} else {
writeCStr(fd, unknown);
writeCStr(fd, line);
writeCStr(fd, unknown);
writeCStr(fd, in);
writeCStr(fd, unknown);
}
writeCStr(fd, "\n");
frame = frame->previousFrame();
}
return NoneType::object();
}
static RawTuple newEmptyTuple(Heap* heap) {
word size = MutableTuple::allocationSize(0);
uword address;
CHECK(heap->allocate(size, &address), "out of memory");
return Tuple::cast(MutableTuple::cast(MutableTuple::initialize(address, 0))
.becomeImmutable());
}
void Runtime::initializePrimitiveInstances() {
empty_tuple_ = newEmptyTuple(heap());
empty_frozen_set_ = newFrozenSet();
empty_mutable_bytes_ = createMutableBytes(0);
empty_slice_ = newInstanceWithSize(LayoutId::kSlice, Slice::kSize);
{
uword address;
CHECK(heap()->allocate(Ellipsis::allocationSize(), &address),
"out of memory");
ellipsis_ = Ellipsis::cast(Ellipsis::initialize(address));
}
}
void Runtime::initializeInterned(Thread*) {
interned_ = newMutableTuple(kInitialInternSetCapacity);
interned_remaining_ = internSetComputeRemaining(kInitialInternSetCapacity);
}
void Runtime::initializeSymbols(Thread* thread) {
HandleScope scope(thread);
symbols_ = new Symbols(this);
for (int i = 0; i < static_cast<int>(SymbolId::kMaxId); i++) {
SymbolId id = static_cast<SymbolId>(i);
Object symbol(&scope, symbols()->at(id));
internStr(thread, symbol);
}
}
RawObject Runtime::implicitBases() {
return Type::cast(typeAt(LayoutId::kObject)).mro();
}
void Runtime::cacheBuildClass(Thread* thread, const Module& builtins) {
HandleScope scope(thread);
Object none(&scope, NoneType::object());
moduleAtPutById(thread, builtins, ID(__build_class__), none);
build_class_ = moduleValueCellAtById(thread, builtins, ID(__build_class__));
CHECK(!build_class_.isErrorNotFound(), "__build_class__ not found");
}
void Runtime::builtinTypeCreated(Thread* thread, const Type& type) {
LayoutId layout_id = type.instanceLayoutId();
switch (layout_id) {
case LayoutId::kObject:
object_dunder_class_ = typeAtById(thread, type, ID(__class__));
object_dunder_eq_ = typeAtById(thread, type, ID(__eq__));
object_dunder_getattribute_ =
typeAtById(thread, type, ID(__getattribute__));
object_dunder_hash_ = typeAtById(thread, type, ID(__hash__));
object_dunder_init_ = typeAtById(thread, type, ID(__init__));
object_dunder_new_ = typeAtById(thread, type, ID(__new__));
object_dunder_setattr_ = typeAtById(thread, type, ID(__setattr__));
type.setFlags(static_cast<Type::Flag>(
type.flags() | Type::Flag::kHasObjectDunderGetattribute |
Type::Flag::kHasObjectDunderNew | Type::Flag::kHasObjectDunderHash));
break;
case LayoutId::kModule:
module_dunder_getattribute_ =
typeAtById(thread, type, ID(__getattribute__));
type.setFlags(static_cast<Type::Flag>(
type.flags() | Type::Flag::kHasModuleDunderGetattribute));
break;
case LayoutId::kNoneType:
// NoneType.__class__ does *not* point to the same object as
// object.__class_ to avoid descriptor resolution for NoneType, but their
// observable behaviors are same. See the definition of NoneType in
// builtins.py.
type.setFlags(static_cast<Type::Flag>(type.flags() |
Type::Flag::kHasObjectDunderClass));
break;
case LayoutId::kStr:
str_dunder_eq_ = typeAtById(thread, type, ID(__eq__));
str_dunder_hash_ = typeAtById(thread, type, ID(__hash__));
type.setFlags(static_cast<Type::Flag>(type.flags() |
Type::Flag::kHasStrDunderHash));
break;
case LayoutId::kType:
type_dunder_getattribute_ =
typeAtById(thread, type, ID(__getattribute__));
type.setFlags(static_cast<Type::Flag>(
type.flags() | Type::Flag::kHasTypeDunderGetattribute));
break;
default:
break;
}
HandleScope scope(thread);
Function dunder_getattribute(
&scope, typeLookupInMroById(thread, *type, ID(__getattribute__)));
// This detects two instances of `object.__getattribute__`:
// 1) Manually created `object.__getattribute__` before initializing `object`.
// 2) Real `object.__getattribute__` after.
if (Str::cast(dunder_getattribute.qualname())
.equalsCStr("object.__getattribute__")) {
type.setFlags(static_cast<Type::Flag>(
type.flags() | Type::Flag::kHasObjectDunderGetattribute));
}
Object dunder_new(&scope, typeLookupInMroById(thread, *type, ID(__new__)));
if (*dunder_new == object_dunder_new_ ||
// __new__ cannot be found in this type and this type is initialized
// before `object`.
(dunder_new.isErrorNotFound() && object_dunder_new_.isNoneType())) {
type.setFlags(static_cast<Type::Flag>(type.flags() |
Type::Flag::kHasObjectDunderNew));
}
Object dunder_hash(&scope, typeLookupInMroById(thread, *type, ID(__hash__)));
if (*dunder_hash == object_dunder_hash_ ||
// __hash__ cannot be found in this type and this type is initialized
// before `object`.
(dunder_hash.isErrorNotFound() && object_dunder_hash_.isNoneType())) {
type.setFlags(static_cast<Type::Flag>(type.flags() |
Type::Flag::kHasObjectDunderHash));
}
if (!typeLookupInMroById(thread, *type, ID(__bool__)).isErrorNotFound()) {
type.setFlags(
static_cast<Type::Flag>(type.flags() | Type::Flag::kHasDunderBool));
}
if (!typeLookupInMroById(thread, *type, ID(__len__)).isErrorNotFound()) {
type.setFlags(
static_cast<Type::Flag>(type.flags() | Type::Flag::kHasDunderLen));
}
Object dunder_class(&scope,
typeLookupInMroById(thread, *type, ID(__class__)));
if (*dunder_class == object_dunder_class_ ||
// __class__ cannot be found in this type and this type is initialized
// before `object`.
(dunder_class.isErrorNotFound() && object_dunder_class_.isNoneType())) {
type.setFlags(static_cast<Type::Flag>(type.flags() |
Type::Flag::kHasObjectDunderClass));
}
Object dunder_eq(&scope, typeLookupInMroById(thread, *type, ID(__eq__)));
if (*dunder_eq == object_dunder_eq_ ||
// __eq__ cannot be found in this type and this type is initialized
// before `object`.
(dunder_eq.isErrorNotFound() && object_dunder_eq_.isNoneType())) {
type.setFlags(
static_cast<Type::Flag>(type.flags() | Type::Flag::kHasObjectDunderEq));
}
if (!typeLookupInMroById(thread, *type, ID(__get__)).isErrorNotFound()) {
type.setFlags(
static_cast<Type::Flag>(type.flags() | Type::Flag::kHasDunderGet));
}
if (!typeLookupInMroById(thread, *type, ID(__set__)).isErrorNotFound()) {
type.setFlags(
static_cast<Type::Flag>(type.flags() | Type::Flag::kHasDunderSet));
}
if (!typeLookupInMroById(thread, *type, ID(__delete__)).isErrorNotFound()) {
type.setFlags(
static_cast<Type::Flag>(type.flags() | Type::Flag::kHasDunderDelete));
}
}
void Runtime::cacheSysInstances(Thread* thread, const Module& sys) {
sys_stderr_ = moduleValueCellAtById(thread, sys, ID(stderr));
CHECK(!sys_stderr_.isErrorNotFound(), "sys.stderr not found");
sys_stdin_ = moduleValueCellAtById(thread, sys, ID(stdin));
CHECK(!sys_stdin_.isErrorNotFound(), "sys.stdin not found");
sys_stdout_ = moduleValueCellAtById(thread, sys, ID(stdout));
CHECK(!sys_stdout_.isErrorNotFound(), "sys.stdout not found");
display_hook_ = moduleValueCellAtById(thread, sys, ID(displayhook));
CHECK(!display_hook_.isErrorNotFound(), "sys.displayhook not found");
}
void Runtime::visitRootsWithoutApiHandles(PointerVisitor* visitor) {
visitRuntimeRoots(visitor);
visitThreadRoots(visitor);
}
void Runtime::visitRuntimeRoots(PointerVisitor* visitor) {
// Visit builtin layouts
for (word i = 0; i <= static_cast<word>(LayoutId::kLastBuiltinId); ++i) {
visitor->visitPointer(
reinterpret_cast<RawObject*>(Tuple::cast(layouts_).address() +
i * kPointerSize),
PointerKind::kLayout);
}
// Visit internal types that are not described by a layout
visitor->visitPointer(&large_bytes_, PointerKind::kRuntime);
visitor->visitPointer(&large_int_, PointerKind::kRuntime);
visitor->visitPointer(&large_str_, PointerKind::kRuntime);
visitor->visitPointer(&small_bytes_, PointerKind::kRuntime);
visitor->visitPointer(&small_int_, PointerKind::kRuntime);
visitor->visitPointer(&small_str_, PointerKind::kRuntime);
// Visit instances
visitor->visitPointer(&build_class_, PointerKind::kRuntime);
visitor->visitPointer(&display_hook_, PointerKind::kRuntime);
visitor->visitPointer(&ellipsis_, PointerKind::kRuntime);
visitor->visitPointer(&empty_frozen_set_, PointerKind::kRuntime);
visitor->visitPointer(&empty_mutable_bytes_, PointerKind::kRuntime);
visitor->visitPointer(&empty_slice_, PointerKind::kRuntime);
visitor->visitPointer(&empty_tuple_, PointerKind::kRuntime);
visitor->visitPointer(&module_dunder_getattribute_, PointerKind::kRuntime);
visitor->visitPointer(&object_dunder_class_, PointerKind::kRuntime);
visitor->visitPointer(&object_dunder_eq_, PointerKind::kRuntime);
visitor->visitPointer(&object_dunder_getattribute_, PointerKind::kRuntime);
visitor->visitPointer(&object_dunder_hash_, PointerKind::kRuntime);
visitor->visitPointer(&object_dunder_init_, PointerKind::kRuntime);
visitor->visitPointer(&object_dunder_new_, PointerKind::kRuntime);
visitor->visitPointer(&object_dunder_setattr_, PointerKind::kRuntime);
visitor->visitPointer(&str_dunder_eq_, PointerKind::kRuntime);
visitor->visitPointer(&str_dunder_hash_, PointerKind::kRuntime);
visitor->visitPointer(&sys_stderr_, PointerKind::kRuntime);
visitor->visitPointer(&sys_stdin_, PointerKind::kRuntime);
visitor->visitPointer(&sys_stdout_, PointerKind::kRuntime);
visitor->visitPointer(&type_dunder_getattribute_, PointerKind::kRuntime);
// Visit interned strings.
visitor->visitPointer(&interned_, PointerKind::kRuntime);
// Visit modules
visitor->visitPointer(&modules_, PointerKind::kRuntime);
// Visit symbols
symbols_->visit(visitor);
// Visit GC callbacks
visitor->visitPointer(&callbacks_, PointerKind::kRuntime);
// Visit signal callbacks
visitor->visitPointer(&signal_callbacks_, PointerKind::kRuntime);
visitor->visitPointer(&profiling_new_thread_, PointerKind::kRuntime);
visitor->visitPointer(&profiling_call_, PointerKind::kRuntime);
visitor->visitPointer(&profiling_return_, PointerKind::kRuntime);
// Visit finalizable native instances
visitor->visitPointer(&finalizable_references_, PointerKind::kRuntime);
}
void Runtime::visitThreadRoots(PointerVisitor* visitor) {
MutexGuard lock(&threads_mutex_);
for (Thread* thread = main_thread_; thread != nullptr;
thread = thread->next()) {
thread->visitRoots(visitor);
}
}
RawObject Runtime::findModule(const Object& name) {
// TODO(T53728922) it is possible to create modules with non-str names.
DCHECK(name.isStr(), "name not a string");
Thread* thread = Thread::current();
HandleScope scope(thread);
Dict dict(&scope, modules());
Str name_str(&scope, *name);
return dictAtByStr(thread, dict, name_str);
}
RawObject Runtime::findModuleById(SymbolId name) {
HandleScope scope(Thread::current());
Object name_obj(&scope, symbols()->at(name));
return findModule(name_obj);
}
RawObject Runtime::lookupNameInModule(Thread* thread, SymbolId module_name,
SymbolId name) {
HandleScope scope(thread);
Object module_obj(&scope, findModuleById(module_name));
DCHECK(!module_obj.isNoneType(),
"The given module '%s' doesn't exist in modules dict",
Symbols::predefinedSymbolAt(module_name));
Module module(&scope, *module_obj);
return moduleAtById(thread, module, name);
}
void Runtime::initializeModules(Thread* thread) {
// This function initializes module data that is fixed at compile time of the
// runtime (code, most functions, types, platform name, ...).
// Variable data should NOT be initialize here (runtime executable path,
// PYTHONPATH, sys.flags, ...)
modules_ = newDict();
for (SymbolId id : kRequiredModules) {
CHECK(!ensureBuiltinModuleById(thread, id).isErrorException(),
"failed to initialize built-in module %s",
Symbols::predefinedSymbolAt(id));
}
}
RawObject Runtime::initialize(Thread* thread) {
RawObject result = thread->invokeFunction0(ID(builtins), ID(_init));
initialized_ = true;
return result;
}
RawObject Runtime::concreteTypeAt(LayoutId layout_id) {
switch (layout_id) {
case LayoutId::kLargeBytes:
return large_bytes_;
case LayoutId::kLargeInt:
return large_int_;
case LayoutId::kLargeStr:
return large_str_;
case LayoutId::kSmallBytes:
return small_bytes_;
case LayoutId::kSmallInt:
return small_int_;
case LayoutId::kSmallStr:
return small_str_;
default:
return Layout::cast(layoutAt(layout_id)).describedType();
}
}
void Runtime::setLargeBytesType(const Type& type) { large_bytes_ = *type; }
void Runtime::setLargeIntType(const Type& type) { large_int_ = *type; }
void Runtime::setLargeStrType(const Type& type) { large_str_ = *type; }
void Runtime::setSmallBytesType(const Type& type) { small_bytes_ = *type; }
void Runtime::setSmallIntType(const Type& type) { small_int_ = *type; }
void Runtime::setSmallStrType(const Type& type) { small_str_ = *type; }
void Runtime::reinitInterpreter() {
// TODO(T79826701) We should interrupt/sync all threads here.
MutexGuard lock(&threads_mutex_);
for (Thread* thread = main_thread_; thread != nullptr;
thread = thread->next()) {
thread->interrupt(Thread::kReinitInterpreter);
}
}
void Runtime::setProfiling(const Object& new_thread_func,
const Object& call_func, const Object& return_func) {
profiling_new_thread_ = *new_thread_func;
profiling_call_ = *call_func;
profiling_return_ = *return_func;
}
void Runtime::layoutAtPut(LayoutId layout_id, RawObject object) {
MutableTuple::cast(layouts_).atPut(static_cast<word>(layout_id), object);
}
RawObject Runtime::typeAt(LayoutId layout_id) {
return Layout::cast(layoutAt(layout_id)).describedType();
}
LayoutId Runtime::reserveLayoutId(Thread* thread) {
word result = num_layouts_++;
CHECK(result < Header::kMaxLayoutId, "layout id overflow");
word length = Tuple::cast(layouts_).length();
if (result >= length) {
HandleScope scope(thread);
Tuple old_tuple(&scope, layouts_);
word new_length = newCapacity(length, kInitialLayoutTupleCapacity);
MutableTuple new_tuple(&scope, newMutableTuple(new_length));
new_tuple.replaceFromWith(0, *old_tuple, length);
layouts_ = *new_tuple;
}
return static_cast<LayoutId>(result);
}
word Runtime::reserveModuleId() {
// TODO(T58039604): Come up with a scheme to reuse deallocated module IDs if
// we run out.
CHECK(max_module_id_ < RawModule::kMaxModuleId, "exceeded module ID space");
return ++max_module_id_;
}
word Runtime::newCapacity(word curr_capacity, word min_capacity) {
word new_capacity = (curr_capacity < kInitialEnsuredCapacity)
? kInitialEnsuredCapacity
: curr_capacity + (curr_capacity >> 1);
if (new_capacity < min_capacity) {
return min_capacity;
}
return Utils::minimum(new_capacity, SmallInt::kMaxValue);
}
// Bytearray
void Runtime::bytearrayEnsureCapacity(Thread* thread, const Bytearray& array,
word min_capacity) {
DCHECK_BOUND(min_capacity, SmallInt::kMaxValue);
word curr_capacity = array.capacity();
if (min_capacity <= curr_capacity) return;
word new_capacity = newCapacity(curr_capacity, min_capacity);
HandleScope scope(thread);
MutableBytes old_bytes(&scope, array.items());
MutableBytes new_bytes(&scope, newMutableBytesZeroed(new_capacity));
byte* dst = reinterpret_cast<byte*>(new_bytes.address());
word old_length = array.numItems();
old_bytes.copyTo(dst, old_length);
array.setItems(*new_bytes);
}
void Runtime::bytearrayExtend(Thread* thread, const Bytearray& array,
View<byte> view) {
word length = view.length();
if (length == 0) return;
word num_items = array.numItems();
word new_length = num_items + length;
bytearrayEnsureCapacity(thread, array, new_length);
byte* dst =
reinterpret_cast<byte*>(MutableBytes::cast(array.items()).address());
std::memcpy(dst + num_items, view.data(), view.length());
array.setNumItems(new_length);
}
void Runtime::bytearrayIadd(Thread* thread, const Bytearray& array,
const Bytes& bytes, word length) {
DCHECK_BOUND(length, bytes.length());
if (length == 0) return;
word num_items = array.numItems();
word new_length = num_items + length;
bytearrayEnsureCapacity(thread, array, new_length);
MutableBytes::cast(array.items())
.replaceFromWithBytes(num_items, *bytes, length);
array.setNumItems(new_length);
}
// Bytes
RawObject Runtime::bytesConcat(Thread* thread, const Bytes& left,
const Bytes& right) {
word left_len = left.length();
word right_len = right.length();
word len = left_len + right_len;
if (len <= SmallBytes::kMaxLength) {
byte buffer[SmallBytes::kMaxLength];
left.copyTo(buffer, left_len);
right.copyTo(buffer + left_len, right_len);
return SmallBytes::fromBytes({buffer, len});
}
HandleScope scope(thread);
MutableBytes result(&scope, newMutableBytesUninitialized(len));
result.replaceFromWithBytes(0, *left, left_len);
result.replaceFromWithBytes(left_len, *right, right_len);
return result.becomeImmutable();
}
RawObject Runtime::bytesCopy(Thread* thread, const Bytes& src) {
if (src.isSmallBytes()) return *src;
return bytesSubseq(thread, src, 0, src.length());
}
RawObject Runtime::bytesCopyWithSize(Thread* thread, const Bytes& original,
word new_length) {
DCHECK(new_length >= 0, "length must be nonnegative");
if (new_length == 0) return Bytes::empty();
word old_length = original.length();
if (new_length <= SmallBytes::kMaxLength) {
byte buffer[SmallBytes::kMaxLength];
original.copyTo(buffer, Utils::minimum(old_length, new_length));
return SmallBytes::fromBytes({buffer, new_length});
}
HandleScope scope(thread);
MutableBytes copy(&scope, newMutableBytesUninitialized(new_length));
if (old_length < new_length) {
copy.replaceFromWithBytes(0, *original, old_length);
copy.replaceFromWithByte(old_length, 0, new_length - old_length);
} else {
copy.replaceFromWith(0, LargeBytes::cast(*original), new_length);
}
return copy.becomeImmutable();
}
RawObject Runtime::bytesEndsWith(const Bytes& bytes, word bytes_len,
const Bytes& suffix, word suffix_len,
word start, word end) {
DCHECK_BOUND(bytes_len, bytes.length());
DCHECK_BOUND(suffix_len, suffix.length());
Slice::adjustSearchIndices(&start, &end, bytes_len);
if (end - start < suffix_len || start > bytes_len) {
return Bool::falseObj();
}
for (word i = end - suffix_len, j = 0; i < end; i++, j++) {
if (bytes.byteAt(i) != suffix.byteAt(j)) {
return Bool::falseObj();
}
}
return Bool::trueObj();
}
RawObject Runtime::bytesFromTuple(Thread* thread, const Tuple& items,
word length) {
DCHECK_BOUND(length, items.length());
HandleScope scope(thread);
MutableBytes result(&scope, empty_mutable_bytes_);
byte buffer[SmallBytes::kMaxLength];
byte* dst;
if (length <= SmallBytes::kMaxLength) {
dst = buffer;
} else {
result = newMutableBytesUninitialized(length);
dst = reinterpret_cast<byte*>(MutableBytes::cast(*result).address());
}
for (word idx = 0; idx < length; idx++) {
Object item(&scope, items.at(idx));
if (!isInstanceOfInt(*item)) {
// escape into slow path
return NoneType::object();
}
OptInt<byte> current_byte = intUnderlying(*item).asInt<byte>();
if (current_byte.error == CastError::None) {
dst[idx] = current_byte.value;
} else {
// TODO(T55871582): Move error handling to caller
return thread->raiseWithFmt(LayoutId::kValueError,
"bytes must be in range(0, 256)");
}
}
return length <= SmallBytes::kMaxLength
? SmallBytes::fromBytes({buffer, length})
: result.becomeImmutable();
}
RawObject Runtime::bytesRepeat(Thread* thread, const Bytes& source, word length,
word count) {
DCHECK_BOUND(length, source.length());
DCHECK_BOUND(count, kMaxWord / length);
if (count == 0 || length == 0) {
return Bytes::empty();
}
bool is_mutable = source.isMutableBytes();
if (length == 1) {
byte item = source.byteAt(0);
return is_mutable ? mutableBytesWith(count, item) : newBytes(count, item);
}
word new_length = length * count;
if (!is_mutable && new_length <= SmallBytes::kMaxLength) {
byte buffer[SmallBytes::kMaxLength];
byte* dst = buffer;
for (word i = 0; i < count; i++, dst += length) {
source.copyTo(dst, length);
}
return SmallBytes::fromBytes({buffer, new_length});
}
HandleScope scope(thread);
MutableBytes result(&scope, newMutableBytesUninitialized(new_length));
for (word i = 0; i < count * length; i += length) {
result.replaceFromWithBytes(i, *source, length);
}
return is_mutable ? *result : result.becomeImmutable();
}
RawObject Runtime::bytesReplace(Thread* thread, const Bytes& src,
const Bytes& old_bytes, word old_len,
const Bytes& new_bytes, word new_len,
word max_count) {
word src_len = src.length();
if (max_count == 0 || src_len == 0 || old_bytes.compare(*new_bytes) == 0) {
return *src;
}
// Update the max_count to the number of occurences of old_bytes in src,
// capped by the given max_count
word count = bytesCount(src, src_len, old_bytes, old_len, 0, src_len);
if (max_count < 0 || max_count > count) {
max_count = count;
}
if (max_count == 0) {
return *src;
}
HandleScope scope(thread);
word result_len = src_len + (new_len - old_len) * max_count;
MutableBytes result(&scope, newMutableBytesUninitialized(result_len));
byte src_buffer[SmallBytes::kMaxLength], old_buffer[SmallBytes::kMaxLength];
byte *src_ptr, *old_ptr;
if (src.isLargeBytes()) {
src_ptr = reinterpret_cast<byte*>(LargeBytes::cast(*src).address());
} else {
src.copyTo(src_buffer, src_len);
src_ptr = src_buffer;
}
if (old_bytes.isLargeBytes()) {
old_ptr = reinterpret_cast<byte*>(LargeBytes::cast(*old_bytes).address());
} else {
old_bytes.copyTo(old_buffer, old_len);
old_ptr = old_buffer;
}
word dst_idx = 0, src_idx = 0;
for (; max_count > 0; max_count--) {
byte* found_ptr = reinterpret_cast<byte*>(
::memmem(src_ptr + src_idx, src_len - src_idx, old_ptr, old_len));
word prefix_len = found_ptr - (src_ptr + src_idx);
result.replaceFromWithBytesStartAt(dst_idx, *src, prefix_len, src_idx);
dst_idx += prefix_len;
result.replaceFromWithBytesStartAt(dst_idx, *new_bytes, new_len, 0);
dst_idx += new_len;
src_idx += prefix_len + old_len;
}
result.replaceFromWithBytesStartAt(dst_idx, *src, src_len - src_idx, src_idx);
return result.becomeImmutable();
}
static void writeByteslikeRepr(const Byteslike& byteslike, byte* dst,
word result_length, byte delimiter) {
byte* ptr = dst;
*ptr++ = 'b';
*ptr++ = delimiter;
word length = byteslike.length();
for (word i = 0; i < length; i++) {
byte current = byteslike.byteAt(i);
if (current == delimiter || current == '\\') {
*ptr++ = '\\';
*ptr++ = current;
} else if (current == '\t') {
*ptr++ = '\\';
*ptr++ = 't';
} else if (current == '\n') {
*ptr++ = '\\';
*ptr++ = 'n';
} else if (current == '\r') {
*ptr++ = '\\';
*ptr++ = 'r';
} else if (ASCII::isPrintable(current)) {
*ptr++ = current;
} else {
*ptr++ = '\\';
*ptr++ = 'x';
uwordToHexadecimal(ptr, /*num_digits=*/2, current);
ptr += 2;
}
}
*ptr++ = delimiter;
DCHECK(ptr - dst == result_length, "precalculated repr length was incorrect");
}
RawObject Runtime::byteslikeRepr(Thread* thread, const Byteslike& byteslike,
word result_length, byte delimiter) {
if (result_length <= SmallStr::kMaxLength) {
byte buffer[SmallStr::kMaxLength];
writeByteslikeRepr(byteslike, buffer, result_length, delimiter);
return SmallStr::fromBytes({buffer, result_length});
}
HandleScope scope(thread);
LargeStr result(&scope, createLargeStr(result_length));
writeByteslikeRepr(byteslike, reinterpret_cast<byte*>(result.address()),
result_length, delimiter);
return *result;
}
RawObject Runtime::bytesSlice(Thread* thread, const Bytes& bytes, word start,
word stop, word step) {
word length = Slice::length(start, stop, step);
if (length <= SmallBytes::kMaxLength) {
byte buffer[SmallBytes::kMaxLength];
for (word i = 0, j = start; i < length; i++, j += step) {
buffer[i] = bytes.byteAt(j);
}
return SmallBytes::fromBytes({buffer, length});
}
HandleScope scope(thread);
MutableBytes result(&scope, newMutableBytesUninitialized(length));
{
byte* dst = reinterpret_cast<byte*>(result.address());
for (word i = 0, j = start; i < length; i++, j += step) {
dst[i] = bytes.byteAt(j);
}
}
return result.becomeImmutable();
}
RawObject Runtime::bytesStartsWith(const Bytes& bytes, word bytes_len,
const Bytes& prefix, word prefix_len,
word start, word end) {
DCHECK_BOUND(bytes_len, bytes.length());
DCHECK_BOUND(prefix_len, prefix.length());
Slice::adjustSearchIndices(&start, &end, bytes_len);
if (start + prefix_len > end) {
return Bool::falseObj();
}
for (word i = start, j = 0; j < prefix_len; i++, j++) {
if (bytes.byteAt(i) != prefix.byteAt(j)) {
return Bool::falseObj();
}
}
return Bool::trueObj();
}
RawObject Runtime::bytesTranslate(Thread* thread, const Bytes& bytes,
word length, const Bytes& table,
word table_len, const Bytes& del,
word del_len) {
DCHECK_BOUND(length, bytes.length());
DCHECK_BOUND(del_len, del.length());
// calculate mapping table
byte new_byte[kByteTranslationTableLength];
if (table == Bytes::empty()) {
for (word i = 0; i < kByteTranslationTableLength; i++) {
new_byte[i] = i;
}
} else {
DCHECK_BOUND(table_len, table.length());
DCHECK(table_len == kByteTranslationTableLength,
"translation table must map every possible byte value");
for (word i = 0; i < kByteTranslationTableLength; i++) {
new_byte[i] = table.byteAt(i);
}
}
// make initial pass to calculate length
bool delete_byte[kByteTranslationTableLength] = {};
for (word i = 0; i < del_len; i++) {
delete_byte[del.byteAt(i)] = true;
}
word new_length = length;
for (word i = 0; i < length; i++) {
if (delete_byte[bytes.byteAt(i)]) {
new_length--;
}
}
// replace or delete each byte
bool is_mutable = bytes.isMutableBytes();
if (new_length <= SmallBytes::kMaxLength && !is_mutable) {
byte buffer[SmallBytes::kMaxLength];
for (word i = 0, j = 0; j < new_length; i++) {
DCHECK(i < length, "reached end of bytes before finishing translation");
byte current = bytes.byteAt(i);
if (!delete_byte[current]) {
buffer[j++] = new_byte[current];
}
}
return SmallBytes::fromBytes({buffer, new_length});
}
HandleScope scope(thread);
MutableBytes result(&scope, newMutableBytesUninitialized(new_length));
for (word i = 0, j = 0; j < new_length; i++) {
DCHECK(i < length, "reached end of bytes before finishing translation");
byte current = bytes.byteAt(i);
if (!delete_byte[current]) {
result.byteAtPut(j++, new_byte[current]);
}
}
return is_mutable ? *result : result.becomeImmutable();
}
// List
void Runtime::listEnsureCapacity(Thread* thread, const List& list,
word min_capacity) {
DCHECK_BOUND(min_capacity, SmallInt::kMaxValue);
word curr_capacity = list.capacity();
if (min_capacity <= curr_capacity) return;
word new_capacity = newCapacity(curr_capacity, min_capacity);
HandleScope scope(thread);
Tuple old_array(&scope, list.items());
MutableTuple new_array(&scope, newMutableTuple(new_capacity));
new_array.replaceFromWith(0, *old_array, list.numItems());
list.setItems(*new_array);
}
void Runtime::listAdd(Thread* thread, const List& list, const Object& value) {
word index = list.numItems();
listEnsureCapacity(thread, list, index + 1);
list.setNumItems(index + 1);
list.atPut(index, *value);
}
// Dict
RawObject Runtime::newDict() {
word num_attributes = Dict::kSize / kPointerSize;
word size = Instance::allocationSize(num_attributes);
uword address;
CHECK(heap()->allocate(size, &address), "out of memory");
return Instance::initializeWithZero(address, num_attributes, LayoutId::kDict);
}
RawObject Runtime::newDictWithSize(word initial_size) {
Thread* thread = Thread::current();
HandleScope scope(thread);
// NOTE: Multiplying 2 will leave (initial_size * 2 * 2/3) - initial_size
// items available after initial_size items are inserted before dict is
// expanded.
word indices_len = Utils::nextPowerOfTwo(initial_size) * 2;
Dict dict(&scope, newDict());
dictAllocateArrays(thread, dict, indices_len);
return *dict;
}
static RawObject NEVER_INLINE callDunderEq(Thread* thread, RawObject o0_raw,
RawObject o1_raw) {
HandleScope scope(thread);
Object o0(&scope, o0_raw);
Object o1(&scope, o1_raw);
Runtime* runtime = thread->runtime();
Type o0_type(&scope, runtime->typeOf(*o0));
Type o1_type(&scope, runtime->typeOf(*o1));
if ((o0_type.flags() & Type::Flag::kHasObjectDunderEq) &&
(o1_type.flags() & Type::Flag::kHasObjectDunderEq)) {
return Bool::fromBool(*o0 == *o1);
}
Object compare_result(
&scope, Interpreter::compareOperation(thread, CompareOp::EQ, o0, o1));
if (compare_result.isErrorException()) return *compare_result;
Object result(&scope, Interpreter::isTrue(thread, *compare_result));
if (result.isErrorException()) return *result;
return *result;
}
RawObject Runtime::objectEquals(Thread* thread, RawObject o0, RawObject o1) {
if (o0 == o1) {
return Bool::trueObj();
}
// Shortcuts to catch the common SmallStr, LargeStr and SmallInt cases.
// Remember that we always have to check the layout/type of `o0` and `o1`
// to ensure `o1` is not a subclass of `o0` which would result in a
// `o1.__eq__(o0)` call.
if (!o0.isHeapObject()) {
if (o1.isLargeStr()) {
return Bool::falseObj();
}
if (!o1.isHeapObject()) {
if (o0.layoutId() != o1.layoutId()) {
if (o0.isBool() && o1.isSmallInt()) {
return Bool::fromBool((Bool::cast(o0).value() ? 1 : 0) ==
SmallInt::cast(o1).value());
}
if (o0.isSmallInt() && o1.isBool()) {
return Bool::fromBool(SmallInt::cast(o0).value() ==
(Bool::cast(o1).value() ? 1 : 0));
}
}
return Bool::falseObj();
}
} else if (o0.isLargeStr()) {
if (o1.isLargeStr()) {
return Bool::fromBool(LargeStr::cast(o0).equals(LargeStr::cast(o1)));
}
if (!o1.isHeapObject()) {
return Bool::falseObj();
}
}
return callDunderEq(thread, o0, o1);
}
// DictItemIterator
RawObject Runtime::newDictItemIterator(Thread* thread, const Dict& dict) {
HandleScope scope(thread);
DictItemIterator result(&scope,
newInstanceWithSize(LayoutId::kDictItemIterator,
DictItemIterator::kSize));
result.setIndex(0);
result.setIterable(*dict);
result.setNumFound(0);
return *result;
}
// DictItems
RawObject Runtime::newDictItems(Thread* thread, const Dict& dict) {
HandleScope scope(thread);
DictItems result(&scope,
newInstanceWithSize(LayoutId::kDictItems, DictItems::kSize));
result.setDict(*dict);
return *result;
}
// DictKeyIterator
RawObject Runtime::newDictKeyIterator(Thread* thread, const Dict& dict) {
HandleScope scope(thread);
DictKeyIterator result(&scope, newInstanceWithSize(LayoutId::kDictKeyIterator,
DictKeyIterator::kSize));
result.setIndex(0);
result.setIterable(*dict);
result.setNumFound(0);
return *result;
}
// DictKeys
RawObject Runtime::newDictKeys(Thread* thread, const Dict& dict) {
HandleScope scope(thread);
DictKeys result(&scope,
newInstanceWithSize(LayoutId::kDictKeys, DictKeys::kSize));
result.setDict(*dict);
return *result;
}
// DictValueIterator
RawObject Runtime::newDictValueIterator(Thread* thread, const Dict& dict) {
HandleScope scope(thread);
DictValueIterator result(&scope,
newInstanceWithSize(LayoutId::kDictValueIterator,
DictValueIterator::kSize));
result.setIndex(0);
result.setIterable(*dict);
result.setNumFound(0);
return *result;
}
// DictValues
RawObject Runtime::newDictValues(Thread* thread, const Dict& dict) {
HandleScope scope(thread);
DictValues result(
&scope, newInstanceWithSize(LayoutId::kDictValues, DictValues::kSize));
result.setDict(*dict);
return *result;
}
// Set
RawObject Runtime::newSet() {
HandleScope scope(Thread::current());
Set result(&scope, newInstanceWithSize(LayoutId::kSet, Set::kSize));
result.setNumItems(0);
result.setNumFilled(0);
result.setData(empty_tuple_);
return *result;
}
RawObject Runtime::newFrozenSet() {
HandleScope scope(Thread::current());
FrozenSet result(&scope,
newInstanceWithSize(LayoutId::kFrozenSet, FrozenSet::kSize));
result.setNumItems(0);
result.setNumFilled(0);
result.setData(empty_tuple_);
return *result;
}
RawObject Runtime::tupleSubseq(Thread* thread, const Tuple& tuple, word start,
word length) {
DCHECK_BOUND(start, tuple.length());
DCHECK_BOUND(length, tuple.length() - start);
if (length == 0) return empty_tuple_;
HandleScope scope(thread);
MutableTuple result(&scope, newMutableTuple(length));
for (word i = 0; i < length; i++) {
result.atPut(i, tuple.at(i + start));
}
return result.becomeImmutable();
}
RawObject Runtime::newValueCell() {
return newInstanceWithSize(LayoutId::kValueCell, ValueCell::kSize);
}
RawObject Runtime::newWeakLink(Thread* thread, const Object& referent,
const Object& prev, const Object& next) {
HandleScope scope(thread);
DCHECK(referent.isNoneType() || referent.isHeapObject(),
"expected heap object or None");
WeakLink link(&scope,
newInstanceWithSize(LayoutId::kWeakLink, WeakLink::kSize));
link.setReferent(*referent);
link.setCallback(NoneType::object());
link.setPrev(*prev);
link.setNext(*next);
return *link;
}
RawObject Runtime::newWeakRef(Thread* thread, const Object& referent) {
DCHECK(referent.isNoneType() || referent.isHeapObject(),
"expected heap object or None");
HandleScope scope(thread);
WeakRef ref(&scope, newInstanceWithSize(LayoutId::kWeakRef, WeakRef::kSize));
ref.setReferent(*referent);
return *ref;
}
void Runtime::collectAttributes(const Code& code, const Dict& attributes) {
Thread* thread = Thread::current();
HandleScope scope(thread);
Bytes bc(&scope, code.code());
Tuple names(&scope, code.names());
word len = bc.length();
for (word i = 0; i < len - 3; i += 2) {
// If the current instruction is EXTENDED_ARG we must skip it and the next
// instruction.
if (bc.byteAt(i) == Bytecode::EXTENDED_ARG) {
i += 2;
continue;
}
// Check for LOAD_FAST 0 (self)
if (bc.byteAt(i) != Bytecode::LOAD_FAST || bc.byteAt(i + 1) != 0) {
continue;
}
// Followed by a STORE_ATTR
if (bc.byteAt(i + 2) != Bytecode::STORE_ATTR) {
continue;
}
word name_index = bc.byteAt(i + 3);
Str name(&scope, names.at(name_index));
dictAtPutByStr(thread, attributes, name, name);
}
}
RawObject Runtime::classConstructor(const Type& type) {
Thread* thread = Thread::current();
return typeAtById(thread, type, ID(__init__));
}
RawObject Runtime::attributeAt(Thread* thread, const Object& receiver,
const Object& name) {
return attributeAtSetLocation(thread, receiver, name, nullptr, nullptr);
}
RawObject Runtime::attributeAtSetLocation(Thread* thread,
const Object& receiver,
const Object& name,
LoadAttrKind* kind,
Object* location_out) {
DCHECK(isInternedStr(thread, name), "name must be an interned str");
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
Type type(&scope, runtime->typeOf(*receiver));
if (kind != nullptr) *kind = LoadAttrKind::kUnknown;
if (type.hasFlag(Type::Flag::kHasObjectDunderGetattribute)) {
DCHECK(objectDunderGetattribute().isNoneType() ||
typeLookupInMroById(thread, *type, ID(__getattribute__)) ==
objectDunderGetattribute(),
"object.__getattribute__ is expected");
Object result(&scope, objectGetAttributeSetLocation(thread, receiver, name,
location_out, kind));
if (!result.isErrorNotFound()) {
return *result;
}
result = thread->invokeMethod2(receiver, ID(__getattr__), name);
if (!result.isErrorNotFound()) {
return *result;
}
return objectRaiseAttributeError(thread, receiver, name);
}
if (type.hasFlag(Type::Flag::kHasModuleDunderGetattribute) &&
runtime->isInstanceOfModule(*receiver)) {
DCHECK(runtime->moduleDunderGetattribute().isNoneType() ||
typeLookupInMroById(thread, *type, ID(__getattribute__)) ==
runtime->moduleDunderGetattribute(),
"module.__getattribute__ is expected");
Module module(&scope, *receiver);
Object result(&scope, moduleGetAttributeSetLocation(thread, module, name,
location_out));
if (!result.isErrorNotFound()) {
// We have a result that can be cached.
if (kind != nullptr) *kind = LoadAttrKind::kModule;
return *result;
}
// Try again
result = thread->invokeMethod2(receiver, ID(__getattr__), name);
if (!result.isErrorNotFound()) {
return *result;
}
return moduleRaiseAttributeError(thread, module, name);
}
if (type.hasFlag(Type::Flag::kHasTypeDunderGetattribute) &&
runtime->isInstanceOfType(*receiver)) {
DCHECK(runtime->typeDunderGetattribute().isNoneType() ||
typeLookupInMroById(thread, *type, ID(__getattribute__)) ==
runtime->typeDunderGetattribute(),
"type.__getattribute__ is expected");
Type object_as_type(&scope, *receiver);
Object result(&scope, typeGetAttributeSetLocation(thread, object_as_type,
name, location_out));
if (!result.isErrorNotFound()) {
// We have a result that can be cached.
if (kind != nullptr && location_out->isValueCell()) {
*kind = LoadAttrKind::kType;
}
return *result;
}
// Try again
result = thread->invokeMethod2(receiver, ID(__getattr__), name);
if (!result.isErrorNotFound()) {
return *result;
}
Object type_name(&scope, object_as_type.name());
return thread->raiseWithFmt(LayoutId::kAttributeError,
"type object '%S' has no attribute '%S'",
&type_name, &name);
}
if (receiver.isSuper()) {
Super object_as_super(&scope, *receiver);
Object result(&scope, superGetAttribute(thread, object_as_super, name));
if (!result.isErrorNotFound()) {
return *result;
}
return thread->raiseWithFmt(LayoutId::kAttributeError,
"super object has no attribute '%S'", &name);
}
Object dunder_getattribute(
&scope,
Interpreter::lookupMethod(thread, receiver, ID(__getattribute__)));
DCHECK(!dunder_getattribute.isErrorNotFound(),
"__getattribute__ is expected to be found");
if (UNLIKELY(dunder_getattribute.isError())) return *dunder_getattribute;
Object result(&scope, Interpreter::callMethod2(thread, dunder_getattribute,
receiver, name));
if (!result.isErrorException() ||
!thread->pendingExceptionMatches(LayoutId::kAttributeError)) {
return *result;
}
// Save the attribute error and clear it then attempt to call `__getattr__`.
Object saved_type(&scope, thread->pendingExceptionType());
Object saved_value(&scope, thread->pendingExceptionValue());
Object saved_traceback(&scope, thread->pendingExceptionTraceback());
thread->clearPendingException();
result = thread->invokeMethod2(receiver, ID(__getattr__), name);
if (result.isErrorNotFound()) {
thread->setPendingExceptionType(*saved_type);
thread->setPendingExceptionValue(*saved_value);
thread->setPendingExceptionTraceback(*saved_traceback);
return Error::exception();
}
return *result;
}
RawObject Runtime::attributeAtById(Thread* thread, const Object& receiver,
SymbolId id) {
HandleScope scope(thread);
Object name(&scope, symbols()->at(id));
return attributeAt(thread, receiver, name);
}
RawObject Runtime::attributeAtByCStr(Thread* thread, const Object& receiver,
const char* name) {
HandleScope scope(thread);
Object name_obj(&scope, internStrFromCStr(thread, name));
return attributeAt(thread, receiver, name_obj);
}
RawObject Runtime::attributeDel(Thread* thread, const Object& receiver,
const Object& name) {
HandleScope scope(thread);
Object dunder_delattr(
&scope, Interpreter::lookupMethod(thread, receiver, ID(__delattr__)));
DCHECK(!dunder_delattr.isErrorNotFound(),
"__delattr__ is expected to be found");
if (dunder_delattr.isErrorException()) return *dunder_delattr;
return Interpreter::callMethod2(thread, dunder_delattr, receiver, name);
}
RawObject Runtime::strConcat(Thread* thread, const Str& left,
const Str& right) {
HandleScope scope(thread);
word left_len = left.length();
word right_len = right.length();
word result_len = left_len + right_len;
// Small result
if (result_len <= RawSmallStr::kMaxLength) {
byte buffer[RawSmallStr::kMaxLength];
left.copyTo(buffer, left_len);
right.copyTo(buffer + left_len, right_len);
return SmallStr::fromBytes(View<byte>(buffer, result_len));
}
// Large result
LargeStr result(&scope, createLargeStr(result_len));
left.copyTo(reinterpret_cast<byte*>(result.address()), left_len);
right.copyTo(reinterpret_cast<byte*>(result.address() + left_len), right_len);
return *result;
}
RawObject Runtime::strJoin(Thread* thread, const Str& sep, const Tuple& items,
word allocated) {
HandleScope scope(thread);
word result_len = 0;
Str str(&scope, Str::empty());
for (word i = 0; i < allocated; ++i) {
str = strUnderlying(items.at(i));
result_len += str.length();
}
if (allocated > 1) {
result_len += sep.length() * (allocated - 1);
}
// Small result
Object elt(&scope, NoneType::object());
if (result_len <= RawSmallStr::kMaxLength) {
byte buffer[RawSmallStr::kMaxLength];
for (word i = 0, offset = 0; i < allocated; ++i) {
elt = items.at(i);
str = strUnderlying(*elt);
word str_len = str.length();
str.copyTo(&buffer[offset], str_len);
offset += str_len;
if ((i + 1) < allocated) {
word sep_len = sep.length();
sep.copyTo(&buffer[offset], sep_len);
offset += sep.length();
}
}
return SmallStr::fromBytes(View<byte>(buffer, result_len));
}
// Large result
LargeStr result(&scope, createLargeStr(result_len));
for (word i = 0, offset = 0; i < allocated; ++i) {
elt = items.at(i);
str = strUnderlying(*elt);
word str_len = str.length();
str.copyTo(reinterpret_cast<byte*>(result.address() + offset), str_len);
offset += str_len;
if ((i + 1) < allocated) {
word sep_len = sep.length();
sep.copyTo(reinterpret_cast<byte*>(result.address() + offset), sep_len);
offset += sep_len;
}
}
return *result;
}
RawObject Runtime::strRepeat(Thread* thread, const Str& str, word count) {
DCHECK(count > 0, "count should be positive");
byte buffer[SmallStr::kMaxLength];
word length = str.length();
DCHECK(length > 0, "length should be positive");
DCHECK_BOUND(count, SmallInt::kMaxValue / length);
word new_length = length * count;
if (new_length <= SmallStr::kMaxLength) {
// SmallStr result
for (word i = 0; i < new_length; i++) {
buffer[i] = str.byteAt(i % length);
}
return SmallStr::fromBytes(View<byte>(buffer, new_length));
}
// LargeStr result
HandleScope scope(thread);
LargeStr result(&scope, createLargeStr(new_length));
const byte* src;
if (length <= SmallStr::kMaxLength) {
// SmallStr original
str.copyTo(buffer, length);
src = buffer;
} else {
// LargeStr original
LargeStr source(&scope, *str);
src = reinterpret_cast<byte*>(source.address());
}
byte* dst = reinterpret_cast<byte*>(result.address());
for (word i = 0; i < count; i++, dst += length) {
std::memcpy(dst, src, length);
}
return *result;
}
RawObject Runtime::strSlice(Thread* thread, const Str& str, word start,
word stop, word step) {
word length = Slice::length(start, stop, step);
word start_index = str.offsetByCodePoints(0, start);
if (step == 1) {
word end_index = str.offsetByCodePoints(start_index, length);
word num_chars = end_index - start_index;
return strSubstr(thread, str, start_index, num_chars);
}
word result_length = 0;
for (word i = 0, char_index = start_index; i < length;
i++, char_index = str.offsetByCodePoints(char_index, step)) {
result_length += UTF8::numChars(str.byteAt(char_index));
}
if (result_length <= SmallStr::kMaxLength) {
byte buffer[SmallStr::kMaxLength];
for (word i = 0, char_index = start_index; i < result_length;
char_index = str.offsetByCodePoints(char_index, step)) {
word num_chars = UTF8::numChars(str.byteAt(char_index));
str.copyToStartAt(&buffer[i], num_chars, char_index);
i += num_chars;
}
return SmallStr::fromBytes({buffer, result_length});
}
HandleScope scope(thread);
MutableBytes result(
&scope, thread->runtime()->newMutableBytesUninitialized(result_length));
for (word i = 0, char_index = start_index; i < result_length;
char_index = str.offsetByCodePoints(char_index, step)) {
word num_chars = UTF8::numChars(str.byteAt(char_index));
result.replaceFromWithStrStartAt(i, *str, num_chars, char_index);
i += num_chars;
}
return result.becomeStr();
}
// StrArray
void Runtime::strArrayAddASCII(Thread* thread, const StrArray& array,
byte code_point) {
DCHECK(code_point <= kMaxASCII, "can only add ASCII in strArrayAddASCII");
word num_items = array.numItems();
word new_length = num_items + 1;
strArrayEnsureCapacity(thread, array, new_length);
array.setNumItems(new_length);
MutableBytes::cast(array.items()).byteAtPut(num_items, code_point);
}
void Runtime::strArrayAddCodePoint(Thread* thread, const StrArray& array,
int32_t code_point) {
DCHECK_BOUND(code_point, kMaxUnicode);
RawSmallStr str = SmallStr::fromCodePoint(code_point);
word num_items = array.numItems();
word length = str.length();
word new_length = num_items + length;
strArrayEnsureCapacity(thread, array, new_length);
byte* dst =
reinterpret_cast<byte*>(MutableBytes::cast(array.items()).address());
str.copyTo(dst + num_items, length);
array.setNumItems(new_length);
}
void Runtime::strArrayAddStr(Thread* thread, const StrArray& array,
const Str& str) {
word length = str.length();
if (length == 0) return;
word num_items = array.numItems();
word new_length = length + num_items;
strArrayEnsureCapacity(thread, array, new_length);
byte* dst =
reinterpret_cast<byte*>(MutableBytes::cast(array.items()).address());
str.copyTo(dst + num_items, length);
array.setNumItems(new_length);
}
void Runtime::strArrayAddStrArray(Thread* thread, const StrArray& array,
const StrArray& str) {
word length = str.numItems();
if (length == 0) return;
word num_items = array.numItems();
word new_length = length + num_items;
strArrayEnsureCapacity(thread, array, new_length);
byte* dst =
reinterpret_cast<byte*>(MutableBytes::cast(array.items()).address());
str.copyTo(dst + num_items, length);
array.setNumItems(new_length);
}
void Runtime::strArrayEnsureCapacity(Thread* thread, const StrArray& array,
word min_capacity) {
DCHECK_BOUND(min_capacity, SmallInt::kMaxValue);
word curr_capacity = array.capacity();
if (min_capacity <= curr_capacity) return;
word new_capacity = newCapacity(curr_capacity, min_capacity);
HandleScope scope(thread);
MutableBytes new_bytes(&scope, newMutableBytesZeroed(new_capacity));
new_bytes.replaceFromWith(0, MutableBytes::cast(array.items()),
array.numItems());
array.setItems(*new_bytes);
}
RawObject Runtime::newCell() {
return newInstanceWithSize(LayoutId::kCell, Cell::kSize);
}
RawObject Runtime::newClassMethod() {
return newInstanceWithSize(LayoutId::kClassMethod, ClassMethod::kSize);
}
RawObject Runtime::newSuper() {
return newInstanceWithSize(LayoutId::kSuper, Super::kSize);
}
RawObject Runtime::newStrIterator(const Object& str) {
HandleScope scope(Thread::current());
StrIterator result(
&scope, newInstanceWithSize(LayoutId::kStrIterator, StrIterator::kSize));
result.setIndex(0);
result.setIterable(*str);
return *result;
}
RawObject Runtime::newTupleIterator(const Tuple& tuple, word length) {
HandleScope scope(Thread::current());
TupleIterator result(&scope, newInstanceWithSize(LayoutId::kTupleIterator,
TupleIterator::kSize));
result.setIndex(0);
result.setIterable(*tuple);
result.setLength(length);
return *result;
}
RawObject Runtime::newContext(const Dict& data) {
HandleScope scope(Thread::current());
Context result(&scope,
newInstanceWithSize(LayoutId::kContext, Context::kSize));
result.setData(*data);
result.setPrevContext(NoneType::object());
return *result;
}
RawObject Runtime::newContextVar(const Str& name, const Object& default_value) {
HandleScope scope(Thread::current());
ContextVar result(
&scope, newInstanceWithSize(LayoutId::kContextVar, ContextVar::kSize));
result.setName(*name);
result.setDefaultValue(*default_value);
return *result;
}
RawObject Runtime::newToken(const Context& ctx, const ContextVar& var,
const Object& old_value) {
HandleScope scope(Thread::current());
Token result(&scope, newInstanceWithSize(LayoutId::kToken, Token::kSize));
result.setContext(*ctx);
result.setVar(*var);
result.setOldValue(*old_value);
result.setUsed(false);
return *result;
}
RawObject Runtime::emptyFrozenSet() { return empty_frozen_set_; }
RawObject Runtime::layoutNewAttribute(const Object& name, AttributeInfo info) {
RawMutableTuple result = MutableTuple::cast(newMutableTuple(2));
result.atPut(0, *name);
result.atPut(1, info.asSmallInt());
return result.becomeImmutable();
}
static RawObject layoutFollowEdge(RawObject edges, RawObject key) {
RawList list = List::cast(edges);
DCHECK(list.numItems() % 2 == 0,
"edges must contain an even number of elements");
for (word i = 0, num_items = list.numItems(); i < num_items; i += 2) {
if (list.at(i) == key) {
return list.at(i + 1);
}
}
return Error::notFound();
}
static void layoutAddEdge(Thread* thread, Runtime* runtime, const List& edges,
const Object& key, const Object& layout) {
DCHECK(edges.numItems() % 2 == 0,
"edges must contain an even number of elements");
runtime->listAdd(thread, edges, key);
runtime->listAdd(thread, edges, layout);
}
bool Runtime::layoutFindAttribute(RawLayout layout, const Object& name,
AttributeInfo* info) {
// Check in-object attributes
RawTuple in_object = Tuple::cast(layout.inObjectAttributes());
RawObject name_raw = *name;
for (word i = in_object.length() - 1; i >= 0; i--) {
RawTuple entry = Tuple::cast(in_object.at(i));
if (entry.at(0) == name_raw) {
*info = AttributeInfo(entry.at(1));
return true;
}
}
// Check overflow attributes
if (!layout.hasTupleOverflow()) {
return false;
}
RawTuple overflow = Tuple::cast(layout.overflowAttributes());
for (word i = overflow.length() - 1; i >= 0; i--) {
RawTuple entry = Tuple::cast(overflow.at(i));
if (entry.at(0) == name_raw) {
*info = AttributeInfo(entry.at(1));
return true;
}
}
return false;
}
RawObject Runtime::layoutCreateCopy(Thread* thread, const Layout& layout) {
HandleScope scope(thread);
LayoutId id = reserveLayoutId(thread);
Layout new_layout(&scope, newLayout(id));
new_layout.setDescribedType(layout.describedType());
new_layout.setNumInObjectAttributes(layout.numInObjectAttributes());
new_layout.setInObjectAttributes(layout.inObjectAttributes());
new_layout.setOverflowAttributes(layout.overflowAttributes());
layoutAtPut(id, *new_layout);
return *new_layout;
}
RawObject Runtime::layoutAddAttributeEntry(Thread* thread, const Tuple& entries,
const Object& name,
AttributeInfo info) {
HandleScope scope(thread);
word entries_len = entries.length();
MutableTuple new_entries(&scope, newMutableTuple(entries_len + 1));
new_entries.replaceFromWith(0, *entries, entries_len);
new_entries.atPut(entries_len, layoutNewAttribute(name, info));
return new_entries.becomeImmutable();
}
static RawUnbound kDictOverflowLayoutTransitionEdgeName = Unbound::object();
RawObject Runtime::typeDictOnlyLayout(Thread* thread, const Type& type,
word num_in_object_attr) {
HandleScope scope(thread);
Layout type_instance_layout(&scope, type.instanceLayout());
// Find a layout derived from type, whose in-object attributes' length matches
// the needed `num_in_object_attr`.
List edges(&scope, type_instance_layout.deletions());
DCHECK(edges.numItems() % 2 == 0,
"edges must contain an even number of elements");
// This name is used only for the internal layout transition edge from
// a layout to a new one with dict overflow.
Object key(&scope, kDictOverflowLayoutTransitionEdgeName);
for (word i = 0, num_items = edges.numItems(); i < num_items; i += 2) {
if (edges.at(i) == key) {
if (Layout::cast(edges.at(i + 1)).numInObjectAttributes() ==
num_in_object_attr) {
return edges.at(i + 1);
}
}
}
Layout new_layout(&scope, layoutCreateCopy(thread, type_instance_layout));
// This annotates `new_layout` to return it when requested next time.
new_layout.setNumInObjectAttributes(num_in_object_attr);
new_layout.setInObjectAttributes(emptyTuple());
new_layout.setOverflowAttributes(emptyTuple());
new_layout.setDictOverflowOffset(new_layout.overflowOffset());
// Add the edge to the existing layout.
layoutAddEdge(thread, this, edges, key, new_layout);
return *new_layout;
}
RawObject Runtime::layoutAddAttribute(Thread* thread, const Layout& layout,
const Object& name, word flags,
AttributeInfo* info) {
// Check if a edge for the attribute addition already exists
RawObject result = layoutFollowEdge(layout.additions(), *name);
if (!result.isError()) {
bool found = layoutFindAttribute(Layout::cast(result), name, info);
DCHECK(found, "couldn't find attribute on new layout");
return result;
}
// Create a new layout and figure out where to place the attribute
HandleScope scope(thread);
Layout new_layout(&scope, layoutCreateCopy(thread, layout));
Tuple inobject(&scope, layout.inObjectAttributes());
if (inobject.length() < layout.numInObjectAttributes()) {
*info = AttributeInfo(inobject.length() * kPointerSize,
flags | AttributeFlags::kInObject);
new_layout.setInObjectAttributes(
layoutAddAttributeEntry(thread, inobject, name, *info));
} else {
Tuple overflow(&scope, layout.overflowAttributes());
*info = AttributeInfo(overflow.length(), flags);
new_layout.setOverflowAttributes(
layoutAddAttributeEntry(thread, overflow, name, *info));
}
// Add the edge to the existing layout
List edges(&scope, layout.additions());
layoutAddEdge(thread, this, edges, name, new_layout);
return *new_layout;
}
RawObject Runtime::layoutSetDescribedType(Thread* thread, const Layout& from,
const Type& to) {
// Check if a edge for the attribute addition already exists
HandleScope scope(thread);
MutableTuple transitions(&scope, layout_type_transitions_);
word length = transitions.length();
word first_free = length;
for (word i = 0; i < length; i += LayoutTypeTransition::kTransitionSize) {
RawObject found_from = transitions.at(i + LayoutTypeTransition::kFrom);
if (found_from == from &&
transitions.at(i + LayoutTypeTransition::kTo) == to) {
// The transition exists; return the result layout
return transitions.at(i + LayoutTypeTransition::kResult);
}
if (first_free == length && found_from == SmallInt::fromWord(0)) {
// There's an empty slot we can use to store the transition
first_free = i;
}
}
// Make a new layout and transition the type
Layout result(&scope, layoutCreateCopy(thread, from));
result.setDescribedType(*to);
// If needed, grow the table
word index = first_free;
word needed_length = index + LayoutTypeTransition::kTransitionSize;
if (needed_length > length) {
word new_length = newCapacity(length, /*min_capacity=*/needed_length);
MutableTuple new_tuple(&scope, newMutableTuple(new_length));
new_tuple.replaceFromWith(0, *transitions, length);
layout_type_transitions_ = *new_tuple;
transitions = *new_tuple;
}
// Add the edge to the layout
transitions.atPut(index + LayoutTypeTransition::kFrom, *from);
transitions.atPut(index + LayoutTypeTransition::kTo, *to);
transitions.atPut(index + LayoutTypeTransition::kResult, *result);
return *result;
}
static RawObject markEntryDeleted(Thread* thread, RawObject entries,
const Object& name) {
HandleScope scope(thread);
Tuple entries_old(&scope, entries);
word length = entries_old.length();
DCHECK(length > 0, "length must be positive");
Runtime* runtime = thread->runtime();
MutableTuple entries_new(&scope, runtime->newMutableTuple(length));
Object zero(&scope, SmallInt::fromWord(0));
Tuple entry(&scope, runtime->emptyTuple());
for (word i = 0; i < length; i++) {
entry = entries_old.at(i);
if (entry.at(0) == name) {
AttributeInfo old_info(entry.at(1));
AttributeInfo info(old_info.offset(), AttributeFlags::kDeleted);
entry = runtime->layoutNewAttribute(/*name=*/zero, info);
}
entries_new.atPut(i, *entry);
}
return entries_new.becomeImmutable();
}
RawObject Runtime::layoutDeleteAttribute(Thread* thread, const Layout& layout,
const Object& name,
AttributeInfo info) {
// Check if an edge exists for removing the attribute
RawObject next_layout = layoutFollowEdge(layout.deletions(), *name);
if (!next_layout.isError()) {
return next_layout;
}
// No edge was found, create a new layout and add an edge
HandleScope scope(thread);
Layout new_layout(&scope, layoutCreateCopy(thread, layout));
if (info.isInObject()) {
new_layout.setInObjectAttributes(
markEntryDeleted(thread, layout.inObjectAttributes(), name));
} else {
new_layout.setOverflowAttributes(
markEntryDeleted(thread, layout.overflowAttributes(), name));
}
// Add the edge to the existing layout
List edges(&scope, layout.deletions());
layoutAddEdge(thread, this, edges, name, new_layout);
return *new_layout;
}
void Runtime::layoutSetTupleOverflow(RawLayout layout) {
layout.setOverflowAttributes(emptyTuple());
}
void Runtime::clearHandleScopes() {
MutexGuard lock(&threads_mutex_);
for (Thread* thread = main_thread_; thread != nullptr;
thread = thread->next()) {
Handles* handles = thread->handles();
Object* handle = handles->head();
while (handle != nullptr) {
handle = handle->nextHandle();
handles->pop(handle);
}
}
}
void Runtime::freeApiHandles() {
// Dealloc the Module handles first as they are the handle roots
Thread* thread = Thread::current();
HandleScope scope(thread);
Dict modules(&scope, modules_);
// Clear the modules dict and run a GC, to run dealloc slots on any now-dead
// NativeProxy objects.
dictClear(thread, modules);
// Dealloc modules referenced by modules_by_index.
freeExtensionModules(thread);
// Process any native instance that is only referenced through the NativeProxy
for (;;) {
word before = numExtensionObjects(this) + numApiHandles(this);
collectGarbage();
word after = numExtensionObjects(this) + numApiHandles(this);
word num_handles_collected = before - after;
if (num_handles_collected == 0) {
// Fixpoint: no change in tracking
break;
}
}
collectGarbage();
// Finally, skip trying to cleanly deallocate the object. Just free the
// memory without calling the deallocation functions.
disposeApiHandles(this);
disposeExtensionObjects(this);
}
RawObject Runtime::bytesToInt(Thread* thread, const Bytes& bytes,
endian endianness, bool is_signed) {
word length = bytes.length();
DCHECK(length <= kMaxWord - kWordSize, "huge length will overflow");
if (length == 0) {
return SmallInt::fromWord(0);
}
// Positive numbers that end up having the highest bit of their highest digit
// set need an extra zero digit.
byte high_byte = bytes.byteAt(endianness == endian::big ? 0 : length - 1);
bool high_bit = high_byte & (1 << (kBitsPerByte - 1));
bool extra_digit = high_bit && !is_signed && length % kWordSize == 0;
word num_digits = (length + (kWordSize - 1)) / kWordSize + extra_digit;
HandleScope scope(thread);
LargeInt result(&scope, createLargeInt(num_digits));
byte sign_extension = (is_signed && high_bit) ? kMaxByte : 0;
if (endianness == endian::little && endian::native == endian::little) {
result.copyFrom(*bytes, sign_extension);
} else {
for (word d = 0; d < num_digits; ++d) {
uword digit = 0;
for (int w = 0; w < kWordSize; ++w) {
word idx = d * kWordSize + w;
byte b;
if (idx >= length) {
b = sign_extension;
} else {
b = bytes.byteAt(endianness == endian::big ? length - idx - 1 : idx);
}
digit |= static_cast<uword>(b) << (w * kBitsPerByte);
}
result.digitAtPut(d, digit);
}
}
return normalizeLargeInt(thread, result);
}
RawObject Runtime::normalizeLargeInt(Thread* thread,
const LargeInt& large_int) {
word shrink_to_digits = large_int.numDigits();
for (word digit = large_int.digitAt(shrink_to_digits - 1), next_digit;
shrink_to_digits > 1; --shrink_to_digits, digit = next_digit) {
next_digit = large_int.digitAt(shrink_to_digits - 2);
// break if we have neither a redundant sign-extension nor a redundnant
// zero-extension.
if ((digit != -1 || next_digit >= 0) && (digit != 0 || next_digit < 0)) {
break;
}
}
if (shrink_to_digits == 1 && SmallInt::isValid(large_int.digitAt(0))) {
return SmallInt::fromWord(large_int.digitAt(0));
}
if (shrink_to_digits == large_int.numDigits()) {
return *large_int;
}
// Shrink. Future Optimization: Shrink in-place instead of copying.
HandleScope scope(thread);
LargeInt result(&scope, createLargeInt(shrink_to_digits));
for (word i = 0; i < shrink_to_digits; ++i) {
result.digitAtPut(i, large_int.digitAt(i));
}
return *result;
}
static uword addWithCarry(uword x, uword y, uword carry_in, uword* carry_out) {
DCHECK(carry_in <= 1, "carry must be 0 or 1");
uword sum;
uword carry0 = __builtin_add_overflow(x, y, &sum);
uword carry1 = __builtin_add_overflow(sum, carry_in, &sum);
*carry_out = carry0 | carry1;
return sum;
}
RawObject Runtime::intAdd(Thread* thread, const Int& left, const Int& right) {
if (left.isSmallInt() && right.isSmallInt()) {
// Take a shortcut because we know the result fits in a word.
word left_digit = SmallInt::cast(*left).value();
word right_digit = SmallInt::cast(*right).value();
return newInt(left_digit + right_digit);
}
HandleScope scope(thread);
word left_digits = left.numDigits();
word right_digits = right.numDigits();
Int longer(&scope, left_digits > right_digits ? *left : *right);
Int shorter(&scope, left_digits <= right_digits ? *left : *right);
word shorter_digits = shorter.numDigits();
word longer_digits = longer.numDigits();
word result_digits = longer_digits + 1;
LargeInt result(&scope, createLargeInt(result_digits));
uword carry = 0;
for (word i = 0; i < shorter_digits; i++) {
uword sum =
addWithCarry(longer.digitAt(i), shorter.digitAt(i), carry, &carry);
result.digitAtPut(i, sum);
}
uword shorter_sign_extension = shorter.isNegative() ? kMaxUword : 0;
for (word i = shorter_digits; i < longer_digits; i++) {
uword sum =
addWithCarry(longer.digitAt(i), shorter_sign_extension, carry, &carry);
result.digitAtPut(i, sum);
}
uword longer_sign_extension = longer.isNegative() ? kMaxUword : 0;
uword high_digit = longer_sign_extension + shorter_sign_extension + carry;
result.digitAtPut(result_digits - 1, high_digit);
return normalizeLargeInt(thread, result);
}
RawObject Runtime::intBinaryAnd(Thread* thread, const Int& left,
const Int& right) {
word left_digits = left.numDigits();
word right_digits = right.numDigits();
if (left_digits == 1 && right_digits == 1) {
return newInt(left.asWord() & right.asWord());
}
HandleScope scope(thread);
Int longer(&scope, left_digits > right_digits ? *left : *right);
Int shorter(&scope, left_digits <= right_digits ? *left : *right);
word num_digits = longer.numDigits();
LargeInt result(&scope, createLargeInt(num_digits));
for (word i = 0, e = shorter.numDigits(); i < e; ++i) {
result.digitAtPut(i, longer.digitAt(i) & shorter.digitAt(i));
}
if (shorter.isNegative()) {
for (word i = shorter.numDigits(); i < num_digits; ++i) {
result.digitAtPut(i, longer.digitAt(i));
}
} else {
for (word i = shorter.numDigits(); i < num_digits; ++i) {
result.digitAtPut(i, 0);
}
}
return normalizeLargeInt(thread, result);
}
RawObject Runtime::intInvert(Thread* thread, const Int& value) {
word num_digits = value.numDigits();
if (num_digits == 1) {
word value_word = value.asWord();
return newInt(~value_word);
}
HandleScope scope(thread);
LargeInt large_int(&scope, *value);
LargeInt result(&scope, createLargeInt(num_digits));
for (word i = 0; i < num_digits; ++i) {
uword digit = large_int.digitAt(i);
result.digitAtPut(i, ~digit);
}
DCHECK(result.isValid(), "valid large integer");
return *result;
}
RawObject Runtime::intNegate(Thread* thread, const Int& value) {
word num_digits = value.numDigits();
if (num_digits == 1) {
word value_word = value.asWord();
// Negating kMinWord results in a number with two digits.
if (value_word == kMinWord) {
const uword min_word[] = {static_cast<uword>(kMinWord), 0};
return newLargeIntWithDigits(min_word);
}
return newInt(-value_word);
}
HandleScope scope(thread);
LargeInt large_int(&scope, *value);
auto digits_zero = [&](word up_to_digit) {
for (word i = 0; i < up_to_digit; i++) {
if (large_int.digitAt(i) != 0) {
return false;
}
}
return true;
};
// The result of negating a number like `digits == {0, 0, ..., 0x800000.. }`
// needs an extra digit.
uword highest_digit = large_int.digitAt(num_digits - 1);
if (highest_digit == static_cast<uword>(kMinWord) &&
digits_zero(num_digits - 1)) {
LargeInt result(&scope, createLargeInt(num_digits + 1));
for (word i = 0; i < num_digits; i++) {
result.digitAtPut(i, large_int.digitAt(i));
}
result.digitAtPut(num_digits, 0);
DCHECK(result.isValid(), "Invalid LargeInt");
return *result;
}
// The result of negating a number like `digits == {0, 0, ..., 0x800000.., 0}`
// is one digit shorter.
if (highest_digit == 0 &&
large_int.digitAt(num_digits - 2) == static_cast<uword>(kMinWord) &&
digits_zero(num_digits - 2)) {
LargeInt result(&scope, createLargeInt(num_digits - 1));
for (word i = 0; i < num_digits - 1; i++) {
result.digitAtPut(i, large_int.digitAt(i));
}
DCHECK(result.isValid(), "Invalid LargeInt");
return *result;
}
LargeInt result(&scope, createLargeInt(num_digits));
word carry = 1;
for (word i = 0; i < num_digits; i++) {
uword digit = large_int.digitAt(i);
static_assert(sizeof(digit) == sizeof(long), "invalid builtin size");
carry = __builtin_uaddl_overflow(~digit, carry, &digit);
result.digitAtPut(i, digit);
}
DCHECK(carry == 0, "Carry should be zero");
DCHECK(result.isValid(), "Invalid LargeInt");
return *result;
}
// The division algorithm operates on half words. This is because to implement
// multiword division we require a doubleword division operation such as
// (`uint128_t / uint64_t -> uint128_t`). Such an operation does not exist on
// most architectures (x86_64 only has `uint128_t / uint64_t -> uint64_t`,
// aarch64 only `uint64_t / uint64_t -> uint64_t`). Instead we perform the
// algorithm on half words and use a `uint64_t / uint32_t -> uint64_t` division.
// This is easier and faster than trying to emulate a doubleword division.
typedef uint32_t halfuword;
static_assert(sizeof(halfuword) == sizeof(uword) / 2, "halfuword size");
const int kBitsPerHalfWord = kBitsPerByte * sizeof(halfuword);
static void halvesInvert(halfuword* halves, word num_halves) {
for (word i = 0; i < num_halves; i++) {
halves[i] = ~halves[i];
}
}
static void halvesNegate(halfuword* halves, word num_halves) {
uword carry = 1;
for (word i = 0; i < num_halves; i++) {
halfuword half = uword{~halves[i]} + carry;
halves[i] = half;
carry &= (half == 0);
}
}
static halfuword halvesAdd(halfuword* dest, const halfuword* src,
word num_halves) {
halfuword carry = 0;
for (word i = 0; i < num_halves; i++) {
uword sum = uword{dest[i]} + src[i] + carry;
dest[i] = static_cast<halfuword>(sum);
carry = sum >> kBitsPerHalfWord;
}
return carry;
}
static void halvesIncrement(halfuword* halves, word num_halves,
bool overflow_ok) {
for (word i = 0; i < num_halves; i++) {
halfuword half = halves[i] + 1;
halves[i] = half;
// We are done if there was no overflow.
if (half != 0) break;
DCHECK(overflow_ok || i < num_halves - 1, "overflow");
}
}
static void halvesFromIntMagnitude(halfuword* halves, const Int& number) {
word num_digits = number.numDigits();
for (word i = 0; i < num_digits; i++) {
uword digit = number.digitAt(i);
halves[i * 2] = static_cast<halfuword>(digit);
halves[i * 2 + 1] = digit >> kBitsPerHalfWord;
}
if (number.isNegative()) {
halvesNegate(halves, num_digits * 2);
}
}
// Given an array of size `num_halves` checks how many items at the end of the
// array is zero and returns a reduced length without them. Put another way:
// It drops leading zeros from an arbitrary precision little endian number.
static word halvesNormalize(halfuword* halves, word num_halves) {
while (halves[num_halves - 1] == 0) {
num_halves--;
DCHECK(num_halves > 0, "must not have every digit zero");
}
return num_halves;
}
static void halvesDecrement(halfuword* halves, word num_halves) {
DCHECK(num_halves > 0, "must have at least one half");
for (word i = 0; i < num_halves; i++) {
halfuword half = halves[i] - 1;
halves[i] = half;
// We are done if there is no borrow left.
if (half != ~halfuword{0}) return;
}
// Must only be used in situations that cannot underflow.
UNREACHABLE("underflow");
}
static void halvesShiftLeft(halfuword* halves, word num_halves, word shift) {
DCHECK(shift < kBitsPerHalfWord, "must not shift more than a halfuword");
if (shift == 0) return;
halfuword prev = 0;
for (word i = 0; i < num_halves; i++) {
halfuword half = halves[i];
halves[i] = (half << shift) | (prev >> (kBitsPerHalfWord - shift));
prev = half;
}
DCHECK(prev >> (kBitsPerHalfWord - shift) == 0, "must not overflow");
}
static void halvesShiftRight(halfuword* halves, word num_halves, word shift) {
DCHECK(shift < kBitsPerHalfWord, "must not shift more than a halfuword");
if (shift == 0) return;
halfuword prev = 0;
for (word i = num_halves - 1; i >= 0; i--) {
halfuword half = halves[i];
halves[i] = (half >> shift) | (prev << (kBitsPerHalfWord - shift));
prev = half;
}
}
static RawObject largeIntFromHalves(Thread* thread, const halfuword* halves,
word num_halves) {
DCHECK(num_halves % 2 == 0, "must have even number of halves");
word digits = num_halves / 2;
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
LargeInt result(&scope, runtime->createLargeInt(digits));
for (word i = 0; i < digits; i++) {
uword digit =
halves[i * 2] | (uword{halves[i * 2 + 1]} << kBitsPerHalfWord);
result.digitAtPut(i, digit);
}
return runtime->normalizeLargeInt(thread, result);
}
// Compute quotient and modulo of dividing a large integer through a divisor
// whose magnitude fits in a `halfuword`.
static void divideModuloSingleHalfDivisor(Thread* thread, const Int& dividend,
word divisor, Object* quotient,
Object* modulo) {
DCHECK(divisor >= 0 ? static_cast<halfuword>(divisor) == divisor
: static_cast<halfuword>(-divisor) == -divisor,
"divisor magnitude fits in half word");
word dividend_digits = dividend.numDigits();
bool same_sign = dividend.isNegative() == (divisor < 0);
halfuword divisor_half = divisor < 0 ? -divisor : divisor;
uword result_halves = dividend_digits * 2;
std::unique_ptr<halfuword[]> result(new halfuword[result_halves]);
halvesFromIntMagnitude(result.get(), dividend);
if (!same_sign) {
halvesDecrement(result.get(), result_halves);
}
word significant_result_halves = halvesNormalize(result.get(), result_halves);
halfuword remainder = 0;
for (word i = significant_result_halves - 1; i >= 0; i--) {
uword digit = (uword{remainder} << kBitsPerHalfWord) | result[i];
result[i] = digit / divisor_half;
remainder = digit % divisor_half;
// Note that the division result fits into a halfuword, because the upper
// half is the remainder from last round and therefore smaller than
// `divisor_half`.
}
Runtime* runtime = thread->runtime();
if (quotient) {
if (!same_sign) {
// Compute `-1 - quotient == -1 + (~quotient + 1) == ~quotient`.
halvesInvert(result.get(), result_halves);
}
*quotient = largeIntFromHalves(thread, result.get(), result_halves);
}
if (modulo) {
word modulo_word;
if (same_sign) {
modulo_word = remainder;
} else {
modulo_word = -static_cast<word>(remainder) + divisor_half - 1;
}
if (divisor < 0) {
modulo_word = -modulo_word;
}
*modulo = runtime->newInt(modulo_word);
}
}
// Perform unsigned integer division with multi-half dividend and divisor.
static void unsignedDivideRemainder(halfuword* result, word result_halves,
halfuword* dividend,
const halfuword* divisor,
word divisor_halves) {
// See Hackers Delight 9-2 "Multiword Division" and Knuth TAOCP volume 2,
// 4.3.1 for this algorithm.
DCHECK(divisor_halves > 1, "need at least 2 divisor halves");
// Expects the divisor to be normalized by left shifting until the highest bit
// is set. This ensures that the guess performed in each iteration step is off
// by no more than 2 (see Knuth for details and a proof).
DCHECK((divisor[divisor_halves - 1] & (1 << (kBitsPerHalfWord - 1))) != 0,
"need normalized divisor");
// Performs some arithmetic with no more than half the bits of a `uword`.
const uword half_mask = (uword{1} << kBitsPerHalfWord) - 1;
for (word r = result_halves - 1; r >= 0; r--) {
// Take the two highest words of the dividend. We implicitly have
// `dividend_halves = result_halves + divisor_halves - 1` (the actual
// dividend array is guaranteed to have at least one more half filled with
// zero on top for the first round). Since the dividend shrinks by 1 half
// each round, the two highest digits can be found starting at
// `r + divisor_halves - 1`.
uword dividend_high_word =
(uword{dividend[r + divisor_halves]} << kBitsPerHalfWord) |
uword{dividend[r + divisor_halves - 1]};
uword divisor_half = divisor[divisor_halves - 1];
// Guess this result half by dividing the two highest dividend halves.
// The guess gets us close: `guess_quot - 2 <= quot <= guess_quot`.
uword guess_quot = dividend_high_word / divisor_half;
uword guess_remainder = dividend_high_word % divisor_half;
// Iterate until the guess is exact.
while (guess_quot > half_mask ||
guess_quot * divisor[divisor_halves - 2] >
((guess_remainder << kBitsPerHalfWord) |
dividend[r + divisor_halves - 2])) {
guess_quot--;
guess_remainder += divisor_half;
if (guess_remainder > half_mask) break;
}
// Multiply and subtract from dividend.
uword borrow = 0;
for (word d = 0; d < divisor_halves; d++) {
uword product = guess_quot * divisor[d];
word diff =
static_cast<word>(dividend[d + r]) - borrow - (product & half_mask);
dividend[d + r] = static_cast<halfuword>(diff);
borrow = (product >> kBitsPerHalfWord) - (diff >> kBitsPerHalfWord);
}
word diff = static_cast<word>(dividend[r + divisor_halves]) - borrow;
dividend[r + divisor_halves] = static_cast<halfuword>(diff);
// If we subtracted too much, add back.
if (diff < 0) {
guess_quot--;
halfuword carry = halvesAdd(÷nd[r], divisor, divisor_halves);
dividend[r + divisor_halves] += carry;
}
result[r] = guess_quot;
}
}
// Like Runtime::intDivideModulo() but specifically for the case of the
// divisor's magnitued being bigger than the dividend's.
static void divideWithBiggerDivisor(Thread* thread, const Int& dividend,
const Int& divisor, Object* quotient,
Object* modulo) {
if (dividend.isZero()) {
if (quotient != nullptr) *quotient = SmallInt::fromWord(0);
if (modulo != nullptr) *modulo = SmallInt::fromWord(0);
return;
}
bool same_sign = dividend.isNegative() == divisor.isNegative();
if (quotient != nullptr) {
*quotient = SmallInt::fromWord(same_sign ? 0 : -1);
}
if (modulo != nullptr) {
if (!same_sign) {
*modulo = thread->runtime()->intAdd(thread, divisor, dividend);
} else if (dividend.isBool()) {
*modulo = convertBoolToInt(*dividend);
} else {
*modulo = *dividend;
}
}
}
bool Runtime::intDivideModulo(Thread* thread, const Int& dividend,
const Int& divisor, Object* quotient,
Object* modulo) {
// Some notes for understanding this code:
// - This is built around an unsigned division algorithm in
// `unsignedDivideRemainder()`.
// - Remember that this implements floor div and modulo which is different
// from C++ giving you truncated div and remainder when operands are
// negative.
// - To build a signed floor division from an unsigned division primitive we
// use the following formula when the sign of dividend and divisor differs:
// floor_div = -1 - (abs(dividend) - 1) / abs(divisor)
// modulo = divisor_sign *
// (abs(divisor) - 1 - (abs(dividend) - 1) % abs(divisor))
// TODO(matthiasb): Optimization idea: Fuse the independent operations/loops
// on arrays of `halfuword`s to reduce the number of passes over the data.
word divisor_digits = divisor.numDigits();
word dividend_digits = dividend.numDigits();
bool same_sign = dividend.isNegative() == divisor.isNegative();
if (divisor_digits == 1) {
word divisor_word = divisor.asWord();
if (divisor_word == 0) {
return false;
}
// Handle -1 as a special case because for dividend being the smallest
// negative number possible for the amount of digits and `divisor == -1`
// produces a result that is bigger than the input.
if (divisor_word == -1) {
if (quotient != nullptr) *quotient = intNegate(thread, dividend);
if (modulo != nullptr) *modulo = SmallInt::fromWord(0);
return true;
}
if (dividend_digits == 1) {
word dividend_word = dividend.asWord();
word quotient_word = dividend_word / divisor_word;
word modulo_word = dividend_word % divisor_word;
if (!same_sign && modulo_word) {
DCHECK(quotient_word > kMinWord, "underflow");
quotient_word--;
modulo_word += divisor_word;
}
if (quotient != nullptr) *quotient = newInt(quotient_word);
if (modulo != nullptr) *modulo = newInt(modulo_word);
return true;
}
// Handle the case where `abs(divisor)` fits in single half word.
// This helps performance and simplifies `unsignedDivideRemainder()` because
// it can assume to have at least 2 divisor half words.
word max_half_uword = (word{1} << kBitsPerHalfWord) - 1;
if (-max_half_uword <= divisor_word && divisor_word <= max_half_uword) {
divideModuloSingleHalfDivisor(thread, dividend, divisor_word, quotient,
modulo);
return true;
}
}
if (divisor_digits > dividend_digits) {
divideWithBiggerDivisor(thread, dividend, divisor, quotient, modulo);
return true;
}
// Convert divisor to `halfuword`s. Normalize by left shifting until the
// highest bit (of the highest half) is set as required by
// `unsignedDivideRemainder()`. We count the non-zero halves in the
// `significant_xxx_halves` variables.
word divisor_halves = divisor_digits * 2;
std::unique_ptr<halfuword[]> divisor_n(new halfuword[divisor_halves]);
halvesFromIntMagnitude(divisor_n.get(), divisor);
word significant_divisor_halves =
halvesNormalize(divisor_n.get(), divisor_halves);
static_assert(sizeof(divisor_n[0]) == sizeof(unsigned),
"choose right builtin");
int shift = __builtin_clz(divisor_n[significant_divisor_halves - 1]);
halvesShiftLeft(divisor_n.get(), significant_divisor_halves, shift);
// Convert dividend to `halfuword`s and shift by the same amount we used for
// the divisor. We reserve 1 extra half so we can save a bounds check in
// `unsignedDivideRemainder()` because `dividend_halves` will still be valid
// to access at index `significant_divisor_halves`.
word dividend_halves = (dividend_digits + 1) * 2;
std::unique_ptr<halfuword[]> dividend_n(new halfuword[dividend_halves]);
halvesFromIntMagnitude(dividend_n.get(), dividend);
dividend_n[dividend_halves - 1] = 0;
dividend_n[dividend_halves - 2] = 0;
if (!same_sign) {
halvesDecrement(dividend_n.get(), dividend_halves);
}
halvesShiftLeft(dividend_n.get(), dividend_halves, shift);
word significant_dividend_halves =
halvesNormalize(dividend_n.get(), dividend_halves);
// Handle special case of divisor being bigger than the dividend.
if (significant_divisor_halves > significant_dividend_halves ||
(significant_divisor_halves == significant_dividend_halves &&
divisor_n[significant_divisor_halves - 1] >
dividend_n[significant_divisor_halves - 1])) {
divideWithBiggerDivisor(thread, dividend, divisor, quotient, modulo);
return true;
}
// Allocate storage for result. Make sure we have an even number of halves.
word result_halves = (dividend_halves - divisor_halves + 2) & ~1;
DCHECK(result_halves % 2 == 0, "even number of halves");
std::unique_ptr<halfuword[]> result(new halfuword[result_halves]);
word significant_result_halves =
significant_dividend_halves - significant_divisor_halves + 1;
DCHECK(significant_result_halves <= result_halves, "no overflow");
unsignedDivideRemainder(result.get(), significant_result_halves,
dividend_n.get(), divisor_n.get(),
significant_divisor_halves);
// TODO(matthiasb): We copy the data in result[] to a new LargeInt,
// normalizeLargeInt will probably just copy it again. Should we normalize on
// result[]? Can we do it without duplicating the normalization code?
if (quotient != nullptr) {
for (word i = significant_result_halves; i < result_halves; i++) {
result[i] = 0;
}
if (!same_sign) {
// Compute `-1 - quotient == -1 + (~quotient + 1) == ~quotient`.
halvesInvert(result.get(), result_halves);
}
*quotient = largeIntFromHalves(thread, result.get(), result_halves);
}
if (modulo != nullptr) {
// `dividend` contains the remainder now. First revert normalization shift.
halvesShiftRight(dividend_n.get(), significant_dividend_halves, shift);
if (!same_sign) {
// Revert divisor shift.
halvesShiftRight(divisor_n.get(), significant_divisor_halves, shift);
// Compute `-remainder + divisor - 1`.
halvesNegate(dividend_n.get(), dividend_halves);
halfuword carry = halvesAdd(dividend_n.get(), divisor_n.get(),
significant_divisor_halves);
DCHECK(carry <= 1, "carry <= 1");
if (carry) {
halvesIncrement(dividend_n.get() + significant_divisor_halves,
dividend_halves - significant_divisor_halves, true);
}
halvesDecrement(dividend_n.get(), dividend_halves);
}
if (divisor.isNegative()) {
halvesNegate(dividend_n.get(), dividend_halves);
}
*modulo = largeIntFromHalves(thread, dividend_n.get(), dividend_halves);
}
return true;
}
static uword subtractWithBorrow(uword x, uword y, uword borrow_in,
uword* borrow_out) {
DCHECK(borrow_in <= 1, "borrow must be 0 or 1");
uword difference;
uword borrow0 = __builtin_sub_overflow(x, y, &difference);
uword borrow1 = __builtin_sub_overflow(difference, borrow_in, &difference);
*borrow_out = borrow0 | borrow1;
return difference;
}
static void fullMultiply(uword x, uword y, uword* result_low,
uword* result_high) {
static_assert(sizeof(uword) == 8, "assuming uword is 64bit");
auto result = __extension__ static_cast<unsigned __int128>(x) * y;
*result_low = static_cast<uword>(result);
*result_high = static_cast<uword>(result >> 64);
}
RawObject Runtime::intMultiply(Thread* thread, const Int& left,
const Int& right) {
// See also Hackers Delight Chapter 8 Multiplication.
word left_digits = left.numDigits();
word right_digits = right.numDigits();
if (left_digits == 1 && right_digits == 1) {
word left_digit = static_cast<word>(left.digitAt(0));
word right_digit = static_cast<word>(right.digitAt(0));
word result;
if (!__builtin_mul_overflow(left_digit, right_digit, &result)) {
return newInt(result);
}
}
HandleScope scope(thread);
word result_digits = left.numDigits() + right.numDigits();
LargeInt result(&scope, createLargeInt(result_digits));
for (word i = 0; i < result_digits; i++) {
result.digitAtPut(i, 0);
}
// Perform an unsigned multiplication.
for (word l = 0; l < left_digits; l++) {
uword digit_left = left.digitAt(l);
uword carry = 0;
for (word r = 0; r < right_digits; r++) {
uword digit_right = right.digitAt(r);
uword result_digit = result.digitAt(l + r);
uword product_low;
uword product_high;
fullMultiply(digit_left, digit_right, &product_low, &product_high);
uword carry0;
uword sum0 = addWithCarry(result_digit, product_low, 0, &carry0);
uword carry1;
uword sum1 = addWithCarry(sum0, carry, 0, &carry1);
result.digitAtPut(l + r, sum1);
// Note that this cannot overflow: Even with digit_left and digit_right
// being kMaxUword the result is something like 0xfff...e0000...1, so
// carry1 will be zero in these cases where the high word is close to
// overflow.
carry = product_high + carry0 + carry1;
}
result.digitAtPut(l + right_digits, carry);
}
// Correct for `left` signedness:
// Interpreting a negative number as unsigned means we are off by
// `2**num_bits` (i.e. for a single byte `-3 = 0b11111101` gets interpreted
// as 253, which is off by `256 = 253 - -3 = 2**8`).
// Hence if we interpreted a negative `left` as unsigned, the multiplication
// result will be off by `right * 2**left_num_bits`. We can correct that by
// subtracting `right << left_num_bits`.
if (left.isNegative()) {
uword borrow = 0;
for (word r = 0; r < right_digits; r++) {
uword right_digit = right.digitAt(r);
uword result_digit = result.digitAt(r + left_digits);
uword difference =
subtractWithBorrow(result_digit, right_digit, borrow, &borrow);
result.digitAtPut(r + left_digits, difference);
}
}
// Correct for `right` signedness, analogous to the `left` correction.
if (right.isNegative()) {
uword borrow = 0;
for (word l = 0; l < left_digits; l++) {
uword left_digit = left.digitAt(l);
uword result_digit = result.digitAt(l + right_digits);
uword difference =
subtractWithBorrow(result_digit, left_digit, borrow, &borrow);
result.digitAtPut(l + right_digits, difference);
}
}
return normalizeLargeInt(thread, result);
}
RawObject Runtime::intBinaryOr(Thread* thread, const Int& left,
const Int& right) {
word left_digits = left.numDigits();
word right_digits = right.numDigits();
if (left_digits == 1 && right_digits == 1) {
return newInt(left.asWord() | right.asWord());
}
HandleScope scope(thread);
Int longer(&scope, left_digits > right_digits ? *left : *right);
Int shorter(&scope, left_digits <= right_digits ? *left : *right);
word num_digits = longer.numDigits();
LargeInt result(&scope, createLargeInt(num_digits));
for (word i = 0, e = shorter.numDigits(); i < e; ++i) {
result.digitAtPut(i, longer.digitAt(i) | shorter.digitAt(i));
}
if (shorter.isNegative()) {
for (word i = shorter.numDigits(); i < num_digits; ++i) {
result.digitAtPut(i, kMaxUword);
}
} else {
for (word i = shorter.numDigits(); i < num_digits; ++i) {
result.digitAtPut(i, longer.digitAt(i));
}
}
return normalizeLargeInt(thread, result);
}
RawObject Runtime::intBinaryRshift(Thread* thread, const Int& num,
const Int& amount) {
DCHECK(!amount.isNegative(), "shift amount must be positive");
if (num.numDigits() == 1) {
if (amount.numDigits() > 1) {
return SmallInt::fromWord(0);
}
word amount_word = amount.asWord();
if (amount_word >= kBitsPerWord) {
return SmallInt::fromWord(0);
}
word num_word = num.asWord();
return newInt(num_word >> amount_word);
}
word amount_digits = amount.numDigits();
uword digit0 = amount.digitAt(0);
word shift_words = digit0 / kBitsPerWord;
word shift_bits = digit0 % kBitsPerWord;
if (amount_digits > 1) {
// It is impossible to create a LargeInt so big that a two-digit amount
// would result in a non-zero result.
if (amount_digits > 2) {
return SmallInt::fromWord(0);
}
uword digit1 = amount.digitAt(1);
// Must fit in a word and be positive.
if (digit1 / kBitsPerWord / 2 != 0) {
return SmallInt::fromWord(0);
}
shift_words |= digit1 * (kMaxUword / kBitsPerWord + 1);
}
word result_digits = num.numDigits() - shift_words;
if (result_digits < 0) {
return SmallInt::fromWord(0);
}
if (shift_bits == 0 && shift_words == 0) {
return *num;
}
HandleScope scope(thread);
LargeInt result(&scope, createLargeInt(result_digits));
if (shift_bits == 0) {
for (word i = 0; i < result_digits; i++) {
result.digitAtPut(i, num.digitAt(shift_words + i));
}
} else {
uword prev = num.isNegative() ? kMaxUword : 0;
word prev_shift = kBitsPerWord - shift_bits;
for (word i = result_digits - 1; i >= 0; i--) {
uword digit = num.digitAt(shift_words + i);
uword result_digit = prev << prev_shift | digit >> shift_bits;
result.digitAtPut(i, result_digit);
prev = digit;
}
}
return normalizeLargeInt(thread, result);
}
RawObject Runtime::intBinaryLshift(Thread* thread, const Int& num,
const Int& amount) {
DCHECK(!amount.isNegative(), "shift amount must be non-negative");
word num_digits = num.numDigits();
if (num_digits == 1 && num.asWord() == 0) return SmallInt::fromWord(0);
CHECK(amount.numDigits() == 1, "lshift result is too large");
word amount_word = amount.asWord();
if (amount_word == 0) {
if (num.isBool()) {
return convertBoolToInt(*num);
}
return *num;
}
word shift_bits = amount_word % kBitsPerWord;
word shift_words = amount_word / kBitsPerWord;
word high_digit = num.digitAt(num.numDigits() - 1);
// check if high digit overflows when shifted - if we need an extra digit
word bit_length =
Utils::highestBit(high_digit >= 0 ? high_digit : ~high_digit);
bool overflow = bit_length + shift_bits >= kBitsPerWord;
// check if result fits into one word
word result_digits = num_digits + shift_words + overflow;
if (result_digits == 1) {
return newInt(high_digit << shift_bits);
}
// allocate large int and zero-initialize low digits
HandleScope scope(thread);
LargeInt result(&scope, createLargeInt(result_digits));
for (word i = 0; i < shift_words; i++) {
result.digitAtPut(i, 0);
}
// iterate over digits of num and handle carrying
if (shift_bits == 0) {
for (word i = 0; i < num_digits; i++) {
result.digitAtPut(i + shift_words, num.digitAt(i));
}
DCHECK(!overflow, "overflow must be false with shift_bits==0");
} else {
word right_shift = kBitsPerWord - shift_bits;
uword prev = 0;
for (word i = 0; i < num_digits; i++) {
uword digit = num.digitAt(i);
uword result_digit = (digit << shift_bits) | (prev >> right_shift);
result.digitAtPut(i + shift_words, result_digit);
prev = digit;
}
if (overflow) {
// signed shift takes cares of keeping the sign
word overflow_digit = static_cast<word>(prev) >> right_shift;
result.digitAtPut(result_digits - 1, static_cast<uword>(overflow_digit));
}
}
DCHECK(result.isValid(), "result must be valid");
return *result;
}
RawObject Runtime::intBinaryXor(Thread* thread, const Int& left,
const Int& right) {
word left_digits = left.numDigits();
word right_digits = right.numDigits();
if (left_digits == 1 && right_digits == 1) {
return newInt(left.asWord() ^ right.asWord());
}
HandleScope scope(thread);
Int longer(&scope, left_digits > right_digits ? *left : *right);
Int shorter(&scope, left_digits <= right_digits ? *left : *right);
word num_digits = longer.numDigits();
LargeInt result(&scope, createLargeInt(num_digits));
for (word i = 0, e = shorter.numDigits(); i < e; ++i) {
result.digitAtPut(i, longer.digitAt(i) ^ shorter.digitAt(i));
}
if (shorter.isNegative()) {
for (word i = shorter.numDigits(); i < num_digits; ++i) {
result.digitAtPut(i, ~longer.digitAt(i));
}
} else {
for (word i = shorter.numDigits(); i < num_digits; ++i) {
result.digitAtPut(i, longer.digitAt(i));
}
}
return normalizeLargeInt(thread, result);
}
RawObject Runtime::intSubtract(Thread* thread, const Int& left,
const Int& right) {
if (left.isSmallInt() && right.isSmallInt()) {
// Take a shortcut because we know the result fits in a word.
word left_digit = SmallInt::cast(*left).value();
word right_digit = SmallInt::cast(*right).value();
return newInt(left_digit - right_digit);
}
HandleScope scope(thread);
word left_digits = left.numDigits();
word right_digits = right.numDigits();
word shorter_digits = Utils::minimum(left_digits, right_digits);
word longer_digits = Utils::maximum(left_digits, right_digits);
word result_digits = longer_digits + 1;
LargeInt result(&scope, createLargeInt(result_digits));
uword borrow = 0;
for (word i = 0; i < shorter_digits; i++) {
uword difference =
subtractWithBorrow(left.digitAt(i), right.digitAt(i), borrow, &borrow);
result.digitAtPut(i, difference);
}
uword left_sign_extension = left.isNegative() ? kMaxUword : 0;
uword right_sign_extension = right.isNegative() ? kMaxUword : 0;
if (right_digits == longer_digits) {
for (word i = shorter_digits; i < longer_digits; i++) {
uword difference = subtractWithBorrow(left_sign_extension,
right.digitAt(i), borrow, &borrow);
result.digitAtPut(i, difference);
}
} else {
for (word i = shorter_digits; i < longer_digits; i++) {
uword difference = subtractWithBorrow(
left.digitAt(i), right_sign_extension, borrow, &borrow);
result.digitAtPut(i, difference);
}
}
uword high_digit = left_sign_extension - right_sign_extension - borrow;
result.digitAtPut(result_digits - 1, high_digit);
return normalizeLargeInt(thread, result);
}
RawObject Runtime::intToBytes(Thread* thread, const Int& num, word length,
endian endianness) {
HandleScope scope(thread);
Object result(&scope, Unbound::object());
byte buffer[SmallBytes::kMaxLength];
byte* dst;
if (length <= SmallBytes::kMaxLength) {
dst = buffer;
} else {
result = thread->runtime()->createLargeBytes(length);
dst = reinterpret_cast<byte*>(LargeBytes::cast(*result).address());
}
word extension_idx;
word extension_length;
if (endianness == endian::little && endian::native == endian::little) {
word copied = num.copyTo(dst, length);
extension_idx = copied;
extension_length = length - copied;
} else {
word num_digits = num.numDigits();
for (word i = 0; i < num_digits; ++i) {
uword digit = num.digitAt(i);
for (int x = 0; x < kWordSize; ++x) {
word idx = i * kWordSize + x;
byte b = digit & kMaxByte;
// The last digit may have more (insignificant) bits than the
// resulting buffer.
if (idx >= length) {
return length <= SmallBytes::kMaxLength
? SmallBytes::fromBytes({buffer, length})
: *result;
}
if (endianness == endian::big) {
idx = length - idx - 1;
}
dst[idx] = b;
digit >>= kBitsPerByte;
}
}
word num_bytes = num_digits * kWordSize;
extension_idx = endianness == endian::big ? 0 : num_bytes;
extension_length = length - num_bytes;
}
if (extension_length > 0) {
byte sign_extension = num.isNegative() ? 0xff : 0;
for (word i = 0; i < extension_length; ++i) {
dst[extension_idx + i] = sign_extension;
}
}
return length <= SmallBytes::kMaxLength
? SmallBytes::fromBytes({buffer, length})
: *result;
}
// Str replacement when the result can fit in SmallStr.
static RawObject strReplaceSmallStr(const Str& src, const Str& oldstr,
const Str& newstr, word count,
word result_len) {
DCHECK_BOUND(result_len, SmallStr::kMaxLength);
word src_len = src.length();
word old_len = oldstr.length();
word new_len = newstr.length();
byte buffer[SmallStr::kMaxLength];
byte* dst = buffer;
for (word i = 0, match_count = 0; i < src_len;) {
if (match_count == count || !strHasPrefix(src, oldstr, i)) {
*dst++ = src.byteAt(i++);
continue;
}
newstr.copyTo(dst, new_len);
dst += new_len;
i += old_len;
match_count++;
}
return SmallStr::fromBytes(View<byte>(buffer, result_len));
}
RawObject Runtime::strReplace(Thread* thread, const Str& src, const Str& oldstr,
const Str& newstr, word count) {
word src_len = src.length();
if (count < 0) {
count = SmallInt::kMaxValue; // PY_SSIZE_T_MAX.
} else if (count == 0 || src_len == 0) {
return *src;
}
if (oldstr.equals(*newstr)) {
return *src;
}
// Update the count to the number of occurences of oldstr in src, capped by
// the given count.
count = strCountSubStr(src, oldstr, count);
if (count == 0) {
return *src;
}
word old_len = oldstr.length();
word new_len = newstr.length();
word result_len = src_len + (new_len - old_len) * count;
if (result_len <= SmallStr::kMaxLength) {
return strReplaceSmallStr(src, oldstr, newstr, count, result_len);
}
HandleScope scope(thread);
LargeStr result(&scope, createLargeStr(result_len));
word diff = new_len - old_len;
word offset = 0;
word match_count = 0;
word i;
for (i = 0; i < src_len && match_count < count;) {
// TODO(T41400083): Use a different search algorithm
if (strHasPrefix(src, oldstr, i)) {
byte* dst = reinterpret_cast<byte*>(LargeStr::cast(*result).address());
newstr.copyTo(dst + i + offset, new_len);
match_count++;
offset += diff;
i += old_len;
continue;
}
byte* dst = reinterpret_cast<byte*>(result.address());
dst[i + offset] = src.byteAt(i);
i++;
}
// Copy the rest of the string.
if (i < src_len) {
if (src.isLargeStr()) {
byte* src_byte = reinterpret_cast<byte*>(LargeStr::cast(*src).address());
byte* dst = reinterpret_cast<byte*>(result.address());
std::memcpy(dst + i + offset, src_byte + i, src_len - i);
} else {
for (; i < src_len; i++) {
byte* dst = reinterpret_cast<byte*>(result.address());
dst[i + offset] = src.byteAt(i);
}
}
}
return *result;
}
// TODO(T30392425) Ensure thread safety
word Runtime::nextModuleIndex() { return ++next_module_index_; }
} // namespace py