Skip to content

Debugging Mode causes JIT failure #308

@leeN

Description

@leeN

If I compile Foxhound in debug mode, I encounter the following crash in the JS shell:

let t = String.tainted("abc"); let f = function(s) { for(let i=0; i < 100; i++) { let c = s.charAt(i%s.length); } }; baselineCompile(f);  for (var i=0; i<1; i++) { f(t) }

[1119479] Assertion failure: !addedFailurePath_ (multiple failure paths for instruction), at /home/leen/project-foxhound/js/src/jit/CacheIRCompiler.h:459
#1: ???[./obj-tf-release/dist/bin/js +0x3754ea4]
#2: ???[./obj-tf-release/dist/bin/js +0x3754b40]
#3: ???[./obj-tf-release/dist/bin/js +0x342ef9e]
#4: ???[./obj-tf-release/dist/bin/js +0x342fba5]
#5: ???[./obj-tf-release/dist/bin/js +0x3422053]
#6: ???[./obj-tf-release/dist/bin/js +0x34156aa]
#7: ???[./obj-tf-release/dist/bin/js +0x3437861]
#8: ???[./obj-tf-release/dist/bin/js +0x33a5448]
#9: ??? (???:???)
Segmentation fault (core dumped)

In Node.js the following code executes normally:

let tt = "abc"; let ff = function(s) { for(let i=0; i < 100; i++) { let c = s.charAt(i%s.length); } }; for (var i=0; i<1; i++) { ff(tt) }

The backtrace is the following:

(gdb) bt
#0 0x0000562bc1d0ceb2 in js::jit::CacheRegisterAllocator::setAddedFailurePath() (this=0x7fff6850c360) at /home/leen/project-foxhound/js/src/jit/CacheIRCompiler.h:459
#1 0x0000562bc1d0cb40 in js::jit::CacheIRCompiler::addFailurePath(js::jit::FailurePath**) (this=0x7fff6850b8d8, failure=0x7fff6850b490) at /home/leen/project-foxhound/js/src/jit/CacheIRCompiler.cpp:1635
#2 0x0000562bc19e6f9e in js::jit::BaselineCacheIRCompiler::emitLoadStringCharResult(js::jit::StringOperandId, js::jit::Int32OperandId, js::jit::BaselineCacheIRCompiler::StringCharOutOfBounds)
(this=0x7fff6850b8d8, strId=..., indexId=..., outOfBounds=js::jit::BaselineCacheIRCompiler::StringCharOutOfBounds::Failure) at /home/leen/project-foxhound/js/src/jit/BaselineCacheIRCompiler.cpp:1240
#3 0x0000562bc19e7ba5 in js::jit::BaselineCacheIRCompiler::emitLoadStringCharResult(js::jit::StringOperandId, js::jit::Int32OperandId, bool) (this=0x7fff6850b8d8, strId=..., indexId=..., handleOOB=false)
at /home/leen/project-foxhound/js/src/jit/BaselineCacheIRCompiler.cpp:1313
#4 0x0000562bc19da053 in js::jit::BaselineCacheIRCompiler::emitLoadStringCharResult(js::jit::CacheIRReader&) (this=0x7fff6850b8d8, reader=...)
at /home/leen/project-foxhound/js/src/jit/BaselineCacheIRCompiler.h:157
#5 0x0000562bc19cd6aa in js::jit::BaselineCacheIRCompiler::compile() (this=0x7fff6850b8d8) at /home/leen/project-foxhound/js/src/jit/BaselineCacheIRCompiler.cpp:227
#6 0x0000562bc19ef861 in js::jit::AttachBaselineCacheIRStub(JSContext*, js::jit::CacheIRWriter const&, js::jit::CacheKind, JSScript*, js::jit::ICScript*, js::jit::ICFallbackStub*, char const*)
(cx=0x7f5f6793a100, writer=..., kind=js::jit::CacheKind::Call, outerScript=0xf6bcfc8e060, icScript=0x7f5f667bed70, stub=0x7f5f667beee0, name=0x562bbeb2c388 "StringCharAt")
at /home/leen/project-foxhound/js/src/jit/BaselineCacheIRCompiler.cpp:2584
#7 0x0000562bc195d448 in js::jit::DoCallFallback(JSContext*, js::jit::BaselineFrame*, js::jit::ICFallbackStub*, unsigned int, JS::Value*, JS::MutableHandleJS::Value)
(cx=0x7f5f6793a100, frame=0x7fff6850cdc8, stub=0x7f5f667beee0, argc=1, vp=0x7fff6850cd68, res=$JS::UndefinedValue()) at /home/leen/project-foxhound/js/src/jit/BaselineIC.cpp:1633

The issue seems to be the following code:

bool BaselineCacheIRCompiler::emitLoadStringCharResult(
    StringOperandId strId, Int32OperandId indexId,
    StringCharOutOfBounds outOfBounds) {
  AutoOutputRegister output(*this);
  Register str = allocator.useRegister(masm, strId);
  Register index = allocator.useRegister(masm, indexId);
  AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
  AutoScratchRegisterMaybeOutputType scratch2(allocator, masm, output);
  AutoScratchRegister scratch3(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // Foxhound: also check whether the string is tainted
  masm.branchPtr(Assembler::NotEqual,
                 Address(str, JSString::offsetOfTaint()),
                 ImmPtr(nullptr),
                 failure->label());

  // Bounds check, load string char.
  Label done;
  Label tagResult;
  Label loadFailed;
  if (outOfBounds == StringCharOutOfBounds::Failure) {
    FailurePath* failure;
    if (!addFailurePath(&failure)) {
      return false;
    }

    masm.spectreBoundsCheck32(index, Address(str, JSString::offsetOfLength()),
                              scratch3, failure->label());
    masm.loadStringChar(str, index, scratch2, scratch1, scratch3,
                        failure->label());

    allocator.discardStack(masm);
  } else {
    // Discard the stack before jumping to |done|.
    allocator.discardStack(masm);

    if (outOfBounds == StringCharOutOfBounds::EmptyString) {
      // Return the empty string for out-of-bounds access.
      masm.movePtr(ImmGCPtr(cx_->names().empty_), scratch1);
    } else {
      // Return |undefined| for out-of-bounds access.
      masm.moveValue(UndefinedValue(), output.valueReg());
    }

    // This CacheIR op is always preceded by |LinearizeForCharAccess|, so we're
    // guaranteed to see no nested ropes.
    masm.spectreBoundsCheck32(index, Address(str, JSString::offsetOfLength()),
                              scratch3, &done);
    masm.loadStringChar(str, index, scratch2, scratch1, scratch3, &loadFailed);
  }

  // Load StaticString for this char. For larger code units perform a VM call.
  Label vmCall;
  masm.lookupStaticString(scratch2, scratch1, cx_->staticStrings(), &vmCall);
  masm.jump(&tagResult);

  if (outOfBounds != StringCharOutOfBounds::Failure) {
    masm.bind(&loadFailed);
    masm.assumeUnreachable("loadStringChar can't fail for linear strings");
  }

  {
    masm.bind(&vmCall);

    AutoStubFrame stubFrame(*this);
    stubFrame.enter(masm, scratch3);

    masm.Push(scratch2);

    using Fn = JSLinearString* (*)(JSContext*, int32_t);
    callVM<Fn, js::StringFromCharCode>(masm);

    stubFrame.leave(masm);

    masm.storeCallPointerResult(scratch1);
  }

  if (outOfBounds != StringCharOutOfBounds::UndefinedValue) {
    masm.bind(&tagResult);
    masm.bind(&done);
    masm.tagValue(JSVAL_TYPE_STRING, scratch1, output.valueReg());
  } else {
    masm.bind(&tagResult);
    masm.tagValue(JSVAL_TYPE_STRING, scratch1, output.valueReg());
    masm.bind(&done);
  }
  return true;
}

The issue seems to be that we register two failure paths.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No fields configured for Bug.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions