exploring js vm in js

Vyacheslav Egorov

@mraleph

navigate with cursor keys ←→ or space bar
jsconf.eu 2012

Disclaimer

This talk was originally given in the special build of Chromium which allowed JavaScript to deeply inspect the state of the underlying virtual machine (V8). You are viewing "shimed" version that uses data serialized from that build.

TOYS

Performance talks in 6 lines

si fueris Romae, Romano vivito more
when working on XYZ VM, try doing everything in XYZ.

Plan A

Plan A

Plan A

Plan A

Plan A

Plan A

Plan A

Plan A

Plan B



 

Plan B

var ics =
    $Scripts()[0].inline_caches;
while (i < ics.length) {
  switch (ics[i++]) {
    case kIC: // ...
    case kPosition: // ...
    case kFunction: // ...
  }
}
        

Plan B

MaybeObject* Accessors::ScriptGetInlineCaches(Object* object, void*) {
  Isolate* isolate = JSValue::cast(object)->GetIsolate();
  Zone zone(isolate);
  ZoneScope zone_scope(&zone, DELETE_ON_EXIT);

  HandleScope handle_scope(isolate);
  Handle<Script> script(Script::cast(JSValue::cast(object)->value()));

  isolate->heap()->CollectAllGarbage(Heap::kMakeHeapIterableMask, "ScriptGetInlineCaches");

  ZoneList<Object*>* ic_data_list = NULL;
  {
    AssertNoAllocation no_allocation;
    HeapIterator iterator(HeapIterator::kFilterUnreachable);

    CodeSet set(&zone);
    for (HeapObject* obj = iterator.next();
         obj != NULL;
         obj = iterator.next()) {
      if (obj->IsJSFunction() && JSFunction::cast(obj)->shared()->script() == *script) {
        JSFunction* func = JSFunction::cast(obj);
        SharedFunctionInfo* shared = func->shared();
        set.Add(shared->code(), shared);
        set.Add(func->code(), func);
      }
    }

    ic_data_list = set.FindICs();
  }

  Handle<FixedArray> ic_data = isolate->factory()->NewFixedArray(ic_data_list->length());
  for (int i = 0; i < ic_data_list->length(); i++) {
    ic_data->set(i, ic_data_list->at(i));
  }

  return *isolate->factory()->NewJSArrayWithElements(ic_data);
}
        
C++ :-(

Plan Z

  // Valid JavaScript!
  var Page = struct({
    next_chunk: addr,
    prev_chunk: addr,
    size: u32,
    flags: i32,
    area_start: addr,
    area_end: addr
  });
        

Plan Z

  var HeapObject = struct({
    // tagged pointer to Map
    map: tptr('Map')
  });

  var Map = struct(HeapObject, {
    instance_size: u32,
    instance_type: u8
  });
        

Plan Z

  var Code = struct(HeapObject, {
    instruction_size: i32,
    reloc_info: tptr(HeapObject),
    handlers: tptr(HeapObject),
    deopt_data: tptr(HeapObject),
    type_feedback: tptr(HeapObject),
    gc_metadata: tptr(HeapObject),
    ic_age: i32,
    flags: i32,
    kind_flags1: u32,
    kind_flags2: u32
  });
        

Plan Z: Typed arrays FTW!

// raw view into V8 heap!
var page = $Heap().code_space[0];
for (var it = ObjectIterator(
                  page.area_start,
                  page.area_end);
     !it.done();
     it.next()) {
  // raw view into an object.
  var obj = it.object;
  console.log(obj.Size());
}
        

Plan Z: WebGL Typed Arrays

        
Note: disassembly is done in pure JavaScript with disasmx86.js. Library is not complete so disassembly sometimes does not make sense.

Plan Z: I have the power

var p = Page($Heap().code_space[0]);
var start = p.area_start & (kPageSize - 1);
var end = p.area_end & (kPageSize - 1);
var m = "\xCCHello JSConfEU";
for (var i = start; i < end; i++) {
  p.memory[i] = m.charCodeAt(i % m.length);
}

Special build of Chromium would crash

So why are we still using C++?

So why are we still using C++?

no idea.

Plan mö.js

function main function reduce loadconst r3, 0 loop: loop 4 load r4, r0, "n" lessthan r3, r4 jmp exit move r4, r1 move r5, r2 load r6, r0, r3 call r4, 2 move r2, r4 add r3, r3, 1 jmp loop exit: ret r2 function dot load r2, r0, "x" load r3, r1, "x" mul r2, r2, r3 load r3, r0, "y" load r4, r1, "y" mul r3, r3, r4 add r2, r2, r3 ret r2 function Point newtable r2 store r2, "x", r0 store r2, "y", r1 ret r2 function MakeArrayOfPoints newtable r1 loadconst r2, -1 loadconst r3, 0 loop: loop 4 lessthan r3, r0 jmp exit mul r2, r2, -1 loadglobal r4, "MakePoint" mul r5, r2, r3 unm r6, r3 mul r6, r2, r6 call r4, 2 store r1, r3, r4 add r3, r3, 1 jmp loop exit: store r1, "n", r0 ret r1 function Benchmark function anon loadglobal r2, "dot" move r3, r1 move r4, r1 call r2, 2 add r2, r0, r2 ret r2 loadglobal r0, "reduce" loadglobal r1, "points" newclosure r2, @anon loadconst r3, 0 call r0, 3 ret newclosure r0, @reduce storeglobal "reduce", r0 newclosure r0, @dot storeglobal "dot", r0 newclosure r0, @MakePoint storeglobal "MakePoint", r0 newclosure r0, @MakeArrayOfPoints storeglobal "MakeArrayOfPoints", r0 storeglobal "N", 1000 loadglobal r0, "MakeArrayOfPoints" loadglobal r1, "N" call r0, 1 storeglobal "points", r0 newclosure r0, @Benchmark ret r0

Too many indirections!

lim
t→∞
mö.js = VM

Plan Ω: can you spot a bug?

Handle<Code> KeyedLoadStubCompiler::CompileLoadField(Handle<String> name,
                                                     Handle<JSObject> receiver,
                                                     Handle<JSObject> holder,
                                                     int index) {
  // ----------- S t a t e -------------
  //  -- ecx    : key
  //  -- edx    : receiver
  //  -- esp[0] : return address
  // -----------------------------------
  Label miss;

  Counters* counters = isolate()->counters();
  __ IncrementCounter(counters->keyed_load_field(), 1);

  // Check that the name has not changed.
  __ cmp(ecx, Immediate(name));
  __ j(not_equal, &miss);

  GenerateLoadField(receiver, holder, edx, ebx, ecx, edi, index, name, &miss);

  __ bind(&miss);
  __ DecrementCounter(counters->keyed_load_field(), 1);
  GenerateLoadMiss(masm(), Code::KEYED_LOAD_IC);

  // Return the generated code.
  return GetCode(FIELD, name);
}
        

Plan Ω: what about now?

  GenerateLoadField(receiver,
                    holder,
                    edx,
                    ebx,
                    ecx,
                    edi,
                    index,
                    name,
                    &miss);
        

Plan Ω: yes, here it is

  GenerateLoadField(receiver,
                    holder,
                    edx,
                    ebx,
                    ecx,
                    edi,
                    index,
                    name,
                    &miss);
        

Plan Ω: fixed!

  GenerateLoadField(receiver,
                    holder,
                    edx,
                    ebx,
                    eax,
                    edi,
                    index,
                    name,
                    &miss);
        

Plan Ω

function CompileLoadField(name, receiver_obj,
                          holder_obj, index) {
  return specialize(function (key, receiver) {
    "STUB (ecx, edx)";
    counters.keyed_load_field().inc();
    if (key === name) {
      return LoadFieldFastPath(
        receiver, index, receiver_obj, holder_obj);
    }
    counters.keyed_load_field().dec();
    return KeyedLoad_Miss(key, receiver);
  });
}
        

Plan Ω

Thank you!
Q&A