├── .gitignore ├── Makefile ├── aotjs_runtime.cpp ├── aotjs_runtime.h ├── readme.md └── samples ├── args.cpp ├── args.js ├── closure.cpp ├── closure.js ├── gc.cpp ├── mandelbrot.cpp ├── mandelbrot.js ├── retval.cpp └── retval.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /build 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=clang++ 2 | #CC=g++ 3 | EMCC=em++ 4 | 5 | SOURCES=aotjs_runtime.cpp 6 | HEADERS=aotjs_runtime.h 7 | CFLAGS=-g -O3 8 | #CFLAGS=-g -Os 9 | #CFLAGS=-g -O3 -DFORCE_GC 10 | #CFLAGS=-g -O0 -DFORCE_GC -DDEBUG 11 | #CFLAGS=-g -O0 12 | 13 | CFLAGS_COMMON=$(CFLAGS) -std=c++14 14 | CFLAGS_NATIVE=$(CFLAGS_COMMON) -flto 15 | #CFLAGS_NATIVE=$(CFLAGS_COMMON) 16 | CFLAGS_WASM=$(CFLAGS_COMMON) -s WASM=1 -s BINARYEN_TRAP_MODE=clamp -s NO_FILESYSTEM=1 --llvm-lto 1 17 | 18 | all : native wasm 19 | 20 | native : build/gc build/closure build/retval build/args build/mandelbrot 21 | 22 | wasm : build/gc.js build/closure.js build/retval.js build/args.js build/mandelbrot.js 23 | 24 | clean : 25 | rm -rf build 26 | 27 | build/%.js : samples/%.cpp $(SOURCES) $(HEADERS) 28 | mkdir -p build 29 | $(EMCC) $(CFLAGS_WASM) -o $@ $< $(SOURCES) 30 | gzip -9 < build/$*.wasm > build/$*.wasm.gz 31 | 32 | build/% :: samples/%.cpp $(SOURCES) $(HEADERS) 33 | mkdir -p build 34 | $(CC) $(CFLAGS_NATIVE) -o $@ $< $(SOURCES) 35 | -------------------------------------------------------------------------------- /aotjs_runtime.cpp: -------------------------------------------------------------------------------- 1 | #include "aotjs_runtime.h" 2 | 3 | #include 4 | 5 | #ifdef DEBUG 6 | #include 7 | #endif 8 | 9 | #include 10 | #include 11 | 12 | namespace AotJS { 13 | TypeOf typeOfGCThing = "gcthing"; // base class should not be exposed 14 | TypeOf typeOfInternal = "internal"; // internal class should not be exposed 15 | TypeOf typeOfJSThing = "jsthing"; // base class should not be exposed 16 | 17 | TypeOf typeOfBoxDouble = "boxdouble"; 18 | TypeOf typeOfBoxInt32 = "boxint32"; 19 | TypeOf typeOfDeleted = "deleted"; 20 | 21 | TypeOf typeOfUndefined = "undefined"; 22 | TypeOf typeOfNull = "null"; 23 | TypeOf typeOfNumber = "number"; 24 | TypeOf typeOfBoolean = "boolean"; 25 | TypeOf typeOfString = "string"; 26 | TypeOf typeOfSymbol = "symbol"; 27 | TypeOf typeOfFunction = "function"; 28 | TypeOf typeOfObject = "object"; 29 | } 30 | 31 | #pragma mark Val hash helpers 32 | 33 | namespace std { 34 | size_t hash<::AotJS::Val>::operator()(::AotJS::Val const& ref) const noexcept { 35 | if (ref.isString()) { 36 | return hash{}(ref.asString()); 37 | } else { 38 | return hash{}(ref.raw()); 39 | } 40 | } 41 | } 42 | 43 | namespace AotJS { 44 | 45 | 46 | bool Val::operator==(const Val &rhs) const { 47 | // Bit-identical always matches! 48 | // This catches matching pointers (same object), matching int31s, etc. 49 | if (raw() == rhs.raw()) { 50 | return true; 51 | } 52 | 53 | if (isString() || rhs.isString()) { 54 | // Two string instances may still compare equal. 55 | return toString()->str() == rhs.toString()->str(); 56 | } 57 | 58 | // fixme handle box types better 59 | if (isInt32() && rhs.isInt32()) { 60 | return asInt32() == rhs.asInt32(); 61 | } 62 | if (isDouble() || rhs.isDouble()) { 63 | return toDouble() == rhs.toDouble(); 64 | } 65 | if (isObject() || rhs.isObject()) { 66 | // Non-identical non-string objects never compare identical. 67 | return false; 68 | } 69 | // hack for box doubles 70 | return toDouble() == rhs.toDouble(); 71 | } 72 | 73 | // Checked conversions 74 | bool Val::toBool() const 75 | { 76 | if (isBool()) { 77 | return asBool(); 78 | } else if (isInt32()) { 79 | return static_cast(asInt32()); 80 | } else if (isDouble()) { 81 | return static_cast(asDouble()); 82 | } else if (isUndefined()) { 83 | return false; 84 | } else if (isNull()) { 85 | return false; 86 | } else if (isString()) { 87 | return asString().length() > 0; 88 | } else { 89 | return true; 90 | } 91 | } 92 | 93 | int32_t Val::toInt32() const 94 | { 95 | if (isGCThing()) { 96 | return asGCThing().toInt32(); 97 | } 98 | if (isInt31()) { 99 | return asInt31(); 100 | } 101 | if (isInt32()) { 102 | return asInt32(); 103 | } 104 | return static_cast(asDouble()); 105 | } 106 | 107 | double Val::toDouble() const 108 | { 109 | #ifdef VAL_TAGGED_POINTER 110 | if (isInt31()) { 111 | return static_cast(asInt31()); 112 | } else { 113 | // Everything else is an object type. 114 | return asGCThing().toDouble(); 115 | } 116 | #endif 117 | 118 | #ifdef VAL_SHIFTED_NAN_BOX 119 | // Microoptimize the tag check 120 | const int64_t tag_ = tag(); 121 | if (tag_ == tagBitsPointer) { 122 | // A few special values are boxed doubles; others may be objects. 123 | return asGCThing().toDouble(); 124 | } else if (tag_ == tagBitsInt32) { 125 | return static_cast(asInt32()); 126 | } else { 127 | // Everything else is an encoded double. 128 | return asDouble(); 129 | } 130 | #endif 131 | } 132 | 133 | Retained Val::toString() const 134 | { 135 | ScopeRet scope; 136 | if (isGCThing()) { 137 | return scope.escape(asGCThing().toString()); 138 | } else { 139 | return scope.escape(retain(dump())); 140 | } 141 | } 142 | 143 | string Val::dump() const { 144 | std::ostringstream buf; 145 | if (isDouble()) { 146 | buf << asDouble(); 147 | } else if (isInt32()) { 148 | buf << asInt32(); 149 | } else if (isInt31()) { 150 | buf << asInt31(); 151 | } else if (isBool()) { 152 | if (asBool()) { 153 | buf << "true"; 154 | } else { 155 | buf << "false"; 156 | } 157 | } else if (isNull()) { 158 | buf << "null"; 159 | } else if (isUndefined()) { 160 | buf << "undefined"; 161 | } else if (isGCThing()) { 162 | buf << asGCThing().dump(); 163 | } 164 | return buf.str(); 165 | } 166 | 167 | Local Val::call(Local aThis, RawArgList aArgs) const 168 | { 169 | ScopeRetVal scope; 170 | if (isFunction()) { 171 | return scope.escape(asFunction().call(aThis, aArgs)); 172 | } else { 173 | #ifdef DEBUG 174 | std::cerr << "not a function\n"; 175 | #endif 176 | std::abort(); 177 | } 178 | } 179 | 180 | #pragma mark operators 181 | 182 | Local operator+(Local lhs, Local rhs) { 183 | ScopeRetVal scope; 184 | // todo implement this correctly 185 | if (lhs->isString() || rhs->isString()) { 186 | return scope.escape(new String(lhs->toString()->str() + rhs->toString()->str())); 187 | } else { 188 | return scope.escape(lhs->toDouble() + rhs->toDouble()); 189 | } 190 | } 191 | 192 | Local operator+(double lhs, Local rhs) { 193 | ScopeRetVal scope; 194 | // todo implement this correctly 195 | if (rhs->isString()) { 196 | return scope.escape(new String(Local(lhs)->toString()->str() + rhs->toString()->str())); 197 | } else { 198 | return scope.escape(lhs + rhs->toDouble()); 199 | } 200 | } 201 | 202 | Local operator+(Local lhs, double rhs) { 203 | ScopeRetVal scope; 204 | // todo implement this correctly 205 | if (lhs->isString()) { 206 | return scope.escape(new String(lhs->toString()->str() + Local(rhs)->toString()->str())); 207 | } else { 208 | return scope.escape(lhs->toDouble() + rhs); 209 | } 210 | } 211 | 212 | double operator-(Local lhs, Local rhs) { 213 | Scope scope; 214 | return lhs->toDouble() - rhs->toDouble(); 215 | } 216 | 217 | double operator-(double lhs, Local rhs) { 218 | Scope scope; 219 | return lhs - rhs->toDouble(); 220 | } 221 | 222 | double operator-(Local lhs, double rhs) { 223 | Scope scope; 224 | return lhs->toDouble() - rhs; 225 | } 226 | 227 | double operator*(Local lhs, Local rhs) { 228 | Scope scope; 229 | return lhs->toDouble() * rhs->toDouble(); 230 | } 231 | 232 | double operator*(double lhs, Local rhs) { 233 | Scope scope; 234 | return lhs * rhs->toDouble(); 235 | } 236 | 237 | double operator*(Local lhs, double rhs) { 238 | Scope scope; 239 | return lhs->toDouble() * rhs; 240 | } 241 | 242 | double operator/(Local lhs, Local rhs) { 243 | Scope scope; 244 | return lhs->toDouble() / rhs->toDouble(); 245 | } 246 | 247 | double operator/(double lhs, Local rhs) { 248 | Scope scope; 249 | return lhs / rhs->toDouble(); 250 | } 251 | 252 | double operator/(Local lhs, double rhs) { 253 | Scope scope; 254 | return lhs->toDouble() / rhs; 255 | } 256 | 257 | bool operator==(Local lhs, Local rhs) { 258 | // todo move implementation? 259 | return *lhs == *rhs; 260 | } 261 | 262 | bool operator<(Local lhs, Local rhs) { 263 | // todo do strings compare too? 264 | // todo do ints and stuff 265 | Scope scope; 266 | return lhs->toDouble() < rhs->toDouble(); 267 | } 268 | 269 | bool operator<(Local lhs, double rhs) { 270 | Scope scope; 271 | return lhs->toDouble() < rhs; 272 | } 273 | 274 | bool operator<(double lhs, Local rhs) { 275 | // todo do strings compare too? 276 | // todo do ints and stuff 277 | Scope scope; 278 | return lhs < rhs->toDouble(); 279 | } 280 | 281 | bool operator>(Local lhs, Local rhs) { 282 | Scope scope; 283 | return lhs->toDouble() > rhs->toDouble(); 284 | } 285 | 286 | bool operator>(double lhs, Local rhs) { 287 | Scope scope; 288 | return lhs > rhs->toDouble(); 289 | } 290 | 291 | bool operator>(Local lhs, double rhs) { 292 | Scope scope; 293 | return lhs->toDouble() > rhs; 294 | } 295 | 296 | Local& operator++(Local& aLocal) { 297 | // because these modify the original parameter, they do not 298 | // go through ScopeRetVal::escape for the return value. 299 | Scope scope; 300 | aLocal = aLocal + 1; 301 | return aLocal; 302 | } 303 | 304 | Local& operator--(Local& aLocal) { 305 | // because these modify the original parameter, they do not 306 | // go through ScopeRetVal::escape for the return value. 307 | Scope scope; 308 | aLocal = aLocal - 1; 309 | return aLocal; 310 | } 311 | 312 | Local operator++(Local& aLocal, int) { 313 | ScopeRetVal scope; 314 | Local prev = aLocal; 315 | aLocal = aLocal + 1; 316 | return scope.escape(prev); 317 | } 318 | 319 | Local operator--(Local& aLocal, int) { 320 | ScopeRetVal scope; 321 | Local prev = aLocal; 322 | aLocal = aLocal - 1; 323 | return scope.escape(prev); 324 | } 325 | 326 | Local& operator+=(Local& lhs, const Local& rhs) 327 | { 328 | Scope scope; 329 | lhs = lhs + rhs; 330 | return lhs; 331 | } 332 | 333 | Local& operator-=(Local& lhs, const Local& rhs) 334 | { 335 | Scope scope; 336 | lhs = lhs - rhs; 337 | return lhs; 338 | } 339 | 340 | Local& operator*=(Local& lhs, const Local& rhs) 341 | { 342 | Scope scope; 343 | lhs = lhs * rhs; 344 | return lhs; 345 | } 346 | 347 | Local& operator/=(Local& lhs, const Local& rhs) 348 | { 349 | Scope scope; 350 | lhs = lhs / rhs; 351 | return lhs; 352 | } 353 | 354 | #pragma mark ArgList 355 | 356 | ArgList::ArgList(Function& func, RawArgList args) 357 | : mStackTop(engine().stackTop()), 358 | mBegin(nullptr), 359 | mSize(0) 360 | { 361 | // Copy all the temporaries passed in to a lower scope 362 | size_t size = 0; 363 | for (auto& local : args) { 364 | Val* binding = engine().pushLocal(*local); 365 | if (size++ == 0) { 366 | mBegin = binding; 367 | } 368 | } 369 | 370 | // Save the original argument list size for `arguments.length` 371 | mSize = size; 372 | 373 | // Assign space for undefined for anything left. 374 | // This means functions can access vars without a bounds check. 375 | // todo: apply es6 default params 376 | while (size < func.arity()) { 377 | Val* binding = engine().pushLocal(Undefined()); 378 | if (size++ == 0) { 379 | mBegin = binding; 380 | } 381 | } 382 | } 383 | 384 | #pragma mark GCThing 385 | 386 | 387 | GCThing::~GCThing() { 388 | // 389 | } 390 | 391 | void GCThing::markRefsForGC() { 392 | // no-op default 393 | } 394 | 395 | string GCThing::dump() { 396 | return string(typeOf()); 397 | } 398 | 399 | Retained GCThing::toString() const { 400 | ScopeRet scope; 401 | std::ostringstream buf; 402 | buf << "[" << typeOf() << "]"; 403 | return scope.escape(retain(buf.str())); 404 | } 405 | 406 | int32_t GCThing::toInt32() const { 407 | return 0; 408 | } 409 | 410 | double GCThing::toDouble() const { 411 | return NAN; 412 | } 413 | 414 | TypeOf GCThing::typeOf() const { 415 | return typeOfGCThing; 416 | } 417 | 418 | #pragma mark Box 419 | 420 | template <> 421 | TypeOf Box::typeOf() const { 422 | return typeOfBoxDouble; 423 | } 424 | 425 | template <> 426 | TypeOf Box::typeOf() const { 427 | return typeOfBoxInt32; 428 | } 429 | 430 | template <> 431 | TypeOf Box::typeOf() const { 432 | return typeOfUndefined; 433 | } 434 | 435 | template <> 436 | TypeOf Box::typeOf() const { 437 | return typeOfNull; 438 | } 439 | 440 | template <> 441 | TypeOf Box::typeOf() const { 442 | return typeOfBoolean; 443 | } 444 | 445 | template <> 446 | TypeOf Box::typeOf() const { 447 | return typeOfDeleted; 448 | } 449 | 450 | template 451 | string Box::dump() { 452 | std::ostringstream buf; 453 | buf << val(); 454 | return buf.str(); 455 | }; 456 | 457 | template<> 458 | string Box::dump() { 459 | return val(); 460 | }; 461 | 462 | template<> 463 | string Box::dump() { 464 | return val(); 465 | }; 466 | 467 | template<> 468 | string Box::dump() { 469 | return val(); 470 | }; 471 | 472 | #pragma mark Object 473 | 474 | Object::~Object() { 475 | // 476 | } 477 | 478 | TypeOf Object::typeOf() const { 479 | return typeOfObject; 480 | } 481 | 482 | static Local normalizePropName(Local aName) { 483 | ScopeRetVal scope; 484 | if (aName->isString()) { 485 | return scope.escape(aName); 486 | } else if (aName->isSymbol()) { 487 | return scope.escape(aName); 488 | } else { 489 | // todo: convert to string 490 | #ifdef DEBUG 491 | std::cerr << "cannot use non-string/symbol as property index"; 492 | #endif 493 | std::abort(); 494 | return scope.escape(Undefined()); 495 | } 496 | } 497 | 498 | // todo: handle numeric indices 499 | // todo: getters 500 | Local Object::getProp(Local aName) { 501 | ScopeRetVal scope; 502 | Local name = normalizePropName(aName); 503 | auto index = mProps.find(*name); 504 | if (index == mProps.end()) { 505 | if (mPrototype) { 506 | return scope.escape(mPrototype->getProp(name)); 507 | } else { 508 | return scope.escape(Undefined()); 509 | } 510 | } else { 511 | return scope.escape(index->first); 512 | } 513 | } 514 | 515 | void Object::setProp(Local aName, Local aVal) { 516 | Local name = *normalizePropName(aName); 517 | mProps.emplace(*name, *aVal); 518 | } 519 | 520 | void Object::markRefsForGC() { 521 | for (auto iter : mProps) { 522 | auto prop_name(iter.first); 523 | auto prop_val(iter.second); 524 | 525 | prop_name.markForGC(); 526 | prop_val.markForGC(); 527 | } 528 | } 529 | 530 | string Object::dump() { 531 | std::ostringstream buf; 532 | buf << "Object({"; 533 | 534 | bool first = true; 535 | for (auto iter : mProps) { 536 | auto name = iter.first; 537 | auto val = iter.second; 538 | 539 | if (first) { 540 | first = false; 541 | } else { 542 | buf << ","; 543 | } 544 | buf << name.dump(); 545 | buf << ":"; 546 | buf << val.dump(); 547 | } 548 | buf << "})"; 549 | return buf.str(); 550 | } 551 | 552 | #pragma mark PropIndex 553 | PropIndex::~PropIndex() { 554 | // 555 | } 556 | 557 | #pragma mark String 558 | 559 | String::~String() { 560 | // 561 | } 562 | 563 | TypeOf String::typeOf() const { 564 | return typeOfString; 565 | } 566 | 567 | string String::dump() { 568 | // todo: emit JSON or something 569 | std::ostringstream buf; 570 | buf << "\""; 571 | buf << data; 572 | buf << "\""; 573 | return buf.str(); 574 | } 575 | 576 | #pragma mark Symbol 577 | 578 | Symbol::~Symbol() { 579 | // 580 | } 581 | 582 | TypeOf Symbol::typeOf() const { 583 | return typeOfSymbol; 584 | } 585 | 586 | string Symbol::dump() { 587 | std::ostringstream buf; 588 | buf << "Symbol(\""; 589 | buf << name; 590 | buf << "\")"; 591 | return buf.str(); 592 | } 593 | 594 | #pragma mark Internal 595 | 596 | Internal::~Internal() { 597 | // 598 | } 599 | 600 | TypeOf Internal::typeOf() const { 601 | return typeOfInternal; 602 | } 603 | 604 | #pragma mark JSThing 605 | 606 | JSThing::~JSThing() { 607 | // 608 | } 609 | 610 | TypeOf JSThing::typeOf() const { 611 | return typeOfJSThing; 612 | } 613 | 614 | Retained JSThing::toString() const { 615 | ScopeRet scope; 616 | return scope.escape(retain("[jsthing JSThing]")); 617 | } 618 | 619 | #pragma mark Cell 620 | 621 | Cell::~Cell() { 622 | // 623 | } 624 | 625 | void Cell::markRefsForGC() { 626 | mVal.markForGC(); 627 | } 628 | 629 | string Cell::dump() { 630 | std::ostringstream buf; 631 | buf << "Cell(" << mVal.dump() << ")"; 632 | return buf.str(); 633 | } 634 | 635 | #pragma mark Function 636 | 637 | Function::~Function() { 638 | // 639 | } 640 | 641 | TypeOf Function::typeOf() const { 642 | return typeOfFunction; 643 | } 644 | 645 | void Function::markRefsForGC() { 646 | for (auto cell : mCaptures) { 647 | cell->markForGC(); 648 | } 649 | } 650 | 651 | 652 | Local Function::call(Local aThis, RawArgList aArgs) { 653 | ScopeRetVal scope; 654 | return scope.escape(*mBody(*this, aThis, ArgList(*this, aArgs))); 655 | } 656 | 657 | string Function::dump() { 658 | std::ostringstream buf; 659 | buf << "Function(\"" << name() << "\")"; 660 | return buf.str(); 661 | } 662 | 663 | #pragma mark Engine 664 | 665 | // warning: if stack goes beyond this it will explode 666 | Engine::Engine(size_t aStackSize) 667 | : mReadyForGC(false), 668 | mUndefined(new Box(Undefined())), 669 | mNull(new Box(Null())), 670 | mDeleted(new Box(Deleted())), 671 | mFalse(new Box(false)), 672 | mTrue(new Box(true)), 673 | mRoot(new Object()), 674 | mStackBegin( new Val[aStackSize]), 675 | mStackTop(mStackBegin), 676 | mStackEnd(mStackBegin + aStackSize), 677 | mObjects() 678 | { 679 | // Ok, now that we've initialized those things it's safe 680 | // to enable the GC registration system. 681 | mReadyForGC = true; 682 | registerForGC(*mUndefined); 683 | registerForGC(*mNull); 684 | registerForGC(*mDeleted); 685 | registerForGC(*mFalse); 686 | registerForGC(*mTrue); 687 | registerForGC(*mRoot); 688 | } 689 | 690 | Engine::~Engine() { 691 | delete[] mStackBegin; 692 | } 693 | 694 | void Engine::registerForGC(GCThing& obj) { 695 | // Because we don't yet control the allocation, we don't know how to walk 696 | // the heap looking for all objects. Instead, we need to keep a separate 697 | // set of all objects in order to do the final sweep. 698 | if (mReadyForGC) { 699 | mObjects.insert(&obj); 700 | } 701 | } 702 | 703 | Val* Engine::stackTop() 704 | { 705 | return mStackTop; 706 | } 707 | 708 | Val* Engine::pushLocal(Val val) 709 | { 710 | Val* ptr = mStackTop; 711 | if (mStackTop++ > mStackEnd) { 712 | #ifdef DEBUG 713 | std::cerr << "stack overflow!\n"; 714 | #endif 715 | std::abort(); 716 | } 717 | *ptr = val; 718 | return ptr; 719 | } 720 | 721 | void Engine::popLocal(Val* expectedTop) { 722 | #ifdef DEBUG 723 | if (expectedTop < mStackBegin || expectedTop > mStackTop) { 724 | std::cerr << "bad stack pointer: " 725 | << stackTop() 726 | << ", expected " 727 | << expectedTop 728 | << "\n"; 729 | std::abort(); 730 | } 731 | #endif 732 | mStackTop = expectedTop; 733 | } 734 | 735 | void Engine::gc() { 736 | #ifdef DEBUG 737 | std::cerr << "starting gc mark/sweep: " << dump() <<"\n"; 738 | #endif 739 | 740 | if (!mReadyForGC) { 741 | // don't try to destroy our singleton sigils during initialization 742 | return; 743 | } 744 | 745 | // 1) Mark! 746 | 747 | // Mark our global sigil objects. 748 | mUndefined->markForGC(); 749 | mNull->markForGC(); 750 | mDeleted->markForGC(); 751 | mFalse->markForGC(); 752 | mTrue->markForGC(); 753 | 754 | // Mark anything reachable from the global root object. 755 | mRoot->markForGC(); 756 | 757 | // Mark anything on the stack of currently open scopes 758 | #if DEBUG 759 | std::cerr << "stack: ["; 760 | bool first = true; 761 | for (Val* record = mStackBegin; record < mStackTop; record++) { 762 | if (first) { 763 | first = false; 764 | } else { 765 | std::cerr << ","; 766 | } 767 | std::cerr << record->dump(); 768 | } 769 | std::cerr << "]\n"; 770 | #endif 771 | for (Val* record = mStackBegin; record < mStackTop; record++) { 772 | record->markForGC(); 773 | } 774 | 775 | // 2) Sweep! 776 | // We alloc a vector here ebecause we can't change the set while iterating. 777 | // Todo: don't require allocating memory to free memory! 778 | std::vector deadObjects; 779 | for (auto obj : mObjects) { 780 | if (obj->isMarkedForGC()) { 781 | // Keep the object, but reset the marker value for next time. 782 | obj->clearForGC(); 783 | } else { 784 | #ifdef DEBUG 785 | std::cerr << "removing dead object: " << obj->dump() << "\n"; 786 | #endif 787 | deadObjects.push_back(obj); 788 | } 789 | } 790 | 791 | for (auto obj : deadObjects) { 792 | // No findable references to this object. Destroy it! 793 | mObjects.erase(obj); 794 | delete obj; 795 | } 796 | } 797 | 798 | void Engine::maybeGC() { 799 | // todo find a good heuristic 800 | if (mAllocations++ % 1024 == 0) { 801 | gc(); 802 | } 803 | } 804 | 805 | string Engine::dump() { 806 | std::ostringstream buf; 807 | buf << "Engine(["; 808 | 809 | bool first = true; 810 | for (auto obj : mObjects) { 811 | if (first) { 812 | first = false; 813 | } else { 814 | buf << ","; 815 | } 816 | buf << obj->dump(); 817 | } 818 | buf << "])"; 819 | return buf.str(); 820 | } 821 | 822 | Engine engine_singleton; 823 | 824 | Engine& engine() { 825 | return engine_singleton; 826 | } 827 | 828 | double Engine::now() { 829 | auto point = std::chrono::high_resolution_clock::now(); 830 | 831 | // fixme dont assume epoch is unix epoch! 832 | auto offset = point.time_since_epoch(); 833 | 834 | // return an integral number for Date.now, but too big for int32_t 835 | auto millis = std::chrono::duration_cast(offset); 836 | return static_cast(millis.count()); 837 | } 838 | 839 | } 840 | -------------------------------------------------------------------------------- /aotjs_runtime.h: -------------------------------------------------------------------------------- 1 | #ifndef AOTJS_RUNTIME 2 | #define AOTJS_RUNTIME 3 | 4 | #ifdef DEBUG 5 | #include 6 | #endif 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace AotJS { 17 | using ::std::string; 18 | using ::std::vector; 19 | using ::std::hash; 20 | using ::std::unordered_set; 21 | using ::std::unordered_map; 22 | 23 | typedef const char *TypeOf; 24 | 25 | extern TypeOf typeOfGCThing; 26 | extern TypeOf typeOfInternal; 27 | extern TypeOf typeOfJSThing; 28 | 29 | extern TypeOf typeOfBoxDouble; 30 | extern TypeOf typeOfBoxInt32; 31 | 32 | extern TypeOf typeOfUndefined; 33 | extern TypeOf typeOfNull; 34 | extern TypeOf typeOfNumber; 35 | extern TypeOf typeOfBoolean; 36 | extern TypeOf typeOfString; 37 | extern TypeOf typeOfSymbol; 38 | extern TypeOf typeOfFunction; 39 | extern TypeOf typeOfObject; 40 | 41 | class Val; 42 | } 43 | 44 | namespace std { 45 | template<> struct hash<::AotJS::Val> { 46 | size_t operator()(::AotJS::Val const& ref) const noexcept; 47 | }; 48 | } 49 | 50 | namespace AotJS { 51 | template class Box; 52 | 53 | class Engine; 54 | 55 | class Local; 56 | template class Retained; 57 | 58 | class Scope; 59 | 60 | typedef std::initializer_list RawArgList; 61 | class ArgList; 62 | 63 | class GCThing; 64 | class Cell; 65 | class Frame; 66 | 67 | typedef std::initializer_list RawCaptureList; 68 | 69 | class PropIndex; 70 | class String; 71 | class Symbol; 72 | 73 | class Object; 74 | class Function; 75 | 76 | class Undefined { 77 | // just a tag 78 | public: 79 | operator int32_t() const { 80 | return 0; 81 | } 82 | operator double() const { 83 | return NAN; 84 | } 85 | operator string() const { 86 | return "undefined"; 87 | } 88 | }; 89 | 90 | class Null { 91 | // just a tag 92 | public: 93 | operator int32_t() const { 94 | return 0; 95 | } 96 | operator double() const { 97 | return 0.0; 98 | } 99 | operator string() const { 100 | return "null"; 101 | } 102 | }; 103 | 104 | class Deleted { 105 | // just a tag 106 | public: 107 | operator int32_t() const { 108 | return 0; 109 | } 110 | operator double() const { 111 | return 0; 112 | } 113 | operator string() const { 114 | return "deleted"; 115 | } 116 | }; 117 | 118 | typedef Local (*FunctionBody)(Function& func, Local this_, ArgList args); 119 | 120 | /// 121 | /// Represents an entire JS world. 122 | /// 123 | /// Garbage collection is run when gc() is called manually, 124 | /// or just let everything deallocate when the engine is 125 | /// destroyed. 126 | /// 127 | class Engine { 128 | // Flag to disable GC until we've finished initializing. 129 | // We need to be able to create a few sigil objects before 130 | // it's possible to cleanly run the GC system. 131 | bool mReadyForGC; 132 | size_t mAllocations; 133 | 134 | // Sigil values with special boxed values 135 | Box* mUndefined; 136 | Box* mNull; 137 | Box* mDeleted; 138 | Box* mFalse; 139 | Box* mTrue; 140 | 141 | // Global root object. 142 | Object* mRoot; 143 | 144 | // a stack of Val cells, to which we keep pointers in vars and 'real' stack. 145 | // Or, in theory we could scan the real stack but may have false values 146 | // and could run into problems with values optimized into locals which 147 | // aren't on the emscripten actual stack in wasm! 148 | // todo: don't need vector really here? 149 | Val* mStackBegin; 150 | Val* mStackTop; 151 | Val* mStackEnd; 152 | 153 | // Set of all live objects. 154 | // Todo: replace this with an allocator we know how to walk! 155 | unordered_set mObjects; 156 | 157 | void registerForGC(GCThing& aObj); 158 | friend class GCThing; 159 | 160 | friend class Local; 161 | friend class Scope; 162 | friend class ScopeRetVal; 163 | template friend class ScopeRet; 164 | friend class ArgList; 165 | Val* stackTop(); 166 | Val* pushLocal(Val ref); 167 | void popLocal(Val* mRecord); 168 | 169 | public: 170 | static const size_t defaultStackSize = 256 * 1024; 171 | 172 | // Todo allow specializing the root object at instantiation time? 173 | Engine(size_t aStackSize); 174 | 175 | Engine() 176 | : Engine(defaultStackSize) 177 | { 178 | // 179 | } 180 | 181 | ~Engine(); 182 | 183 | // ick! 184 | void setRoot(Object& aRoot) { 185 | mRoot = &aRoot; 186 | } 187 | 188 | Object* root() const { 189 | return mRoot; 190 | } 191 | 192 | // Sigil values 193 | Box* undefinedRef() const { 194 | return mUndefined; 195 | } 196 | 197 | Box* nullRef() const { 198 | return mNull; 199 | } 200 | 201 | Box* deletedRef() const { 202 | return mDeleted; 203 | } 204 | 205 | Box* falseRef() const { 206 | return mFalse; 207 | } 208 | 209 | Box* trueRef() const { 210 | return mTrue; 211 | } 212 | 213 | void gc(); 214 | void maybeGC(); 215 | string dump(); 216 | double now(); 217 | }; 218 | 219 | // Singleton 220 | extern Engine engine_singleton; 221 | 222 | // Singleton access 223 | Engine& engine(); 224 | 225 | /// 226 | /// Base class for an item that can be garbage-collected and may 227 | /// reference other GC-able items. 228 | /// 229 | /// Not necessarily exposed to JS. 230 | /// 231 | class GCThing { 232 | /// 233 | /// GC mark state -- normally false, except during GC marking 234 | /// when it records true if the object is reachable. 235 | /// 236 | bool mMarked; 237 | 238 | public: 239 | GCThing() 240 | : 241 | mMarked(false) 242 | { 243 | #ifdef FORCE_GC 244 | // Force garbage collection to happen on every allocation. 245 | // Should shake out some bugs. 246 | engine().gc(); 247 | #else 248 | engine().maybeGC(); 249 | #endif 250 | 251 | // We need a set of *all* allocated objects to do sweep. 252 | // todo: put this in the allocator? 253 | engine().registerForGC(*this); 254 | } 255 | 256 | virtual ~GCThing(); 257 | 258 | // To be called only by Engine... 259 | 260 | bool isMarkedForGC() const { 261 | return mMarked; 262 | } 263 | 264 | void markForGC() { 265 | if (!isMarkedForGC()) { 266 | mMarked = true; 267 | #ifdef DEBUG 268 | std::cerr << "marking object live " << dump() << "\n"; 269 | #endif 270 | markRefsForGC(); 271 | } 272 | } 273 | 274 | void clearForGC() { 275 | mMarked = false; 276 | } 277 | 278 | virtual void markRefsForGC(); 279 | 280 | // Really public! 281 | virtual string dump(); 282 | 283 | virtual TypeOf typeOf() const; 284 | 285 | virtual Retained toString() const; 286 | virtual int32_t toInt32() const; 287 | virtual double toDouble() const; 288 | }; 289 | 290 | // Internal classes that should not be exposed to JS 291 | class Internal : public GCThing { 292 | public: 293 | Internal() 294 | : GCThing() 295 | { 296 | // 297 | } 298 | 299 | ~Internal() override; 300 | TypeOf typeOf() const override; 301 | }; 302 | 303 | /// 304 | /// Child classes are JS-exposed objects, but not necessarily Objects. 305 | /// 306 | class JSThing : public GCThing { 307 | public: 308 | JSThing() 309 | : GCThing() 310 | { 311 | // 312 | } 313 | 314 | ~JSThing() override; 315 | TypeOf typeOf() const override; 316 | 317 | Retained toString() const override; 318 | }; 319 | 320 | template 321 | class Box : public GCThing { 322 | T mVal; 323 | 324 | public: 325 | Box(T aVal) : mVal(aVal) {} 326 | 327 | T val() const { 328 | return mVal; 329 | } 330 | 331 | int32_t toInt32() const override { 332 | return static_cast(val()); 333 | } 334 | 335 | double toDouble() const override { 336 | return static_cast(val()); 337 | } 338 | 339 | TypeOf typeOf() const override; 340 | 341 | string dump() override; 342 | }; 343 | 344 | //#define VAL_TAGGED_POINTER 1 345 | #define VAL_SHIFTED_NAN_BOX 1 346 | 347 | class Val { 348 | #ifdef VAL_TAGGED_POINTER 349 | /// 350 | /// Polymorphic JS values are handled by using pointer-sized values with 351 | /// either a pointer to a GCThing or a tagged 31-bit integer with a tag bit 352 | /// in the lowest bit. 353 | /// 354 | /// Unlike NaN-boxing this means double-precision floats and some int32s 355 | /// must be boxed into GCThing subclasses and allocated on the heap. 356 | /// Other values like undefined, null, and boolean use special sigil 357 | /// objects that don't have to be allocated on each use. 358 | /// 359 | /// However it is closer to the available reference types in the Wasm 360 | /// garbage collection proposal: https://github.com/WebAssembly/gc/pull/34 361 | /// which includes an int31ref tagged type which can be freely mixed with 362 | /// references. 363 | /// 364 | /// And I don't expect high-performance float math to be a big use case 365 | /// for this plugin model, so we'll live with the boxing. 366 | union { 367 | size_t mRaw; 368 | GCThing* mPtr; 369 | }; 370 | 371 | static size_t tagPointer(GCThing *aPtr) { 372 | return reinterpret_cast(aPtr); 373 | } 374 | 375 | static bool isValidInt31(int32_t aInt) { 376 | return ((aInt << 1) >> 1) == aInt; 377 | } 378 | 379 | static size_t tagInt31(int32_t aInt) { 380 | return static_cast((aInt << 1) | 1); 381 | } 382 | 383 | static size_t tagOrBoxInt32(int32_t aInt) { 384 | if (isValidInt31(aInt)) { 385 | return tagInt31(aInt); 386 | } else { 387 | return reinterpret_cast(new Box(aInt)); 388 | } 389 | } 390 | 391 | static size_t tagOrBoxDouble(double aDouble) { 392 | return reinterpret_cast(new Box(aDouble)); 393 | } 394 | 395 | static int32_t derefInt31(size_t aRaw) { 396 | return static_cast(reinterpret_cast(aRaw)) >> 1; 397 | } 398 | 399 | GCThing* asPointer() const { 400 | return mPtr; 401 | } 402 | #endif 403 | 404 | #ifdef VAL_SHIFTED_NAN_BOX 405 | // JavaScriptCore-style NaN boxing. 406 | // Value is shifted with an addition to turn NaNs into 0s. 407 | union { 408 | int64_t mRaw; 409 | }; 410 | 411 | static const int64_t tagShift = 0x0010'0000'0000'0000; 412 | static const int64_t tagMask = 0xffff'0000'0000'0000; 413 | static const int64_t tagBitShift = 48; 414 | static const int64_t tagBitsPointer = 0; 415 | static const int64_t tagBitsInt32 = -1; 416 | 417 | GCThing* asPointer() const { 418 | return reinterpret_cast(mRaw); 419 | } 420 | 421 | static int64_t tagPointer(GCThing *aPtr) { 422 | return static_cast(reinterpret_cast(aPtr)); 423 | } 424 | 425 | static int64_t tagOrBoxInt32(int32_t aInt) { 426 | return static_cast( 427 | static_cast(static_cast(aInt)) | 428 | (static_cast(tagBitsInt32) << tagBitShift) 429 | ); 430 | } 431 | 432 | static int64_t tagOrBoxDouble(double aDouble) { 433 | if (aDouble == -INFINITY) { 434 | // turns into 0 with our bitshift 435 | return tagPointer(new Box(aDouble)); 436 | } else { 437 | return *reinterpret_cast(&aDouble) + tagShift; 438 | } 439 | } 440 | #endif 441 | 442 | public: 443 | 444 | #ifdef DEBUG 445 | Val(GCThing* aRef) { 446 | if (!aRef){ 447 | // don't use null pointers! 448 | std::abort(); 449 | } 450 | mRaw = tagPointer(aRef); 451 | } 452 | #else 453 | Val(GCThing* aRef) : mRaw(tagPointer(aRef)) {} 454 | #endif 455 | 456 | Val(const GCThing* aRef) : Val(const_cast(aRef)) {} // don't use null pointers! 457 | Val(double aDouble) : mRaw(tagOrBoxDouble(aDouble)) {} 458 | Val(int32_t aInt) : mRaw(tagOrBoxInt32(aInt)) {}; 459 | Val(bool aBool) : Val(aBool ? engine().trueRef() : engine().falseRef()) {} 460 | Val(Null aNull) : Val(engine().nullRef()) {} 461 | Val(Undefined aUndefined) : Val(engine().undefinedRef()) {} 462 | Val(Deleted aDeleted) : Val(engine().deletedRef()) {} 463 | 464 | Val(const Val &aVal) : mRaw(aVal.raw()) {} 465 | Val(Val &aVal) : mRaw(aVal.raw()) {} 466 | Val() : Val(Undefined()) {} 467 | 468 | Val &operator=(const Val &aVal) { 469 | mRaw = aVal.mRaw; 470 | return *this; 471 | } 472 | 473 | #ifdef VAL_TAGGED_POINTER 474 | size_t raw() const { 475 | return mRaw; 476 | } 477 | 478 | bool tag() const { 479 | return mRaw & 1; 480 | } 481 | 482 | bool isInt31() const { 483 | return tag(); 484 | } 485 | 486 | bool isGCThing() const { 487 | return !tag(); 488 | } 489 | 490 | bool isDouble() const { 491 | return false; // not supported in pointer tag 492 | } 493 | 494 | bool isInt32() const { 495 | return false; // not supported in pointer tag 496 | } 497 | #endif 498 | 499 | #ifdef VAL_SHIFTED_NAN_BOX 500 | int64_t raw() const { 501 | return mRaw; 502 | } 503 | 504 | int64_t tag() const { 505 | return mRaw >> tagBitShift; 506 | } 507 | 508 | bool isGCThing() const { 509 | return tag() == tagBitsPointer; 510 | } 511 | 512 | bool isDouble() const { 513 | int64_t tag_ = tag(); 514 | return tag_ != tagBitsPointer && tag_ != tagBitsInt32; 515 | } 516 | 517 | bool isInt31() const { 518 | return false; // not supported 519 | } 520 | 521 | bool isInt32() const { 522 | return tag() == tagBitsInt32; 523 | } 524 | 525 | #endif 526 | 527 | bool isTypeOf(TypeOf aExpected) const { 528 | return isGCThing() && (asPointer()->typeOf() == aExpected); 529 | } 530 | 531 | bool isBool() const { 532 | return (asPointer() == engine().trueRef()) || (asPointer() == engine().falseRef()); 533 | } 534 | 535 | bool isUndefined() const { 536 | return (asPointer() == engine().undefinedRef()); 537 | } 538 | 539 | bool isNull() const { 540 | return (asPointer() == engine().nullRef()); 541 | } 542 | 543 | bool isInternal() const { 544 | return isGCThing() && isTypeOf(typeOfInternal); 545 | } 546 | 547 | bool isJSThing() const { 548 | return isGCThing() && !isTypeOf(typeOfInternal); 549 | } 550 | 551 | bool isObject() const { 552 | return isTypeOf(typeOfObject); 553 | } 554 | 555 | bool isString() const { 556 | return isTypeOf(typeOfString); 557 | } 558 | 559 | bool isSymbol() const { 560 | return isTypeOf(typeOfSymbol); 561 | } 562 | 563 | bool isFunction() const { 564 | return isTypeOf(typeOfFunction); 565 | } 566 | 567 | #ifdef VAL_TAGGED_POINTER 568 | double asDouble() const { 569 | // cannot happen 570 | return NAN; 571 | } 572 | #endif 573 | #ifdef VAL_SHIFTED_NAN_BOX 574 | double asDouble() const { 575 | int64_t shifted = mRaw - tagShift; 576 | return *reinterpret_cast(&shifted); 577 | } 578 | #endif 579 | 580 | #ifdef VAL_TAGGED_POINTER 581 | int32_t asInt31() const { 582 | return derefInt31(mRaw); 583 | } 584 | 585 | int32_t asInt32() const { 586 | // not supported 587 | return false; 588 | } 589 | #endif 590 | 591 | #ifdef VAL_SHIFTED_NAN_BOX 592 | int32_t asInt31() const { 593 | return 0; // can never happen 594 | } 595 | 596 | int32_t asInt32() const { 597 | return static_cast(static_cast(mRaw)); 598 | } 599 | #endif 600 | 601 | bool asBool() const { 602 | return reinterpret_cast*>(asPointer())->val(); 603 | } 604 | 605 | Null asNull() const { 606 | return Null(); 607 | } 608 | 609 | Undefined asUndefined() const { 610 | return Undefined(); 611 | } 612 | 613 | Deleted asDeleted() const { 614 | return Deleted(); 615 | } 616 | 617 | // Un-checked conversions returning a raw thingy 618 | 619 | GCThing& asGCThing() const { 620 | return *asPointer(); 621 | } 622 | 623 | Internal& asInternal() const { 624 | return *static_cast(asPointer()); 625 | } 626 | 627 | JSThing& asJSThing() const { 628 | return *static_cast(asPointer()); 629 | } 630 | 631 | Object& asObject() const { 632 | // fixme it doesn't understand the type rels 633 | // clean up these methods later 634 | return *reinterpret_cast(asPointer()); 635 | } 636 | 637 | String& asString() const { 638 | return *reinterpret_cast(asPointer()); 639 | } 640 | 641 | Symbol& asSymbol() const { 642 | return *reinterpret_cast(asPointer()); 643 | } 644 | 645 | Function& asFunction() const { 646 | return *reinterpret_cast(asPointer()); 647 | } 648 | 649 | // Unchecked conversions returning a *T, castable to Retained 650 | template 651 | T& as() const { 652 | return *static_cast(asPointer()); 653 | } 654 | 655 | // Checked conversions 656 | bool toBool() const; 657 | int32_t toInt32() const; 658 | double toDouble() const; 659 | Retained toString() const; 660 | 661 | bool operator==(const Val& rhs) const; 662 | 663 | string dump() const; 664 | 665 | void markForGC() const { 666 | if (isGCThing()) { 667 | asGCThing().markForGC(); 668 | } 669 | } 670 | 671 | Local call(Local aThis, RawArgList aArgs) const; 672 | 673 | }; 674 | 675 | class SmartVal { 676 | protected: 677 | // The smart pointer contains a single pointer word to the value we want 678 | // to work with. 679 | Val* mRecord; 680 | 681 | SmartVal(Val *aRecord) 682 | : mRecord(aRecord) 683 | { 684 | } 685 | 686 | public: 687 | // Deref ops 688 | Val& operator*() const { 689 | return *mRecord; 690 | } 691 | 692 | const Val* operator->() const { 693 | return mRecord; 694 | } 695 | }; 696 | 697 | /// 698 | /// GC-safe wrapper cell for a Val allocated on the stack. 699 | /// Do NOT store a Local in the heap or return it as a value; it will explode! 700 | /// 701 | /// The engine keeps a second stack with the actual Val cells, 702 | /// so while we're in the scope they stay alive during GC. When we go out 703 | /// of scope we pop back off the stack in correct order. 704 | /// 705 | class Local : public SmartVal { 706 | public: 707 | 708 | /// 709 | /// The constructor pushes a value onto the engine-managed stack. 710 | /// It will be cleaned up when the current Scope goes out of ... scope. 711 | /// 712 | Local(Val aVal) 713 | : SmartVal(engine().pushLocal(aVal)) 714 | { 715 | // 716 | } 717 | 718 | Local(Val* aPtr) 719 | : SmartVal(aPtr) 720 | { 721 | // 722 | } 723 | 724 | /// C++ doesn't want to do multiple implicit conversions, so let's add 725 | /// some explicit constructors. 726 | Local(bool aVal) : Local(Val(aVal)) {} 727 | Local(int32_t aVal) : Local(Val(aVal)) {} 728 | Local(double aVal) : Local(Val(aVal)) {} 729 | Local(Undefined aVal) : Local(Val(aVal)) {} 730 | Local(Null aVal) : Local(Val(aVal)) {} 731 | Local(GCThing* aVal) : Local(Val(aVal)) {} 732 | Local(const GCThing* aVal) : Local(Val(aVal)) {} 733 | 734 | // don't need these 735 | Local(const Local& aLocal) 736 | : SmartVal(aLocal.mRecord) 737 | { 738 | // override the copy constructor .. 739 | } 740 | 741 | Local(Local&& aMoved) 742 | : SmartVal(aMoved.mRecord) 743 | { 744 | // overide move constructor 745 | } 746 | 747 | Local() 748 | : Local(Undefined()) 749 | { 750 | // 751 | } 752 | 753 | /// 754 | /// Override the = operator for natural use as a binding. 755 | /// 756 | Local& operator=(const Local &aLocal) { 757 | *mRecord = *aLocal; 758 | return *this; 759 | } 760 | 761 | }; 762 | 763 | Local operator+(Local lhs, Local rhs); 764 | Local operator+(double lhs, Local rhs); 765 | Local operator+(Local lhs, double rhs); 766 | double operator-(Local lhs, Local rhs); 767 | double operator-(double lhs, Local rhs); 768 | double operator-(Local lhs, double rhs); 769 | double operator*(Local lhs, Local rhs); 770 | double operator*(double lhs, Local rhs); 771 | double operator*(Local lhs, double rhs); 772 | double operator/(Local lhs, Local rhs); 773 | double operator/(double lhs, Local rhs); 774 | double operator/(Local lhs, double rhs); 775 | bool operator==(Local lhs, Local rhs); 776 | bool operator<(Local lhs, Local rhs); 777 | bool operator<(double lhs, Local rhs); 778 | bool operator<(Local lhs, double rhs); 779 | bool operator>(Local lhs, Local rhs); 780 | bool operator>(double lhs, Local rhs); 781 | bool operator>(Local lhs, double rhs); 782 | 783 | Local& operator++(Local& aLocal); 784 | Local& operator--(Local& aLocal); 785 | Local operator++(Local& aLocal, int); 786 | Local operator--(Local& aLocal, int); 787 | 788 | Local& operator+=(Local& lhs, const Local& rhs); 789 | Local& operator-=(Local& lhs, const Local& rhs); 790 | Local& operator*=(Local& lhs, const Local& rhs); 791 | Local& operator/=(Local& lhs, const Local& rhs); 792 | // ... etc ... 793 | 794 | 795 | template 796 | class Retained { 797 | Local mLocal; 798 | 799 | public: 800 | 801 | Retained() : mLocal() {} 802 | Retained(T* aPtr) : mLocal(aPtr) {} 803 | Retained(const T* aPtr) : mLocal(aPtr) {} 804 | 805 | T* asPointer() const { 806 | if (mLocal->isGCThing()) { 807 | // fixme this should be static_cast but it won't let me 808 | // since it doesn't think they're related by inheritence 809 | return reinterpret_cast(&(mLocal->asGCThing())); 810 | } else { 811 | // todo: handle 812 | std::abort(); 813 | } 814 | } 815 | 816 | // Deref ops 817 | 818 | T& operator*() const { 819 | return *asPointer(); 820 | } 821 | 822 | T* operator->() const { 823 | return asPointer(); 824 | } 825 | 826 | // Conversion ops 827 | 828 | operator T*() const { 829 | return asPointer(); 830 | } 831 | 832 | operator const T*() const { 833 | return asPointer(); 834 | } 835 | 836 | operator Local() const { 837 | return mLocal; 838 | } 839 | 840 | /// 841 | /// Override the = operator for natural use as a binding. 842 | /// 843 | Retained& operator=(const Retained& aRet) { 844 | mLocal = aRet.mLocal; 845 | return *this; 846 | } 847 | }; 848 | 849 | template 850 | Retained retain(Args&&... aArgs) { 851 | // Initialize a shiny new object 852 | return Retained(new T(std::forward(aArgs)...)); 853 | } 854 | 855 | /// 856 | /// Any function that does not return a JS value should declare a 857 | /// Scope instance before allocating any local variables of the 858 | /// Local or Retained types. 859 | /// 860 | /// When the Scope goes out of scope (usually at end of function), 861 | /// it will remove all later-allocated Local variables from the 862 | /// stack, allowing object references to be garbage collected. 863 | /// 864 | /// If your function returns a value, use ScopeRetVal or 865 | /// ScopeRet instead. 866 | /// 867 | class Scope { 868 | Val* mFirstRecord; 869 | 870 | public: 871 | Scope() 872 | : mFirstRecord(engine().stackTop()) 873 | { 874 | // 875 | } 876 | 877 | ~Scope() { 878 | // Return the stack to its initial state. 879 | engine().popLocal(mFirstRecord); 880 | } 881 | }; 882 | 883 | /// 884 | /// Functions returning a JS value that could be a garbage-collected 885 | /// reference should return the Local or Retained types, and allocate 886 | /// a ScopeRetVal immediately at the start before allocating any 887 | /// Local or Retained variables. 888 | /// 889 | /// This allocates space on the _parent_ Scope for a return value, 890 | /// which should be intermediated through the escape() function. 891 | /// 892 | class ScopeRetVal { 893 | Local mRetVal; 894 | Scope mScope; 895 | 896 | public: 897 | ScopeRetVal() 898 | : mRetVal(), // must be allocated before mScope! 899 | mScope() // the new scope, which won't roll back mRetVal 900 | { 901 | // We saved space for a return value on the parent scope... 902 | } 903 | 904 | Local escape(Local aVal) { 905 | *mRetVal = *aVal; 906 | #if DEBUG 907 | std::cerr << "escaping (" << aVal->dump() << ")\n"; 908 | #endif 909 | return Local(&*mRetVal); 910 | } 911 | }; 912 | 913 | /// 914 | template 915 | class ScopeRet { 916 | Retained mRetVal; 917 | Scope mScope; 918 | 919 | public: 920 | ScopeRet() 921 | : mRetVal(), // must be allocated before mScope! 922 | mScope() 923 | { 924 | // We saved space for a return value on the parent scope... 925 | } 926 | 927 | Retained escape(Retained aVal) { 928 | #if DEBUG 929 | std::cerr << "escaping (" << aVal->dump() << ")\n"; 930 | #endif 931 | mRetVal = aVal; 932 | return Retained(&*mRetVal); 933 | } 934 | }; 935 | 936 | class ArgList { 937 | Val* mStackTop; 938 | Val* mBegin; 939 | size_t mSize; 940 | 941 | friend class Function; 942 | 943 | // Never create an ArgList yourselve, use an initializer list (RawArgList) 944 | ArgList(Function& func, RawArgList args); 945 | 946 | public: 947 | 948 | ~ArgList() { 949 | // Like a Scope, we pop back to the beginning. 950 | engine().popLocal(mStackTop); 951 | } 952 | 953 | /// 954 | /// Return the original argument list length 955 | /// 956 | size_t size() const { 957 | return mSize; 958 | } 959 | 960 | /// 961 | /// Return a Val* for the given argument index. 962 | /// Valid up to the expected arity of the function. 963 | /// 964 | Val* operator[](size_t index) const { 965 | return &mBegin[index]; 966 | } 967 | }; 968 | 969 | class PropIndex : public JSThing { 970 | public: 971 | PropIndex() 972 | : JSThing() {} 973 | 974 | ~PropIndex() override; 975 | }; 976 | 977 | class String : public PropIndex { 978 | string data; 979 | 980 | public: 981 | String(string const &aStr) 982 | : PropIndex(), 983 | data(aStr) 984 | { 985 | // 986 | } 987 | 988 | ~String() override; 989 | 990 | TypeOf typeOf() const override; 991 | 992 | string str() const { 993 | return data; 994 | } 995 | 996 | string dump() override; 997 | 998 | size_t length() const { 999 | return data.size(); 1000 | } 1001 | 1002 | operator string() const { 1003 | return data; 1004 | } 1005 | 1006 | Retained toString() const override { 1007 | ScopeRet scope; 1008 | #ifdef DEBUG 1009 | std::cerr << "toString for string: " << data << "\n"; 1010 | #endif 1011 | return scope.escape(this); 1012 | } 1013 | 1014 | bool operator==(const String &rhs) const { 1015 | return data == rhs.data; 1016 | } 1017 | 1018 | Retained operator+(const String &rhs) const { 1019 | ScopeRet scope; 1020 | return scope.escape(new String(data + rhs.data)); 1021 | } 1022 | }; 1023 | 1024 | class Symbol : public PropIndex { 1025 | string name; 1026 | 1027 | public: 1028 | Symbol(string const &aName) 1029 | : PropIndex(), 1030 | name(aName) 1031 | { 1032 | // 1033 | } 1034 | 1035 | ~Symbol() override; 1036 | 1037 | TypeOf typeOf() const override; 1038 | 1039 | string dump() override; 1040 | 1041 | Retained toString() const override { 1042 | ScopeRet scope; 1043 | return scope.escape(new String("Symbol(" + getName() + ")")); 1044 | } 1045 | 1046 | const string &getName() const { 1047 | return name; 1048 | } 1049 | }; 1050 | 1051 | /// 1052 | /// Represents a regular JavaScript object, with properties and 1053 | /// a prototype chain. String and Symbol are subclasses. 1054 | /// 1055 | /// Todo: throw exceptions on bad prop lookups 1056 | /// Todo: implement getters, setters, enumeration, etc 1057 | /// 1058 | class Object : public JSThing { 1059 | Object *mPrototype; 1060 | unordered_map mProps; 1061 | 1062 | public: 1063 | Object() 1064 | : JSThing(), 1065 | mPrototype(nullptr) 1066 | { 1067 | // 1068 | } 1069 | 1070 | Object(Object& aPrototype) 1071 | : JSThing(), 1072 | mPrototype(&aPrototype) 1073 | { 1074 | // Note this doesn't call the constructor, 1075 | // which would be done by outside code. 1076 | } 1077 | 1078 | ~Object() override; 1079 | 1080 | void markRefsForGC() override; 1081 | string dump() override; 1082 | TypeOf typeOf() const override; 1083 | 1084 | Retained toString() const override { 1085 | ScopeRet scope; 1086 | // todo get the constructor name 1087 | return scope.escape(new String("[object Object]")); 1088 | } 1089 | 1090 | Local getProp(Local name); 1091 | void setProp(Local name, Local val); 1092 | }; 1093 | 1094 | /// 1095 | /// Represents a closure-captured JS variable, allocated on the heap in 1096 | /// this wrapper object. 1097 | /// 1098 | class Cell : public Internal { 1099 | Val mVal; 1100 | 1101 | public: 1102 | Cell() 1103 | : mVal(Undefined()) 1104 | { 1105 | // 1106 | } 1107 | 1108 | Cell(Val aVal) 1109 | : mVal(aVal) 1110 | { 1111 | // 1112 | } 1113 | 1114 | ~Cell() override; 1115 | 1116 | Val* binding() { 1117 | return &mVal; 1118 | } 1119 | 1120 | Val& val() { 1121 | return mVal; 1122 | } 1123 | 1124 | void markRefsForGC() override; 1125 | string dump() override; 1126 | }; 1127 | 1128 | /// 1129 | /// Represents a runtime function object. 1130 | /// References the 1131 | /// 1132 | class Function : public Object { 1133 | std::string mName; 1134 | size_t mArity; 1135 | std::vector mCaptures; 1136 | FunctionBody mBody; 1137 | 1138 | public: 1139 | // For function with no captures 1140 | Function( 1141 | std::string aName, 1142 | size_t aArity, 1143 | FunctionBody aBody) 1144 | : Object(), // todo: have a function prototype object! 1145 | mName(aName), 1146 | mArity(aArity), 1147 | mCaptures(), 1148 | mBody(aBody) 1149 | { 1150 | // 1151 | } 1152 | 1153 | // For function with captures 1154 | Function( 1155 | std::string aName, 1156 | size_t aArity, 1157 | RawCaptureList aCaptures, 1158 | FunctionBody aBody) 1159 | : Object(), // todo: have a function prototype object! 1160 | mName(aName), 1161 | mArity(aArity), 1162 | mCaptures(aCaptures), 1163 | mBody(aBody) 1164 | { 1165 | // 1166 | } 1167 | 1168 | ~Function() override; 1169 | 1170 | TypeOf typeOf() const override; 1171 | 1172 | std::string name() const { 1173 | return mName; 1174 | } 1175 | 1176 | /// 1177 | /// Number of defined arguments 1178 | /// 1179 | size_t arity() const { 1180 | return mArity; 1181 | } 1182 | 1183 | Local call(Local aThis, RawArgList aArgs); 1184 | 1185 | /// 1186 | /// Return one of the captured variable pointers. 1187 | /// 1188 | Cell& capture(size_t aIndex) { 1189 | return *mCaptures[aIndex]; 1190 | } 1191 | 1192 | void markRefsForGC() override; 1193 | string dump() override; 1194 | 1195 | Retained toString() const override { 1196 | ScopeRet scope; 1197 | return scope.escape(new String("[Function: " + name() + "]")); 1198 | } 1199 | 1200 | }; 1201 | 1202 | } 1203 | 1204 | #endif 1205 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | aotjs -- ahead-of-time compilation of JavaScript to LLVM 2 | 3 | # Intro 4 | 5 | At this stage this is a thought experiment, with a very early proof of concept 6 | of a garbage-collecting JS-like runtime written in C++, which works when 7 | compiled to WebAssembly. 8 | 9 | ## Intent 10 | 11 | Safe, small, moderate-performance JS runtime for application extensions. 12 | 13 | ## Goals 14 | 15 | * static compilation of JavaScript to LLVM bitcode 16 | * native and WebAssembly-based execution 17 | * memory safety & sandboxing 18 | * JS code cannot affect things not exposed to it 19 | * small code size 20 | * better performance than an interpreter 21 | * small, but standard-ish JS language implementation 22 | * target ES6 or so for starters 23 | * ability to call to the JS runtime from host 24 | * ability to add JS objects from host 25 | 26 | ## Non-goals 27 | 28 | * not expecting optimized code for polymorphic stuff 29 | * no support for loading new code at runtime 30 | * no support for runtime eval() or new Function("source") 31 | * no in-process resource limits for memory or execution time 32 | * WebAssembly sandbox can apply a hard memory limit 33 | * browser will eventually halt super-long loops 34 | 35 | ## Things to compare with 36 | 37 | Big engines 38 | * SpiderMonkey's interpreter mode 39 | * ChakraCore's interpreter mode 40 | * V8's interpreter mode 41 | 42 | Smaller interpreters 43 | * duktape 44 | * jerryscript 45 | 46 | Smaller compilers 47 | * found a random old project https://github.com/eddid/jslang 48 | 49 | # How it might work 50 | 51 | * direct from LLVM to wasm 52 | * hello.js -> hello.o -> hello.wasm 53 | * emit C++ source code 54 | * hello.js -> hello.cpp -> hello.o -> hello.wasm 55 | 56 | 57 | # Language considerations 58 | 59 | ## Types 60 | 61 | JS values may be of these types: 62 | * number (double) 63 | * number (int32_t optimized subset of doubles) 64 | * string, symbol, object (pointer to obj struct) 65 | * null, undefined (special values) 66 | * boolean (special values) 67 | 68 | Many VMs do crazy things like using NaN bits in doubles to signal that the 69 | 64-bit value actually contains an int32_t or pointer instead of a double. 70 | While this appears to work in WebAssembly -- as long as you don't transfer 71 | double values outside to the host environment -- I'm going with tagged 72 | pointers instead to be more similar to planned native Wasm GC in the future. 73 | 74 | 75 | ## Exceptions 76 | 77 | WebAssembly participates in the host JavaScript environment's stack unwinding 78 | when exceptions are thrown, but provides no way to catch an exception or get 79 | information about a stack trace. Ouch! 80 | 81 | This means we either have to avoid doing exception handling, or every function 82 | call has to be followed by a check for exception state. emscripten does this 83 | optionally for C++ code but it's known to be slow, so it's generally recommended 84 | to disable exceptions (!). 85 | 86 | Hypothetically you could also build both fast-path and slow-path versions of 87 | all functions, so the slow-path gets called if inside a `try` block. This would 88 | increase binary size, though, and is probably not worth it for my use case 89 | where safety and download size are more likely important than runtime speed. 90 | 91 | 92 | ## Garbage collection 93 | 94 | Because JS is garbage-collected, we need to use a GC engine of some kind 95 | that can link into C/C++. 96 | 97 | The traditional Boehm libgc seems like it won't work here since it can't 98 | access the wasm/js stack directly. 99 | 100 | I'm trying my own naive mark-and-sweep GC based on maintaining my own 101 | stack of scope objects. 102 | 103 | Each scope contains a pointer to its parent scope (if it has captures), 104 | a vector of JS values that were allocated for locals. 105 | 106 | The actual 'local variables' used in the compiled functions will be pointers 107 | (C++ references in the current PoC) allowing them to be modified in-place in 108 | the stack frames by closures, which should keep the correct behavior. 109 | 110 | The marking phase starts with all our roots (global object, the scope stack, 111 | and the call frame stack) so any live objects get marked. Then a sweep goes 112 | through the set of all objects (maintained manually for now, eventually should 113 | be integrated with the allocator) and deletes any non-reachable objects. 114 | 115 | Currently in the PoC, GC is either run manually, or if FORCE_GC is defined 116 | then on every allocation to force debugging behavior. 117 | 118 | The actual objects may do cleanup of their non-GC'd resources from a virtual 119 | destructor, which gets called by the delete operation in the GC sweep. 120 | 121 | ## Closures 122 | 123 | Oh man this is more complex than I thought if don't want to leak memory. 124 | 125 | Ok there's several classes of variable bindings in JS: 126 | 127 | * uncaptured local args/vars 128 | * allocate in the frame, kept alive by stack push/pop 129 | * names captured from an outside scope 130 | * get a pointer to the actual value from the scope 131 | * names that are captured by any lower scope 132 | * allocate in a scope, which is kept alive by the function definition 133 | * referencing higher scopes? 134 | * copy the used pointers into the local array 135 | * scope chain to retain GC 136 | * what about... capturing an argument that's modified? 137 | * could rewrite that into copying the arg into the capture state. 138 | * have the _Scope_ carry vals 139 | * have the _Capture_ carry pointers, which can reach arbitrarily high 140 | * the Capture references the current scope. Or a list of all scopes it uses? 141 | 142 | Changed this to use a parallel stack of Val cells, moderated by smart pointers 143 | in Local/Retained vars and managed by Scope and ScopeRet instances. This is 144 | inspired by V8's Local/Handle and EscapableHandleScope stuff. 145 | 146 | Each function has at top-level scope a Scope object, which saves the stack 147 | position. Then any non-captured locals are allocated through Local instances, 148 | which advance the stack and hold a pointer to the cell. At the end of the func 149 | the Scope goes out of scope and resets the stack pointer, invalidating all the 150 | Local instances used in the meantime. 151 | 152 | For return values, a ScopeRetVal or ScopeRet is used instead, which allocates 153 | a cell on the stack before its scope opens, which is still readable on the 154 | calling function's scope. The function then returns a RetVal which points to 155 | that value. Currently must call scope.escape(foo) but may be able to elide that. 156 | 157 | Captured variables are individually allocated in GC'd Cells on the heap, 158 | themselves kept alive on the locals stack until they're passed as a list of 159 | Cell references into the Function object at creation time. This eats up a few 160 | extra words per captured variable, but usually there aren't that many so 161 | probably fine. And it means there's no worry about dependencies if multiple 162 | closures capture overlapping subsets of variables -- each captured var has 163 | its own GC-managed lifetime. 164 | 165 | 166 | # NaN-boxing 167 | 168 | (Note: decided against NaN-boxing and am going with tagged pointers and 169 | boxed floating-point instead.) 170 | 171 | NaN boxing makes use of the NaN space in double-precision floating point 172 | numbers, so variable-type values can be stored in a single 64-bit word. 173 | If the top 13 bits are all set, we have a "signalling NaN" that can't be 174 | produced by natural operations, so we can use the remaining 51 bits for 175 | a type tag and a payload. 176 | 177 | For 32-bit targets like WebAssembly this leaves you plenty of space for 178 | a 32-bit pointer! However on 64-bit native targets it's more worrisome. 179 | 180 | SpiderMonkey uses NaN boxing with 4 bits of tag space and up to 47 bits of 181 | pointer addressing, which is enough for current x86-64 code in user space, but 182 | can fail on AArch64. 183 | 184 | JSC uses a different format, with a bias added to doubles to shift NaNs 185 | around so that pointers always have 0x0000 in the top 16 bits (giving you 186 | 48 bits of addressing, still potentially problematic), 0xffff for 32-bit 187 | ints, and 0x0001..0xfffe for the shifted doubles. 188 | 189 | The biggest differences between the two are that SpiderMonkey's system is 190 | double-biased (you don't have to manipulate a double to get it in/out) and 191 | JSC's is object-biased (you don't have to manipulate a pointer to get it 192 | in/out). Since most JS code uses more objects than doubles, that may be a win. 193 | On 32-bit it makes less difference, but is still maybe nicer for letting 194 | you do a single 32-to-64 extend opcode instead of extending and then ORing 195 | the tag. It does mean that the -Infinity value has to be stored as another 196 | NaN value. 197 | 198 | * [SpiderMonkey thinking of changing their boxing format](https://bugzilla.mozilla.org/show_bug.cgi?id=1401624) 199 | * [JSC's format](https://github.com/adobe/webkit/blob/master/Source/JavaScriptCore/runtime/JSValue.h#L307) 200 | 201 | I tried using a format similar to SpiderMonkey's but using only 3 bits 202 | for the tag (8 types), so top 16 bits is (signalling nan + tag) and bottom 203 | 48 bits is the pointer payload. 204 | 205 | One downside is that the constants for the tag checks are large, and get 206 | encoded at every check site. Alternately could right-shift by 48 and compare 207 | against smaller constants, but that's probably an extra clock cycle vs extra 208 | bytes in the binary. 209 | 210 | # Tagged pointers 211 | 212 | Instead of NaN boxing, I'm going with pointer-size words and a low-bit tag to 213 | indicate 31-bit integer values. This is more similar to what the native Wasm 214 | garbage collection proposal provides, and will make it easier to move over 215 | in the future. 216 | 217 | * Values are native pointer size (32 bit in wasm32, 64 bit on native testing) 218 | * If the lowest bit is 0, it's interpreted as a pointer. 219 | * If the lowest bit is 1, it's interpreted as an int32 and shifted right by one 220 | 221 | This means most common integers can be represented directly in the value, but 222 | larger ints and double-precision floats won't fit -- they must be boxed in a 223 | heap type, which is relatively inefficient. 224 | 225 | The other JS types -- undefined, null, boolean, etc -- are represented as 226 | objects, with well-known singleton values that can be bit-compared without 227 | allocating extra instances. 228 | 229 | In the Wasm GC spec, the tagged ints would be the 'int31ref' type, while others 230 | are references to objects. 231 | 232 | Downside is that floating-point math may be sslloowweerr due to need to allocate 233 | and destroy lots of tiny objects. Upside is that this is not expected to be a 234 | primary use case in terms of needing super-high performance. 235 | 236 | # Naive implementation 237 | 238 | Let's assume the runtime is implemented in C++. Naive implementation for totally 239 | polymorphic code looks something like: 240 | * [aotjs_runtime.h](aotjs_runtime.h) 241 | * [aotjs_runtime.cpp](aotjs_runtime.cpp) 242 | 243 | Example hand-compiled programs using it: 244 | * [samples/gc.cpp](samples/gc.cpp) 245 | * [samples/closure.js](samples/closure.js) -> [samples/closure.cpp](samples/closure.cpp) 246 | * [samples/retval.js](samples/retval.js) -> [samples/retval.cpp](samples/retval.cpp) 247 | * [samples/args.js](samples/args.js) -> [samples/args.cpp](samples/args.cpp) 248 | 249 | Next steps (runtime): 250 | * use PropIndex* instead of Val as property keys 251 | * make sure hash map behaves right in properties 252 | 253 | Next steps (features): 254 | * provide string <-> number conversions 255 | * exercise the Val <-> int and double conversions 256 | * add operator overloads on Val for arithmetic 257 | * add arrays and use operator[] for access of both arrays and obj props 258 | * implement 'console' with at least basic 'log' and 'error' methods 259 | 260 | Next steps (exceptions): 261 | * signal exceptions with an explicit return value (or let emscripten do it?) 262 | * provide a way to catch exceptions 263 | * throw exceptions on various conditions 264 | 265 | Next steps (compiler): 266 | * pick a JavaScript-in-JavaScript parser 267 | * write a JS/node app that takes a parsed AST and translates to C++ 268 | * start building tiny test programs and see what breaks or needs adding 269 | -------------------------------------------------------------------------------- /samples/args.cpp: -------------------------------------------------------------------------------- 1 | #include "../aotjs_runtime.h" 2 | 3 | #include 4 | 5 | using namespace AotJS; 6 | 7 | int main() { 8 | { 9 | Scope scope; 10 | Local report; 11 | 12 | // Register the function! 13 | report = new Function( 14 | "report", 15 | 3, // argument count 16 | [] (Function& func, Local this_, ArgList args) -> Local { 17 | ScopeRetVal scope; 18 | 19 | // this might work in c++17 mode with suitable iteators added 20 | //Local [a, b, c] = args; 21 | 22 | Local a(args[0]); 23 | Local b(args[1]); 24 | Local c(args[2]); 25 | 26 | std::cout << a->dump() << ", " << b->dump() << ", " << c->dump() << "\n"; 27 | 28 | return scope.escape(Undefined()); 29 | } 30 | ); 31 | 32 | report->call(Null(), {new String("a"), new String("b"), new String("c")}); 33 | report->call(Null(), {new String("a"), new String("b")}); 34 | report->call(Null(), {1, 2, 3}); 35 | report->call(Null(), {new Object(), 3.5, Null()}); 36 | } 37 | 38 | std::cout << "before gc\n"; 39 | std::cout << engine().dump(); 40 | std::cout << "\n\n"; 41 | 42 | engine().gc(); 43 | 44 | std::cout << "after gc\n"; 45 | std::cout << engine().dump(); 46 | std::cout << "\n"; 47 | 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /samples/args.js: -------------------------------------------------------------------------------- 1 | function report(a, b, c) { 2 | console.log(a, b, c); 3 | } 4 | 5 | report('a', 'b', 'c'); 6 | report('a', 'b'); 7 | report(1, 2, 3); 8 | report({}, 3.5, null); 9 | -------------------------------------------------------------------------------- /samples/closure.cpp: -------------------------------------------------------------------------------- 1 | #include "../aotjs_runtime.h" 2 | 3 | #include 4 | 5 | using namespace AotJS; 6 | 7 | int main() { 8 | Scope scope; 9 | Local work; 10 | 11 | // Register the function! 12 | work = new Function( 13 | "work", 14 | 1, // argument count 15 | // no closures 16 | // Use a lambda for source prettiness. 17 | // Must be no C++ captures so we can turn it into a raw function pointer! 18 | [] (Function& func_, Local this_, ArgList args) -> Local { 19 | ScopeRetVal scope; 20 | 21 | // Variable hoisting! 22 | // Conceptually we allocate all the locals at the start of the scope. 23 | // They'll all be filled with the JS `undefined` value initially. 24 | // 25 | // We allocate most vars in Locals, which are wrapped pointers into a 26 | // stack of Vals that are kept alive while. In the case of closure 27 | // captures, they're allocated in a GC'd HeapCell object on the heap, 28 | // which itself is kept on the same stack. 29 | // 30 | // JS variable bindings are all pointers, either wrapped with a Local 31 | // or Retained smart pointer to the stack or straight as a Binding 32 | // pointer into one of those. 33 | Retained _b; 34 | 35 | Local a; 36 | Local b(_b->binding()); 37 | Local func; 38 | 39 | // function declarations/definitions happen at the top of the scope too. 40 | // This is where we capture the `b` variable's location, knowing 41 | // its actual value can change. 42 | func = new Function( 43 | "func", // name 44 | 0, // arg arity 45 | {_b}, // captures 46 | // implementation 47 | [] (Function& func, Local this_, ArgList args) -> Local { 48 | // Allocate local scope for the return value. 49 | ScopeRetVal scope; 50 | 51 | // Note we cannot use C++'s captures here -- they're not on GC heap and 52 | // would turn our call reference into a fat pointer, which we don't want. 53 | // 54 | // The capture binding gives you a reference into one of the linked 55 | // heap Cells, which are retained via a list in the Function object. 56 | Local b(func.capture(0).binding()); 57 | 58 | // replace the variable in the parent scope 59 | b = new String("b plus one"); 60 | 61 | return scope.escape(Undefined()); 62 | } 63 | ); 64 | 65 | // Now we get to the body of the function: 66 | a = new String("a"); 67 | b = new String("b"); 68 | 69 | std::cout << "should say 'b': " << b->dump() << "\n"; 70 | 71 | // Make the call! 72 | func->call(Null(), {}); 73 | 74 | // should say "b plus one" 75 | std::cout << "should say 'b plus one': " << b->dump() << "\n"; 76 | 77 | return scope.escape(Undefined()); 78 | } 79 | ); 80 | 81 | work->call(Null(), {}); 82 | 83 | std::cout << "before gc\n"; 84 | std::cout << engine().dump(); 85 | std::cout << "\n\n"; 86 | 87 | engine().gc(); 88 | 89 | std::cout << "after gc\n"; 90 | std::cout << engine().dump(); 91 | std::cout << "\n"; 92 | 93 | return 0; 94 | } 95 | -------------------------------------------------------------------------------- /samples/closure.js: -------------------------------------------------------------------------------- 1 | function work() { 2 | var a = "a"; 3 | var b = "b"; 4 | 5 | function func() { 6 | b = "b plus one"; 7 | } 8 | 9 | // prints "b" 10 | console.log(b); 11 | 12 | func(); 13 | 14 | // prints "b plus one" 15 | console.log(b); 16 | } 17 | 18 | work(); 19 | -------------------------------------------------------------------------------- /samples/gc.cpp: -------------------------------------------------------------------------------- 1 | #include "../aotjs_runtime.h" 2 | 3 | #include 4 | 5 | using namespace AotJS; 6 | 7 | int main() { 8 | // To protect them from being GC'd, local variables are kept on a 9 | // stack and we use Local smart pointers to manage the stack record's 10 | // lifetime. 11 | // 12 | // A 'Local' otherwise works like a 'Val*', and is bit-equivalent. 13 | Scope scope; 14 | Local func; 15 | 16 | // Register the function! 17 | func = new Function( 18 | "work", 19 | 1, // argument count 20 | // no scope capture 21 | [] (Function& func, Local this_, ArgList args) -> Local { 22 | // Functions which can return a value must allocate stack space for the 23 | // retval, Or Else. 24 | ScopeRetVal scope; 25 | 26 | // Fetch the arguments into local Capture (Val*) refs, which can be 27 | // either read/modified or passed on to a 28 | // The function arity must be correct! 29 | // Attempting to read beyond the actual number will be invalid. 30 | // 31 | // If it were captured by a lamdba, we'd have to copy it into locals below. 32 | Local root(args[0]); 33 | 34 | // JavaScript local variable definitions are hoisted to the top. 35 | // They are all initialized to Undefined() when allocated on the stack. 36 | // These are variant wrappers (Val) which are also retained while on 37 | // the live-scope-call stack in the Local wrappers. 38 | Local obj; 39 | Local objname; 40 | Local propname; 41 | Local propval; 42 | Local unused; 43 | Local notpropname; 44 | 45 | // Now our function body actually starts! 46 | // Remember JS variable bindings are modeled as pointers to Val cells, 47 | // which we are filling with object pointers. 48 | obj = new Object(); 49 | objname = new String("an_obj"); 50 | propname = new String("propname"); 51 | propval = new String("propval"); 52 | unused = new String("unused"); 53 | 54 | // This string is a different instance from the earlier "propname" one, 55 | // and should not survive GC. 56 | notpropname = new String(std::string("prop") + std::string("name")); 57 | 58 | // Retain a couple strings on an object 59 | // @todo this would be done with a wrapper interface probably 60 | obj->asObject().setProp(propname, propval); 61 | 62 | root->asObject().setProp(objname, obj); 63 | 64 | return scope.escape(Undefined()); 65 | } 66 | ); 67 | 68 | func->call(Null(), {engine().root()}); 69 | 70 | std::cout << "before gc\n"; 71 | std::cout << engine().dump(); 72 | std::cout << "\n\n"; 73 | 74 | engine().gc(); 75 | 76 | std::cout << "after gc\n"; 77 | std::cout << engine().dump(); 78 | std::cout << "\n"; 79 | 80 | return 0; 81 | } 82 | -------------------------------------------------------------------------------- /samples/mandelbrot.cpp: -------------------------------------------------------------------------------- 1 | #include "../aotjs_runtime.h" 2 | 3 | #include 4 | 5 | using namespace AotJS; 6 | 7 | int main() { 8 | Scope scope; 9 | 10 | // Hoist all variable declarations 11 | Local start; 12 | Local iterate_mandelbrot; 13 | Local x0, x1, y0, y1; 14 | Local cols, rows; 15 | Local maxIters; 16 | Local row, y, str, col, x, iters; 17 | Local end; 18 | 19 | start = engine().now(); 20 | 21 | iterate_mandelbrot = new Function( 22 | "iterate_mandelbrot", 23 | 3, // argument count 24 | // no scope capture 25 | [] (Function& func, Local this_, ArgList args) -> Local { 26 | ScopeRetVal scope; 27 | 28 | Local cx(args[0]); 29 | Local cy(args[1]); 30 | Local maxIters(args[2]); 31 | 32 | Local zx, zy, i, new_zx; 33 | 34 | zx = 0; 35 | zy = 0; 36 | 37 | for (i = 0; i < maxIters && (zx * zx + zy * zy) < 4.0; i++) { 38 | Scope subscope; 39 | new_zx = zx * zx - zy * zy + cx; 40 | zy = 2 * zx * zy + cy; 41 | zx = new_zx; 42 | } 43 | 44 | return scope.escape(i); 45 | } 46 | ); 47 | 48 | x0 = -2.5; x1 = 1; y0 = -1; y1 = 1; 49 | cols = 72; rows = 24; 50 | maxIters = 1000; 51 | 52 | for (row = 0; row < rows; row++) { 53 | Scope scopeRow; 54 | 55 | y = (row / rows) * (y1 - y0) + y0; 56 | str = new String(""); 57 | 58 | for (col = 0; col < cols; col++) { 59 | Scope scopeCol; 60 | 61 | x = (col / cols) * (x1 - x0) + x0; 62 | iters = iterate_mandelbrot->call(Null(), {x, y, maxIters}); 63 | 64 | if (iters == 0) { 65 | str += new String("."); 66 | } else if (iters == 1) { 67 | str += new String("%"); 68 | } else if (iters == 2) { 69 | str += new String("@"); 70 | } else if (iters == maxIters) { 71 | str += new String(" "); 72 | } else { 73 | str += new String("#"); 74 | } 75 | } 76 | std::cout << str->dump() << "\n"; 77 | } 78 | 79 | end = engine().now() - start; 80 | std::cout << end->dump() << " milliseconds runtime\n"; 81 | 82 | return 0; 83 | } 84 | -------------------------------------------------------------------------------- /samples/mandelbrot.js: -------------------------------------------------------------------------------- 1 | var start = Date.now(); 2 | 3 | // z(n+1) = z(n)^2 + c 4 | function iterate_mandelbrot(cx, cy, maxIters) { 5 | var zx = 0, zy = 0; 6 | for (var i = 0; i < maxIters && (zx * zx + zy * zy) < 4.0; i++) { 7 | var new_zx = zx * zx - zy * zy + cx; 8 | zy = 2 * zx * zy + cy; 9 | zx = new_zx; 10 | } 11 | return i; 12 | } 13 | 14 | var x0 = -2.5, x1 = 1, y0 = -1, y1 = 1; 15 | var cols = 72, rows = 24; 16 | var maxIters = 1000; 17 | 18 | for (var row = 0; row < rows; row++) { 19 | var y = (row / rows) * (y1 - y0) + y0; 20 | var str = ''; 21 | for (var col = 0; col < cols; col++) { 22 | var x = (col / cols) * (x1 - x0) + x0; 23 | var iters = iterate_mandelbrot(x, y, maxIters); 24 | if (iters == 0) { 25 | str += '.'; 26 | } else if (iters == 1) { 27 | str += '%'; 28 | } else if (iters == 2) { 29 | str += '@'; 30 | } else if (iters == maxIters) { 31 | str += ' '; 32 | } else { 33 | str += '#'; 34 | } 35 | } 36 | console.log(str); 37 | } 38 | 39 | var end = Date.now() - start; 40 | console.log(end, 'milliseconds runtime'); 41 | -------------------------------------------------------------------------------- /samples/retval.cpp: -------------------------------------------------------------------------------- 1 | #include "../aotjs_runtime.h" 2 | 3 | #include 4 | 5 | using namespace AotJS; 6 | 7 | int main() { 8 | // Variables hoisted... 9 | Scope scope; 10 | 11 | { 12 | Scope scope2; 13 | 14 | Local work; 15 | Local play; 16 | Local life; 17 | 18 | work = new Function( 19 | "work", 20 | 0, // argument count 21 | // no scope capture 22 | [] (Function& func, Local this_, ArgList args) -> Local { 23 | ScopeRetVal scope; 24 | return scope.escape(new String("work")); 25 | } 26 | ); 27 | 28 | play = new Function( 29 | "play", 30 | 0, // argument count 31 | [] (Function& func, Local this_, ArgList args) -> Local { 32 | ScopeRetVal scope; 33 | return scope.escape(new String("play")); 34 | } 35 | ); 36 | 37 | // todo: operator overloading on Val 38 | life = work->call(Null(), {}) + play->call(Null(), {}); 39 | 40 | // should say "workplay" 41 | std::cout << "should say 'workplay': " << life->dump() << "\n"; 42 | 43 | std::cout << "before gc\n"; 44 | std::cout << engine().dump(); 45 | std::cout << "\n\n"; 46 | 47 | } 48 | 49 | engine().gc(); 50 | 51 | std::cout << "after gc\n"; 52 | std::cout << engine().dump(); 53 | std::cout << "\n"; 54 | 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /samples/retval.js: -------------------------------------------------------------------------------- 1 | function work() { 2 | return "work"; 3 | } 4 | 5 | function play() { 6 | return "play"; 7 | } 8 | 9 | var life = work() + play(); 10 | 11 | // should say "workplay"; 12 | console.log("should say 'workplay':", life); 13 | --------------------------------------------------------------------------------