diff --git a/modules/Kernel/ActiveProcess.st b/modules/Kernel/ActiveProcess.st index 8373bf6..e986227 100644 --- a/modules/Kernel/ActiveProcess.st +++ b/modules/Kernel/ActiveProcess.st @@ -19,6 +19,11 @@ ActiveProcess >> beInactive [ self changeClassTo: SuspendedProcess ] +{ #category : #testing } +ActiveProcess >> isActive [ + ^true +] + { #category : #converting } ActiveProcess >> canReturnTo: homeFrame [ diff --git a/modules/Kernel/Context.st b/modules/Kernel/Context.st index 75e3e35..cbcc63a 100644 --- a/modules/Kernel/Context.st +++ b/modules/Kernel/Context.st @@ -66,8 +66,8 @@ Context >> code: anExecutableCode method: aCompiledMethod [ { #category : #services } Context >> copyAllTo: aProcessStack [ - | start offset | - start := parent copyAllTo: aProcessStack. + | start | + start := caller copyAllTo: aProcessStack. ^self copyTo: aProcessStack at: start ] diff --git a/modules/Kernel/HostSystem.st b/modules/Kernel/HostSystem.st index 3c94a35..c13cfd9 100644 --- a/modules/Kernel/HostSystem.st +++ b/modules/Kernel/HostSystem.st @@ -152,12 +152,16 @@ HostSystem >> prependSearchPath: aString type: aSymbol [ { #category : #loading } HostSystem >> primitiveLoad: aSymbol [ + | error | + ^Error signal: error ] { #category : #loading } HostSystem >> primitiveLoadModuleFromPath: aString [ + | error | + ^Error signal: error ] { #category : #services } @@ -177,16 +181,15 @@ HostSystem >> setupDefaultSearchPaths [ platform-native PATH separator) supplies extra source directories. We also walk a few levels up from the current directory so the runtime works both from the project root and from a build subdirectory." - | env home cwd prefixes | + | env home prefixes | env := self getEnv: 'EGG_MODULES_PATH'. env ifNotNil: [ (env substringsSplitBy: self pathSeparator) do: [:p | self addSearchPath: p type: #tonel. self addSearchPath: p type: #ems]]. - cwd := self currentDirectory. prefixes := #('' '../' '../../' '../../../' '../../../../'). prefixes do: [:p | | base | - base := cwd , '/' , p , 'modules'. + base := p , 'modules'. (self pathExists: base) ifTrue: [ self addSearchPath: base type: #tonel. self addSearchPath: base type: #ems]]. @@ -200,7 +203,9 @@ HostSystem >> setupDefaultSearchPaths [ { #category : #private } HostSystem >> suspended: aProcess because: anException [ - + self + logError: 'Error: ' , anException description; + exit: 1 ] { #category : #services } diff --git a/modules/Kernel/KernelModule.st b/modules/Kernel/KernelModule.st index 88cc1b1..d38b741 100644 --- a/modules/Kernel/KernelModule.st +++ b/modules/Kernel/KernelModule.st @@ -255,7 +255,6 @@ KernelModule >> suspendOnUnhandledExceptions [ KernelModule >> suspendBecause: anException [ | active process | active := Processor activeProcess. - "active snapshot ifNil: [^self]." process := Process sending: #suspended:because: to: host with: {active. anException}. process takeControl ] diff --git a/modules/Kernel/Process.st b/modules/Kernel/Process.st index b074ae9..dda1b57 100644 --- a/modules/Kernel/Process.st +++ b/modules/Kernel/Process.st @@ -84,7 +84,12 @@ Process >> findHandlerFor: anException [ { #category : #private } Process >> initializeStack [ - nativeStack := ProcessStack on: self + nativeStack := ProcessVMStack on: self +] + +{ #category : #testing } +Process >> isActive [ + ^false ] { #category : #private } diff --git a/modules/Kernel/ProcessStack.st b/modules/Kernel/ProcessStack.st index 76ed70c..32e6a66 100644 --- a/modules/Kernel/ProcessStack.st +++ b/modules/Kernel/ProcessStack.st @@ -9,7 +9,8 @@ Class { #instVars : [ 'process', 'sp', - 'bp' + 'bp', + 'env' ], #category : #Kernel } @@ -35,8 +36,18 @@ ProcessStack >> contextSwitchTo: next [ { #category : #services } ProcessStack >> fillFrom: aContext [ #CRITICAL. - sp := (aContext copyAllTo: self). - self at: sp put: aContext lastEnvironment + sp := aContext copyAllTo: self. + env := aContext lastEnvironment +] + +{ #category : #accessing } +ProcessStack >> env [ + ^env +] + +{ #category : #accessing } +ProcessStack >> env: anObject [ + env := anObject ] { #category : #accessing } diff --git a/modules/Kernel/ProcessVMStack.st b/modules/Kernel/ProcessVMStack.st index 67bb144..3080a28 100644 --- a/modules/Kernel/ProcessVMStack.st +++ b/modules/Kernel/ProcessVMStack.st @@ -6,9 +6,18 @@ Class { #name : #ProcessVMStack, #superclass : #ProcessStack, + #instVars : [ + 'buffer', + 'bufferSize' + ], #category : #Kernel } +{ #category : 'instance creation' } +ProcessVMStack class >> on: aProcess [ + ^self new process: aProcess +] + { #category : 'Primitives' } ProcessVMStack >> at: anInteger [ @@ -20,8 +29,13 @@ ProcessVMStack >> at: anInteger put: anObject [ ] { #category : 'Primitives' } -ProcessVMStack >> basicInitialize [ - +ProcessVMStack >> initializeWithNewBuffer [ + +] + +{ #category : 'Primitives' } +ProcessVMStack >> initializeWithActiveBuffer [ + ] { #category : #primitives } @@ -49,10 +63,12 @@ ProcessVMStack >> contextSwitchTo: aProcessStack [ ] -{ #category : 'initializing' } -ProcessVMStack >> initialize [ - super initialize. - self basicInitialize +{ #category : 'accessing' } +ProcessVMStack >> process: aProcess [ + super process: aProcess. + aProcess isActive + ifTrue: [self initializeWithActiveBuffer] + ifFalse: [self initializeWithNewBuffer] ] { #category : 'Primitives' } diff --git a/modules/Kernel/String.st b/modules/Kernel/String.st index 733a11f..f2c079e 100644 --- a/modules/Kernel/String.st +++ b/modules/Kernel/String.st @@ -44,6 +44,11 @@ String class >> fromUTF8: aByteArray [ ifFalse: [UTF8 current decode: aByteArray] ] +{ #category : #'instance creation' } +String class >> new [ + ^self new: 0 +] + { #category : #'instance creation' } String class >> new: anInteger [ ^self primitiveNewBytes: anInteger + 1 diff --git a/runtime/cpp/Allocator/GarbageCollector.cpp b/runtime/cpp/Allocator/GarbageCollector.cpp index e1f018b..9a45267 100644 --- a/runtime/cpp/Allocator/GarbageCollector.cpp +++ b/runtime/cpp/Allocator/GarbageCollector.cpp @@ -192,30 +192,23 @@ void GarbageCollector::nativeFramesStartingAt_bp_do_(uintptr_t **stack, uintptr_ } } -void GarbageCollector::scanFirstStackChunk_(HeapObject *aProcessVMStack) { - /** - * Scanning needs to fetch a chain of stack (frame) pointers. The head - * of the chain is either of two cases: - * - The active process. - * In that case, the top of the stack is a common frame (probably a call - * to a primitive). No special action needs to be done. - * - A sleeping (native) process. - * In that case, the top of the stack is that process' env, followed - * by a retaddr. The GC has to scan that addr and then continue normally. - * (TODO: make env an instvar of the ProcessVMStack) - */ - //if (aProcessVMStack != runtime->_activeProcessStack) - // this->scanTopSlot_(aProcessVMStack); - - auto firstSP = _runtime->processVMStackSP_(aProcessVMStack) + 2; - auto firstBP = _runtime->processVMStackBP_(aProcessVMStack); - auto stack = (uintptr_t**)nullptr; //_runtime->processVMStackContext_(aProcessVMStack)->stack(); - this->nativeFramesStartingAt_bp_do_(stack, firstSP, firstBP, +void GarbageCollector::scanStack_sp_bp_(uintptr_t **stack, uintptr_t sp, uintptr_t bp) { + this->nativeFramesStartingAt_bp_do_(stack, sp, bp, [this](uintptr_t *frame, uintptr_t size) { this->scanNativeStackFrame_sized_(frame, size); }); } +void GarbageCollector::scanSuspendedProcessStack_(HeapObject *aProcessVMStack) { + // A suspended process' topmost frame was laid out by ProcessStack>>fillFrom:. + // `sp` ivar is the slot just below the saved-bp slot of that topmost frame + // Start the frame walk at sp+1 (saved-bp slot) up to bp. + auto firstSP = _runtime->processStackSP_(aProcessVMStack) + 1; + auto firstBP = _runtime->processStackBP_(aProcessVMStack); + auto stack = (uintptr_t**)_runtime->processVMStackBuffer_(aProcessVMStack); + this->scanStack_sp_bp_(stack, firstSP, firstBP); +} + void GarbageCollector::scanPointer_(Object** pointer) { this->scanRoot_(pointer); @@ -230,27 +223,24 @@ void GarbageCollector::scanCurrentContext() { this->scanPointer_((Object**)&context->_regE); this->scanPointer_((Object**)&context->_regM); - - auto firstSP = context->stackPointer(); - auto firstBP = context->framePointer(); - auto stack = (uintptr_t**)context->stack(); - this->nativeFramesStartingAt_bp_do_(stack, firstSP, firstBP, - [this](uintptr_t *frame, uintptr_t size) { - this->scanNativeStackFrame_sized_(frame, size); - }); + this->scanStack_sp_bp_( + (uintptr_t**)context->stack(), + context->stackPointer(), + context->framePointer()); } void GarbageCollector::scanStack_(HeapObject *aProcessVMStack) { - //auto context = _runtime->processVMStackContext_(aProcessVMStack); - - // skip this stack if it corresponds to active process, which has already been scanned - //if (context == _runtime->_evaluator->context()) + // Skip this stack if it corresponds to the active process: its buffer is + // the one bound to the live evaluator context, which scanCurrentContext + // has already walked using the live SP/BP/regs. + auto buffer = _runtime->processVMStackBuffer_(aProcessVMStack); + if (buffer == _runtime->_evaluator->context()->stack()) return; - auto process = _runtime->processVMStackProcess_(aProcessVMStack); + auto process = _runtime->processStackProcess_(aProcessVMStack); if (_runtime->processStackIsValid_(process)) - this->scanFirstStackChunk_(aProcessVMStack); + this->scanSuspendedProcessStack_(aProcessVMStack); /* unimplemented GC in callbacks this->stackFramesBeneathCallbackIn_Do_(aProcessVMStack, diff --git a/runtime/cpp/Allocator/GarbageCollector.h b/runtime/cpp/Allocator/GarbageCollector.h index 2400c6d..9441ea1 100644 --- a/runtime/cpp/Allocator/GarbageCollector.h +++ b/runtime/cpp/Allocator/GarbageCollector.h @@ -57,7 +57,8 @@ class GarbageCollector { void scanSpecialSlots_(HeapObject *special); void nativeFramesStartingAt_bp_do_(uintptr_t **stack, uintptr_t sp, uintptr_t bp, std::function block); - void scanFirstStackChunk_(HeapObject * aProcessVMStack); + void scanStack_sp_bp_(uintptr_t **stack, uintptr_t sp, uintptr_t bp); + void scanSuspendedProcessStack_(HeapObject * aProcessVMStack); void scanPointer_(Object **pointer); void scanCurrentContext(); void scanStack_(HeapObject *aProcessVMStack); diff --git a/runtime/cpp/Bootstrap/Bootstrapper.cpp b/runtime/cpp/Bootstrap/Bootstrapper.cpp index b73ea3f..dd3c368 100644 --- a/runtime/cpp/Bootstrap/Bootstrapper.cpp +++ b/runtime/cpp/Bootstrap/Bootstrapper.cpp @@ -88,7 +88,7 @@ void Bootstrapper::loadKernelSpecs() { if (className == "package") continue; std::string source = readSourceFile_(className); - ClassSpec* spec = reader.parseFile(source); + ClassSpec* spec = reader.parseFile(source, entry.path().string()); _moduleSpec.addClass(spec); } } @@ -107,7 +107,7 @@ void Bootstrapper::loadKernelSpecs() { if (!file.is_open()) continue; std::string source((std::istreambuf_iterator(file)), std::istreambuf_iterator()); - ClassSpec* extensionSpec = reader.parseFile(source); + ClassSpec* extensionSpec = reader.parseFile(source, entry.path().string()); ClassSpec* existingSpec = _moduleSpec.resolveClass(className); if (!existingSpec) { std::cerr << " Warning: VM extension for unknown class " << className << std::endl; diff --git a/runtime/cpp/Bootstrap/SourceModuleLoader.cpp b/runtime/cpp/Bootstrap/SourceModuleLoader.cpp index 0557da6..e76857a 100644 --- a/runtime/cpp/Bootstrap/SourceModuleLoader.cpp +++ b/runtime/cpp/Bootstrap/SourceModuleLoader.cpp @@ -18,6 +18,8 @@ #include #include #include +#include +#include namespace Egg { @@ -59,7 +61,7 @@ HeapObject* SourceModuleLoader::loadModuleFromSource(const std::string& modulePa std::string source((std::istreambuf_iterator(file)), std::istreambuf_iterator()); - ClassSpec* spec = reader.parseFile(source); + ClassSpec* spec = reader.parseFile(source, entry.path().string()); _moduleSpec.addClass(spec); if (spec->isExtension()) { extensionNames.push_back(spec->name()); @@ -302,17 +304,34 @@ Object* SourceModuleLoader::createNewClassFrom_(ClassSpec* spec, Object* module) void SourceModuleLoader::createMethodsOf_(Object* cls, ClassSpec* spec) { GCedRef clsRef(cls); - // Instance methods - for (const auto& method : spec->methods()) { - createNewMethod_(method.source(), clsRef.get(), method.category()); - } + auto runMethod = [&](const MethodSpec& method, Object* receiver, const std::string& side) { + try { + createNewMethod_(method.source(), receiver, method.category()); + } catch (const std::exception& e) { + rethrowWithContext_(e, spec->name(), side, method.source()); + } + }; + + for (const auto& method : spec->methods()) + runMethod(method, clsRef.get(), ""); - // Class methods auto metaclass = _runtime->sendLocal_to_("class", clsRef.get()); GCedRef metaRef(metaclass); - for (const auto& method : spec->metaclass()->methods()) { - createNewMethod_(method.source(), metaRef.get(), method.category()); - } + for (const auto& method : spec->metaclass()->methods()) + runMethod(method, metaRef.get(), " class"); +} + +[[noreturn]] void SourceModuleLoader::rethrowWithContext_(const std::exception& e, + const Egg::string& className, + const std::string& side, + const Egg::string& source) { + size_t nl = source.find(U'\n'); + Egg::string sig = nl == Egg::string::npos ? source : source.substr(0, nl); + std::stringstream ss; + ss << e.what() + << "\n while compiling " << className.toUtf8() << side + << " >> " << sig.toUtf8(); + throw std::runtime_error(ss.str()); } void SourceModuleLoader::createNewMethod_(const Egg::string& source, Object* species, const Egg::string& category) { diff --git a/runtime/cpp/Bootstrap/SourceModuleLoader.h b/runtime/cpp/Bootstrap/SourceModuleLoader.h index 496410e..67e74c8 100644 --- a/runtime/cpp/Bootstrap/SourceModuleLoader.h +++ b/runtime/cpp/Bootstrap/SourceModuleLoader.h @@ -35,6 +35,10 @@ class SourceModuleLoader { Object* createNewClassFrom_(ClassSpec* spec, Object* module); void createMethodsOf_(Object* cls, ClassSpec* spec); void createNewMethod_(const Egg::string& source, Object* species, const Egg::string& category = Egg::string("")); + [[noreturn]] static void rethrowWithContext_(const std::exception& e, + const Egg::string& className, + const std::string& side, + const Egg::string& source); Object* createExtensionMethod_(const Egg::string& source, Object* species); // Literal transfer helpers diff --git a/runtime/cpp/Bootstrap/TonelReader.cpp b/runtime/cpp/Bootstrap/TonelReader.cpp index 0ed103e..428e1eb 100644 --- a/runtime/cpp/Bootstrap/TonelReader.cpp +++ b/runtime/cpp/Bootstrap/TonelReader.cpp @@ -9,6 +9,9 @@ #include "CodeSpecs.h" #include "Egg.h" +#include +#include + namespace Egg { // ── Stream primitives ──────────────────────────────────────────────── @@ -38,9 +41,11 @@ void TonelReader::skipLine() { // ── Tonel structure ────────────────────────────────────────────────── -ClassSpec* TonelReader::parseFile(const std::string& utf8Source) { +ClassSpec* TonelReader::parseFile(const std::string& utf8Source, + const std::string& filename) { _source = Egg::string(utf8Source); _pos = 0; + _filename = filename; Egg::string comment = readComments(); Egg::string type = readType(); @@ -60,7 +65,7 @@ ClassSpec* TonelReader::parseFile(const std::string& utf8Source) { } else if (type == "Class") { spec->supername(fields.count("superclass") ? fields["superclass"] : Egg::string("")); } else { - error_("Unknown Tonel type: " + type.toUtf8()); + parseError_("Unknown Tonel type: " + type.toUtf8()); } // #type field: #variable (pointer-indexed), #bytes (byte-indexed) @@ -219,7 +224,7 @@ Egg::string TonelReader::nextBlock() { char32_t prev = 0; while (nested > 0) { if (atEnd()) - error_("unterminated method body"); + parseError_("unterminated method body"); char32_t ch = next(); if (prev == U'$') { // This character is a character literal value (e.g. $[ $] $' $" $$) @@ -264,6 +269,20 @@ void TonelReader::skipComment() { skipToMatch(U'"'); } +void TonelReader::parseError_(const std::string& message) const { + // Compute 1-based line/column from _pos in _source. + size_t line = 1, col = 1; + size_t limit = _pos < _source.length() ? _pos : _source.length(); + for (size_t i = 0; i < limit; ++i) { + if (_source[i] == U'\n') { line++; col = 1; } + else { col++; } + } + std::stringstream ss; + ss << (_filename.empty() ? "" : _filename) + << ":" << line << ":" << col << ": " << message; + throw std::runtime_error(ss.str()); +} + // Mirrors TonelReader >> skipToMatch: // Skips until a non-doubled occurrence of `ch` is found. void TonelReader::skipToMatch(char32_t ch) { diff --git a/runtime/cpp/Bootstrap/TonelReader.h b/runtime/cpp/Bootstrap/TonelReader.h index 2eb948d..db7d476 100644 --- a/runtime/cpp/Bootstrap/TonelReader.h +++ b/runtime/cpp/Bootstrap/TonelReader.h @@ -21,7 +21,9 @@ class TonelReader { // Parse a complete Tonel-format .st file and return a ClassSpec. // The returned ClassSpec is heap-allocated; caller takes ownership. - ClassSpec* parseFile(const std::string& utf8Source); + // The optional filename is only used to enrich parse-error messages. + ClassSpec* parseFile(const std::string& utf8Source, + const std::string& filename = ""); private: // Stream-style helpers @@ -51,9 +53,13 @@ class TonelReader { Egg::string parseSTONString(); std::vector parseSTONArray(); + // Throws std::runtime_error with file:line:col context. + [[noreturn]] void parseError_(const std::string& message) const; + // State Egg::string _source; size_t _pos = 0; + std::string _filename; }; } // namespace Egg diff --git a/runtime/cpp/Evaluator/EvaluationContext.cpp b/runtime/cpp/Evaluator/EvaluationContext.cpp index 34d89ac..9115dc2 100644 --- a/runtime/cpp/Evaluator/EvaluationContext.cpp +++ b/runtime/cpp/Evaluator/EvaluationContext.cpp @@ -2,6 +2,7 @@ #include "EvaluationContext.h" #include +#include #include #include "Runtime.h" @@ -24,6 +25,7 @@ EvaluationContext::EvaluationContext(Runtime *runtime) : _runtime(runtime) _regBP = _regPC = 0; _regS = nullptr; _stack = new Object*[STACK_SIZE]; + _stackSize = STACK_SIZE; } HeapObject* EvaluationContext::classBinding() diff --git a/runtime/cpp/Evaluator/EvaluationContext.h b/runtime/cpp/Evaluator/EvaluationContext.h index 765b750..9b3b7dc 100644 --- a/runtime/cpp/Evaluator/EvaluationContext.h +++ b/runtime/cpp/Evaluator/EvaluationContext.h @@ -23,6 +23,7 @@ class EvaluationContext { HeapObject *_regM, *_regE; uintptr_t _regSP, _regBP, _regPC; Object *_regS, **_stack; + uintptr_t _stackSize; Runtime *_runtime; const int FRAME_TO_RECEIVER_DELTA = 1; @@ -42,12 +43,20 @@ class EvaluationContext { Object* self() { return _regS; } HeapObject* environment() { return _regE; } + void environment_(HeapObject* env) { _regE = env; } HeapObject* compiledCode() { return _regM; } uintptr_t stackPointer() { return _regSP; } + void stackPointer_(uintptr_t sp) { _regSP = sp; } uintptr_t framePointer() { return _regBP; } void framePointer_(uintptr_t bp) { _regBP = bp; } Object** stack() { return _stack; } + uintptr_t stackSize() { return _stackSize; } + + void bindToBuffer_size_(Object **buffer, uintptr_t size) { + _stack = buffer; + _stackSize = size; + } HeapObject* classBinding(); diff --git a/runtime/cpp/Evaluator/Evaluator.cpp b/runtime/cpp/Evaluator/Evaluator.cpp index dbee7d1..9ee917e 100644 --- a/runtime/cpp/Evaluator/Evaluator.cpp +++ b/runtime/cpp/Evaluator/Evaluator.cpp @@ -185,6 +185,7 @@ void Evaluator::initializePrimitives() this->addPrimitive("HostPlatformName", &Evaluator::primitiveHostPlatformName); this->addPrimitive("HostCurrentMilliseconds", &Evaluator::primitiveHostCurrentMilliseconds); this->addPrimitive("HostLog", &Evaluator::primitiveHostLog); + this->addPrimitive("HostExit", &Evaluator::primitiveHostExit); this->addPrimitive("HostReadFile", &Evaluator::primitiveHostReadFile); this->addPrimitive("HostWriteFile", &Evaluator::primitiveHostWriteFile); this->addPrimitive("HostCreateDirectory", &Evaluator::primitiveHostCreateDirectory); @@ -194,15 +195,16 @@ void Evaluator::initializePrimitives() this->addPrimitive("HostLoadModuleFromPath", &Evaluator::primitiveHostLoadModuleFromPath); - //this->addPrimitive("PrepareForExecution", &Evaluator::primitivePrepareForExecution); - //this->addPrimitive("ProcessVMStackInitialize", &Evaluator::primitiveProcessVMStackInitialize); + this->addPrimitive("PrepareForExecution", &Evaluator::primitivePrepareForExecution); + this->addPrimitive("ProcessVMStackInitializeWithNewBuffer", &Evaluator::primitiveProcessVMStackInitializeWithNewBuffer); + this->addPrimitive("ProcessVMStackInitializeWithActiveBuffer", &Evaluator::primitiveProcessVMStackInitializeWithActiveBuffer); this->addPrimitive("ProcessVMStackAt", &Evaluator::primitiveProcessStackAt); - //this->addPrimitive("ProcessVMStackAtPut", &Evaluator::primitiveProcessVMStackAtPut); - //this->addPrimitive("ProcessVMStackBpAtPut", &Evaluator::primitiveProcessVMStackBpAtPut); - //this->addPrimitive("ProcessVMStackPcAtPut", &Evaluator::primitiveProcessVMStackPcAtPut); + this->addPrimitive("ProcessVMStackAtPut", &Evaluator::primitiveProcessVMStackAtPut); + this->addPrimitive("ProcessVMStackBpAtPut", &Evaluator::primitiveProcessVMStackBpAtPut); + this->addPrimitive("ProcessVMStackPcAtPut", &Evaluator::primitiveProcessVMStackPcAtPut); this->addPrimitive("ProcessVMStackBP", &Evaluator::primitiveProcessBP); - //this->addPrimitive("ProcessVMStackBufferSize", &Evaluator::primitiveProcessVMStackBufferSize); - //this->addPrimitive("ProcessVMStackContextSwitchTo", &Evaluator::primitiveProcessVMStackContextSwitchTo); + this->addPrimitive("ProcessVMStackBufferSize", &Evaluator::primitiveProcessVMStackBufferSize); + this->addPrimitive("ProcessVMStackContextSwitchTo", &Evaluator::primitiveProcessVMStackContextSwitchTo); _linearizer->primitives_(_primitives); } @@ -316,14 +318,14 @@ void Egg::Evaluator::messageNotUnderstood_(SAbstractMessage *message) auto array = _runtime->newArray_(args); _context->push_(message->selector()); _context->push_((Object*)array); - auto symbol = _runtime->existingSymbolFrom_("doesNotUnderstand:"); + auto symbol = _runtime->existingSymbolFrom_("_doesNotUnderstand:with:"); auto behavior = _runtime->behaviorOf_(_regR); auto dnu = _runtime->lookup_startingAt_((Object*)symbol, behavior); if (!dnu) { std::string errmsg = std::string("Message not understood!\n") + this->_regR->printString() + " does not understand " + message->selector()->printString() + - "\nmethod #doesNotUnderstand: not found on receiver"; + "\nmethod #_doesNotUnderstand:with: not found on receiver"; error_(errmsg); } @@ -519,6 +521,18 @@ Object* Evaluator::failPrimitive() return this->_regR; } +Object* Evaluator::failPrimitiveWith_(Object* errorObject) +{ + // Same as failPrimitive, but additionally publishes `errorObject` to the + // Smalltalk fallback code by writing it into the method's first temp. + // The fallback method should declare a `| error |` temp to receive it. + // Temp indices are 1-based (matches compiler/linearizer convention). + auto method = this->_context->method(); + if (this->_runtime->methodTempCount_(method) > 0) + this->_context->stackTemporaryAt_put_(1, errorObject); + return this->failPrimitive(); +} + Object* Evaluator::primitiveAt() { auto receiver = this->_context->self(); @@ -885,6 +899,15 @@ Object* Evaluator::primitiveHostLog() { return this->_regR; } +Object* Evaluator::primitiveHostExit() { + auto arg = this->_context->firstArgument(); + int code = 0; + if (arg->isSmallInteger()) + code = (int)arg->asSmallInteger()->asNative(); + std::exit(code); + return (Object*)this->_runtime->_nilObj; +} + Object* Evaluator::primitiveHostReadFile() { auto filename = this->_context->firstArgument(); std::ifstream file(filename->asHeapObject()->asLocalString(), std::ios::binary); @@ -912,9 +935,13 @@ Object* Evaluator::primitiveHostLoadModule() { auto guard = this->_runtime->_heap->atGCUnsafepoint(); auto name = this->_context->firstArgument()->asHeapObject()->asLocalString(); std::cout << "loading " << name << "..." << std::endl; - auto module = (Object*)this->_runtime->loadModule_(this->_context->firstArgument()->asHeapObject()); - std::cout << " done loading " << name << std::endl; - return module; + try { + auto module = (Object*)this->_runtime->loadModule_(this->_context->firstArgument()->asHeapObject()); + std::cout << " done loading " << name << std::endl; + return module; + } catch (const std::exception& e) { + return this->failPrimitiveWith_((Object*)this->_runtime->newString_(e.what())); + } } Object* Evaluator::primitiveHostWriteFile() { @@ -964,9 +991,13 @@ Object* Evaluator::primitiveHostLoadModuleFromPath() { auto guard = this->_runtime->_heap->atGCUnsafepoint(); auto path = this->_context->firstArgument()->asHeapObject()->asLocalString(); std::cout << "loading from " << path << "..." << std::endl; - auto module = (Object*)this->_runtime->loadModuleFromPath_(path); - std::cout << " done loading " << path << std::endl; - return module; + try { + auto module = (Object*)this->_runtime->loadModuleFromPath_(path); + std::cout << " done loading " << path << std::endl; + return module; + } catch (const std::exception& e) { + return this->failPrimitiveWith_((Object*)this->_runtime->newString_(e.what())); + } } Object* Evaluator::primitiveNew() { @@ -1010,6 +1041,128 @@ Object* Evaluator::primitiveProcessStackAt() return _context->stackAt_(this->_context->firstArgument()->asSmallInteger()->asNative()); } +Object* Evaluator::primitiveProcessVMStackInitializeWithNewBuffer() +{ + // Allocate the off-heap buffer that backs a (suspended) process's stack and + // stash it (tagged as a SmallInteger so the GC won't try to follow it) into + // the receiver. + // TODO: this buffer leaks when the wrapping ProcessVMStack is GC'd. + // Wiring a real finalizer requires GC-sweep hooks (see GarbageCollector:: + // rememberSpecial_ for the special-class machinery). For now the leak is + // bounded by the number of Process objects ever created (1 OS-page-sized + // buffer each) and reclaimed by the OS on exit. + auto pvm = this->_context->self()->asHeapObject(); + uintptr_t size = this->_context->stackSize(); + Object **buffer = new Object*[size]; + for (uintptr_t i = 0; i < size; ++i) buffer[i] = (Object*)_runtime->_nilObj; + _runtime->processVMStackBuffer_put_(pvm, buffer); + _runtime->processVMStackBufferSize_put_(pvm, size); + return (Object*)pvm; +} + +Object* Evaluator::primitiveProcessVMStackInitializeWithActiveBuffer() +{ + // Adopt the live C++ evaluator stack: the receiver becomes the wrapper + // around the buffer that the running native code is currently using. + // SP/BP/env are written by the suspending side (contextSwitchTo:) so we + // don't initialize them here. The GC distinguishes the active stack from + // suspended ones by comparing buffer pointers with the evaluator context. + auto pvm = this->_context->self()->asHeapObject(); + auto ctx = this->_context; + _runtime->processVMStackBuffer_put_(pvm, ctx->stack()); + _runtime->processVMStackBufferSize_put_(pvm, ctx->stackSize()); + return (Object*)pvm; +} + +Object* Evaluator::primitiveProcessVMStackBufferSize() +{ + auto receiver = this->_context->self()->asHeapObject(); + return receiver->slot(Offsets::ProcessVMStackBufferSize); +} + +Object* Evaluator::primitiveProcessVMStackAtPut() +{ + auto receiver = this->_context->self()->asHeapObject(); + auto buffer = this->_runtime->processVMStackBuffer_(receiver); + if (!buffer) return failPrimitive(); + auto index = this->_context->firstArgument()->asSmallInteger()->asNative(); + auto value = this->_context->secondArgument(); + auto size = this->_runtime->processVMStackBufferSize_(receiver); + if (index < 1 || (uintptr_t)index > size) return failPrimitive(); + buffer[index - 1] = value; + return value; +} + +Object* Evaluator::primitiveProcessVMStackBpAtPut() +{ + auto receiver = this->_context->self()->asHeapObject(); + auto buffer = this->_runtime->processVMStackBuffer_(receiver); + if (!buffer) return failPrimitive(); + auto index = this->_context->firstArgument()->asSmallInteger()->asNative(); + auto bp = this->_context->secondArgument()->asSmallInteger()->asNative(); + auto size = this->_runtime->processVMStackBufferSize_(receiver); + if (index < 1 || (uintptr_t)index > size) return failPrimitive(); + buffer[index - 1] = (Object*)(uintptr_t)bp; + return this->_context->secondArgument(); +} + +Object* Evaluator::primitiveProcessVMStackPcAtPut() +{ + auto receiver = this->_context->self()->asHeapObject(); + auto buffer = this->_runtime->processVMStackBuffer_(receiver); + if (!buffer) return failPrimitive(); + auto index = this->_context->firstArgument()->asSmallInteger()->asNative(); + auto pc = this->_context->secondArgument()->asSmallInteger()->asNative(); + // third argument is the code object; not stored at this slot in our model, + // but mirrors the Smalltalk signature `pcAt:put:of:`. + auto size = this->_runtime->processVMStackBufferSize_(receiver); + if (index < 1 || (uintptr_t)index > size) return failPrimitive(); + buffer[index - 1] = (Object*)(uintptr_t)pc; + return this->_context->secondArgument(); +} + +Object* Evaluator::primitiveProcessVMStackContextSwitchTo() +{ + auto outgoing = this->_context->self()->asHeapObject(); // current stack + auto incoming = this->_context->firstArgument()->asHeapObject(); // target stack + + // Snapshot live registers into the outgoing PVMStack ivars. + this->_runtime->processStackSP_put_(outgoing, this->_context->stackPointer()); + this->_runtime->processStackBP_put_(outgoing, this->_context->framePointer()); + this->_runtime->processStackEnv_put_(outgoing, (Object*)this->_context->environment()); + + // Bind the evaluator buffer to the incoming stack's buffer. + auto buffer = this->_runtime->processVMStackBuffer_(incoming); + auto size = this->_runtime->processVMStackBufferSize_(incoming); + this->_context->bindToBuffer_size_(buffer, size); + + // Restore SP from the incoming PVMStack. + // Just popFrame to load M/E/S/PC/BP from the topmost frame. + auto sp = this->_runtime->processStackSP_(incoming); + this->_context->stackPointer_(sp + 1); + this->_context->framePointer_(this->_context->stackPointer()); + + // popFrame reads the method out of the buffer; ensure that method has + // its executable code prepared (the Smalltalk-level prepareForExecution + // primitive is a no-op so that may not yet have happened). + this->_context->popFrame(); + auto method = this->_context->compiledCode(); + if (this->_runtime->methodExecutableCode_(method) == this->_runtime->_nilObj) + this->prepareForExecution_(method); + auto code = this->_runtime->methodExecutableCode_(method); + _work = this->_runtime->executableCodeWork_(code); + + + return (Object*)this->_runtime->_trueObj; +} + +Object* Evaluator::primitivePrepareForExecution() +{ + // CompiledMethod>>prepareForExecution. In this runtime methods are + // compiled lazily on first invoke, so this is a no-op. + return this->_context->self(); +} + Object* Evaluator::primitivePrimeFor() { return this->primitivePrimeFor_(this->_context->firstArgument()->asSmallInteger()->asNative()); } diff --git a/runtime/cpp/Evaluator/Evaluator.h b/runtime/cpp/Evaluator/Evaluator.h index 6b48323..cf9bf69 100644 --- a/runtime/cpp/Evaluator/Evaluator.h +++ b/runtime/cpp/Evaluator/Evaluator.h @@ -182,6 +182,7 @@ class Evaluator : public SExpressionVisitor { Object* boolObject(bool aBoolean); Object* failPrimitive(); + Object* failPrimitiveWith_(Object* errorObject); Object* primitiveAt(); Object* primitiveAtPut(); @@ -227,6 +228,7 @@ class Evaluator : public SExpressionVisitor { Object* primitiveHostLoadModule(); Object* primitiveHostLoadModuleFromPath(); Object* primitiveHostLog(); + Object* primitiveHostExit(); Object* primitiveHostPathExists(); Object* primitiveHostPlatformName(); Object* primitiveHostReadFile(); @@ -238,6 +240,14 @@ class Evaluator : public SExpressionVisitor { Object* primitivePerformWithArguments(); Object* primitiveProcessBP(); Object* primitiveProcessStackAt(); + Object* primitiveProcessVMStackInitializeWithNewBuffer(); + Object* primitiveProcessVMStackInitializeWithActiveBuffer(); + Object* primitiveProcessVMStackBufferSize(); + Object* primitiveProcessVMStackAtPut(); + Object* primitiveProcessVMStackBpAtPut(); + Object* primitiveProcessVMStackPcAtPut(); + Object* primitiveProcessVMStackContextSwitchTo(); + Object* primitivePrepareForExecution(); Object* primitivePrimeFor(); Object* primitivePrimeFor_(auto anInteger); Object* primitiveSMIBitAnd(); diff --git a/runtime/cpp/Evaluator/Runtime.h b/runtime/cpp/Evaluator/Runtime.h index 7b67ace..c16cf6c 100644 --- a/runtime/cpp/Evaluator/Runtime.h +++ b/runtime/cpp/Evaluator/Runtime.h @@ -476,18 +476,59 @@ class Runtime { return process->slot(Offsets::ProcessTopContext) != (Object*)KnownObjects::nil; } - HeapObject* processVMStackProcess_(HeapObject * processVMStack) { - return processVMStack->slot(Offsets::ProcessVMStackProcess)->asHeapObject(); + HeapObject* processStackProcess_(HeapObject * processStack) { + return processStack->slot(Offsets::ProcessStackProcess)->asHeapObject(); } - uintptr_t processVMStackSP_(HeapObject * processVMStack) { - return processVMStack->slot(Offsets::ProcessVMStackSP)->asSmallInteger()->asNative(); + void processStackProcess_put_(HeapObject * processStack, Object *process) { + processStack->slot(Offsets::ProcessStackProcess) = process; } - uintptr_t processVMStackBP_(HeapObject * processVMStack) { - return processVMStack->slot(Offsets::ProcessVMStackBP)->asSmallInteger()->asNative(); + uintptr_t processStackSP_(HeapObject * processStack) { + return processStack->slot(Offsets::ProcessStackSP)->asSmallInteger()->asNative(); } + void processStackSP_put_(HeapObject * processStack, uintptr_t sp) { + processStack->slot(Offsets::ProcessStackSP) = (Object*)newInteger_(sp); + } + + uintptr_t processStackBP_(HeapObject * processStack) { + return processStack->slot(Offsets::ProcessStackBP)->asSmallInteger()->asNative(); + } + + void processStackBP_put_(HeapObject * processStack, uintptr_t bp) { + processStack->slot(Offsets::ProcessStackBP) = (Object*)newInteger_(bp); + } + + Object* processStackEnv_(HeapObject * processStack) { + return processStack->slot(Offsets::ProcessStackEnv); + } + + void processStackEnv_put_(HeapObject * processStack, Object *env) { + processStack->slot(Offsets::ProcessStackEnv) = env; + } + + Object** processVMStackBuffer_(HeapObject * processVMStack) { + auto smi = processVMStack->slot(Offsets::ProcessVMStackBuffer); + if (smi == (Object*)_nilObj) return nullptr; + return reinterpret_cast(smi->asSmallInteger()->asNative()); + } + + void processVMStackBuffer_put_(HeapObject * processVMStack, Object **buffer) { + processVMStack->slot(Offsets::ProcessVMStackBuffer) = + (Object*)newInteger_(reinterpret_cast(buffer)); + } + + uintptr_t processVMStackBufferSize_(HeapObject * processVMStack) { + return processVMStack->slot(Offsets::ProcessVMStackBufferSize)->asSmallInteger()->asNative(); + } + + void processVMStackBufferSize_put_(HeapObject * processVMStack, uintptr_t size) { + processVMStack->slot(Offsets::ProcessVMStackBufferSize) = (Object*)newInteger_(size); + } + + + void initializeKernelObjects() { this->_falseObj = _kernel->_exports["false"]; diff --git a/runtime/cpp/KnownConstants.h b/runtime/cpp/KnownConstants.h index eacf796..33ea64a 100644 --- a/runtime/cpp/KnownConstants.h +++ b/runtime/cpp/KnownConstants.h @@ -93,10 +93,13 @@ enum Offsets { ProcessNativeStack = 1, ProcessTopContext = 2, - // inst vars of ProcessVMStack (the VM's internal stack object for processes) - ProcessVMStackProcess = 0, - ProcessVMStackSP = 1, - ProcessVMStackBP = 2, + // inst vars of ProcessStack (and its VM-internal subclass ProcessVMStack) + ProcessStackProcess = 0, + ProcessStackSP = 1, + ProcessStackBP = 2, + ProcessStackEnv = 3, + ProcessVMStackBuffer = 4, + ProcessVMStackBufferSize = 5, FFI_uint8 = 0, FFI_sint8 = 1, diff --git a/runtime/cpp/Loader.cpp b/runtime/cpp/Loader.cpp index e21b9e4..a618bdc 100644 --- a/runtime/cpp/Loader.cpp +++ b/runtime/cpp/Loader.cpp @@ -9,6 +9,7 @@ #include "FileImageSegment.h" #include #include +#include namespace Egg { @@ -25,7 +26,7 @@ std::string Loader::findModulesDir_() { return _modulesDir; std::vector prefixes = { - "../../../../", "../../../", "../../", "../", "./" + "./", "../", "../../", "../../../", "../../../../" }; for (const auto& prefix : prefixes) { auto candidate = prefix + _modulesDir; @@ -96,8 +97,7 @@ HeapObject* Loader::loadModule_(const std::string& name) { module = sourceLoader.loadModuleFromSource(modPath); } else { - error(("Module not found: " + name).c_str()); - return nullptr; + throw std::runtime_error("Module not found: " + name); } _loadedModules[name] = module; @@ -143,8 +143,7 @@ HeapObject* Loader::loadModuleFromPath_(const std::string& path) { module = sourceLoader.loadModuleFromSource(path); } else { - error(("Module not found at path: " + path).c_str()); - return nullptr; + throw std::runtime_error("Module not found at path: " + path); } _loadedModules[name] = module; diff --git a/runtime/pharo/Powerlang-Core/EggBootstrapModule.class.st b/runtime/pharo/Powerlang-Core/EggBootstrapModule.class.st index bbb2110..3eb22a2 100644 --- a/runtime/pharo/Powerlang-Core/EggBootstrapModule.class.st +++ b/runtime/pharo/Powerlang-Core/EggBootstrapModule.class.st @@ -41,7 +41,7 @@ EggBootstrapModule class >> fromSpec [ EggBootstrapModule class >> kernelSpec [ KernelSpec ifNil: [ - KernelSpec := self readSpec: #Kernel at: '.'. + KernelSpec := self readSpec: #Kernel at: '../../modules'. KernelSpec initializeConstants ]. ^ KernelSpec ] @@ -57,7 +57,8 @@ EggBootstrapModule class >> new [ { #category : 'accessing' } EggBootstrapModule class >> readSpec: specName at: path [ | dir subdir | - dir := '../../modules' asFileReference. + + dir := '.' asFileReference. subdir := path isString ifTrue: [ path ] ifFalse: [ path asPath pathString ]. subdir notEmpty ifTrue: [ dir := dir / subdir]. @@ -95,7 +96,7 @@ EggBootstrapModule class >> resetSpecs [ { #category : 'accessing' } EggBootstrapModule class >> vmPrimitivesSpec [ ^ VMPrimitivesSpec - ifNil: [ VMPrimitivesSpec := self readSpec: #VM at: 'Kernel' ] + ifNil: [ VMPrimitivesSpec := self readSpec: #VM at: '../../modules/Kernel' ] ] { #category : 'initialization' } diff --git a/runtime/pharo/Powerlang-Core/EggEvaluator.class.st b/runtime/pharo/Powerlang-Core/EggEvaluator.class.st index d6dd185..417f597 100644 --- a/runtime/pharo/Powerlang-Core/EggEvaluator.class.st +++ b/runtime/pharo/Powerlang-Core/EggEvaluator.class.st @@ -215,8 +215,10 @@ EggEvaluator >> initializePrimitives [ at: #PrimeFor put: self primitivePrimeFor; at: #FlushFromCaches put: self primitiveFlushFromCaches; at: #PrepareForExecution put: self primitivePrepareForExecution; - at: #ProcessVMStackInitialize - put: self primitiveProcessVMStackInitialize; + at: #ProcessVMStackInitializeWithActiveBuffer + put: self primitiveProcessVMStackInitializeWithActiveBuffer; + at: #ProcessVMStackInitializeWithNewBuffer + put: self primitiveProcessVMStackInitializeWithNewBuffer; at: #ProcessVMStackAt put: self primitiveProcessVMStackAt; at: #ProcessVMStackAtPut put: self primitiveProcessVMStackAtPut; at: #ProcessVMStackBpAtPut put: self primitiveProcessVMStackBpAtPut; @@ -618,6 +620,18 @@ EggEvaluator >> primitiveProcessVMStackInitialize [ stacks at: context self put: new ] ] +{ #category : 'primitives' } +EggEvaluator >> primitiveProcessVMStackInitializeWithActiveBuffer [ + + ^ [ stacks at: context self put: context ] +] + +{ #category : 'primitives' } +EggEvaluator >> primitiveProcessVMStackInitializeWithNewBuffer [ + + ^ [ stacks at: context self put: self halt newEvaluationContext ] +] + { #category : 'primitives' } EggEvaluator >> primitiveProcessVMStackPcAtPut [