JS: use native string to represent internals of java.lang.String

This commit is contained in:
Alexey Andreev 2023-10-18 19:24:47 +02:00
parent 0e2052d91c
commit 02b3c92912
18 changed files with 835 additions and 163 deletions

View File

@ -29,6 +29,10 @@ import org.teavm.classlib.impl.currency.CurrencyHelper;
import org.teavm.classlib.impl.lambda.LambdaMetafactorySubstitutor; import org.teavm.classlib.impl.lambda.LambdaMetafactorySubstitutor;
import org.teavm.classlib.impl.record.ObjectMethodsSubstitutor; import org.teavm.classlib.impl.record.ObjectMethodsSubstitutor;
import org.teavm.classlib.impl.reflection.ReflectionTransformer; import org.teavm.classlib.impl.reflection.ReflectionTransformer;
import org.teavm.classlib.impl.string.DefaultStringTransformer;
import org.teavm.classlib.impl.string.JSStringConstructorGenerator;
import org.teavm.classlib.impl.string.JSStringInjector;
import org.teavm.classlib.impl.string.JSStringTransformer;
import org.teavm.classlib.impl.tz.DateTimeZoneProvider; import org.teavm.classlib.impl.tz.DateTimeZoneProvider;
import org.teavm.classlib.impl.tz.DateTimeZoneProviderIntrinsic; import org.teavm.classlib.impl.tz.DateTimeZoneProviderIntrinsic;
import org.teavm.classlib.impl.tz.DateTimeZoneProviderPatch; import org.teavm.classlib.impl.tz.DateTimeZoneProviderPatch;
@ -186,6 +190,16 @@ public class JCLPlugin implements TeaVMPlugin {
installMetadata(host.getService(MetadataRegistration.class)); installMetadata(host.getService(MetadataRegistration.class));
host.add(new DeclaringClassDependencyListener()); host.add(new DeclaringClassDependencyListener());
applyTimeZoneDetection(host); applyTimeZoneDetection(host);
var js = host.getExtension(TeaVMJavaScriptHost.class);
if (js != null) {
host.add(new JSStringTransformer());
js.addInjectorProvider(new JSStringInjector());
js.add(new MethodReference(String.class, "<init>", Object.class, void.class),
new JSStringConstructorGenerator());
} else {
host.add(new DefaultStringTransformer());
}
} }
private void applyTimeZoneDetection(TeaVMHost host) { private void applyTimeZoneDetection(TeaVMHost host) {

View File

@ -0,0 +1,280 @@
/*
* Copyright 2023 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.impl.string;
import java.util.List;
import org.teavm.model.AccessLevel;
import org.teavm.model.ClassHolder;
import org.teavm.model.ClassHolderTransformer;
import org.teavm.model.ClassHolderTransformerContext;
import org.teavm.model.FieldHolder;
import org.teavm.model.FieldReference;
import org.teavm.model.MethodReference;
import org.teavm.model.Program;
import org.teavm.model.ValueType;
import org.teavm.model.instructions.ArrayElementType;
import org.teavm.model.instructions.ArrayLengthInstruction;
import org.teavm.model.instructions.ConstructArrayInstruction;
import org.teavm.model.instructions.GetElementInstruction;
import org.teavm.model.instructions.GetFieldInstruction;
import org.teavm.model.instructions.IntegerConstantInstruction;
import org.teavm.model.instructions.InvocationType;
import org.teavm.model.instructions.InvokeInstruction;
import org.teavm.model.instructions.PutFieldInstruction;
import org.teavm.model.instructions.UnwrapArrayInstruction;
public class DefaultStringTransformer implements ClassHolderTransformer {
@Override
public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) {
if (cls.getName().equals("java.lang.String")) {
transformString(cls);
}
}
private void transformString(ClassHolder cls) {
var fields = List.copyOf(cls.getFields());
for (var field : fields) {
cls.removeField(field);
}
var charactersField = new FieldHolder("characters");
charactersField.setType(ValueType.arrayOf(ValueType.CHARACTER));
charactersField.setLevel(AccessLevel.PRIVATE);
cls.addField(charactersField);
for (var field : fields) {
cls.addField(field);
}
for (var method : cls.getMethods()) {
if (method.getProgram() != null) {
transformProgram(method.getProgram());
}
}
}
private void transformProgram(Program program) {
for (var block : program.getBasicBlocks()) {
for (var instruction : block) {
if (!(instruction instanceof InvokeInstruction)) {
continue;
}
var invoke = (InvokeInstruction) instruction;
if (!invoke.getMethod().getClassName().equals("java.lang.String")) {
continue;
}
switch (invoke.getMethod().getName()) {
case "initWithEmptyChars":
replaceInitWithEmptyChars(invoke);
break;
case "borrowChars":
replaceBorrowChars(invoke);
break;
case "initWithCharArray":
replaceInitWithCharArray(invoke);
break;
case "takeCharArray":
replaceTakeCharArray(invoke);
break;
case "charactersLength":
replaceCharactersLength(invoke);
break;
case "charactersGet":
replaceCharactersGet(invoke);
break;
case "copyCharsToArray":
replaceCopyCharsToArray(invoke);
break;
case "fastCharArray":
replaceFastCharArray(invoke);
break;
}
}
}
}
private void replaceInitWithEmptyChars(InvokeInstruction invoke) {
var program = invoke.getProgram();
var getField = new GetFieldInstruction();
getField.setField(new FieldReference("java.lang.String", "EMPTY_CHARS"));
getField.setFieldType(ValueType.arrayOf(ValueType.CHARACTER));
getField.setReceiver(program.createVariable());
getField.setLocation(invoke.getLocation());
invoke.insertNext(getField);
var putField = new PutFieldInstruction();
putField.setField(new FieldReference("java.lang.String", "characters"));
putField.setFieldType(ValueType.arrayOf(ValueType.CHARACTER));
putField.setInstance(invoke.getInstance());
putField.setValue(getField.getReceiver());
putField.setLocation(invoke.getLocation());
getField.insertNext(putField);
invoke.delete();
}
private void replaceBorrowChars(InvokeInstruction invoke) {
var program = invoke.getProgram();
var getField = new GetFieldInstruction();
getField.setField(new FieldReference("java.lang.String", "characters"));
getField.setFieldType(ValueType.arrayOf(ValueType.CHARACTER));
getField.setInstance(invoke.getArguments().get(0));
getField.setReceiver(program.createVariable());
getField.setLocation(invoke.getLocation());
invoke.insertNext(getField);
var putField = new PutFieldInstruction();
putField.setField(new FieldReference("java.lang.String", "characters"));
putField.setFieldType(ValueType.arrayOf(ValueType.CHARACTER));
putField.setInstance(invoke.getInstance());
putField.setValue(getField.getReceiver());
putField.setLocation(invoke.getLocation());
getField.insertNext(putField);
invoke.delete();
}
private void replaceInitWithCharArray(InvokeInstruction invoke) {
var program = invoke.getProgram();
var createArray = new ConstructArrayInstruction();
createArray.setItemType(ValueType.CHARACTER);
createArray.setSize(invoke.getArguments().get(2));
createArray.setReceiver(program.createVariable());
createArray.setLocation(invoke.getLocation());
invoke.insertNext(createArray);
var zero = new IntegerConstantInstruction();
zero.setReceiver(program.createVariable());
zero.setLocation(invoke.getLocation());
createArray.insertNext(zero);
var arrayCopy = new InvokeInstruction();
arrayCopy.setType(InvocationType.SPECIAL);
arrayCopy.setMethod(new MethodReference(System.class, "arraycopy", Object.class, int.class,
Object.class, int.class, int.class, void.class));
arrayCopy.setArguments(invoke.getArguments().get(0), invoke.getArguments().get(1),
createArray.getReceiver(), zero.getReceiver(), invoke.getArguments().get(2));
zero.insertNext(arrayCopy);
var putField = new PutFieldInstruction();
putField.setField(new FieldReference("java.lang.String", "characters"));
putField.setFieldType(ValueType.arrayOf(ValueType.CHARACTER));
putField.setInstance(program.variableAt(0));
putField.setValue(createArray.getReceiver());
putField.setLocation(invoke.getLocation());
arrayCopy.insertNext(putField);
invoke.delete();
}
private void replaceTakeCharArray(InvokeInstruction invoke) {
var putField = new PutFieldInstruction();
putField.setField(new FieldReference("java.lang.String", "characters"));
putField.setFieldType(ValueType.arrayOf(ValueType.CHARACTER));
putField.setInstance(invoke.getInstance());
putField.setValue(invoke.getArguments().get(0));
putField.setLocation(invoke.getLocation());
invoke.replace(putField);
}
private void replaceCharactersLength(InvokeInstruction invoke) {
var program = invoke.getProgram();
var getField = new GetFieldInstruction();
getField.setField(new FieldReference("java.lang.String", "characters"));
getField.setFieldType(ValueType.arrayOf(ValueType.CHARACTER));
getField.setInstance(invoke.getInstance());
getField.setReceiver(program.createVariable());
getField.setLocation(invoke.getLocation());
invoke.insertNext(getField);
var unwrapArray = new UnwrapArrayInstruction(ArrayElementType.CHAR);
unwrapArray.setArray(getField.getReceiver());
unwrapArray.setReceiver(program.createVariable());
unwrapArray.setLocation(invoke.getLocation());
getField.insertNext(unwrapArray);
var getLength = new ArrayLengthInstruction();
getLength.setArray(unwrapArray.getReceiver());
getLength.setReceiver(invoke.getReceiver());
getLength.setLocation(invoke.getLocation());
unwrapArray.insertNext(getLength);
invoke.delete();
}
private void replaceCharactersGet(InvokeInstruction invoke) {
var program = invoke.getProgram();
var getField = new GetFieldInstruction();
getField.setField(new FieldReference("java.lang.String", "characters"));
getField.setFieldType(ValueType.arrayOf(ValueType.CHARACTER));
getField.setInstance(invoke.getInstance());
getField.setReceiver(program.createVariable());
getField.setLocation(invoke.getLocation());
invoke.insertNext(getField);
var unwrapArray = new UnwrapArrayInstruction(ArrayElementType.CHAR);
unwrapArray.setArray(getField.getReceiver());
unwrapArray.setReceiver(program.createVariable());
unwrapArray.setLocation(invoke.getLocation());
getField.insertNext(unwrapArray);
var getFromArray = new GetElementInstruction(ArrayElementType.CHAR);
getFromArray.setArray(unwrapArray.getReceiver());
getFromArray.setReceiver(invoke.getReceiver());
getFromArray.setIndex(invoke.getArguments().get(0));
getFromArray.setLocation(invoke.getLocation());
unwrapArray.insertNext(getFromArray);
invoke.delete();
}
private void replaceCopyCharsToArray(InvokeInstruction invoke) {
var program = invoke.getProgram();
var getField = new GetFieldInstruction();
getField.setField(new FieldReference("java.lang.String", "characters"));
getField.setFieldType(ValueType.arrayOf(ValueType.CHARACTER));
getField.setInstance(invoke.getInstance());
getField.setReceiver(program.createVariable());
getField.setLocation(invoke.getLocation());
invoke.insertNext(getField);
var arrayCopy = new InvokeInstruction();
arrayCopy.setType(InvocationType.SPECIAL);
arrayCopy.setMethod(new MethodReference(System.class, "arraycopy", Object.class, int.class,
Object.class, int.class, int.class, void.class));
arrayCopy.setArguments(getField.getReceiver(), invoke.getArguments().get(0), invoke.getArguments().get(1),
invoke.getArguments().get(2), invoke.getArguments().get(3));
getField.insertNext(arrayCopy);
invoke.delete();
}
private void replaceFastCharArray(InvokeInstruction invoke) {
var getField = new GetFieldInstruction();
getField.setField(new FieldReference("java.lang.String", "characters"));
getField.setFieldType(ValueType.arrayOf(ValueType.CHARACTER));
getField.setInstance(invoke.getInstance());
getField.setReceiver(invoke.getReceiver());
getField.setLocation(invoke.getLocation());
invoke.replace(getField);
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright 2023 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.impl.string;
import java.io.IOException;
import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.backend.javascript.spi.Generator;
import org.teavm.backend.javascript.spi.GeneratorContext;
import org.teavm.model.MethodReference;
public class JSStringConstructorGenerator implements Generator {
@Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
writer.append(context.getParameterName(0));
writer.append(".").appendField(JSStringInjector.NATIVE_FIELD).ws().append("=").ws();
writer.append(context.getParameterName(1)).append(";").softNewLine();
}
}

View File

@ -0,0 +1,234 @@
/*
* Copyright 2023 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.impl.string;
import java.io.IOException;
import java.util.function.Function;
import org.teavm.backend.javascript.ProviderContext;
import org.teavm.backend.javascript.spi.Injector;
import org.teavm.backend.javascript.spi.InjectorContext;
import org.teavm.model.FieldReference;
import org.teavm.model.MethodReference;
public class JSStringInjector implements Injector, Function<ProviderContext, Injector> {
static final FieldReference NATIVE_FIELD = new FieldReference("java.lang.String", "nativeString");
@Override
public Injector apply(ProviderContext providerContext) {
switch (providerContext.getMethod().getName()) {
case "initWithEmptyChars":
case "borrowChars":
case "initWithCharArray":
case "takeCharArray":
case "charactersLength":
case "charactersGet":
case "copyCharsToArray":
case "fastCharArray":
case "nativeString":
case "substringJS":
case "toLowerCaseJS":
case "toUpperCaseJS":
case "intern":
case "stripJS":
case "stripLeadingJS":
case "stripTrailingJS":
return this;
}
return null;
}
@Override
public void generate(InjectorContext context, MethodReference methodRef) throws IOException {
switch (methodRef.getName()) {
case "initWithEmptyChars":
initWithEmptyChars(context);
break;
case "borrowChars":
borrowChars(context);
break;
case "initWithCharArray":
initWithCharArray(context);
break;
case "takeCharArray":
takeCharArray(context);
break;
case "charactersLength":
charactersLength(context);
break;
case "charactersGet":
charactersGet(context);
break;
case "copyCharsToArray":
copyCharsToArray(context);
break;
case "fastCharArray":
fastCharArray(context);
break;
case "nativeString":
nativeString(context);
break;
case "substringJS":
substringJS(context);
break;
case "toLowerCaseJS":
toLowerCaseJS(context);
break;
case "toUpperCaseJS":
toUpperCaseJS(context);
break;
case "intern":
intern(context);
break;
case "stripJS":
stripJS(context);
break;
case "stripLeadingJS":
stripLeadingJS(context);
break;
case "stripTrailingJS":
stripTrailingJS(context);
break;
}
}
private void initWithEmptyChars(InjectorContext context) throws IOException {
var writer = context.getWriter();
context.writeExpr(context.getArgument(0));
writer.append(".").appendField(NATIVE_FIELD).ws().append("=").ws().append("\"\"");
}
private void borrowChars(InjectorContext context) throws IOException {
var writer = context.getWriter();
context.writeExpr(context.getArgument(0));
writer.append(".").appendField(NATIVE_FIELD).ws().append("=").ws();
context.writeExpr(context.getArgument(1));
writer.append(".").appendField(NATIVE_FIELD);
}
private void initWithCharArray(InjectorContext context) throws IOException {
var writer = context.getWriter();
context.writeExpr(context.getArgument(0));
writer.append(".").appendField(NATIVE_FIELD).ws().append("=").ws();
writer.append("$rt_charArrayToString").append("(");
context.writeExpr(context.getArgument(1));
writer.append(".data,").ws();
context.writeExpr(context.getArgument(2));
writer.append(",").ws();
context.writeExpr(context.getArgument(3));
writer.append(")");
}
private void takeCharArray(InjectorContext context) throws IOException {
var writer = context.getWriter();
context.writeExpr(context.getArgument(0));
writer.append(".").appendField(NATIVE_FIELD).ws().append("=").ws();
writer.append("$rt_fullArrayToString").append("(");
context.writeExpr(context.getArgument(1));
writer.append(".data)");
}
private void charactersLength(InjectorContext context) throws IOException {
var writer = context.getWriter();
context.writeExpr(context.getArgument(0));
writer.append(".").appendField(NATIVE_FIELD).append(".length");
}
private void charactersGet(InjectorContext context) throws IOException {
var writer = context.getWriter();
context.writeExpr(context.getArgument(0));
writer.append(".").appendField(NATIVE_FIELD).append(".charCodeAt(");
context.writeExpr(context.getArgument(1));
writer.append(")");
}
private void copyCharsToArray(InjectorContext context) throws IOException {
var writer = context.getWriter();
writer.append("$rt_stringToCharArray").append("(");
context.writeExpr(context.getArgument(0));
writer.append(".").appendField(NATIVE_FIELD);
writer.append(",").ws();
context.writeExpr(context.getArgument(1));
writer.append(",").ws();
context.writeExpr(context.getArgument(2));
writer.append(".data");
writer.append(",").ws();
context.writeExpr(context.getArgument(3));
writer.append(",").ws();
context.writeExpr(context.getArgument(4));
writer.append(")");
}
private void substringJS(InjectorContext context) throws IOException {
var writer = context.getWriter();
context.writeExpr(context.getArgument(0));
writer.append(".substring(");
context.writeExpr(context.getArgument(1));
writer.append(",").ws();
context.writeExpr(context.getArgument(2));
writer.append(")");
}
private void fastCharArray(InjectorContext context) throws IOException {
var writer = context.getWriter();
writer.append("$rt_fastStringToCharArray").append("(");
context.writeExpr(context.getArgument(0));
writer.append(".").appendField(NATIVE_FIELD);
writer.append(")");
}
private void nativeString(InjectorContext context) throws IOException {
var writer = context.getWriter();
context.writeExpr(context.getArgument(0));
writer.append(".").appendField(NATIVE_FIELD);
}
private void toLowerCaseJS(InjectorContext context) throws IOException {
var writer = context.getWriter();
context.writeExpr(context.getArgument(0));
writer.append(".toLowerCase()");
}
private void toUpperCaseJS(InjectorContext context) throws IOException {
var writer = context.getWriter();
context.writeExpr(context.getArgument(0));
writer.append(".toUpperCase()");
}
private void intern(InjectorContext context) throws IOException {
var writer = context.getWriter();
writer.appendFunction("$rt_intern").append("(");
context.writeExpr(context.getArgument(0));
writer.append(")");
}
private void stripJS(InjectorContext context) throws IOException {
var writer = context.getWriter();
context.writeExpr(context.getArgument(0));
writer.append(".trim()");
}
private void stripLeadingJS(InjectorContext context) throws IOException {
var writer = context.getWriter();
context.writeExpr(context.getArgument(0));
writer.append(".trimStart()");
}
private void stripTrailingJS(InjectorContext context) throws IOException {
var writer = context.getWriter();
context.writeExpr(context.getArgument(0));
writer.append(".trimEnd()");
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2023 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.impl.string;
import org.teavm.interop.NoSideEffects;
import org.teavm.model.AccessLevel;
import org.teavm.model.AnnotationHolder;
import org.teavm.model.ClassHolder;
import org.teavm.model.ClassHolderTransformer;
import org.teavm.model.ClassHolderTransformerContext;
import org.teavm.model.ElementModifier;
import org.teavm.model.FieldHolder;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.ValueType;
public class JSStringTransformer implements ClassHolderTransformer {
@Override
public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) {
if (cls.getName().equals("java.lang.String")) {
var charactersField = new FieldHolder("nativeString");
charactersField.setType(ValueType.object("java.lang.Object"));
charactersField.setLevel(AccessLevel.PRIVATE);
cls.addField(charactersField);
var method = cls.getMethod(new MethodDescriptor("<init>", Object.class, void.class));
method.setProgram(null);
method.getModifiers().add(ElementModifier.NATIVE);
method.getAnnotations().add(new AnnotationHolder(NoSideEffects.class.getName()));
}
}
}

View File

@ -15,23 +15,12 @@
*/ */
package org.teavm.classlib.java.lang; package org.teavm.classlib.java.lang;
import java.io.IOException;
import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.backend.javascript.spi.Generator;
import org.teavm.backend.javascript.spi.GeneratorContext;
import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyAgent;
import org.teavm.dependency.DependencyPlugin; import org.teavm.dependency.DependencyPlugin;
import org.teavm.dependency.MethodDependency; import org.teavm.dependency.MethodDependency;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
public class StringNativeGenerator implements Generator, DependencyPlugin { public class StringNativeDependency implements DependencyPlugin {
@Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
if (methodRef.getName().equals("intern")) {
writer.append("return $rt_intern(").append(context.getParameterName(0)).append(");").softNewLine();
}
}
@Override @Override
public void methodReached(DependencyAgent agent, MethodDependency method) { public void methodReached(DependencyAgent agent, MethodDependency method) {
if (method.getReference().getName().equals("intern")) { if (method.getReference().getName().equals("intern")) {

View File

@ -16,7 +16,7 @@
package org.teavm.classlib.java.lang; package org.teavm.classlib.java.lang;
import java.util.Locale; import java.util.Locale;
import org.teavm.backend.javascript.spi.GeneratedBy; import org.teavm.classlib.PlatformDetector;
import org.teavm.classlib.java.io.TSerializable; import org.teavm.classlib.java.io.TSerializable;
import org.teavm.classlib.java.io.TUnsupportedEncodingException; import org.teavm.classlib.java.io.TUnsupportedEncodingException;
import org.teavm.classlib.java.nio.TByteBuffer; import org.teavm.classlib.java.nio.TByteBuffer;
@ -35,32 +35,50 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
private static final char[] EMPTY_CHARS = new char[0]; private static final char[] EMPTY_CHARS = new char[0];
private static final TString EMPTY = new TString(); private static final TString EMPTY = new TString();
public static final TComparator<TString> CASE_INSENSITIVE_ORDER = (o1, o2) -> o1.compareToIgnoreCase(o2); public static final TComparator<TString> CASE_INSENSITIVE_ORDER = (o1, o2) -> o1.compareToIgnoreCase(o2);
private char[] characters;
private transient int hashCode; private transient int hashCode;
public TString() { public TString() {
this.characters = EMPTY_CHARS; initWithEmptyChars();
} }
@NoSideEffects
private native void initWithEmptyChars();
public TString(TString other) { public TString(TString other) {
characters = other.characters; borrowChars(other);
} }
@NoSideEffects
private native void borrowChars(TString other);
public TString(char[] characters) { public TString(char[] characters) {
this(characters, 0, characters.length); this(characters, 0, characters.length);
} }
public TString(char[] value, int offset, int count) { public TString(Object nativeString) {
this.characters = new char[count];
System.arraycopy(value, offset, this.characters, 0, count);
} }
private native Object nativeString();
public TString(char[] value, int offset, int count) {
if (offset < 0 || count < 0 || offset + count > value.length) {
throw new IndexOutOfBoundsException();
}
initWithCharArray(value, offset, count);
}
@NoSideEffects
private native void initWithCharArray(char[] value, int offset, int count);
static TString fromArray(char[] characters) { static TString fromArray(char[] characters) {
var s = new TString(); var s = new TString();
s.characters = characters; s.takeCharArray(characters);
return s; return s;
} }
@NoSideEffects
private native void takeCharArray(char[] characters);
public TString(byte[] bytes, int offset, int length, TString charsetName) throws TUnsupportedEncodingException { public TString(byte[] bytes, int offset, int length, TString charsetName) throws TUnsupportedEncodingException {
this(bytes, offset, length, TCharset.forName(charsetName.toString())); this(bytes, offset, length, TCharset.forName(charsetName.toString()));
} }
@ -86,7 +104,7 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
} }
public TString(int[] codePoints, int offset, int count) { public TString(int[] codePoints, int offset, int count) {
characters = new char[count * 2]; var characters = new char[count * 2];
int charCount = 0; int charCount = 0;
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
int codePoint = codePoints[offset++]; int codePoint = codePoints[offset++];
@ -100,16 +118,19 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
if (charCount < characters.length) { if (charCount < characters.length) {
characters = TArrays.copyOf(characters, charCount); characters = TArrays.copyOf(characters, charCount);
} }
takeCharArray(characters);
} }
private void initWithBytes(byte[] bytes, int offset, int length, TCharset charset) { private void initWithBytes(byte[] bytes, int offset, int length, TCharset charset) {
TCharBuffer buffer = charset.decode(TByteBuffer.wrap(bytes, offset, length)); TCharBuffer buffer = charset.decode(TByteBuffer.wrap(bytes, offset, length));
char[] characters;
if (buffer.hasArray() && buffer.position() == 0 && buffer.limit() == buffer.capacity()) { if (buffer.hasArray() && buffer.position() == 0 && buffer.limit() == buffer.capacity()) {
characters = buffer.array(); characters = buffer.array();
} else { } else {
characters = new char[buffer.remaining()]; characters = new char[buffer.remaining()];
buffer.get(characters); buffer.get(characters);
} }
takeCharArray(characters);
} }
public TString(TStringBuilder sb) { public TString(TStringBuilder sb) {
@ -117,7 +138,7 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
} }
private TString(int length) { private TString(int length) {
this.characters = new char[length]; takeCharArray(new char[length]);
} }
private static TString allocate(int size) { private static TString allocate(int size) {
@ -126,10 +147,10 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
@Override @Override
public char charAt(int index) { public char charAt(int index) {
if (index < 0 || index >= characters.length) { if (index < 0 || index >= charactersLength()) {
throw new TStringIndexOutOfBoundsException(); throw new TStringIndexOutOfBoundsException();
} }
return characters[index]; return charactersGet(index);
} }
public int codePointAt(int index) { public int codePointAt(int index) {
@ -150,17 +171,23 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
@Override @Override
public int length() { public int length() {
return characters.length; return charactersLength();
} }
@NoSideEffects
private native int charactersLength();
@NoSideEffects
private native char charactersGet(int index);
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
return characters.length == 0; return charactersLength() == 0;
} }
public boolean isBlank() { public boolean isBlank() {
for (int i = 0; i < characters.length; i++) { for (int i = 0; i < charactersLength(); i++) {
if (characters[i] != ' ') { if (charactersGet(i) != ' ') {
return false; return false;
} }
} }
@ -172,15 +199,18 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
|| dstBegin + (srcEnd - srcBegin) > dst.length) { || dstBegin + (srcEnd - srcBegin) > dst.length) {
throw new TIndexOutOfBoundsException(); throw new TIndexOutOfBoundsException();
} }
System.arraycopy(characters, srcBegin, dst, dstBegin, srcEnd - srcBegin); copyCharsToArray(srcBegin, dst, dstBegin, srcEnd - srcBegin);
} }
@NoSideEffects
private native void copyCharsToArray(int begin, char[] dst, int dstBegin, int length);
public boolean contentEquals(TStringBuffer buffer) { public boolean contentEquals(TStringBuffer buffer) {
if (characters.length != buffer.length()) { if (charactersLength() != buffer.length()) {
return false; return false;
} }
for (int i = 0; i < characters.length; ++i) { for (int i = 0; i < charactersLength(); ++i) {
if (characters[i] != buffer.charAt(i)) { if (charactersGet(i) != buffer.charAt(i)) {
return false; return false;
} }
} }
@ -191,11 +221,11 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
if (this == charSeq) { if (this == charSeq) {
return true; return true;
} }
if (characters.length != charSeq.length()) { if (charactersLength() != charSeq.length()) {
return false; return false;
} }
for (int i = 0; i < characters.length; ++i) { for (int i = 0; i < charactersLength(); ++i) {
if (characters[i] != charSeq.charAt(i)) { if (charactersGet(i) != charSeq.charAt(i)) {
return false; return false;
} }
} }
@ -302,8 +332,8 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
fromIndex = Math.max(0, fromIndex); fromIndex = Math.max(0, fromIndex);
if (ch < TCharacter.MIN_SUPPLEMENTARY_CODE_POINT) { if (ch < TCharacter.MIN_SUPPLEMENTARY_CODE_POINT) {
char bmpChar = (char) ch; char bmpChar = (char) ch;
for (int i = fromIndex; i < characters.length; ++i) { for (int i = fromIndex; i < charactersLength(); ++i) {
if (characters[i] == bmpChar) { if (charactersGet(i) == bmpChar) {
return i; return i;
} }
} }
@ -311,8 +341,8 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
} else { } else {
char hi = TCharacter.highSurrogate(ch); char hi = TCharacter.highSurrogate(ch);
char lo = TCharacter.lowSurrogate(ch); char lo = TCharacter.lowSurrogate(ch);
for (int i = fromIndex; i < characters.length - 1; ++i) { for (int i = fromIndex; i < charactersLength() - 1; ++i) {
if (characters[i] == hi && characters[i + 1] == lo) { if (charactersGet(i) == hi && charactersGet(i + 1) == lo) {
return i; return i;
} }
} }
@ -329,7 +359,7 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
if (ch < TCharacter.MIN_SUPPLEMENTARY_CODE_POINT) { if (ch < TCharacter.MIN_SUPPLEMENTARY_CODE_POINT) {
char bmpChar = (char) ch; char bmpChar = (char) ch;
for (int i = fromIndex; i >= 0; --i) { for (int i = fromIndex; i >= 0; --i) {
if (characters[i] == bmpChar) { if (charactersGet(i) == bmpChar) {
return i; return i;
} }
} }
@ -338,7 +368,7 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
char hi = TCharacter.highSurrogate(ch); char hi = TCharacter.highSurrogate(ch);
char lo = TCharacter.lowSurrogate(ch); char lo = TCharacter.lowSurrogate(ch);
for (int i = fromIndex; i >= 1; --i) { for (int i = fromIndex; i >= 1; --i) {
if (characters[i] == lo && characters[i - 1] == hi) { if (charactersGet(i) == lo && charactersGet(i - 1) == hi) {
return i - 1; return i - 1;
} }
} }
@ -394,12 +424,21 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
if (beginIndex == endIndex) { if (beginIndex == endIndex) {
return EMPTY; return EMPTY;
} }
if (PlatformDetector.isJavaScript()) {
var nativeSubstring = substringJS(nativeString(), beginIndex, endIndex);
return nativeSubstring != nativeString() ? new TString(nativeSubstring) : this;
}
if (beginIndex == 0 && endIndex == length()) { if (beginIndex == 0 && endIndex == length()) {
return this; return this;
} }
return new TString(characters, beginIndex, endIndex - beginIndex); return new TString(fastCharArray(), beginIndex, endIndex - beginIndex);
} }
@NoSideEffects
private native static Object substringJS(Object nativeString, int start, int end);
public TString substring(int beginIndex) { public TString substring(int beginIndex) {
return substring(beginIndex, length()); return substring(beginIndex, length());
} }
@ -485,6 +524,10 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
} }
public TString strip() { public TString strip() {
if (PlatformDetector.isJavaScript()) {
var result = stripJS(nativeString());
return result != nativeString() ? new TString(result) : this;
}
var lower = 0; var lower = 0;
var upper = length() - 1; var upper = length() - 1;
while (lower <= upper && Character.isWhitespace(charAt(lower))) { while (lower <= upper && Character.isWhitespace(charAt(lower))) {
@ -496,7 +539,13 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
return substring(lower, upper + 1); return substring(lower, upper + 1);
} }
private static native Object stripJS(Object nativeString);
public TString stripLeading() { public TString stripLeading() {
if (PlatformDetector.isJavaScript()) {
var result = stripLeadingJS(nativeString());
return result != nativeString() ? new TString(result) : this;
}
var lower = 0; var lower = 0;
while (lower < length() && Character.isWhitespace(charAt(lower))) { while (lower < length() && Character.isWhitespace(charAt(lower))) {
++lower; ++lower;
@ -504,7 +553,13 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
return substring(lower, length()); return substring(lower, length());
} }
private static native Object stripLeadingJS(Object nativeString);
public TString stripTrailing() { public TString stripTrailing() {
if (PlatformDetector.isJavaScript()) {
var result = stripTrailingJS(nativeString());
return result != nativeString() ? new TString(result) : this;
}
var upper = length() - 1; var upper = length() - 1;
while (0 <= upper && Character.isWhitespace(charAt(upper))) { while (0 <= upper && Character.isWhitespace(charAt(upper))) {
--upper; --upper;
@ -512,15 +567,17 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
return substring(0, upper + 1); return substring(0, upper + 1);
} }
private static native Object stripTrailingJS(Object nativeString);
@Override @Override
public String toString() { public String toString() {
return (String) (Object) this; return (String) (Object) this;
} }
public char[] toCharArray() { public char[] toCharArray() {
char[] array = new char[characters.length]; char[] array = new char[charactersLength()];
for (int i = 0; i < array.length; ++i) { for (int i = 0; i < array.length; ++i) {
array[i] = characters[i]; array[i] = charAt(i);
} }
return array; return array;
} }
@ -577,7 +634,10 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
if (!(other instanceof TString)) { if (!(other instanceof TString)) {
return false; return false;
} }
TString str = (TString) other; var str = (TString) other;
if (PlatformDetector.isJavaScript()) {
return nativeString() == str.nativeString();
} else {
if (str.length() != length()) { if (str.length() != length()) {
return false; return false;
} }
@ -588,6 +648,7 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
} }
return true; return true;
} }
}
public boolean equalsIgnoreCase(TString other) { public boolean equalsIgnoreCase(TString other) {
if (this == other) { if (this == other) {
@ -616,7 +677,7 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
} }
public byte[] getBytes(TCharset charset) { public byte[] getBytes(TCharset charset) {
TByteBuffer buffer = charset.encode(TCharBuffer.wrap(characters)); TByteBuffer buffer = charset.encode(TCharBuffer.wrap(fastCharArray()));
if (buffer.hasArray() && buffer.position() == 0 && buffer.limit() == buffer.capacity()) { if (buffer.hasArray() && buffer.position() == 0 && buffer.limit() == buffer.capacity()) {
return buffer.array(); return buffer.array();
} else { } else {
@ -626,24 +687,33 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
} }
} }
@NoSideEffects
private native char[] fastCharArray();
@Override @Override
public int hashCode() { public int hashCode() {
if (hashCode == 0) { if (hashCode == 0) {
for (char c : characters) { for (var i = 0; i < charactersLength(); ++i) {
hashCode = 31 * hashCode + c; hashCode = 31 * hashCode + charactersGet(i);
} }
} }
return hashCode; return hashCode;
} }
public TString toLowerCase() { public TString toLowerCase() {
if (PlatformDetector.isJavaScript()) {
var lowerCase = toLowerCaseJS(nativeString());
return lowerCase != nativeString() ? new TString(lowerCase) : this;
}
if (isEmpty()) { if (isEmpty()) {
return this; return this;
} }
var hasCharsToTransform = false; var hasCharsToTransform = false;
var hasSurrogates = false; var hasSurrogates = false;
for (var c : characters) { for (var i = 0; i < charactersLength(); ++i) {
var c = charactersGet(i);
if (Character.toLowerCase(c) != c) { if (Character.toLowerCase(c) != c) {
hasCharsToTransform = true; hasCharsToTransform = true;
break; break;
@ -658,24 +728,28 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
return hasSurrogates ? toLowerCaseCodePoints() : toLowerCaseChars(); return hasSurrogates ? toLowerCaseCodePoints() : toLowerCaseChars();
} }
@NoSideEffects
private static native Object toLowerCaseJS(Object nativeString);
private TString toLowerCaseChars() { private TString toLowerCaseChars() {
var chars = new char[characters.length]; var chars = new char[charactersLength()];
for (int i = 0; i < characters.length; ++i) { for (int i = 0; i < charactersLength(); ++i) {
chars[i] = TCharacter.toLowerCase(characters[i]); chars[i] = TCharacter.toLowerCase(charactersGet(i));
} }
return new TString(chars); return new TString(chars);
} }
private TString toLowerCaseCodePoints() { private TString toLowerCaseCodePoints() {
int[] codePoints = new int[characters.length]; int[] codePoints = new int[charactersLength()];
int codePointCount = 0; int codePointCount = 0;
for (int i = 0; i < characters.length; ++i) { var length = charactersLength();
if (i == characters.length - 1 || !TCharacter.isHighSurrogate(characters[i]) for (int i = 0; i < charactersLength(); ++i) {
|| !TCharacter.isLowSurrogate(characters[i + 1])) { if (i == length - 1 || !TCharacter.isHighSurrogate(charactersGet(i))
codePoints[codePointCount++] = TCharacter.toLowerCase(characters[i]); || !TCharacter.isLowSurrogate(charactersGet(i + 1))) {
codePoints[codePointCount++] = TCharacter.toLowerCase(charactersGet(i));
} else { } else {
codePoints[codePointCount++] = TCharacter.toLowerCase(TCharacter.toCodePoint( codePoints[codePointCount++] = TCharacter.toLowerCase(TCharacter.toCodePoint(
characters[i], characters[i + 1])); charactersGet(i), charactersGet(i + 1)));
++i; ++i;
} }
} }
@ -687,13 +761,19 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
} }
public TString toUpperCase() { public TString toUpperCase() {
if (PlatformDetector.isJavaScript()) {
var upperCase = toUpperCaseJS(nativeString());
return upperCase != nativeString() ? new TString(upperCase) : this;
}
if (isEmpty()) { if (isEmpty()) {
return this; return this;
} }
var hasCharsToTransform = false; var hasCharsToTransform = false;
var hasSurrogates = false; var hasSurrogates = false;
for (var c : characters) { for (var i = 0; i < charactersLength(); ++i) {
var c = charactersGet(i);
if (Character.toUpperCase(c) != c) { if (Character.toUpperCase(c) != c) {
hasCharsToTransform = true; hasCharsToTransform = true;
break; break;
@ -708,24 +788,27 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
return hasSurrogates ? toUpperCaseCodePoints() : toUpperCaseChars(); return hasSurrogates ? toUpperCaseCodePoints() : toUpperCaseChars();
} }
@NoSideEffects
private static native Object toUpperCaseJS(Object nativeString);
private TString toUpperCaseChars() { private TString toUpperCaseChars() {
var chars = new char[characters.length]; var chars = new char[charactersLength()];
for (int i = 0; i < characters.length; ++i) { for (int i = 0; i < charactersLength(); ++i) {
chars[i] = TCharacter.toUpperCase(characters[i]); chars[i] = TCharacter.toUpperCase(charactersGet(i));
} }
return new TString(chars); return new TString(chars);
} }
private TString toUpperCaseCodePoints() { private TString toUpperCaseCodePoints() {
int[] codePoints = new int[characters.length]; int[] codePoints = new int[charactersLength()];
int codePointCount = 0; int codePointCount = 0;
for (int i = 0; i < characters.length; ++i) { for (int i = 0; i < charactersLength(); ++i) {
if (i == characters.length - 1 || !TCharacter.isHighSurrogate(characters[i]) if (i == charactersLength() - 1 || !TCharacter.isHighSurrogate(charactersGet(i))
|| !TCharacter.isLowSurrogate(characters[i + 1])) { || !TCharacter.isLowSurrogate(charactersGet(i + 1))) {
codePoints[codePointCount++] = TCharacter.toUpperCase(characters[i]); codePoints[codePointCount++] = TCharacter.toUpperCase(charactersGet(i));
} else { } else {
codePoints[codePointCount++] = TCharacter.toUpperCase(TCharacter.toCodePoint( codePoints[codePointCount++] = TCharacter.toUpperCase(TCharacter.toCodePoint(
characters[i], characters[i + 1])); charactersGet(i), charactersGet(i + 1)));
++i; ++i;
} }
} }
@ -736,8 +819,7 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
return toUpperCase(); return toUpperCase();
} }
@GeneratedBy(StringNativeGenerator.class) @PluggableDependency(StringNativeDependency.class)
@PluggableDependency(StringNativeGenerator.class)
@NoSideEffects @NoSideEffects
public native TString intern(); public native TString intern();
@ -820,10 +902,10 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
if (count == 1) { if (count == 1) {
return this; return this;
} }
if (characters.length == 0 || count == 0) { if (charactersLength() == 0 || count == 0) {
return EMPTY; return EMPTY;
} }
var chars = new char[characters.length * count]; var chars = new char[charactersLength() * count];
var j = 0; var j = 0;
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
getChars(0, length(), chars, j); getChars(0, length(), chars, j);

View File

@ -266,9 +266,8 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
dep.getResult().propagate(dependencyAnalyzer.getType("java.lang.Class")); dep.getResult().propagate(dependencyAnalyzer.getType("java.lang.Class"));
dep.use(); dep.use();
dep = dependencyAnalyzer.linkMethod(new MethodReference(String.class, "<init>", char[].class, void.class)); dep = dependencyAnalyzer.linkMethod(new MethodReference(String.class, "<init>", Object.class, void.class));
dep.getVariable(0).propagate(stringType); dep.getVariable(0).propagate(stringType);
dep.getVariable(1).propagate(dependencyAnalyzer.getType("[C"));
dep.use(); dep.use();
dependencyAnalyzer.linkField(new FieldReference(String.class.getName(), "characters")); dependencyAnalyzer.linkField(new FieldReference(String.class.getName(), "characters"));

View File

@ -263,7 +263,8 @@ public class Renderer implements RenderingManager {
"$rt_createShortArray", "$rt_createCharArray", "$rt_createIntArray", "$rt_createLongArray", "$rt_createShortArray", "$rt_createCharArray", "$rt_createIntArray", "$rt_createLongArray",
"$rt_createFloatArray", "$rt_createDoubleArray", "$rt_compare", "$rt_createFloatArray", "$rt_createDoubleArray", "$rt_compare",
"$rt_castToClass", "$rt_castToInterface", "$rt_equalDoubles", "$rt_castToClass", "$rt_castToInterface", "$rt_equalDoubles",
"Long_toNumber", "Long_fromInt", "Long_fromNumber", "Long_create", "Long_ZERO", "$rt_str", "Long_toNumber", "Long_fromInt", "Long_fromNumber", "Long_create", "Long_ZERO",
"$rt_intern", "$rt_substring",
"Long_hi", "Long_lo"); "Long_hi", "Long_lo");
} }

View File

@ -73,6 +73,7 @@ public class RuntimeRenderer {
renderRuntimeThrowableMethods(); renderRuntimeThrowableMethods();
renderRuntimeNullCheck(); renderRuntimeNullCheck();
renderRuntimeIntern(); renderRuntimeIntern();
renderStringClassInit();
renderRuntimeThreads(); renderRuntimeThreads();
renderRuntimeCreateException(); renderRuntimeCreateException();
renderCreateStackTraceElement(); renderCreateStackTraceElement();
@ -123,33 +124,21 @@ public class RuntimeRenderer {
} }
private void renderRuntimeString() throws IOException { private void renderRuntimeString() throws IOException {
MethodReference stringCons = new MethodReference(String.class, "<init>", char[].class, void.class); MethodReference stringCons = new MethodReference(String.class, "<init>", Object.class, void.class);
writer.append("function $rt_str(str) {").indent().softNewLine(); writer.append("function $rt_str(str)").ws().append("{").indent().softNewLine();
writer.append("if (str === null) {").indent().softNewLine(); writer.append("if (str === null) {").indent().softNewLine();
writer.append("return null;").softNewLine(); writer.append("return null;").softNewLine();
writer.outdent().append("}").softNewLine(); writer.outdent().append("}").softNewLine();
writer.append("var characters = $rt_createCharArray(str.length);").softNewLine(); writer.append("return ").appendInit(stringCons).append("(str);").softNewLine();
writer.append("var charsBuffer = characters.data;").softNewLine();
writer.append("for (var i = 0; i < str.length; i = (i + 1) | 0) {").indent().softNewLine();
writer.append("charsBuffer[i] = str.charCodeAt(i) & 0xFFFF;").softNewLine();
writer.outdent().append("}").softNewLine();
writer.append("return ").appendInit(stringCons).append("(characters);").softNewLine();
writer.outdent().append("}").newLine(); writer.outdent().append("}").newLine();
} }
private void renderRuntimeUnwrapString() throws IOException { private void renderRuntimeUnwrapString() throws IOException {
FieldReference stringChars = new FieldReference(STRING_CLASS, "characters"); FieldReference stringChars = new FieldReference(STRING_CLASS, "nativeString");
writer.append("function $rt_ustr(str) {").indent().softNewLine(); writer.append("function $rt_ustr(str)").ws().append("{").indent().softNewLine();
writer.append("if (str === null) {").indent().softNewLine(); writer.append("return str").ws().append("!==").ws().append("null");
writer.append("return null;").softNewLine(); writer.ws().append("?").ws().append("str.").appendField(stringChars);
writer.outdent().append("}").softNewLine(); writer.ws().append(":").ws().append("null").append(";").softNewLine();
writer.append("var data = str.").appendField(stringChars).append(".data;").softNewLine();
writer.append("var result = \"\";").softNewLine();
writer.append("for (var i = 0; i < data.length; i = (i + 1) | 0) {").indent().softNewLine();
writer.append("result += String.fromCharCode(data[i]);").softNewLine();
writer.outdent().append("}").softNewLine();
writer.append("return result;").softNewLine();
writer.outdent().append("}").newLine(); writer.outdent().append("}").newLine();
} }
@ -169,17 +158,15 @@ public class RuntimeRenderer {
writer.outdent().append("}").softNewLine(); writer.outdent().append("}").softNewLine();
} else { } else {
renderHandWrittenRuntime("intern.js"); renderHandWrittenRuntime("intern.js");
writer.append("function $rt_stringHash(s)").ws().append("{").indent().softNewLine();
writer.append("return ").appendMethodBody(String.class, "hashCode", int.class)
.append("(s);").softNewLine();
writer.outdent().append("}").softNewLine();
writer.append("function $rt_stringEquals(a,").ws().append("b)").ws().append("{").indent().softNewLine();
writer.append("return ").appendMethodBody(String.class, "equals", Object.class, boolean.class)
.append("(a").ws().append(",b);").softNewLine();
writer.outdent().append("}").softNewLine();
} }
} }
private void renderStringClassInit() throws IOException {
writer.append("function $rt_stringClassInit(str)").ws().append("{").indent().softNewLine();
writer.appendClassInit("java.lang.String").append("();").softNewLine();
writer.outdent().append("}").softNewLine();
}
private boolean needInternMethod() { private boolean needInternMethod() {
ClassReader cls = classSource.get(STRING_CLASS); ClassReader cls = classSource.get(STRING_CLASS);
if (cls == null) { if (cls == null) {

View File

@ -16,49 +16,34 @@
"use strict"; "use strict";
var $rt_intern = function() { var $rt_intern = function() {
var table = new Array(100); var map = Object.create(null);
var size = 0;
function get(str) { var get;
var hash = $rt_stringHash(str); if (typeof WeakRef !== 'undefined') {
var bucket = getBucket(hash); var registry = new FinalizationRegistry(value => {
for (var i = 0; i < bucket.length; ++i) { delete map[value];
if ($rt_stringEquals(bucket[i], str)) { });
return bucket[i];
}
}
bucket.push(str);
return str;
}
function getBucket(hash) { get = function(str) {
while (true) { var key = $rt_ustr(str);
var position = hash % table.length; var ref = map[key];
var bucket = table[position]; var result = typeof ref !== 'undefined' ? ref.deref() : void 0;
if (typeof bucket !== "undefined") { if (typeof result !== 'object') {
return bucket; result = str;
map[key] = new WeakRef(result);
registry.register(result, key);
}
return result;
} }
if (++size / table.length > 0.5) {
rehash();
} else { } else {
bucket = []; get = function(str) {
table[position] = bucket; var key = $rt_ustr(str);
return bucket; var result = map[key];
} if (typeof result !== 'object') {
} result = str;
} map[key] = result;
function rehash() {
var old = table;
table = new Array(table.length * 2);
size = 0;
for (var i = 0; i < old.length; ++i) {
var bucket = old[i];
if (typeof bucket !== "undefined") {
for (var j = 0; j < bucket.length; ++j) {
get(bucket[j]);
}
} }
return result;
} }
} }

View File

@ -644,6 +644,7 @@ function $rt_mainStarter(f) {
} }
var $rt_stringPool_instance; var $rt_stringPool_instance;
function $rt_stringPool(strings) { function $rt_stringPool(strings) {
$rt_stringClassInit();
$rt_stringPool_instance = new Array(strings.length); $rt_stringPool_instance = new Array(strings.length);
for (var i = 0; i < strings.length; ++i) { for (var i = 0; i < strings.length; ++i) {
$rt_stringPool_instance[i] = $rt_intern($rt_str(strings[i])); $rt_stringPool_instance[i] = $rt_intern($rt_str(strings[i]));
@ -938,3 +939,35 @@ function $rt_classWithoutFields(superclass) {
superclass.call(this); superclass.call(this);
}; };
} }
function $rt_charArrayToString(array, offset, count) {
var result = "";
var limit = offset + count;
for (var i = offset; i < limit; i = (i + 1024) | 0) {
var next = Math.min(limit, (i + 1024) | 0);
result += String.fromCharCode.apply(null, array.subarray(i, next));
}
return result;
}
function $rt_fullArrayToString(array) {
return $rt_charArrayToString(array, 0, array.length);
}
function $rt_stringToCharArray(string, begin, dst, dstBegin, count) {
for (var i = 0; i < count; i = (i + 1) | 0) {
dst[dstBegin + i] = string.charCodeAt(begin + i);
}
}
function $rt_fastStringToCharArray(string) {
var array = new Uint16Array(string.length);
for (var i = 0; i < array.length; ++i) {
array[i] = string.charCodeAt(i);
}
return $rt_createNumericArray($rt_charcls(), array);
}
function $rt_substring(string, start, end) {
if (start === 0 && end === string.length) {
return string;
}
var result = start.substring(start, end - 1) + start.substring(end - 1, end);
$rt_substringSink = ($rt_substringSink + result.charCodeAt(result.length - 1)) | 0;
}
var $rt_substringSink = 0;

View File

@ -125,7 +125,7 @@ public class JavaScriptConvGenerator implements Generator {
writer.outdent().append("} else if (" + type + " === ").appendClass("java.lang.String") writer.outdent().append("} else if (" + type + " === ").appendClass("java.lang.String")
.append(") {").indent().softNewLine(); .append(") {").indent().softNewLine();
writer.append("return $rt_str(" + obj + ");").softNewLine(); writer.append("return ").appendFunction("$rt_str").append("(").append(obj).append(");").softNewLine();
writer.outdent().append("} else if (" + type + " === ").appendClass("java.lang.Boolean") writer.outdent().append("} else if (" + type + " === ").appendClass("java.lang.Boolean")
.append(") {").indent().softNewLine(); .append(") {").indent().softNewLine();
@ -179,7 +179,7 @@ public class JavaScriptConvGenerator implements Generator {
writer.append("return arr;").softNewLine(); writer.append("return arr;").softNewLine();
writer.outdent().append("} else if (typeof " + obj + " === 'string') {").indent().softNewLine(); writer.outdent().append("} else if (typeof " + obj + " === 'string') {").indent().softNewLine();
writer.append("return $rt_str(" + obj + ");").softNewLine(); writer.append("return ").appendFunction("$rt_str").append("(" + obj + ");").softNewLine();
writer.outdent().append("} else if (typeof " + obj + " === 'number') {").indent().softNewLine(); writer.outdent().append("} else if (typeof " + obj + " === 'number') {").indent().softNewLine();
writer.append("if ((" + obj + "|0) === " + obj + ") {").indent().softNewLine(); writer.append("if ((" + obj + "|0) === " + obj + ") {").indent().softNewLine();

View File

@ -166,7 +166,7 @@ public class JSNativeGenerator implements Injector, DependencyPlugin, Generator
} }
break; break;
case "unwrapString": case "unwrapString":
writer.append("$rt_str("); writer.appendFunction("$rt_str").append("(");
context.writeExpr(context.getArgument(0), Precedence.min()); context.writeExpr(context.getArgument(0), Precedence.min());
writer.append(")"); writer.append(")");
break; break;

View File

@ -87,7 +87,7 @@ class ResourceAccessorInjector implements Injector {
context.getWriter().append('('); context.getWriter().append('(');
context.writeExpr(context.getArgument(0)); context.writeExpr(context.getArgument(0));
context.getWriter().ws().append("!==").ws().append("null").ws().append("?").ws(); context.getWriter().ws().append("!==").ws().append("null").ws().append("?").ws();
context.getWriter().append("$rt_str("); context.getWriter().appendFunction("$rt_str").append("(");
context.writeExpr(context.getArgument(0)); context.writeExpr(context.getArgument(0));
context.getWriter().append(")").ws().append(':').ws().append("null)"); context.getWriter().append(")").ws().append(':').ws().append("null)");
break; break;

View File

@ -26,7 +26,6 @@ public class EntryPointGenerator implements Injector {
public void generate(InjectorContext context, MethodReference methodRef) throws IOException { public void generate(InjectorContext context, MethodReference methodRef) throws IOException {
context.getWriter().append("main.result = ("); context.getWriter().append("main.result = (");
context.writeExpr(context.getArgument(0)); context.writeExpr(context.getArgument(0));
context.getWriter().append(").").appendField(new FieldReference("java.lang.String", "characters")); context.getWriter().append(").").appendField(new FieldReference("java.lang.String", "nativeString"));
context.getWriter().append(".data");
} }
} }

View File

@ -40,7 +40,6 @@ import org.mozilla.javascript.ScriptRuntime;
import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject; import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.Undefined; import org.mozilla.javascript.Undefined;
import org.mozilla.javascript.typedarrays.NativeUint16Array;
import org.teavm.ast.AsyncMethodNode; import org.teavm.ast.AsyncMethodNode;
import org.teavm.backend.javascript.JavaScriptTarget; import org.teavm.backend.javascript.JavaScriptTarget;
import org.teavm.cache.AlwaysStaleCacheStatus; import org.teavm.cache.AlwaysStaleCacheStatus;
@ -169,12 +168,7 @@ public class IncrementalTest {
Function main = (Function) scope.get("main", scope); Function main = (Function) scope.get("main", scope);
ScriptRuntime.doTopCall(main, rhinoContext, scope, scope, ScriptRuntime.doTopCall(main, rhinoContext, scope, scope,
new Object[] { new NativeArray(0), Undefined.instance }); new Object[] { new NativeArray(0), Undefined.instance });
NativeUint16Array jsChars = (NativeUint16Array) main.get("result", main); return main.get("result", main).toString();
char[] chars = new char[jsChars.getArrayLength()];
for (int i = 0; i < chars.length; ++i) {
chars[i] = (char) jsChars.get(i).intValue();
}
return new String(chars);
} }
private static String getSimpleName(String name) { private static String getSimpleName(String name) {