diff --git a/docs/capabilities.md b/docs/capabilities.md index a2abb130..17e55d88 100644 --- a/docs/capabilities.md +++ b/docs/capabilities.md @@ -92,7 +92,7 @@ Auto-generated by `script/capability-matrix.php`. Do not edit by hand. | `str_ends_with` | yes | yes | yes | standard | AOT PHPT | | `str_pad` | yes | yes | yes | standard | AOT PHPT | | `str_repeat` | yes | yes | yes | standard | AOT PHPT | -| `str_replace` | yes | no | no | standard | doc: VM only; not implemented for JIT in this compiler build | +| `str_replace` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `str_split` | yes | no | no | standard | doc: VM only; not implemented for JIT in this compiler build | | `str_starts_with` | yes | yes | yes | standard | AOT PHPT | | `strcmp` | yes | yes | yes | standard | | diff --git a/ext/standard/JitStrReplace.php b/ext/standard/JitStrReplace.php new file mode 100644 index 00000000..26b20217 --- /dev/null +++ b/ext/standard/JitStrReplace.php @@ -0,0 +1,92 @@ +structFieldMap['__string__']; + $searchLen = $context->builder->load( + $context->builder->structGep($search, $map['length']) + ); + $subjectLen = $context->builder->load( + $context->builder->structGep($subject, $map['length']) + ); + $subjectPtr = $context->builder->structGep($subject, $map['value']); + $searchPtr = $context->builder->structGep($search, $map['value']); + + $i64 = $context->getTypeFromString('int64'); + $zero = $i64->constInt(0, false); + + $resultSlot = $context->builder->alloca( + $context->getTypeFromString('__string__*'), + 1, + 'str_replace_result' + ); + $offsetSlot = $context->builder->alloca($i64, 1, 'str_replace_offset'); + $emptyResult = $context->builder->call($context->lookupFunction('__string__alloc'), $zero); + $context->builder->store($emptyResult, $resultSlot); + $context->builder->store($zero, $offsetSlot); + + $loopHead = BasicBlockHelper::append($context, 'str_replace_head'); + $loopBody = BasicBlockHelper::append($context, 'str_replace_body'); + $tailBlock = BasicBlockHelper::append($context, 'str_replace_tail'); + $doneBlock = BasicBlockHelper::append($context, 'str_replace_done'); + $context->builder->branch($loopHead); + + $context->builder->positionAtEnd($loopHead); + $offset = $context->builder->load($offsetSlot); + $pastEnd = $context->builder->icmp(Builder::INT_SGE, $offset, $subjectLen); + $context->builder->branchIf($pastEnd, $doneBlock, $loopBody); + + $context->builder->positionAtEnd($loopBody); + $searchFrom = $context->builder->gep($subjectPtr, $offset); + $found = $context->builder->call( + $context->lookupFunction('strstr'), + $searchFrom, + $searchPtr + ); + $null = $context->getTypeFromString('int8*')->constNull(); + $notFound = $context->builder->icmp(Builder::INT_EQ, $found, $null); + $matchBlock = BasicBlockHelper::append($context, 'str_replace_match'); + $context->builder->branchIf($notFound, $tailBlock, $matchBlock); + + $context->builder->positionAtEnd($matchBlock); + $foundInt = $context->builder->ptrToInt($found, $i64); + $baseInt = $context->builder->ptrToInt($subjectPtr, $i64); + $pos = $context->builder->sub($foundInt, $baseInt); + $prefixLen = $context->builder->sub($pos, $offset); + $prefix = string_trim::jitCopySlice($context, $subject, $subjectPtr, $offset, $prefixLen); + $acc = $context->builder->load($resultSlot); + $withPrefix = JitStringConcat::concat($context, $acc, $prefix); + $withReplace = JitStringConcat::concat($context, $withPrefix, $replace); + $context->builder->store($withReplace, $resultSlot); + $newOffset = $context->builder->add($pos, $searchLen); + $context->builder->store($newOffset, $offsetSlot); + $context->builder->branch($loopHead); + + $context->builder->positionAtEnd($tailBlock); + $offset = $context->builder->load($offsetSlot); + $tailLen = $context->builder->sub($subjectLen, $offset); + $tail = string_trim::jitCopySlice($context, $subject, $subjectPtr, $offset, $tailLen); + $acc = $context->builder->load($resultSlot); + $context->builder->store(JitStringConcat::concat($context, $acc, $tail), $resultSlot); + $context->builder->branch($doneBlock); + + $context->builder->positionAtEnd($doneBlock); + + return $context->builder->load($resultSlot); + } +} diff --git a/ext/standard/str_replace.php b/ext/standard/str_replace.php index 00d538cb..1c23cd76 100644 --- a/ext/standard/str_replace.php +++ b/ext/standard/str_replace.php @@ -19,7 +19,7 @@ use PHPLLVM\Value; /** - * str_replace() with string search, replace, and subject (subset of PHP; VM only). + * str_replace() with string search, replace, and subject (subset of PHP; LLVM JIT/AOT). */ final class str_replace extends Internal { @@ -50,6 +50,36 @@ public function execute(Frame $frame): void public function call(Context $context, JITVariable ...$args): Value { - throw new \LogicException('str_replace() is not implemented for JIT in this compiler build'); + $this->context = $context; + if (3 !== \count($args)) { + throw new \LogicException('str_replace() requires exactly three arguments in this compiler build'); + } + foreach ($args as $arg) { + if (JITVariable::TYPE_STRING !== $arg->type && JITVariable::TYPE_VALUE !== $arg->type) { + throw new \LogicException('str_replace() requires string arguments in this compiler build'); + } + } + + return JitStrReplace::replace( + $context, + self::jitStringArg($context, $args[0]), + self::jitStringArg($context, $args[1]), + self::jitStringArg($context, $args[2]) + ); + } + + private static function jitStringArg(Context $context, JITVariable $arg): Value + { + if (JITVariable::TYPE_STRING === $arg->type) { + return $context->helper->loadValue($arg); + } + if (JITVariable::TYPE_VALUE === $arg->type) { + return $context->builder->call( + $context->lookupFunction('__value__readString'), + $arg->value + ); + } + + throw new \LogicException('str_replace() requires string arguments in this compiler build'); } } diff --git a/test/compliance/cases/stdlib/str_replace_jit.phpt b/test/compliance/cases/stdlib/str_replace_jit.phpt new file mode 100644 index 00000000..82d3d19a --- /dev/null +++ b/test/compliance/cases/stdlib/str_replace_jit.phpt @@ -0,0 +1,11 @@ +--TEST-- +stdlib str_replace() JIT +--FILE-- +