123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890 |
- // Copyright 2019-2024 Leaning Technologies Ltd.
- #include <cheerp/coroutine.h>
- #include "coredata.h"
- #include "devices/vgaout.h"
- #include "cxuibase.h"
- #include "workerclock.h"
- #include "cxuidevices.h"
- // This file should contain the public interface for the CheerpX system
- CheerpX::Device* CheerpXBase::getDeviceById(uint32_t id)
- {
- for (CheerpX::Device* d: devices)
- {
- if (d->devId == id)
- return d;
- }
- return nullptr;
- }
- namespace [[cheerp::genericjs]] client
- {
- struct FloppyConfiguration: public Object
- {
- CheerpX::BlockDevice* get_dev();
- uint32_t get_size();
- // TODO: Add option to specify floppy id
- };
- struct DiskConfiguration: public Object
- {
- CheerpX::BlockDevice* get_dev();
- const client::String& get_type();
- uint32_t get_id();
- };
- struct SystemConfiguration: public Object
- {
- uint32_t get_MhZ();
- CheerpX::BlockDevice* get_bios();
- CheerpX::BlockDevice* get_vgaBios();
- TArray<FloppyConfiguration*>* get_floppies();
- TArray<DiskConfiguration*>* get_disks();
- uint32_t get_mem();
- };
- client::Uint8Array* HEAP8;
- client::Uint16Array* HEAP16;
- client::Int32Array* HEAP32;
- struct PromiseCallbacks: public Object
- {
- client::EventListener* get_fullfill();
- client::EventListener* get_reject();
- };
- Promise<_Any*>* import(const String&);
- namespace CheerpX {
- struct System: public Object {
- // NOTE: Only pass jsexported types!
- template<typename T>
- static client::Object* wrap(T*);
- };
- }
- }
- namespace [[cheerp::genericjs]] CheerpX
- {
- class [[cheerp::genericjs]] [[cheerp::jsexport_unsafe]] System: public CheerpXBase
- {
- private:
- void handleCoreMessage(client::CoreMessage* m);
- // 'index' represent the attachment point of the disk in a controller
- client::Object* createIdeDiskMsg(CORE_DISK_TYPE type, uint32_t index, uint32_t id, uint32_t imageLen);
- static client::Object* createFloppyDiskMsg(uint32_t index, uint32_t imageLen);
- static client::Object* swapFloppyMsg(uint32_t index, uint32_t id, uint32_t imageLen, bool isWriteProtected);
- static Thread runImpl(System* t, client::SystemConfiguration* conf);
- static Thread runIOReadRequest(System* t, uint32_t id, uint32_t start, uint32_t len, uint32_t ioTransaction, uint32_t bufOffset);
- static Thread runIOWriteRequest(System* t, uint32_t id, uint32_t start, uint32_t len, uint32_t ioTransaction, uint32_t bufOffset);
- void handleKeyDown(client::KeyboardEvent* ev);
- void handleKeyUp(client::KeyboardEvent* ev);
- Task<client::Object*> cheerpOSInit();
- public:
- System();
- ~System()
- {
- }
- static client::Promise<client::_Any*>* create();
- void run(client::SystemConfiguration* conf)
- {
- runImpl(this, conf);
- }
- void createHud()
- {
- createHudImpl();
- }
- // Hack to allow us to wrap the object returned to js by the create promise
- // Used together with the client declaration below
- static client::Object* wrap(client::Object* o)
- {
- return o;
- }
- };
- }
- [[cheerp::genericjs]] client::String* getCheerpXUrl()
- {
- client::TArray<client::String*>* tmp = new client::TArray<client::String*>();
- __asm__("try{throw new Error();}catch(e){%0.push(e.stack);}" : : "r"(tmp));
- client::String* stackStr = (*tmp)[0];
- int cxStart = stackStr->indexOf("/" CXFILE);
- assert(cxStart > 0);
- int httpStart = stackStr->lastIndexOf("http:", cxStart);
- int httpsStart = stackStr->lastIndexOf("https:", cxStart);
- int urlStart = httpStart > httpsStart ? httpStart : httpsStart;
- if (urlStart < 0)
- urlStart = stackStr->lastIndexOf("chrome-extension:", cxStart);
- assert(urlStart > 0);
- return stackStr->substring(urlStart, cxStart+1);
- }
- CheerpXBase::CHEERP_OS_STATE CheerpXBase::cheerpOSState = NOT_LOADED;
- CheerpXBase* CheerpXBase::waitingForCheerpOSList;
- client::NetworkConf* CheerpXBase::tsNetworkConf = nullptr;
- std::vector<CheerpX::Device*> CheerpXBase::devices;
- void CheerpXBase::handleCheerpOSLoadEvent()
- {
- if(cheerpOSState == LOADING_1)
- cheerpOSState = LOADING_2;
- else if(cheerpOSState == LOADING_2)
- {
- cheerpOSState = READY;
- CheerpXBase* cur = waitingForCheerpOSList;
- waitingForCheerpOSList = nullptr;
- while(cur)
- {
- cur->createCoreWorker();
- CheerpXBase* next = cur->next;
- cur->next = nullptr;
- cur = next;
- }
- }
- }
- void CheerpXBase::loadTailScale()
- {
- client::String* baseUrl = getCheerpXUrl();
- client::String* tsNetworkUrl = baseUrl->concat("tun/tailscale_tun_auto.js");
- client::Promise<client::_Any*>* tsNetworkConfP = client::import(tsNetworkUrl);
- tsNetworkConfP->then(cheerp::Callback([](client::NetworkConf* o)
- {
- tsNetworkConf = o;
- handleCheerpOSLoadEvent();
- }));
- }
- client::Promise<client::_Any*>* CheerpXBase::loadCheerpOS()
- {
- PromiseData p = createPromise();
- client::String* baseUrl = getCheerpXUrl();
- cheerpOSState = LOADING_1;
- client::String* cheerpOSUrl = baseUrl->concat("cheerpOS.js");
- client::HTMLScriptElement* s2 = (client::HTMLScriptElement*)client::document.createElement("script");
- s2->set_src(cheerpOSUrl);
- s2->set_onload(cheerp::Callback([p]() -> void
- {
- handleCheerpOSLoadEvent();
- ((PromiseFullfiller)p.f)(nullptr);
- }));
- //TODO: CheerpOS should not be appended to head to make sure it's not accessible to users
- client::document.get_head()->appendChild(s2);
- return p.p;
- }
- CheerpXBase::CheerpXBase():next(nullptr),coreMessageHandler(nullptr),fullfillPromise(nullptr),rejectPromise(nullptr),
- jitErrorCallback(nullptr),core(nullptr),asyncPtrOffset(0),bridgeURL(nullptr),hudDiv(nullptr),statsDiv(nullptr),
- dbgCtxsDiv(nullptr),dbgControlDiv(nullptr),dbgCtxSelect(nullptr),dbgStartStopBtn(nullptr),dbgDisasDiv(nullptr),
- dbgDisasMode(nullptr),dbgDisasAddr(nullptr),dbgDisasBtn(nullptr),dbgDisasView(nullptr),jitBisectArea(nullptr),
- dbgCurCtx(nullptr)
- {
- }
- CheerpX::System::System()
- {
- coreMessageHandler = cheerp::Callback([this](client::MessageEvent<client::Object*>* e)
- {
- client::CoreMessage* m = (client::CoreMessage*)e->get_data();
- handleCoreMessage(m);
- });
- }
- void CheerpXBase::init(PromiseFullfiller f, PromiseRejecter r)
- {
- fullfillPromise = f;
- rejectPromise = r;
- // First of all we need to load cheerpOS components if they are not loaded already
- if(cheerpOSState == NOT_LOADED)
- loadCheerpOS();
- if(cheerpOSState != READY)
- loadTailScale();
- if(cheerpOSState == READY)
- createCoreWorker();
- else
- {
- next = waitingForCheerpOSList;
- waitingForCheerpOSList = this;
- }
- }
- void CheerpXBase::handleCoreMessageBase(client::CoreMessage* m)
- {
- if(m->get_type() == CORE_INIT)
- {
- // Ok, we now have (some) internal data about the core thread
- client::HEAP8 = new client::Uint8Array(m->get_buffer());
- client::HEAP16 = new client::Uint16Array(m->get_buffer());
- client::HEAP32 = new client::Int32Array(m->get_buffer());
- asyncPtrOffset = m->get_asyncPtrOffset() >> 2;
- bool needWorkerClock = m->get_startRealTime() >= 0;
- if (needWorkerClock)
- {
- // Start the timer worker
- auto StartTimerWorker = [this, m]() -> Thread
- {
- client::String* cxCoreUrl = getCheerpXUrl();
- client::Response* r = co_await *client::fetch(cxCoreUrl->concat("workerclock.js"));
- client::String* code = co_await *r->text();
- client::Blob* b = new client::Blob(new client::Array(code));
- client::String* bUrl = client::URL.createObjectURL(b);
- client::Worker* workerClock = new client::Worker(bUrl);
- client::MessageChannel* c = new client::MessageChannel();
- client::MessagePort* corePort = c->get_port1();
- // Build a channel between the worker clock and the core
- client::Object* ret = nullptr;
- __asm__("{type:%1, value: %2}" : "=r"(ret) : "r"(CORE_TIMER_PORT), "r"(corePort));
- core->postMessage(ret, new client::Array(corePort));
- workerClock->set_onmessage(cheerp::Callback([this]()
- {
- cheerpOsInitImpl();
- }));
- client::MessagePort* timerPort = c->get_port2();
- client::Object* tmp;
- __asm__("{kind:%1, buffer:%2, basePtr:%3, startRealTime:%4, port: %5}" : "=r"(tmp) : "r"(INIT_MEMORY), "r"(m->get_buffer()), "r"(m->get_asyncPtrOffset()), "r"(m->get_startRealTime()), "r"(timerPort));
- workerClock->postMessage(tmp, new client::Array(timerPort));
- };
- StartTimerWorker();
- }
- else
- {
- cheerpOsInitImpl();
- }
- }
- else if(m->get_type() == CORE_INIT_RETRY)
- {
- coreWorker("cxcore-no-return-call.js", CORE_INIT_FAILED);
- }
- else if(m->get_type() == CORE_INIT_FAILED)
- {
- client::String* msg = new client::String("CheerpX initialization failed: ");
- msg = msg->concat(m->get_value<client::String*>());
- rejectPromise(msg);
- rejectPromise = nullptr;
- }
- else if(m->get_type() == CORE_COMPILE_WASM_REQUEST)
- {
- uint32_t moduleBufferStart = m->get_start();
- uint32_t moduleBufferLength = m->get_len();
- client::Uint8Array* buf = client::HEAP8->subarray(moduleBufferStart, moduleBufferStart+moduleBufferLength);
- CORE_MESSAGE replyType = m ->get_replyType();
- #ifdef MODULE_TIMINGS
- double requestTime = m->get_requestTime();
- double compileStartTime = client::Date::now();
- client::WebAssembly::compile(buf)->then(cheerp::Callback([this,replyType,requestTime,compileStartTime,buf](client::Object* wasmModule)
- #else
- client::WebAssembly::compile(buf)->then(cheerp::Callback([this,replyType,buf](client::Object* wasmModule)
- #endif
- {
- client::Object* ret = nullptr;
- #ifdef MODULE_TIMINGS
- __asm__("{type:%1, wasmModule:%2, requestTime:%3, compileStartTime:%4, compileEndTime:%5, fileSize:%6}" : "=r"(ret) : "r"(replyType), "r"(wasmModule),
- "r"(requestTime), "r"(compileStartTime), "r"(client::Date::now()), "r"(buf->get_length()));
- #else
- __asm__("{type:%1, wasmModule:%2}" : "=r"(ret) : "r"(replyType), "r"(wasmModule));
- #endif
- if(replyType == CORE_COMPILE_WASM_RESULT)
- (*client::HEAP32)[asyncPtrOffset + 5] = WASM_MODULE_COMPLETE;
- postMessage(ret, /*sendInterrupt*/true);
- #if 0
- if(buf->get_length() > 18000000)
- {
- client::Blob* b = new client::Blob(new client::Array(buf));
- client::String* url = client::URL.createObjectURL(b);
- client::CoreMessage* ret = nullptr;
- __asm__("{type:%1, path:%2, value:%3}" : "=r"(ret) : "r"(DUMP_DATA), "r"(url), "r"(new client::String("big.wasm")));
- handleCoreMessageBase(ret);
- }
- #endif
- }),
- cheerp::Callback([this,buf](client::String* s)
- {
- // Send a message to this same thread, to keep dump handling unified
- client::console.log(s);
- // Also let the user code handle this
- if(jitErrorCallback)
- jitErrorCallback(s);
- client::Blob* b = new client::Blob(new client::Array(new client::Uint8Array(buf)));
- client::String* url = client::URL.createObjectURL(b);
- client::CoreMessage* ret = nullptr;
- __asm__("{type:%1, path:%2, value:%3}" : "=r"(ret) : "r"(DUMP_DATA), "r"(url), "r"(new client::String("fail.wasm")));
- handleCoreMessageBase(ret);
- }));
- }
- else if(m->get_type() == CORE_HUD_GLOBAL_STAT)
- {
- uint32_t* linearPtr = __builtin_cheerp_make_regular<uint32_t>(client::HEAP32, m->get_intWrapper() >> 2);
- globalStats.emplace_back(statsDiv, m->get_statName(), linearPtr, m->get_statType());
- }
- else if(m->get_type() == CORE_HUD_ADD_CONTEXT)
- {
- ContextData* newCtx = new ContextData{m->get_ctxType(), m->get_value(), m->get_dbgState()};
- dbgCtxs.push_back(newCtx);
- updateContexts();
- }
- else if(m->get_type() == CORE_HUD_REMOVE_CONTEXT)
- {
- ContextData* newCtx = new ContextData{m->get_ctxType(), m->get_value(), m->get_dbgState()};
- auto it = std::remove_if(dbgCtxs.begin(), dbgCtxs.end(), [m](const ContextData* c)
- {
- return c->ctxType == m->get_ctxType() && c->ctxId == m->get_value();
- });
- if(it != dbgCtxs.end())
- {
- dbgCtxs.erase(it, dbgCtxs.end());
- updateContexts();
- }
- }
- else if(m->get_type() == CORE_HUD_UPDATE_CONTEXT)
- {
- ContextData* c = getCtxDataForId(m->get_ctxType(), m->get_value());
- if(c)
- {
- c->state = m->get_dbgState();
- selectContext(dbgCurCtx);
- }
- }
- else if(m->get_type() == CORE_DBG_DISAS_RESULT)
- {
- dbgDisasView->set_textContent(m->get_text());
- }
- else if(m->get_type() == CORE_JIT_GET_CUR_TRACES)
- {
- client::String* text = new client::String();
- auto& traces = *m->get_traces();
- for(int i = 0; i < traces.get_length(); ++i)
- {
- text = text->concat((new client::Number(traces[i]))->toString(16))->concat("\n");
- }
- text = text->trim();
- jitBisectArea->set_value(text);
- }
- else if(m->get_type() == DUMP_DATA)
- {
- client::String* str = m->get_path();
- uint32_t counter = m->get_value();
- client::HTMLElement* a = client::document.createElement("a");
- a->setAttribute("href", str);
- a->setAttribute("download", m->get_value<client::String*>());
- a->click();
- client::URL::revokeObjectURL(str);
- }
- else
- {
- __asm__("debugger");
- }
- }
- void CheerpX::System::handleCoreMessage(client::CoreMessage* m)
- {
- if(m->get_type() == CORE_START_VGA)
- {
- // Create the VGA renderer
- VGAShared* vgaShared = __builtin_cheerp_make_regular<VGAShared>(new client::DataView(client::HEAP8->get_buffer()), m->get_vgaDevice());
- VGAOutput::initialize(__builtin_cheerp_make_regular<uint8_t>(client::HEAP8, m->get_vgaRamOffset()), *vgaShared);
- // Also start the keyboard handlers
- client::document.addEventListener("keydown", cheerp::Callback([this](client::KeyboardEvent* ev){handleKeyDown(ev);}));
- client::document.addEventListener("keyup", cheerp::Callback([this](client::KeyboardEvent* ev){handleKeyUp(ev);}));
- }
- else if(m->get_type() == CORE_VGA_MODE)
- {
- VGA_RENDER_MODE mode = (VGA_RENDER_MODE)m->get_value();
- VGAOutput::setRenderMode(mode);
- }
- else if(m->get_type() == CORE_VGA_SET_WIDTH)
- {
- VGAOutput::setWidth(m->get_value());
- }
- else if(m->get_type() == CORE_VGA_SET_HEIGHT)
- {
- VGAOutput::setHeight(m->get_value());
- }
- else if(m->get_type() == CORE_IO_READ_REQUEST)
- {
- runIOReadRequest(this, m->get_devId(), m->get_start(), m->get_len(), m->get_ioTransaction(), m->get_value());
- }
- else if(m->get_type() == CORE_IO_WRITE_REQUEST)
- {
- runIOWriteRequest(this, m->get_devId(), m->get_start(), m->get_len(), m->get_ioTransaction(), m->get_value());
- }
- else
- handleCoreMessageBase(m);
- }
- void CheerpXBase::createCoreWorker()
- {
- if(bridgeURL != nullptr)
- {
- client::String* cxBridgeUrl = getCheerpXUrl()->concat("cxbridge.js");
- core = new client::Worker(cxBridgeUrl);
- // Before proceding we need to wait for the core worker to be initialized
- core->set_onmessage(coreMessageHandler);
- }
- else
- {
- coreWorker("cxcore.js", CORE_INIT_RETRY);
- }
- }
- Thread CheerpXBase::coreWorker(const client::String& coreFile, CORE_MESSAGE m)
- {
- client::String* cxCoreUrl = getCheerpXUrl();
- client::Response* r = co_await *client::fetch(cxCoreUrl->concat(coreFile));
- client::String* code = co_await *r->text();
- client::String* wasmFile = coreFile.replace(".js", ".wasm");
- code = code->replace(wasmFile, cxCoreUrl->concat(wasmFile));
- code = code->concat("cxCoreInit.promise.then(function(){cxCoreInit();}).catch(function(e){postMessage({type:", m, ",value:e.toString()});})");
- client::Blob* b = new client::Blob(new client::Array(code));
- client::String* bUrl = client::URL.createObjectURL(b);
- core = new client::Worker(bUrl);
- core->set_onmessage(coreMessageHandler);
- }
- Thread CheerpXBase::cheerpOsInitImpl()
- {
- client::Object* ret = co_await cheerpOSInit();
- // Everything is ready, fullfill the promise
- if(ret == nullptr)
- {
- rejectPromise("CheerpX initialization failed");
- }
- else
- {
- fullfillPromise(ret);
- fullfillPromise = nullptr;
- }
- }
- CheerpXBase::PromiseData CheerpXBase::createPromise()
- {
- // Create the promise object, store the fullfill callback in a temporary object
- client::Promise<client::_Any*>* ret = nullptr;
- client::PromiseCallbacks* tmp = new client::PromiseCallbacks();
- __asm__("new Promise(function(f,r){%1.fullfill=f;%1.reject=r;});" : "=r"(ret) : "r"(tmp));
- return PromiseData{ret, tmp->get_fullfill(), tmp->get_reject()};
- }
- client::Promise<client::_Any*>* CheerpX::System::create()
- {
- System* s = new System();
- PromiseData d = createPromise();
- s->init((PromiseFullfiller)d.f, (PromiseRejecter)d.r);
- return d.p;
- }
- client::Object* CheerpX::System::createIdeDiskMsg(CORE_DISK_TYPE type, uint32_t index, uint32_t id, uint32_t imageLen)
- {
- client::Object* result;
- __asm__("{type:%1,diskType:%2,index:%3,devId:%4,len:%5}" : "=r"(result) : "r"(CORE_CREATE_IDE_DISK), "r"(type), "r"(index), "r"(id), "r"(imageLen));
- return result;
- }
- client::Object* CheerpX::System::createFloppyDiskMsg(uint32_t index, uint32_t imageLen)
- {
- client::Object* result;
- __asm__("{type:%1,index:%2,len:%3}" : "=r"(result) : "r"(CORE_CREATE_FLOPPY_DISK), "r"(index), "r"(imageLen));
- return result;
- }
- client::Object* CheerpX::System::swapFloppyMsg(uint32_t index, uint32_t id, uint32_t imageLen, bool isWriteProtected)
- {
- client::Object* result;
- __asm__("{type:%1,index:%2,devId:%3,len:%4,writeProtected:%5}" : "=r"(result) : "r"(CORE_SWAP_FLOPPY), "r"(index), "r"(id), "r"(imageLen), "r"(isWriteProtected));
- return result;
- }
- Thread CheerpX::System::runImpl(System* t, client::SystemConfiguration* conf)
- {
- if (!conf->hasOwnProperty("bios") || !conf->hasOwnProperty("vgaBios"))
- {
- client::console.log("bios and vgaBios must be defined");
- co_await Suspender<uint32_t>();
- }
- CheerpX::BlockDevice* biosDevice = conf->get_bios();
- assert(biosDevice->type == CheerpX::Device::BLOCK);
- client::Uint8Array* biosData = new client::Uint8Array(biosDevice->length);
- co_await biosDevice->read(t, 0, biosDevice->length, biosData, 0);
- CheerpX::BlockDevice* vgaBiosDevice = conf->get_vgaBios();
- assert(vgaBiosDevice->type == CheerpX::Device::BLOCK);
- client::Uint8Array* vgaBiosData = new client::Uint8Array(vgaBiosDevice->length);
- co_await vgaBiosDevice->read(t, 0, vgaBiosDevice->length, vgaBiosData, 0);
- // Initialize an address space layout with the information in 'conf'
- client::Array* transferList = new client::Array();;
- client::Object* result = nullptr;
- uint32_t mhz = 0;
- if (conf->hasOwnProperty("MhZ"))
- mhz = conf->get_MhZ();
- __asm__("{type:%1,mhz:%2,mem:%3,bios:%4,vgaBios:%5}" : "=r"(result) : "r"(CORE_INIT_SYSTEM),
- "r"(mhz), "r"(conf->get_mem()), "r"(biosData), "r"(vgaBiosData));
- transferList->push(biosData->get_buffer());;
- transferList->push(vgaBiosData->get_buffer());;
- t->core->postMessage(result, transferList);
- auto HandleFloppyConf = [](System* t, uint32_t floppyIndex, client::FloppyConfiguration* floppyConf) -> Task<void>
- {
- if (floppyIndex > 1)
- {
- client::console.log("Invalid floppy id", floppyIndex);
- co_await Suspender<uint32_t>();
- }
- if (floppyConf->hasOwnProperty("dev"))
- {
- BlockDevice* flp = floppyConf->get_dev();
- assert(flp->type != CheerpX::Device::CHEERPOS);
- if (floppyConf->hasOwnProperty("size") && floppyConf->get_size()*1024 != flp->length)
- {
- client::console.log("Unexpected Floppy size");
- co_await Suspender<uint32_t>();
- }
- bool isWriteProtected = (co_await flp->getPermType() & 2) == 0;
- t->core->postMessage(System::createFloppyDiskMsg(floppyIndex, flp->length));
- t->core->postMessage(System::swapFloppyMsg(floppyIndex, flp->devId, flp->length, isWriteProtected));
- }
- if (floppyConf->hasOwnProperty("size"))
- t->core->postMessage(CheerpX::System::createFloppyDiskMsg(1, floppyConf->get_size() * 1024));
- };
- if(conf->hasOwnProperty("floppies") && client::Array::isArray(conf->get_floppies()))
- {
- const client::TArray<client::FloppyConfiguration*>& floppies = *conf->get_floppies();
- for(uint32_t i=0;i<floppies.get_length();i++)
- {
- co_await HandleFloppyConf(t, i, floppies[i]);
- }
- }
- if(conf->hasOwnProperty("disks") && client::Array::isArray(conf->get_disks()))
- {
- const client::TArray<client::DiskConfiguration*>& disks = *conf->get_disks();
- bool diskIds[2] = {false, false};
- for(uint32_t i=0;i<disks.get_length();i++)
- {
- BlockDevice* dev = disks[i]->get_dev();
- uint32_t diskId;
- if(disks[i]->hasOwnProperty("id"))
- diskId = disks[i]->get_id();
- else
- diskId = i;
- if(diskId > 1)
- {
- client::console.log("Invalid disk id", diskId);
- co_await Suspender<uint32_t>();
- }
- if(diskIds[diskId])
- {
- client::console.log("Overwriting disk id", diskId);
- co_await Suspender<uint32_t>();
- }
- diskIds[diskId] = true;
- CORE_DISK_TYPE type;
- if(disks[i]->get_type().localeCompare("ata")==0)
- type = DISK_HD;
- else if(disks[i]->get_type().localeCompare("atapi")==0)
- type = DISK_CD;
- else
- {
- client::console.log("Unknown disk type");
- co_await Suspender<uint32_t>();
- }
- t->core->postMessage(t->createIdeDiskMsg(type, diskId, dev->devId, dev->length));
- }
- }
- __asm__("{type:%1}" : "=r"(result) : "r"(CORE_START_SYSTEM));
- t->core->postMessage(result);
- }
- Thread CheerpX::System::runIOReadRequest(System* t, uint32_t id, uint32_t start, uint32_t len, uint32_t ioTransaction, uint32_t bufOffset)
- {
- CheerpX::Device* dev = t->getDeviceById(id);
- assert(dev->type == Device::TYPE::BLOCK);
- CheerpX::BlockDevice* device = static_cast<CheerpX::BlockDevice*>(dev);
- uint32_t readBytes = co_await device->read(t, start, len, client::HEAP8, bufOffset);
- client::Object* result = nullptr;
- __asm__("{type:%1,ioTransaction:%2}" : "=r"(result) : "r"(CORE_IO_RESULT), "r"(ioTransaction));
- t->postMessage(result, /*sendInterrupt*/true);
- }
- Thread CheerpX::System::runIOWriteRequest(System* t, uint32_t id, uint32_t start, uint32_t len, uint32_t ioTransaction, uint32_t bufOffset)
- {
- CheerpX::Device* dev = t->getDeviceById(id);
- assert(dev->type == Device::TYPE::BLOCK);
- CheerpX::BlockDevice* device = static_cast<CheerpX::BlockDevice*>(dev);
- int doneBytes = co_await device->write(t, start, len, client::HEAP8, bufOffset);
- client::Object* result = nullptr;
- __asm__("{type:%1,ioTransaction:%2}" : "=r"(result) : "r"(CORE_IO_RESULT), "r"(ioTransaction));
- t->postMessage(result, /*sendInterrupt*/true);
- }
- // Populate the queue using genericjs code, concurrently with the core using the data
- // Send an IRQ 1 message and force the thread to stop by setting the flag
- // TODO: This is not great, it would be much better to have a way to safely mark the IRQ from here
- void CheerpX::System::handleKeyDown(client::KeyboardEvent* ev)
- {
- // Do not handle Ctrl+Shift combos
- if(ev->get_ctrlKey() && ev->get_shiftKey())
- return;
- ev->preventDefault();
- client::Object* result = nullptr;
- __asm__("{type:%1,value:%2}" : "=r"(result) : "r"(CORE_QUEUE_KEYDOWN), "r"(ev->get_keyCode()));
- postMessage(result, /*sendInterrupt*/true);
- }
- void CheerpX::System::handleKeyUp(client::KeyboardEvent* ev)
- {
- // Do not handle Ctrl+Shift combos
- if(ev->get_ctrlKey() && ev->get_shiftKey())
- return;
- ev->preventDefault();
- client::Object* result = nullptr;
- __asm__("{type:%1,value:%2}" : "=r"(result) : "r"(CORE_QUEUE_KEYUP), "r"(ev->get_keyCode()));
- postMessage(result, /*sendInterrupt*/true);
- }
- Task<client::Object*> CheerpX::System::cheerpOSInit()
- {
- co_return client::CheerpX::System::wrap(this);
- }
- void CheerpXBase::postMessage(client::Object* msg, bool sendInterrupt)
- {
- if(sendInterrupt)
- (*client::HEAP32)[asyncPtrOffset + 0] = -2;
- core->postMessage(msg);
- }
- void CheerpXBase::updateHud()
- {
- for(HudGlobalStat& s: globalStats)
- s.update();
- }
- void CheerpXBase::sliceWidth(client::HTMLElement* e, const client::String& w)
- {
- e->get_style()->set_width(w);
- e->get_style()->set_boxSizing("border-box");
- }
- CheerpXBase::ContextData* CheerpXBase::getCtxDataForId(CONTEXT_TYPE t, uint32_t i) const
- {
- for(ContextData* c: dbgCtxs)
- {
- if(c->ctxType == t && c->ctxId == i)
- return c;
- }
- return nullptr;
- }
- CheerpXBase::ContextData* CheerpXBase::getCtxDataForName(client::String* n) const
- {
- for(ContextData* c: dbgCtxs)
- {
- if(c->displayName->localeCompare(*n) == 0)
- return c;
- }
- return nullptr;
- }
- void CheerpXBase::createHudImpl()
- {
- hudDiv = client::document.createElement("div");
- auto stopEvent = [](client::Event* e) { e->stopPropagation(); };
- hudDiv->addEventListener("keydown", cheerp::Callback(stopEvent));
- hudDiv->addEventListener("keyup", cheerp::Callback(stopEvent));
- hudDiv->addEventListener("keypress", cheerp::Callback(stopEvent));
- hudDiv->setAttribute("style", "position:absolute;width:25%;height:100%;top:0;right:0;overflow-y:scroll;");
- statsDiv = client::document.createElement("div");
- // This block will contain variuos global statistics
- appendHudBlock("Global stats", statsDiv);
- dbgCtxsDiv = client::document.createElement("div");
- // This block will contain the list of contexts that we can debug
- appendHudBlock("Debugger - Contexts", dbgCtxsDiv);
- dbgCtxSelect = (client::HTMLSelectElement*)client::document.createElement("select");
- dbgCtxsDiv->appendChild(dbgCtxSelect);
- sliceWidth(dbgCtxSelect, "50%");
- dbgControlDiv = client::document.createElement("div");
- // This block will contain the controls for starting/stopping/stepping a context
- appendHudBlock("Debugger - Control", dbgControlDiv);
- dbgStartStopBtn = (client::HTMLButtonElement*)client::document.createElement("button");
- sliceWidth(dbgStartStopBtn, "50%");
- dbgControlDiv->appendChild(dbgStartStopBtn);
- dbgDisasDiv = client::document.createElement("div");
- // This block will contain the disassembly/memory views
- appendHudBlock("Debugger - Disassembly", dbgDisasDiv);
- dbgDisasMode = (client::HTMLSelectElement*)client::document.createElement("select");
- dbgDisasMode->appendChild(createOption("16-bit", "0"));
- dbgDisasMode->appendChild(createOption("32-bit", "1"));
- dbgDisasMode->appendChild(createOption("Wasm (dump)", "2"));
- sliceWidth(dbgDisasMode, "20%");
- dbgDisasAddr = (client::HTMLInputElement*)client::document.createElement("input");
- sliceWidth(dbgDisasAddr, "20%");
- dbgDisasBtn = (client::HTMLButtonElement*)client::document.createElement("button");
- sliceWidth(dbgDisasBtn, "20%");
- dbgDisasBtn->set_textContent("Show");
- dbgDisasView = client::document.createElement("pre");
- dbgDisasDiv->appendChild(dbgDisasMode);
- dbgDisasDiv->appendChild(dbgDisasAddr);
- dbgDisasDiv->appendChild(dbgDisasBtn);
- dbgDisasDiv->appendChild(dbgDisasView);
- dbgDisasBtn->set_onclick(cheerp::Callback([this]()
- {
- assert(dbgCurCtx && dbgCurCtx->state == DBG_STOPPED);
- uint32_t selectedMode = client::parseInt(dbgDisasMode->get_value());
- client::String* addrStr = dbgDisasAddr->get_value();
- if(addrStr->get_length() == 0)
- return;
- uint32_t selectedAddr = client::parseInt(addrStr, 16);
- client::Object* result = nullptr;
- CORE_MESSAGE msg;
- if(selectedMode == 0)
- msg = CORE_DBG_DISAS_16;
- else if(selectedMode == 1)
- msg = CORE_DBG_DISAS_32;
- else if(selectedMode == 2)
- msg = CORE_DBG_DUMP_WASM;
- else
- return;
- __asm__("{type:%1,ctxType:%2,value:%3,addr:%4}" : "=r"(result) : "r"(msg), "r"(dbgCurCtx->ctxType), "r"(dbgCurCtx->ctxId), "r"(selectedAddr));
- postMessage(result, /*sendInterrupt*/true);
- }));
- client::Element* jitBisectDiv = client::document.createElement("div");
- appendHudBlock("JIT - Bisect", jitBisectDiv);
- jitBisectArea = (client::HTMLInputElement*)client::document.createElement("textarea");
- jitBisectSet = (client::HTMLButtonElement*)client::document.createElement("button");
- jitBisectCur = (client::HTMLButtonElement*)client::document.createElement("button");
- jitBisectDiv->appendChild(jitBisectArea);
- jitBisectDiv->appendChild(jitBisectSet);
- jitBisectDiv->appendChild(jitBisectCur);
- sliceWidth(jitBisectArea, "100%");
- sliceWidth(jitBisectSet, "50%");
- sliceWidth(jitBisectCur, "50%");
- jitBisectSet->set_textContent("Apply");
- jitBisectCur->set_textContent("Load Current");
- auto applyBisect = [this]()
- {
- client::localStorage.setItem("cxLastBisect", jitBisectArea->get_value());
- client::String* areaStr = jitBisectArea->get_value()->trim();
- if(areaStr->get_length() == 0)
- return;
- auto& lines = *areaStr->split("\n");
- client::Uint32Array* traces = new client::Uint32Array(lines.get_length());
- for (int i = 0; i < lines.get_length(); ++i)
- {
- (*traces)[i] = client::parseInt(lines[i], 16);
- }
- client::Object* result = nullptr;
- __asm__("{type:%1,traces:%2}" : "=r"(result) : "r"(CORE_JIT_BISECT), "r"(traces));
- postMessage(result, /*sendInterrupt*/true);
- };
- if(client::String* o = (client::String*)client::localStorage.getItem("cxLastBisect"))
- {
- jitBisectArea->set_value(o);
- if(o->get_length() != 0)
- applyBisect();
- }
- jitBisectSet->set_onclick(cheerp::Callback(applyBisect));
- jitBisectCur->set_onclick(cheerp::Callback([this]()
- {
- client::Object* result = nullptr;
- __asm__("{type:%1}" : "=r"(result) : "r"(CORE_JIT_GET_CUR_TRACES));
- postMessage(result, /*sendInterrupt*/true);
- }));
- selectContext(nullptr);
- client::document.get_body()->appendChild(hudDiv);
- dbgStartStopBtn->set_onclick(cheerp::Callback([this]
- {
- assert(dbgCurCtx && (dbgCurCtx->state == DBG_DETATCHED || dbgCurCtx->state == DBG_STOPPED));
- client::Object* result = nullptr;
- __asm__("{type:%1,ctxType:%2,value:%3}" : "=r"(result) : "r"(dbgCurCtx->state == DBG_DETATCHED ? CORE_DBG_ATTACH : CORE_DBG_DETACH), "r"(dbgCurCtx->ctxType), "r"(dbgCurCtx->ctxId));
- postMessage(result, /*sendInterrupt*/true);
- }));
- dbgCtxSelect->set_onchange(cheerp::Callback([this]()
- {
- client::String* s = dbgCtxSelect->get_value();
- selectContext(getCtxDataForName(s));
- }));
- updateContexts();
- client::Object* result = nullptr;
- __asm__("{type:%1}" : "=r"(result) : "r"(CORE_ATTACH_HUD));
- postMessage(result, /*sendInterrupt*/true);
- client::setInterval(cheerp::Callback([this]() { updateHud(); }), 1000);
- }
- void CheerpXBase::updateContexts()
- {
- if(dbgCtxSelect == nullptr)
- return;
- while(client::Node* c = dbgCtxSelect->get_firstChild())
- dbgCtxSelect->removeChild(c);
- dbgCtxSelect->appendChild(createOption("<none>", ""));
- for(const ContextData* c: dbgCtxs)
- {
- auto* o = createOption(c->displayName, c->displayName);
- dbgCtxSelect->appendChild(o);
- // Automatically reselect the last selected context
- if(client::localStorage.getItem("cxLastCtx") == c->displayName)
- {
- o->set_selected(true);
- selectContext(c);
- }
- }
- }
- void CheerpXBase::selectContext(const ContextData* c)
- {
- dbgCurCtx = c;
- dbgDisasMode->set_disabled(true);
- dbgDisasAddr->set_disabled(true);
- dbgDisasBtn->set_disabled(true);
- dbgDisasView->set_textContent("");
- if(c == nullptr)
- {
- dbgStartStopBtn->set_textContent("Invalid");
- dbgStartStopBtn->set_disabled(true);
- jitBisectArea->set_disabled(true);
- jitBisectSet->set_disabled(true);
- return;
- }
- else
- {
- jitBisectArea->set_disabled(false);
- jitBisectSet->set_disabled(false);
- client::localStorage.setItem("cxLastCtx", c->displayName);
- }
- switch(c->state)
- {
- case DBG_DETATCHED:
- dbgStartStopBtn->set_textContent("Attach");
- dbgStartStopBtn->set_disabled(false);
- break;
- case DBG_STOPPED:
- dbgStartStopBtn->set_textContent("Detach");
- dbgStartStopBtn->set_disabled(false);
- dbgDisasMode->set_disabled(false);
- dbgDisasAddr->set_disabled(false);
- dbgDisasBtn->set_disabled(false);
- break;
- case DBG_SINGLE_STEP:
- dbgStartStopBtn->set_textContent("Stepping");
- dbgStartStopBtn->set_disabled(true);
- break;
- }
- }
- client::HTMLOptionElement* CheerpXBase::createOption(const client::String& text, const client::String& value)
- {
- client::HTMLOptionElement* o = (client::HTMLOptionElement*)client::document.createElement("option");
- o->set_textContent(text);
- o->set_value(value);
- return o;
- }
- void CheerpXBase::appendHudBlock(const client::String& blockTitle, client::Element* e)
- {
- client::Element* container = client::document.createElement("div");
- client::HTMLElement* p = client::document.createElement("p");
- p->get_style()->set_fontWeight("bold");
- p->set_textContent(blockTitle);
- container->appendChild(p);
- container->appendChild(e);
- hudDiv->appendChild(container);
- }
|