4

《Chrome V8源码》28.分析substring源码和隐式约定

 2 years ago
source link: https://zhuanlan.zhihu.com/p/440077934
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

《Chrome V8源码》28.分析substring源码和隐式约定

V8粉丝一枚,正在努力做一名V8源码的朗读者。

本篇文章是Builtin专题的第四篇,主要分析substring的源码。substring有两种实现方法,一种采用CSA实现,另一种采用Runtime实现。本文讲解CSA实现的substring方法以及V8对字符串长度和类型的隐式约定。

2 substring的CSA实现

提取字符串中介于两个指定下标之间的子字符串时,V8优先使用CSA实现的substring方法,源码如下:

1.  TF_BUILTIN(StringPrototypeSubstring, CodeStubAssembler) {
2.    if (block0.is_used()) {//省略了很多代码.......................................
3.      ca_.SetSourcePosition("../../../src/builtins/string-substring.tq", 33);
4.      tmp5 = FromConstexpr6String18ATconstexpr_string_156(state_, "String.prototype.substring");
5.      tmp6 = CodeStubAssembler(state_).ToThisString(compiler::TNode<Context>{tmp3}, compiler::TNode<Object>{tmp4}, compiler::TNode<String>{tmp5});
6.      ca_.SetSourcePosition("../../../src/builtins/string-substring.tq", 34);
7.      tmp7 = CodeStubAssembler(state_).LoadStringLengthAsSmi(compiler::TNode<String>{tmp6});
8.      ca_.SetSourcePosition("../../../src/builtins/string-substring.tq", 37);
9.      tmp8 = FromConstexpr8ATintptr17ATconstexpr_int31_150(state_, 0);
10.      tmp9 = CodeStubAssembler(state_).GetArgumentValue(TorqueStructArguments{compiler::TNode<RawPtrT>{tmp0}, compiler::TNode<RawPtrT>{tmp1}, compiler::TNode<IntPtrT>{tmp2}}, compiler::TNode<IntPtrT>{tmp8});
11.      tmp10 = ToSmiBetweenZeroAnd_343(state_, compiler::TNode<Context>{tmp3}, compiler::TNode<Object>{tmp9}, compiler::TNode<Smi>{tmp7});
12.      ca_.SetSourcePosition("../../../src/builtins/string-substring.tq", 40);
13.      tmp11 = FromConstexpr8ATintptr17ATconstexpr_int31_150(state_, 1);
14.      tmp12 = CodeStubAssembler(state_).GetArgumentValue(TorqueStructArguments{compiler::TNode<RawPtrT>{tmp0}, compiler::TNode<RawPtrT>{tmp1}, compiler::TNode<IntPtrT>{tmp2}}, compiler::TNode<IntPtrT>{tmp11});
15.      tmp13 = Undefined_64(state_);
16.      tmp14 = CodeStubAssembler(state_).TaggedEqual(compiler::TNode<Object>{tmp12}, compiler::TNode<HeapObject>{tmp13});
17.      ca_.Branch(tmp14, &block1, &block2, tmp0, tmp1, tmp2, tmp3, tmp4, tmp6, tmp7, tmp10);
18.    }
19.    if (block1.is_used()) {
20.      ca_.SetSourcePosition("../../../src/builtins/string-substring.tq", 41);
21.      ca_.SetSourcePosition("../../../src/builtins/string-substring.tq", 40);
22.      ca_.Goto(&block4, tmp15, tmp16, tmp17, tmp18, tmp19, tmp20, tmp21, tmp22, tmp21);
23.    }
24.    if (block2.is_used()) {
25.      ca_.SetSourcePosition("../../../src/builtins/string-substring.tq", 42);
26.      tmp31 = FromConstexpr8ATintptr17ATconstexpr_int31_150(state_, 1);
27.      tmp32 = CodeStubAssembler(state_).GetArgumentValue(TorqueStructArguments{compiler::TNode<RawPtrT>{tmp23}, compiler::TNode<RawPtrT>{tmp24}, compiler::TNode<IntPtrT>{tmp25}}, compiler::TNode<IntPtrT>{tmp31});
28.      tmp33 = ToSmiBetweenZeroAnd_343(state_, compiler::TNode<Context>{tmp26}, compiler::TNode<Object>{tmp32}, compiler::TNode<Smi>{tmp29});
29.      ca_.SetSourcePosition("../../../src/builtins/string-substring.tq", 40);
30.      ca_.Goto(&block3, tmp23, tmp24, tmp25, tmp26, tmp27, tmp28, tmp29, tmp30, tmp33);
31.    }
32.    if (block4.is_used()) {
33.      ca_.Goto(&block3, tmp34, tmp35, tmp36, tmp37, tmp38, tmp39, tmp40, tmp41, tmp42);
34.    }
35.    if (block3.is_used()) {
36.      ca_.SetSourcePosition("../../../src/builtins/string-substring.tq", 43);
37.      tmp52 = CodeStubAssembler(state_).SmiLessThan(compiler::TNode<Smi>{tmp51}, compiler::TNode<Smi>{tmp50});
38.      ca_.Branch(tmp52, &block5, &block6, tmp43, tmp44, tmp45, tmp46, tmp47, tmp48, tmp49, tmp50, tmp51);
39.    }
40.    if (block5.is_used()) {
41.      ca_.SetSourcePosition("../../../src/builtins/string-substring.tq", 44);
42.      ca_.SetSourcePosition("../../../src/builtins/string-substring.tq", 45);
43.      ca_.SetSourcePosition("../../../src/builtins/string-substring.tq", 46);
44.      ca_.SetSourcePosition("../../../src/builtins/string-substring.tq", 43);
45.      ca_.Goto(&block6, tmp53, tmp54, tmp55, tmp56, tmp57, tmp58, tmp59, tmp61, tmp60);
46.    }
47.    if (block6.is_used()) {
48.      ca_.SetSourcePosition("../../../src/builtins/string-substring.tq", 48);
49.      tmp71 = CodeStubAssembler(state_).SmiUntag(compiler::TNode<Smi>{tmp69});
50.      tmp72 = CodeStubAssembler(state_).SmiUntag(compiler::TNode<Smi>{tmp70});
51.      tmp73 = CodeStubAssembler(state_).SubString(compiler::TNode<String>{tmp67}, compiler::TNode<IntPtrT>{tmp71}, compiler::TNode<IntPtrT>{tmp72});
52.      arguments.PopAndReturn(tmp73);
53.    }
54.  }

上述代码由string-substring.tq指导编译器生成,其位置在V8\v8\src\out\default\gen\torque-generated\src\builtins目录下,这意味它在编译V8过程中生成。
(1) 第3行代码设置源码,源码来自string-substring.tq文件的第33行,见图1;
(2) codeStubAssembler(state_).ToThisString()(第5行代码)把this转成字串符;
第6行代码设置源码,见图1;CodeStubAssembler(state_).LoadStringLengthAsSmi()(第7行代码)计算字符串长度,参数tmp6的值是第5行代码的执行结果。由于第6、7行代码与第3、5行的编码风格一样,所以可以通过对string-substring.tq的逐行分析看懂CodeStubAssembler。

下面说明substring源码中的其它关键功能:
(1) ca_.Goto()跳转到标签位置,它的第一参数是标签,源码如下:

template <class... T, class... Args>
  void Goto(CodeAssemblerParameterizedLabel<T...>* label, Args... args) {
    label->AddInputs(args...);
    Goto(label->plain_label());
  }

(2)ca_.Bind()设置标签,源码如下:

template <class... T>
  void Bind(CodeAssemblerParameterizedLabel<T...>* label, TNode<T>*... phis) {
    Bind(label->plain_label());
    label->CreatePhis(phis...);
  }

(3)ca_.Branch()分支跳转,源码如下:

template <class... T, class... Args>
  void Branch(TNode<BoolT> condition,
              CodeAssemblerParameterizedLabel<T...>* if_true,
              CodeAssemblerParameterizedLabel<T...>* if_false, Args... args) {
    if_true->AddInputs(args...);
    if_false->AddInputs(args...);
    Branch(condition, if_true->plain_label(), if_false->plain_label());
  }

其中参数condition是条件,参数if_true、if_false是跳转标签。
(4) LoadStringLengthAsSmi()SmiUntag()CodeStubAssembler的成员方法。
总结TF_BUILTIN(StringPrototypeSubstring, CodeStubAssembler)的功能为如下三点:
(1) 把this转换为字符串并获取长度length;
(2) 判断substring的长度(sublen)是否小于length;
(3) 调用CodeStubAssembler.SubString完成substring操作。
CodeStubAssembler.SubString的源码如下:

1.  TNode<String> CodeStubAssembler::SubString(TNode<String> string,
2.                                            TNode<IntPtrT> from,
3.                                            TNode<IntPtrT> to) {
4.  //省略很多
5.    Label external_string(this);
6.    {
7.      if (FLAG_string_slices) {
8.        Label next(this);
9.        GotoIf(IntPtrLessThan(substr_length,
10.                              IntPtrConstant(SlicedString::kMinLength)),
11.               &next);
12.        Counters* counters = isolate()->counters();
13.        IncrementCounter(counters->sub_string_native(), 1);
14.        Label one_byte_slice(this), two_byte_slice(this);
15.        Branch(IsOneByteStringInstanceType(to_direct.instance_type()),
16.               &one_byte_slice, &two_byte_slice);
17.        BIND(&one_byte_slice);
18.        {
19.          var_result = AllocateSlicedOneByteString(
20.              Unsigned(TruncateIntPtrToInt32(substr_length)), direct_string,
21.              SmiTag(offset));
22.          Goto(&end);
23.        }
24.        BIND(&two_byte_slice);
25.        {
26.          var_result = AllocateSlicedTwoByteString(
27.              Unsigned(TruncateIntPtrToInt32(substr_length)), direct_string,
28.              SmiTag(offset));
29.          Goto(&end);
30.        }
31.        BIND(&next);
32.      }
33.      GotoIf(to_direct.is_external(), &external_string);
34.      var_result = AllocAndCopyStringCharacters(direct_string, instance_type,
35.                                                offset, substr_length);
36.      Counters* counters = isolate()->counters();
37.      IncrementCounter(counters->sub_string_native(), 1);
38.      Goto(&end);
39.    }
40.    BIND(&external_string);
41.    {
42.      TNode<RawPtrT> const fake_sequential_string =
43.          to_direct.PointerToString(&runtime);
44.      var_result = AllocAndCopyStringCharacters(
45.          fake_sequential_string, instance_type, offset, substr_length);
46.      Counters* counters = isolate()->counters();
47.      IncrementCounter(counters->sub_string_native(), 1);
48.      Goto(&end);
49.    }
50.    BIND(&empty);
51.    {
52.    }
53.    BIND(&single_char);
54.    {
55.      TNode<Int32T> char_code = StringCharCodeAt(string, from);
56.      var_result = StringFromSingleCharCode(char_code);
57.      Goto(&end);
58.    }
59.    BIND(&original_string_or_invalid_length);
60.    {
61.  //省略很多
62.    }
63.    BIND(&runtime);
64.    {
65.      var_result =
66.          CAST(CallRuntime(Runtime::kStringSubstring, NoContextConstant(), string,
67.                           SmiTag(from), SmiTag(to)));
68.      Goto(&end);
69.    }
70.    BIND(&end);
71.    return var_result.value();
72.  }

FLAG_string_slices(上述第7行代码)是切片的使能标记,它定义在flag-definitions.h中,源码如下:

// Flags for data representation optimizations
DEFINE_BOOL_READONLY(string_slices, true, "use string slices")

第9行代码GotoIf()计算substr_length的值,如果小于13则跳转到标签next。
第15行代码Branch()判断字符串是单字节字符还是双字节字符。
第17-23行、24-30行分别处理单字节、双字节两种情况,稍后讲解。
第40-49行代码BIND(&external_string)操作外部字符串,外部字符串指的是不在V8 heap中的字符串,如从DOM中引用的字符串就是外部字符串。操作外部字符串时使用Runtime方法。
第53-58行代码:当sublength=1时,调用StringCharCodeAt()完成相应的操作并返回结果。
第63-70行代码:当字符串为外部字符串时,调用Runtime_StringSubstring完成相应的操作并返回结果。
在V8中,slice生成新字符串时,如果新字符串长度大于SlicedString::kMinLength则不申请新内存,而是使用开始指针和结束指针引用原字符串。以单字节字串符为例讲解slice方法,源码如下:

1.  TNode<String> CodeStubAssembler::AllocateSlicedOneByteString(
2.      TNode<Uint32T> length, TNode<String> parent, TNode<Smi> offset) {
3.    return AllocateSlicedString(RootIndex::kSlicedOneByteStringMap, length,
4.                                parent, offset);
5.  }
6.  //分隔线..............................
7.  TNode<String> CodeStubAssembler::AllocateSlicedString(RootIndex map_root_index,
8.                                                        TNode<Uint32T> length,
9.                                                        TNode<String> parent,
10.                                                        TNode<Smi> offset) {
11.    DCHECK(map_root_index == RootIndex::kSlicedOneByteStringMap ||
12.           map_root_index == RootIndex::kSlicedStringMap);
13.    TNode<HeapObject> result = Allocate(SlicedString::kSize);
14.    DCHECK(RootsTable::IsImmortalImmovable(map_root_index));
15.    StoreMapNoWriteBarrier(result, map_root_index);
16.    StoreObjectFieldNoWriteBarrier(result, SlicedString::kHashFieldOffset,
17.                                   Int32Constant(String::kEmptyHashField),
18.                                   MachineRepresentation::kWord32);
19.    StoreObjectFieldNoWriteBarrier(result, SlicedString::kLengthOffset, length,
20.                                   MachineRepresentation::kWord32);
21.    StoreObjectFieldNoWriteBarrier(result, SlicedString::kParentOffset, parent,
22.                                   MachineRepresentation::kTagged);
23.    StoreObjectFieldNoWriteBarrier(result, SlicedString::kOffsetOffset, offset,
24.                                   MachineRepresentation::kTagged);
25.    return CAST(result);
26.  }

上述代码中AllocateSlicedOneByteString()是入口函数,调用AllocateSlicedString()函数。第13行代码创建SlicedString对象(result);第16-24行代码把sublength、父亲字符串基址和偏移量存入result中,slice完毕。
技术总结
(1) string-substring.tq是开发者手写的Builtin源码,http://string-substring-tq-csa.cc和.h是Tq生成的Builtin源码;
(2) SlicedString::kMinLength的值是13,news=substring(start,stop),news的长度小于13时用copy机制,大于13时用引用机制;
(3) 因为使用了Runtime_substring方法,所以外部字符串的操作效率低。
好了,今天到这里,下次见。

个人能力有限,有不足与纰漏,欢迎批评指正
微信:qq9123013 备注:v8交流 邮箱:[email protected]

本文由灰豆原创发布

转载出处:https://www.anquanke.com/post/id/260386

安全客 - 有思想的安全新媒体


Recommend

  • 82
    • 掘金 juejin.im 6 years ago
    • Cache

    笔记:隐式转换规则

    学习并背诵全文 类型 Undefined Null String Boolean Number 值 undefined null 所有字符串 true false

  • 46
    • 掘金 juejin.im 6 years ago
    • Cache

    你所忽略的js隐式转换

    你有没有在面试中遇到特别奇葩的js隐形转换的面试题,第一反应是怎么会是这样呢?难以自信,js到底是怎么去计算得到结果,你是否有深入去了解其原理呢?下面将深入讲解其实现原理。 其实这篇文章初稿三个月前就写好了,在我读一些源码库时,遇到了这些基础知识,想...

  • 20

    前方提醒: 篇幅较长,点个赞或者收藏一下,可以在下一次阅读时方便查找 提到js的隐式转换,很多人第一反应都是:坑。 的确,对于不熟悉的人来说,js隐式转换存在着很多的让人无法预测的地方,相信很多人都深受其害,所以,大家在开发过程中,可能会使用===来尽量...

  • 31

    从字节码看java中 this 隐式传参具体体现,也发现了 static 与 非 static 方法的区别所在! static与非static方法都是存储java的方法区。在static 方法中,没有this引用,因此无法使用当前类中所定义的变量,而非static方法则会...

  • 36

    数据库优化是一个任重而道远的任务,想要做优化必须深入理解数据库的各种特性。在开发过程中我们经常会遇到一些原因很简单但造成的后果却很严重的疑难杂症,这类问题往往还不容易定位,排查费时费力最后发现是一个很小的疏忽造成的,又或...

  • 28
    • www.cnblogs.com 4 years ago
    • Cache

    ORACLE隐式类型转换

    隐式类型转换简介 通常 ORACLE 数据库存在显式类型转换( Explicit Datatype Conversion

  • 3
    • ourai.ws 3 years ago
    • Cache

    页面间隐式传递参数

    页面间隐式传递参数 欧雷 发表于 8 年之前 0 条评论 标签:

  • 13

    在360云引擎技术博客的“深入剖析linux GCC 4.4的STL string”这篇blog的指导下,看了一些STL string的实现代码,并针对我们平时对string的一些常规用法做了一些测试。这...

  • 6
    • segmentfault.com 3 years ago
    • Cache

    有趣的JS-隐式类型转换

    有趣的JS-隐式类型转换当两个不同数据类型的操作数在做运算,或者操作数与操作符不匹配的时候,js引擎不会报错,会把操作数转成对应的数据类型继续执行下去,这个转换是自动完成的,经常被叫做隐式类型转换。其实大部分开发者都或多或少了解过这一点,...

  • 3

    《Chrome V8 源码》40. Runtime substring 详解灰豆V8粉丝一枚,正在努力做一名V8源码的朗读者。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK