Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions lib/JIT.php
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,15 @@ private function compileBlockInternal(
$dimOp = $block->getOperand($op->arg3);
$dim = $this->context->getVariableFromOp($dimOp);
$resultOp = $block->getOperand($op->arg1);
if ($value->type === Variable::TYPE_STRING) {
$charPtr = StringOffsetHelper::dimFetch(
$this->context,
$value->value,
$dim
);
$this->context->makeVariableFromValueOp($charPtr, $resultOp);
break;
}
if ($value->type === Variable::TYPE_HASHTABLE) {
$this->assignOperand(
$resultOp,
Expand Down Expand Up @@ -314,6 +323,21 @@ private function compileBlockInternal(
JIT\ValueEchoHelper::echo($this->context, $arg->value);
break;
case Variable::TYPE_STRING:
if ($arg->kind === Variable::KIND_VALUE
&& 'i8*' === $this->context->getStringFromType($arg->value->typeOf())
) {
$byte = $this->context->builder->load($arg->value);
$fmt = $this->context->builder->pointerCast(
$this->context->constantFromString('%c'),
$this->context->getTypeFromString('char*')
);
$this->context->builder->call(
$this->context->lookupFunction('printf'),
$fmt,
$byte
);
break;
}
$argValue = $this->context->helper->loadValue($arg);
$fmt = $this->context->builder->pointerCast(
$this->context->constantFromString("%.*s"),
Expand Down Expand Up @@ -565,6 +589,11 @@ private function assignOperand(Operand $result, Variable $value): void {
return;
}
$result = $this->context->getVariableFromOp($result);
if ($result->kind === Variable::KIND_VALUE && $result->type === Variable::TYPE_STRING) {
StringOffsetHelper::dimAssign($this->context, $result->value, $value);

return;
}
if ($result->kind !== Variable::KIND_VARIABLE) {
throw new \LogicException("Cannot assign to a value");
}
Expand Down
52 changes: 52 additions & 0 deletions lib/JIT/StringOffsetHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace PHPCompiler\JIT;

use PHPLLVM;

/**
* Byte-level string offset read/write for JIT (issue #198).
*/
final class StringOffsetHelper
{
public static function dimFetch(Context $context, PHPLLVM\Value $strSlot, Variable $dim): PHPLLVM\Value
{
$str = $context->builder->load($strSlot);
$map = $context->structFieldMap['__string__'];
$chars = $context->builder->structGep($str, $map['value']);
$offset = $context->builder->truncOrBitCast(
$context->helper->loadValue($dim),
$context->getTypeFromString('size_t')
);

return $context->builder->gep($chars, $offset);
}

public static function dimAssign(Context $context, PHPLLVM\Value $charPtr, Variable $value): void
{
$byte = self::assignByte($context, $value);
$context->builder->store($byte, $charPtr);
}

private static function assignByte(Context $context, Variable $value): PHPLLVM\Value
{
$i8 = $context->getTypeFromString('int8');
switch ($value->type) {
case Variable::TYPE_NATIVE_LONG:
$long = $context->helper->loadValue($value);
$trunc = $context->builder->truncOrBitCast($long, $i8);

return $trunc;
case Variable::TYPE_STRING:
$str = $context->helper->loadValue($value);
$map = $context->structFieldMap['__string__'];
$chars = $context->builder->structGep($str, $map['value']);

return $context->builder->load($chars);
default:
throw new \LogicException(
'String offset assignment supports int or string RHS in JIT (got type ' . $value->type . ')'
);
}
}
}
7 changes: 6 additions & 1 deletion lib/JIT/Variable.php
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,12 @@ public function toString(\gcc_jit_block_ptr $block): Variable {
public function dimFetch(self $dim, ?Type $expectedType = null): Variable {
switch ($this->type) {
case self::TYPE_STRING:
$ptr = $this->context->type->string->dimFetch($this->value, $dim->value);
$ptr = StringOffsetHelper::dimFetch(
$this->context,
$this->value,
$dim
);

return new Variable(
$this->context,
self::TYPE_STRING,
Expand Down
4 changes: 3 additions & 1 deletion lib/VM.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ public function run(Block $block): int {
}
$arg3 = $frame->scope[$op->arg3];
if ($container->type === Variable::TYPE_STRING) {
$arg1->string($container->toString()[$arg3->toInt()]);
$offset = new Variable(Variable::TYPE_STRING_OFFSET);
$offset->stringOffset($container, $arg3->toInt());
$arg1->indirect($offset);
} elseif ($container->type === Variable::TYPE_ARRAY) {
$arg1->indirect($container->toArray()->findVariable($arg3, false));
} else {
Expand Down
75 changes: 75 additions & 0 deletions lib/VM/Variable.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ final class Variable {
const TYPE_OBJECT = 5;
const TYPE_ARRAY = 6;
const TYPE_INDIRECT = 7;
/** Writable single-byte view of a parent string (Zend-style $str[$i]). */
const TYPE_STRING_OFFSET = 8;


const NUMERIC = self::TYPE_INTEGER | self::TYPE_FLOAT;
Expand All @@ -35,6 +37,8 @@ final class Variable {
private ObjectEntry $object;
private Variable $indirect;
private HashTable $array;
private Variable $stringOffsetParent;
private int $stringOffsetIndex;


public int $next = -1;
Expand Down Expand Up @@ -231,6 +235,8 @@ public function toString(): string {
return $this->bool ? '1' : '';
case self::TYPE_INDIRECT:
return $this->indirect->toString();
case self::TYPE_STRING_OFFSET:
return $this->readStringOffset();
case self::TYPE_ARRAY:
// todo: raise notice
return 'Array';
Expand Down Expand Up @@ -267,6 +273,16 @@ public function reset(): void {
unset($this->bool);
unset($this->object);
unset($this->indirect);
unset($this->stringOffsetParent);
unset($this->stringOffsetIndex);
}

public function stringOffset(Variable $parent, int $index): void
{
$this->reset();
$this->type = self::TYPE_STRING_OFFSET;
$this->stringOffsetParent = $parent;
$this->stringOffsetIndex = $index;
}

public function castFrom(int $type, self $var) {
Expand Down Expand Up @@ -307,6 +323,11 @@ public function copyFrom(self $var): void {
// destroy the indirection
$var = $var->indirect;
}
if ($this->type === self::TYPE_STRING_OFFSET) {
$this->writeStringOffset($var);

return;
}
switch ($var->type) {
case self::TYPE_NULL:
$this->null();
Expand Down Expand Up @@ -643,6 +664,60 @@ public function unaryOp(int $opCode, Variable $expr): void {
}
throw new \LogicException("UnaryOp $opCode not implemented for type $expr->type");
}

private function readStringOffset(): string
{
$parent = $this->stringOffsetParent->resolveIndirect();
if ($parent->type !== self::TYPE_STRING) {
throw new \LogicException('String offset parent is not a string');
}
$str = $parent->string;
$index = $this->stringOffsetIndex;
if ($index < 0 || $index >= strlen($str)) {
return '';
}

return $str[$index];
}

private function writeStringOffset(self $value): void
{
$parent = $this->stringOffsetParent->resolveIndirect();
if ($parent->type !== self::TYPE_STRING) {
throw new \LogicException('String offset parent is not a string');
}
$str = $parent->string;
$index = $this->stringOffsetIndex;
if ($index < 0) {
throw new \LogicException('Illegal string offset');
}
$byte = self::byteFromAssignValue($value);
$len = strlen($str);
if ($index >= $len) {
$str .= str_repeat("\0", $index - $len + 1);
}
$str[$index] = $byte;
$parent->string($str);
}

private static function byteFromAssignValue(self $value): string
{
$value = $value->resolveIndirect();
switch ($value->type) {
case self::TYPE_STRING:
$s = $value->string;

return '' === $s ? '' : $s[0];
case self::TYPE_INTEGER:
return chr($value->integer & 0xff);
case self::TYPE_NULL:
return "\0";
default:
$s = $value->toString();

return '' === $s ? '' : $s[0];
}
}
}

const TYPE_PAIR_INTEGER_INTEGER = (Variable::TYPE_INTEGER << 8) | Variable::TYPE_INTEGER;
Expand Down
14 changes: 14 additions & 0 deletions test/compliance/cases/language/string_offset.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
String offset read and assignment (ASCII bytes)
--FILE--
<?php
$s = 'abc';
echo $s[0], $s[1], $s[2], "\n";
$s[1] = 'z';
echo $s, "\n";
$s[0] = 65;
echo $s, "\n";
--EXPECT--
abc
azc
Azc
11 changes: 11 additions & 0 deletions test/compliance/cases/language/string_offset_jit.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
String offset read and assignment under JIT
--FILE--
<?php
$s = 'abc';
echo $s[0], $s[1], $s[2], "\n";
$s[1] = 'z';
echo $s, "\n";
--EXPECT--
abc
azc
Loading