cxui.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890
  1. // Copyright 2019-2024 Leaning Technologies Ltd.
  2. #include <cheerp/coroutine.h>
  3. #include "coredata.h"
  4. #include "devices/vgaout.h"
  5. #include "cxuibase.h"
  6. #include "workerclock.h"
  7. #include "cxuidevices.h"
  8. // This file should contain the public interface for the CheerpX system
  9. CheerpX::Device* CheerpXBase::getDeviceById(uint32_t id)
  10. {
  11. for (CheerpX::Device* d: devices)
  12. {
  13. if (d->devId == id)
  14. return d;
  15. }
  16. return nullptr;
  17. }
  18. namespace [[cheerp::genericjs]] client
  19. {
  20. struct FloppyConfiguration: public Object
  21. {
  22. CheerpX::BlockDevice* get_dev();
  23. uint32_t get_size();
  24. // TODO: Add option to specify floppy id
  25. };
  26. struct DiskConfiguration: public Object
  27. {
  28. CheerpX::BlockDevice* get_dev();
  29. const client::String& get_type();
  30. uint32_t get_id();
  31. };
  32. struct SystemConfiguration: public Object
  33. {
  34. uint32_t get_MhZ();
  35. CheerpX::BlockDevice* get_bios();
  36. CheerpX::BlockDevice* get_vgaBios();
  37. TArray<FloppyConfiguration*>* get_floppies();
  38. TArray<DiskConfiguration*>* get_disks();
  39. uint32_t get_mem();
  40. };
  41. client::Uint8Array* HEAP8;
  42. client::Uint16Array* HEAP16;
  43. client::Int32Array* HEAP32;
  44. struct PromiseCallbacks: public Object
  45. {
  46. client::EventListener* get_fullfill();
  47. client::EventListener* get_reject();
  48. };
  49. Promise<_Any*>* import(const String&);
  50. namespace CheerpX {
  51. struct System: public Object {
  52. // NOTE: Only pass jsexported types!
  53. template<typename T>
  54. static client::Object* wrap(T*);
  55. };
  56. }
  57. }
  58. namespace [[cheerp::genericjs]] CheerpX
  59. {
  60. class [[cheerp::genericjs]] [[cheerp::jsexport_unsafe]] System: public CheerpXBase
  61. {
  62. private:
  63. void handleCoreMessage(client::CoreMessage* m);
  64. // 'index' represent the attachment point of the disk in a controller
  65. client::Object* createIdeDiskMsg(CORE_DISK_TYPE type, uint32_t index, uint32_t id, uint32_t imageLen);
  66. static client::Object* createFloppyDiskMsg(uint32_t index, uint32_t imageLen);
  67. static client::Object* swapFloppyMsg(uint32_t index, uint32_t id, uint32_t imageLen, bool isWriteProtected);
  68. static Thread runImpl(System* t, client::SystemConfiguration* conf);
  69. static Thread runIOReadRequest(System* t, uint32_t id, uint32_t start, uint32_t len, uint32_t ioTransaction, uint32_t bufOffset);
  70. static Thread runIOWriteRequest(System* t, uint32_t id, uint32_t start, uint32_t len, uint32_t ioTransaction, uint32_t bufOffset);
  71. void handleKeyDown(client::KeyboardEvent* ev);
  72. void handleKeyUp(client::KeyboardEvent* ev);
  73. Task<client::Object*> cheerpOSInit();
  74. public:
  75. System();
  76. ~System()
  77. {
  78. }
  79. static client::Promise<client::_Any*>* create();
  80. void run(client::SystemConfiguration* conf)
  81. {
  82. runImpl(this, conf);
  83. }
  84. void createHud()
  85. {
  86. createHudImpl();
  87. }
  88. // Hack to allow us to wrap the object returned to js by the create promise
  89. // Used together with the client declaration below
  90. static client::Object* wrap(client::Object* o)
  91. {
  92. return o;
  93. }
  94. };
  95. }
  96. [[cheerp::genericjs]] client::String* getCheerpXUrl()
  97. {
  98. client::TArray<client::String*>* tmp = new client::TArray<client::String*>();
  99. __asm__("try{throw new Error();}catch(e){%0.push(e.stack);}" : : "r"(tmp));
  100. client::String* stackStr = (*tmp)[0];
  101. int cxStart = stackStr->indexOf("/" CXFILE);
  102. assert(cxStart > 0);
  103. int httpStart = stackStr->lastIndexOf("http:", cxStart);
  104. int httpsStart = stackStr->lastIndexOf("https:", cxStart);
  105. int urlStart = httpStart > httpsStart ? httpStart : httpsStart;
  106. if (urlStart < 0)
  107. urlStart = stackStr->lastIndexOf("chrome-extension:", cxStart);
  108. assert(urlStart > 0);
  109. return stackStr->substring(urlStart, cxStart+1);
  110. }
  111. CheerpXBase::CHEERP_OS_STATE CheerpXBase::cheerpOSState = NOT_LOADED;
  112. CheerpXBase* CheerpXBase::waitingForCheerpOSList;
  113. client::NetworkConf* CheerpXBase::tsNetworkConf = nullptr;
  114. std::vector<CheerpX::Device*> CheerpXBase::devices;
  115. void CheerpXBase::handleCheerpOSLoadEvent()
  116. {
  117. if(cheerpOSState == LOADING_1)
  118. cheerpOSState = LOADING_2;
  119. else if(cheerpOSState == LOADING_2)
  120. {
  121. cheerpOSState = READY;
  122. CheerpXBase* cur = waitingForCheerpOSList;
  123. waitingForCheerpOSList = nullptr;
  124. while(cur)
  125. {
  126. cur->createCoreWorker();
  127. CheerpXBase* next = cur->next;
  128. cur->next = nullptr;
  129. cur = next;
  130. }
  131. }
  132. }
  133. void CheerpXBase::loadTailScale()
  134. {
  135. client::String* baseUrl = getCheerpXUrl();
  136. client::String* tsNetworkUrl = baseUrl->concat("tun/tailscale_tun_auto.js");
  137. client::Promise<client::_Any*>* tsNetworkConfP = client::import(tsNetworkUrl);
  138. tsNetworkConfP->then(cheerp::Callback([](client::NetworkConf* o)
  139. {
  140. tsNetworkConf = o;
  141. handleCheerpOSLoadEvent();
  142. }));
  143. }
  144. client::Promise<client::_Any*>* CheerpXBase::loadCheerpOS()
  145. {
  146. PromiseData p = createPromise();
  147. client::String* baseUrl = getCheerpXUrl();
  148. cheerpOSState = LOADING_1;
  149. client::String* cheerpOSUrl = baseUrl->concat("cheerpOS.js");
  150. client::HTMLScriptElement* s2 = (client::HTMLScriptElement*)client::document.createElement("script");
  151. s2->set_src(cheerpOSUrl);
  152. s2->set_onload(cheerp::Callback([p]() -> void
  153. {
  154. handleCheerpOSLoadEvent();
  155. ((PromiseFullfiller)p.f)(nullptr);
  156. }));
  157. //TODO: CheerpOS should not be appended to head to make sure it's not accessible to users
  158. client::document.get_head()->appendChild(s2);
  159. return p.p;
  160. }
  161. CheerpXBase::CheerpXBase():next(nullptr),coreMessageHandler(nullptr),fullfillPromise(nullptr),rejectPromise(nullptr),
  162. jitErrorCallback(nullptr),core(nullptr),asyncPtrOffset(0),bridgeURL(nullptr),hudDiv(nullptr),statsDiv(nullptr),
  163. dbgCtxsDiv(nullptr),dbgControlDiv(nullptr),dbgCtxSelect(nullptr),dbgStartStopBtn(nullptr),dbgDisasDiv(nullptr),
  164. dbgDisasMode(nullptr),dbgDisasAddr(nullptr),dbgDisasBtn(nullptr),dbgDisasView(nullptr),jitBisectArea(nullptr),
  165. dbgCurCtx(nullptr)
  166. {
  167. }
  168. CheerpX::System::System()
  169. {
  170. coreMessageHandler = cheerp::Callback([this](client::MessageEvent<client::Object*>* e)
  171. {
  172. client::CoreMessage* m = (client::CoreMessage*)e->get_data();
  173. handleCoreMessage(m);
  174. });
  175. }
  176. void CheerpXBase::init(PromiseFullfiller f, PromiseRejecter r)
  177. {
  178. fullfillPromise = f;
  179. rejectPromise = r;
  180. // First of all we need to load cheerpOS components if they are not loaded already
  181. if(cheerpOSState == NOT_LOADED)
  182. loadCheerpOS();
  183. if(cheerpOSState != READY)
  184. loadTailScale();
  185. if(cheerpOSState == READY)
  186. createCoreWorker();
  187. else
  188. {
  189. next = waitingForCheerpOSList;
  190. waitingForCheerpOSList = this;
  191. }
  192. }
  193. void CheerpXBase::handleCoreMessageBase(client::CoreMessage* m)
  194. {
  195. if(m->get_type() == CORE_INIT)
  196. {
  197. // Ok, we now have (some) internal data about the core thread
  198. client::HEAP8 = new client::Uint8Array(m->get_buffer());
  199. client::HEAP16 = new client::Uint16Array(m->get_buffer());
  200. client::HEAP32 = new client::Int32Array(m->get_buffer());
  201. asyncPtrOffset = m->get_asyncPtrOffset() >> 2;
  202. bool needWorkerClock = m->get_startRealTime() >= 0;
  203. if (needWorkerClock)
  204. {
  205. // Start the timer worker
  206. auto StartTimerWorker = [this, m]() -> Thread
  207. {
  208. client::String* cxCoreUrl = getCheerpXUrl();
  209. client::Response* r = co_await *client::fetch(cxCoreUrl->concat("workerclock.js"));
  210. client::String* code = co_await *r->text();
  211. client::Blob* b = new client::Blob(new client::Array(code));
  212. client::String* bUrl = client::URL.createObjectURL(b);
  213. client::Worker* workerClock = new client::Worker(bUrl);
  214. client::MessageChannel* c = new client::MessageChannel();
  215. client::MessagePort* corePort = c->get_port1();
  216. // Build a channel between the worker clock and the core
  217. client::Object* ret = nullptr;
  218. __asm__("{type:%1, value: %2}" : "=r"(ret) : "r"(CORE_TIMER_PORT), "r"(corePort));
  219. core->postMessage(ret, new client::Array(corePort));
  220. workerClock->set_onmessage(cheerp::Callback([this]()
  221. {
  222. cheerpOsInitImpl();
  223. }));
  224. client::MessagePort* timerPort = c->get_port2();
  225. client::Object* tmp;
  226. __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));
  227. workerClock->postMessage(tmp, new client::Array(timerPort));
  228. };
  229. StartTimerWorker();
  230. }
  231. else
  232. {
  233. cheerpOsInitImpl();
  234. }
  235. }
  236. else if(m->get_type() == CORE_INIT_RETRY)
  237. {
  238. coreWorker("cxcore-no-return-call.js", CORE_INIT_FAILED);
  239. }
  240. else if(m->get_type() == CORE_INIT_FAILED)
  241. {
  242. client::String* msg = new client::String("CheerpX initialization failed: ");
  243. msg = msg->concat(m->get_value<client::String*>());
  244. rejectPromise(msg);
  245. rejectPromise = nullptr;
  246. }
  247. else if(m->get_type() == CORE_COMPILE_WASM_REQUEST)
  248. {
  249. uint32_t moduleBufferStart = m->get_start();
  250. uint32_t moduleBufferLength = m->get_len();
  251. client::Uint8Array* buf = client::HEAP8->subarray(moduleBufferStart, moduleBufferStart+moduleBufferLength);
  252. CORE_MESSAGE replyType = m ->get_replyType();
  253. #ifdef MODULE_TIMINGS
  254. double requestTime = m->get_requestTime();
  255. double compileStartTime = client::Date::now();
  256. client::WebAssembly::compile(buf)->then(cheerp::Callback([this,replyType,requestTime,compileStartTime,buf](client::Object* wasmModule)
  257. #else
  258. client::WebAssembly::compile(buf)->then(cheerp::Callback([this,replyType,buf](client::Object* wasmModule)
  259. #endif
  260. {
  261. client::Object* ret = nullptr;
  262. #ifdef MODULE_TIMINGS
  263. __asm__("{type:%1, wasmModule:%2, requestTime:%3, compileStartTime:%4, compileEndTime:%5, fileSize:%6}" : "=r"(ret) : "r"(replyType), "r"(wasmModule),
  264. "r"(requestTime), "r"(compileStartTime), "r"(client::Date::now()), "r"(buf->get_length()));
  265. #else
  266. __asm__("{type:%1, wasmModule:%2}" : "=r"(ret) : "r"(replyType), "r"(wasmModule));
  267. #endif
  268. if(replyType == CORE_COMPILE_WASM_RESULT)
  269. (*client::HEAP32)[asyncPtrOffset + 5] = WASM_MODULE_COMPLETE;
  270. postMessage(ret, /*sendInterrupt*/true);
  271. #if 0
  272. if(buf->get_length() > 18000000)
  273. {
  274. client::Blob* b = new client::Blob(new client::Array(buf));
  275. client::String* url = client::URL.createObjectURL(b);
  276. client::CoreMessage* ret = nullptr;
  277. __asm__("{type:%1, path:%2, value:%3}" : "=r"(ret) : "r"(DUMP_DATA), "r"(url), "r"(new client::String("big.wasm")));
  278. handleCoreMessageBase(ret);
  279. }
  280. #endif
  281. }),
  282. cheerp::Callback([this,buf](client::String* s)
  283. {
  284. // Send a message to this same thread, to keep dump handling unified
  285. client::console.log(s);
  286. // Also let the user code handle this
  287. if(jitErrorCallback)
  288. jitErrorCallback(s);
  289. client::Blob* b = new client::Blob(new client::Array(new client::Uint8Array(buf)));
  290. client::String* url = client::URL.createObjectURL(b);
  291. client::CoreMessage* ret = nullptr;
  292. __asm__("{type:%1, path:%2, value:%3}" : "=r"(ret) : "r"(DUMP_DATA), "r"(url), "r"(new client::String("fail.wasm")));
  293. handleCoreMessageBase(ret);
  294. }));
  295. }
  296. else if(m->get_type() == CORE_HUD_GLOBAL_STAT)
  297. {
  298. uint32_t* linearPtr = __builtin_cheerp_make_regular<uint32_t>(client::HEAP32, m->get_intWrapper() >> 2);
  299. globalStats.emplace_back(statsDiv, m->get_statName(), linearPtr, m->get_statType());
  300. }
  301. else if(m->get_type() == CORE_HUD_ADD_CONTEXT)
  302. {
  303. ContextData* newCtx = new ContextData{m->get_ctxType(), m->get_value(), m->get_dbgState()};
  304. dbgCtxs.push_back(newCtx);
  305. updateContexts();
  306. }
  307. else if(m->get_type() == CORE_HUD_REMOVE_CONTEXT)
  308. {
  309. ContextData* newCtx = new ContextData{m->get_ctxType(), m->get_value(), m->get_dbgState()};
  310. auto it = std::remove_if(dbgCtxs.begin(), dbgCtxs.end(), [m](const ContextData* c)
  311. {
  312. return c->ctxType == m->get_ctxType() && c->ctxId == m->get_value();
  313. });
  314. if(it != dbgCtxs.end())
  315. {
  316. dbgCtxs.erase(it, dbgCtxs.end());
  317. updateContexts();
  318. }
  319. }
  320. else if(m->get_type() == CORE_HUD_UPDATE_CONTEXT)
  321. {
  322. ContextData* c = getCtxDataForId(m->get_ctxType(), m->get_value());
  323. if(c)
  324. {
  325. c->state = m->get_dbgState();
  326. selectContext(dbgCurCtx);
  327. }
  328. }
  329. else if(m->get_type() == CORE_DBG_DISAS_RESULT)
  330. {
  331. dbgDisasView->set_textContent(m->get_text());
  332. }
  333. else if(m->get_type() == CORE_JIT_GET_CUR_TRACES)
  334. {
  335. client::String* text = new client::String();
  336. auto& traces = *m->get_traces();
  337. for(int i = 0; i < traces.get_length(); ++i)
  338. {
  339. text = text->concat((new client::Number(traces[i]))->toString(16))->concat("\n");
  340. }
  341. text = text->trim();
  342. jitBisectArea->set_value(text);
  343. }
  344. else if(m->get_type() == DUMP_DATA)
  345. {
  346. client::String* str = m->get_path();
  347. uint32_t counter = m->get_value();
  348. client::HTMLElement* a = client::document.createElement("a");
  349. a->setAttribute("href", str);
  350. a->setAttribute("download", m->get_value<client::String*>());
  351. a->click();
  352. client::URL::revokeObjectURL(str);
  353. }
  354. else
  355. {
  356. __asm__("debugger");
  357. }
  358. }
  359. void CheerpX::System::handleCoreMessage(client::CoreMessage* m)
  360. {
  361. if(m->get_type() == CORE_START_VGA)
  362. {
  363. // Create the VGA renderer
  364. VGAShared* vgaShared = __builtin_cheerp_make_regular<VGAShared>(new client::DataView(client::HEAP8->get_buffer()), m->get_vgaDevice());
  365. VGAOutput::initialize(__builtin_cheerp_make_regular<uint8_t>(client::HEAP8, m->get_vgaRamOffset()), *vgaShared);
  366. // Also start the keyboard handlers
  367. client::document.addEventListener("keydown", cheerp::Callback([this](client::KeyboardEvent* ev){handleKeyDown(ev);}));
  368. client::document.addEventListener("keyup", cheerp::Callback([this](client::KeyboardEvent* ev){handleKeyUp(ev);}));
  369. }
  370. else if(m->get_type() == CORE_VGA_MODE)
  371. {
  372. VGA_RENDER_MODE mode = (VGA_RENDER_MODE)m->get_value();
  373. VGAOutput::setRenderMode(mode);
  374. }
  375. else if(m->get_type() == CORE_VGA_SET_WIDTH)
  376. {
  377. VGAOutput::setWidth(m->get_value());
  378. }
  379. else if(m->get_type() == CORE_VGA_SET_HEIGHT)
  380. {
  381. VGAOutput::setHeight(m->get_value());
  382. }
  383. else if(m->get_type() == CORE_IO_READ_REQUEST)
  384. {
  385. runIOReadRequest(this, m->get_devId(), m->get_start(), m->get_len(), m->get_ioTransaction(), m->get_value());
  386. }
  387. else if(m->get_type() == CORE_IO_WRITE_REQUEST)
  388. {
  389. runIOWriteRequest(this, m->get_devId(), m->get_start(), m->get_len(), m->get_ioTransaction(), m->get_value());
  390. }
  391. else
  392. handleCoreMessageBase(m);
  393. }
  394. void CheerpXBase::createCoreWorker()
  395. {
  396. if(bridgeURL != nullptr)
  397. {
  398. client::String* cxBridgeUrl = getCheerpXUrl()->concat("cxbridge.js");
  399. core = new client::Worker(cxBridgeUrl);
  400. // Before proceding we need to wait for the core worker to be initialized
  401. core->set_onmessage(coreMessageHandler);
  402. }
  403. else
  404. {
  405. coreWorker("cxcore.js", CORE_INIT_RETRY);
  406. }
  407. }
  408. Thread CheerpXBase::coreWorker(const client::String& coreFile, CORE_MESSAGE m)
  409. {
  410. client::String* cxCoreUrl = getCheerpXUrl();
  411. client::Response* r = co_await *client::fetch(cxCoreUrl->concat(coreFile));
  412. client::String* code = co_await *r->text();
  413. client::String* wasmFile = coreFile.replace(".js", ".wasm");
  414. code = code->replace(wasmFile, cxCoreUrl->concat(wasmFile));
  415. code = code->concat("cxCoreInit.promise.then(function(){cxCoreInit();}).catch(function(e){postMessage({type:", m, ",value:e.toString()});})");
  416. client::Blob* b = new client::Blob(new client::Array(code));
  417. client::String* bUrl = client::URL.createObjectURL(b);
  418. core = new client::Worker(bUrl);
  419. core->set_onmessage(coreMessageHandler);
  420. }
  421. Thread CheerpXBase::cheerpOsInitImpl()
  422. {
  423. client::Object* ret = co_await cheerpOSInit();
  424. // Everything is ready, fullfill the promise
  425. if(ret == nullptr)
  426. {
  427. rejectPromise("CheerpX initialization failed");
  428. }
  429. else
  430. {
  431. fullfillPromise(ret);
  432. fullfillPromise = nullptr;
  433. }
  434. }
  435. CheerpXBase::PromiseData CheerpXBase::createPromise()
  436. {
  437. // Create the promise object, store the fullfill callback in a temporary object
  438. client::Promise<client::_Any*>* ret = nullptr;
  439. client::PromiseCallbacks* tmp = new client::PromiseCallbacks();
  440. __asm__("new Promise(function(f,r){%1.fullfill=f;%1.reject=r;});" : "=r"(ret) : "r"(tmp));
  441. return PromiseData{ret, tmp->get_fullfill(), tmp->get_reject()};
  442. }
  443. client::Promise<client::_Any*>* CheerpX::System::create()
  444. {
  445. System* s = new System();
  446. PromiseData d = createPromise();
  447. s->init((PromiseFullfiller)d.f, (PromiseRejecter)d.r);
  448. return d.p;
  449. }
  450. client::Object* CheerpX::System::createIdeDiskMsg(CORE_DISK_TYPE type, uint32_t index, uint32_t id, uint32_t imageLen)
  451. {
  452. client::Object* result;
  453. __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));
  454. return result;
  455. }
  456. client::Object* CheerpX::System::createFloppyDiskMsg(uint32_t index, uint32_t imageLen)
  457. {
  458. client::Object* result;
  459. __asm__("{type:%1,index:%2,len:%3}" : "=r"(result) : "r"(CORE_CREATE_FLOPPY_DISK), "r"(index), "r"(imageLen));
  460. return result;
  461. }
  462. client::Object* CheerpX::System::swapFloppyMsg(uint32_t index, uint32_t id, uint32_t imageLen, bool isWriteProtected)
  463. {
  464. client::Object* result;
  465. __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));
  466. return result;
  467. }
  468. Thread CheerpX::System::runImpl(System* t, client::SystemConfiguration* conf)
  469. {
  470. if (!conf->hasOwnProperty("bios") || !conf->hasOwnProperty("vgaBios"))
  471. {
  472. client::console.log("bios and vgaBios must be defined");
  473. co_await Suspender<uint32_t>();
  474. }
  475. CheerpX::BlockDevice* biosDevice = conf->get_bios();
  476. assert(biosDevice->type == CheerpX::Device::BLOCK);
  477. client::Uint8Array* biosData = new client::Uint8Array(biosDevice->length);
  478. co_await biosDevice->read(t, 0, biosDevice->length, biosData, 0);
  479. CheerpX::BlockDevice* vgaBiosDevice = conf->get_vgaBios();
  480. assert(vgaBiosDevice->type == CheerpX::Device::BLOCK);
  481. client::Uint8Array* vgaBiosData = new client::Uint8Array(vgaBiosDevice->length);
  482. co_await vgaBiosDevice->read(t, 0, vgaBiosDevice->length, vgaBiosData, 0);
  483. // Initialize an address space layout with the information in 'conf'
  484. client::Array* transferList = new client::Array();;
  485. client::Object* result = nullptr;
  486. uint32_t mhz = 0;
  487. if (conf->hasOwnProperty("MhZ"))
  488. mhz = conf->get_MhZ();
  489. __asm__("{type:%1,mhz:%2,mem:%3,bios:%4,vgaBios:%5}" : "=r"(result) : "r"(CORE_INIT_SYSTEM),
  490. "r"(mhz), "r"(conf->get_mem()), "r"(biosData), "r"(vgaBiosData));
  491. transferList->push(biosData->get_buffer());;
  492. transferList->push(vgaBiosData->get_buffer());;
  493. t->core->postMessage(result, transferList);
  494. auto HandleFloppyConf = [](System* t, uint32_t floppyIndex, client::FloppyConfiguration* floppyConf) -> Task<void>
  495. {
  496. if (floppyIndex > 1)
  497. {
  498. client::console.log("Invalid floppy id", floppyIndex);
  499. co_await Suspender<uint32_t>();
  500. }
  501. if (floppyConf->hasOwnProperty("dev"))
  502. {
  503. BlockDevice* flp = floppyConf->get_dev();
  504. assert(flp->type != CheerpX::Device::CHEERPOS);
  505. if (floppyConf->hasOwnProperty("size") && floppyConf->get_size()*1024 != flp->length)
  506. {
  507. client::console.log("Unexpected Floppy size");
  508. co_await Suspender<uint32_t>();
  509. }
  510. bool isWriteProtected = (co_await flp->getPermType() & 2) == 0;
  511. t->core->postMessage(System::createFloppyDiskMsg(floppyIndex, flp->length));
  512. t->core->postMessage(System::swapFloppyMsg(floppyIndex, flp->devId, flp->length, isWriteProtected));
  513. }
  514. if (floppyConf->hasOwnProperty("size"))
  515. t->core->postMessage(CheerpX::System::createFloppyDiskMsg(1, floppyConf->get_size() * 1024));
  516. };
  517. if(conf->hasOwnProperty("floppies") && client::Array::isArray(conf->get_floppies()))
  518. {
  519. const client::TArray<client::FloppyConfiguration*>& floppies = *conf->get_floppies();
  520. for(uint32_t i=0;i<floppies.get_length();i++)
  521. {
  522. co_await HandleFloppyConf(t, i, floppies[i]);
  523. }
  524. }
  525. if(conf->hasOwnProperty("disks") && client::Array::isArray(conf->get_disks()))
  526. {
  527. const client::TArray<client::DiskConfiguration*>& disks = *conf->get_disks();
  528. bool diskIds[2] = {false, false};
  529. for(uint32_t i=0;i<disks.get_length();i++)
  530. {
  531. BlockDevice* dev = disks[i]->get_dev();
  532. uint32_t diskId;
  533. if(disks[i]->hasOwnProperty("id"))
  534. diskId = disks[i]->get_id();
  535. else
  536. diskId = i;
  537. if(diskId > 1)
  538. {
  539. client::console.log("Invalid disk id", diskId);
  540. co_await Suspender<uint32_t>();
  541. }
  542. if(diskIds[diskId])
  543. {
  544. client::console.log("Overwriting disk id", diskId);
  545. co_await Suspender<uint32_t>();
  546. }
  547. diskIds[diskId] = true;
  548. CORE_DISK_TYPE type;
  549. if(disks[i]->get_type().localeCompare("ata")==0)
  550. type = DISK_HD;
  551. else if(disks[i]->get_type().localeCompare("atapi")==0)
  552. type = DISK_CD;
  553. else
  554. {
  555. client::console.log("Unknown disk type");
  556. co_await Suspender<uint32_t>();
  557. }
  558. t->core->postMessage(t->createIdeDiskMsg(type, diskId, dev->devId, dev->length));
  559. }
  560. }
  561. __asm__("{type:%1}" : "=r"(result) : "r"(CORE_START_SYSTEM));
  562. t->core->postMessage(result);
  563. }
  564. Thread CheerpX::System::runIOReadRequest(System* t, uint32_t id, uint32_t start, uint32_t len, uint32_t ioTransaction, uint32_t bufOffset)
  565. {
  566. CheerpX::Device* dev = t->getDeviceById(id);
  567. assert(dev->type == Device::TYPE::BLOCK);
  568. CheerpX::BlockDevice* device = static_cast<CheerpX::BlockDevice*>(dev);
  569. uint32_t readBytes = co_await device->read(t, start, len, client::HEAP8, bufOffset);
  570. client::Object* result = nullptr;
  571. __asm__("{type:%1,ioTransaction:%2}" : "=r"(result) : "r"(CORE_IO_RESULT), "r"(ioTransaction));
  572. t->postMessage(result, /*sendInterrupt*/true);
  573. }
  574. Thread CheerpX::System::runIOWriteRequest(System* t, uint32_t id, uint32_t start, uint32_t len, uint32_t ioTransaction, uint32_t bufOffset)
  575. {
  576. CheerpX::Device* dev = t->getDeviceById(id);
  577. assert(dev->type == Device::TYPE::BLOCK);
  578. CheerpX::BlockDevice* device = static_cast<CheerpX::BlockDevice*>(dev);
  579. int doneBytes = co_await device->write(t, start, len, client::HEAP8, bufOffset);
  580. client::Object* result = nullptr;
  581. __asm__("{type:%1,ioTransaction:%2}" : "=r"(result) : "r"(CORE_IO_RESULT), "r"(ioTransaction));
  582. t->postMessage(result, /*sendInterrupt*/true);
  583. }
  584. // Populate the queue using genericjs code, concurrently with the core using the data
  585. // Send an IRQ 1 message and force the thread to stop by setting the flag
  586. // TODO: This is not great, it would be much better to have a way to safely mark the IRQ from here
  587. void CheerpX::System::handleKeyDown(client::KeyboardEvent* ev)
  588. {
  589. // Do not handle Ctrl+Shift combos
  590. if(ev->get_ctrlKey() && ev->get_shiftKey())
  591. return;
  592. ev->preventDefault();
  593. client::Object* result = nullptr;
  594. __asm__("{type:%1,value:%2}" : "=r"(result) : "r"(CORE_QUEUE_KEYDOWN), "r"(ev->get_keyCode()));
  595. postMessage(result, /*sendInterrupt*/true);
  596. }
  597. void CheerpX::System::handleKeyUp(client::KeyboardEvent* ev)
  598. {
  599. // Do not handle Ctrl+Shift combos
  600. if(ev->get_ctrlKey() && ev->get_shiftKey())
  601. return;
  602. ev->preventDefault();
  603. client::Object* result = nullptr;
  604. __asm__("{type:%1,value:%2}" : "=r"(result) : "r"(CORE_QUEUE_KEYUP), "r"(ev->get_keyCode()));
  605. postMessage(result, /*sendInterrupt*/true);
  606. }
  607. Task<client::Object*> CheerpX::System::cheerpOSInit()
  608. {
  609. co_return client::CheerpX::System::wrap(this);
  610. }
  611. void CheerpXBase::postMessage(client::Object* msg, bool sendInterrupt)
  612. {
  613. if(sendInterrupt)
  614. (*client::HEAP32)[asyncPtrOffset + 0] = -2;
  615. core->postMessage(msg);
  616. }
  617. void CheerpXBase::updateHud()
  618. {
  619. for(HudGlobalStat& s: globalStats)
  620. s.update();
  621. }
  622. void CheerpXBase::sliceWidth(client::HTMLElement* e, const client::String& w)
  623. {
  624. e->get_style()->set_width(w);
  625. e->get_style()->set_boxSizing("border-box");
  626. }
  627. CheerpXBase::ContextData* CheerpXBase::getCtxDataForId(CONTEXT_TYPE t, uint32_t i) const
  628. {
  629. for(ContextData* c: dbgCtxs)
  630. {
  631. if(c->ctxType == t && c->ctxId == i)
  632. return c;
  633. }
  634. return nullptr;
  635. }
  636. CheerpXBase::ContextData* CheerpXBase::getCtxDataForName(client::String* n) const
  637. {
  638. for(ContextData* c: dbgCtxs)
  639. {
  640. if(c->displayName->localeCompare(*n) == 0)
  641. return c;
  642. }
  643. return nullptr;
  644. }
  645. void CheerpXBase::createHudImpl()
  646. {
  647. hudDiv = client::document.createElement("div");
  648. auto stopEvent = [](client::Event* e) { e->stopPropagation(); };
  649. hudDiv->addEventListener("keydown", cheerp::Callback(stopEvent));
  650. hudDiv->addEventListener("keyup", cheerp::Callback(stopEvent));
  651. hudDiv->addEventListener("keypress", cheerp::Callback(stopEvent));
  652. hudDiv->setAttribute("style", "position:absolute;width:25%;height:100%;top:0;right:0;overflow-y:scroll;");
  653. statsDiv = client::document.createElement("div");
  654. // This block will contain variuos global statistics
  655. appendHudBlock("Global stats", statsDiv);
  656. dbgCtxsDiv = client::document.createElement("div");
  657. // This block will contain the list of contexts that we can debug
  658. appendHudBlock("Debugger - Contexts", dbgCtxsDiv);
  659. dbgCtxSelect = (client::HTMLSelectElement*)client::document.createElement("select");
  660. dbgCtxsDiv->appendChild(dbgCtxSelect);
  661. sliceWidth(dbgCtxSelect, "50%");
  662. dbgControlDiv = client::document.createElement("div");
  663. // This block will contain the controls for starting/stopping/stepping a context
  664. appendHudBlock("Debugger - Control", dbgControlDiv);
  665. dbgStartStopBtn = (client::HTMLButtonElement*)client::document.createElement("button");
  666. sliceWidth(dbgStartStopBtn, "50%");
  667. dbgControlDiv->appendChild(dbgStartStopBtn);
  668. dbgDisasDiv = client::document.createElement("div");
  669. // This block will contain the disassembly/memory views
  670. appendHudBlock("Debugger - Disassembly", dbgDisasDiv);
  671. dbgDisasMode = (client::HTMLSelectElement*)client::document.createElement("select");
  672. dbgDisasMode->appendChild(createOption("16-bit", "0"));
  673. dbgDisasMode->appendChild(createOption("32-bit", "1"));
  674. dbgDisasMode->appendChild(createOption("Wasm (dump)", "2"));
  675. sliceWidth(dbgDisasMode, "20%");
  676. dbgDisasAddr = (client::HTMLInputElement*)client::document.createElement("input");
  677. sliceWidth(dbgDisasAddr, "20%");
  678. dbgDisasBtn = (client::HTMLButtonElement*)client::document.createElement("button");
  679. sliceWidth(dbgDisasBtn, "20%");
  680. dbgDisasBtn->set_textContent("Show");
  681. dbgDisasView = client::document.createElement("pre");
  682. dbgDisasDiv->appendChild(dbgDisasMode);
  683. dbgDisasDiv->appendChild(dbgDisasAddr);
  684. dbgDisasDiv->appendChild(dbgDisasBtn);
  685. dbgDisasDiv->appendChild(dbgDisasView);
  686. dbgDisasBtn->set_onclick(cheerp::Callback([this]()
  687. {
  688. assert(dbgCurCtx && dbgCurCtx->state == DBG_STOPPED);
  689. uint32_t selectedMode = client::parseInt(dbgDisasMode->get_value());
  690. client::String* addrStr = dbgDisasAddr->get_value();
  691. if(addrStr->get_length() == 0)
  692. return;
  693. uint32_t selectedAddr = client::parseInt(addrStr, 16);
  694. client::Object* result = nullptr;
  695. CORE_MESSAGE msg;
  696. if(selectedMode == 0)
  697. msg = CORE_DBG_DISAS_16;
  698. else if(selectedMode == 1)
  699. msg = CORE_DBG_DISAS_32;
  700. else if(selectedMode == 2)
  701. msg = CORE_DBG_DUMP_WASM;
  702. else
  703. return;
  704. __asm__("{type:%1,ctxType:%2,value:%3,addr:%4}" : "=r"(result) : "r"(msg), "r"(dbgCurCtx->ctxType), "r"(dbgCurCtx->ctxId), "r"(selectedAddr));
  705. postMessage(result, /*sendInterrupt*/true);
  706. }));
  707. client::Element* jitBisectDiv = client::document.createElement("div");
  708. appendHudBlock("JIT - Bisect", jitBisectDiv);
  709. jitBisectArea = (client::HTMLInputElement*)client::document.createElement("textarea");
  710. jitBisectSet = (client::HTMLButtonElement*)client::document.createElement("button");
  711. jitBisectCur = (client::HTMLButtonElement*)client::document.createElement("button");
  712. jitBisectDiv->appendChild(jitBisectArea);
  713. jitBisectDiv->appendChild(jitBisectSet);
  714. jitBisectDiv->appendChild(jitBisectCur);
  715. sliceWidth(jitBisectArea, "100%");
  716. sliceWidth(jitBisectSet, "50%");
  717. sliceWidth(jitBisectCur, "50%");
  718. jitBisectSet->set_textContent("Apply");
  719. jitBisectCur->set_textContent("Load Current");
  720. auto applyBisect = [this]()
  721. {
  722. client::localStorage.setItem("cxLastBisect", jitBisectArea->get_value());
  723. client::String* areaStr = jitBisectArea->get_value()->trim();
  724. if(areaStr->get_length() == 0)
  725. return;
  726. auto& lines = *areaStr->split("\n");
  727. client::Uint32Array* traces = new client::Uint32Array(lines.get_length());
  728. for (int i = 0; i < lines.get_length(); ++i)
  729. {
  730. (*traces)[i] = client::parseInt(lines[i], 16);
  731. }
  732. client::Object* result = nullptr;
  733. __asm__("{type:%1,traces:%2}" : "=r"(result) : "r"(CORE_JIT_BISECT), "r"(traces));
  734. postMessage(result, /*sendInterrupt*/true);
  735. };
  736. if(client::String* o = (client::String*)client::localStorage.getItem("cxLastBisect"))
  737. {
  738. jitBisectArea->set_value(o);
  739. if(o->get_length() != 0)
  740. applyBisect();
  741. }
  742. jitBisectSet->set_onclick(cheerp::Callback(applyBisect));
  743. jitBisectCur->set_onclick(cheerp::Callback([this]()
  744. {
  745. client::Object* result = nullptr;
  746. __asm__("{type:%1}" : "=r"(result) : "r"(CORE_JIT_GET_CUR_TRACES));
  747. postMessage(result, /*sendInterrupt*/true);
  748. }));
  749. selectContext(nullptr);
  750. client::document.get_body()->appendChild(hudDiv);
  751. dbgStartStopBtn->set_onclick(cheerp::Callback([this]
  752. {
  753. assert(dbgCurCtx && (dbgCurCtx->state == DBG_DETATCHED || dbgCurCtx->state == DBG_STOPPED));
  754. client::Object* result = nullptr;
  755. __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));
  756. postMessage(result, /*sendInterrupt*/true);
  757. }));
  758. dbgCtxSelect->set_onchange(cheerp::Callback([this]()
  759. {
  760. client::String* s = dbgCtxSelect->get_value();
  761. selectContext(getCtxDataForName(s));
  762. }));
  763. updateContexts();
  764. client::Object* result = nullptr;
  765. __asm__("{type:%1}" : "=r"(result) : "r"(CORE_ATTACH_HUD));
  766. postMessage(result, /*sendInterrupt*/true);
  767. client::setInterval(cheerp::Callback([this]() { updateHud(); }), 1000);
  768. }
  769. void CheerpXBase::updateContexts()
  770. {
  771. if(dbgCtxSelect == nullptr)
  772. return;
  773. while(client::Node* c = dbgCtxSelect->get_firstChild())
  774. dbgCtxSelect->removeChild(c);
  775. dbgCtxSelect->appendChild(createOption("<none>", ""));
  776. for(const ContextData* c: dbgCtxs)
  777. {
  778. auto* o = createOption(c->displayName, c->displayName);
  779. dbgCtxSelect->appendChild(o);
  780. // Automatically reselect the last selected context
  781. if(client::localStorage.getItem("cxLastCtx") == c->displayName)
  782. {
  783. o->set_selected(true);
  784. selectContext(c);
  785. }
  786. }
  787. }
  788. void CheerpXBase::selectContext(const ContextData* c)
  789. {
  790. dbgCurCtx = c;
  791. dbgDisasMode->set_disabled(true);
  792. dbgDisasAddr->set_disabled(true);
  793. dbgDisasBtn->set_disabled(true);
  794. dbgDisasView->set_textContent("");
  795. if(c == nullptr)
  796. {
  797. dbgStartStopBtn->set_textContent("Invalid");
  798. dbgStartStopBtn->set_disabled(true);
  799. jitBisectArea->set_disabled(true);
  800. jitBisectSet->set_disabled(true);
  801. return;
  802. }
  803. else
  804. {
  805. jitBisectArea->set_disabled(false);
  806. jitBisectSet->set_disabled(false);
  807. client::localStorage.setItem("cxLastCtx", c->displayName);
  808. }
  809. switch(c->state)
  810. {
  811. case DBG_DETATCHED:
  812. dbgStartStopBtn->set_textContent("Attach");
  813. dbgStartStopBtn->set_disabled(false);
  814. break;
  815. case DBG_STOPPED:
  816. dbgStartStopBtn->set_textContent("Detach");
  817. dbgStartStopBtn->set_disabled(false);
  818. dbgDisasMode->set_disabled(false);
  819. dbgDisasAddr->set_disabled(false);
  820. dbgDisasBtn->set_disabled(false);
  821. break;
  822. case DBG_SINGLE_STEP:
  823. dbgStartStopBtn->set_textContent("Stepping");
  824. dbgStartStopBtn->set_disabled(true);
  825. break;
  826. }
  827. }
  828. client::HTMLOptionElement* CheerpXBase::createOption(const client::String& text, const client::String& value)
  829. {
  830. client::HTMLOptionElement* o = (client::HTMLOptionElement*)client::document.createElement("option");
  831. o->set_textContent(text);
  832. o->set_value(value);
  833. return o;
  834. }
  835. void CheerpXBase::appendHudBlock(const client::String& blockTitle, client::Element* e)
  836. {
  837. client::Element* container = client::document.createElement("div");
  838. client::HTMLElement* p = client::document.createElement("p");
  839. p->get_style()->set_fontWeight("bold");
  840. p->set_textContent(blockTitle);
  841. container->appendChild(p);
  842. container->appendChild(e);
  843. hudDiv->appendChild(container);
  844. }