classlib: optimize console output in JS backend

This commit is contained in:
Alexey Andreev 2023-11-07 20:00:33 +01:00
parent 0ee994e913
commit 9c6f23d280
7 changed files with 303 additions and 88 deletions

View File

@ -0,0 +1,28 @@
/*
* 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.console;
import org.teavm.jso.JSBody;
public class JSStderrPrintStream extends JsConsolePrintStream {
@Override
public void print(String s) {
writeJs(s);
}
@JSBody(params = "b", script = "$rt_putStderr(b);")
private static native void writeJs(String s);
}

View File

@ -0,0 +1,28 @@
/*
* 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.console;
import org.teavm.jso.JSBody;
public class JSStdoutPrintStream extends JsConsolePrintStream {
@Override
public void print(String s) {
writeJs(s);
}
@JSBody(params = "b", script = "$rt_putStdout(b);")
private static native void writeJs(String s);
}

View File

@ -0,0 +1,196 @@
/*
* 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.console;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
public abstract class JsConsolePrintStream extends PrintStream {
private ByteEncoder byteEncoder;
private Runnable flushAction;
public JsConsolePrintStream() {
super(new ByteArrayOutputStream());
}
@Override
public void println(String s) {
print(s);
print("\n");
}
@Override
public void print(int i) {
print(Integer.toString(i));
}
@Override
public void print(char c) {
print(Character.toString(c));
}
@Override
public void print(long l) {
print(Long.toString(l));
}
@Override
public void print(double d) {
super.print(d);
}
@Override
public void print(Object s) {
print(Objects.toString(s));
}
@Override
public void print(char[] s) {
print(new String(s));
}
@Override
public void println() {
print("\n");
}
@Override
public void println(int i) {
println(Integer.toString(i));
}
@Override
public void println(char c) {
println(Character.toString(c));
}
@Override
public void println(long l) {
println(Long.toString(l));
}
@Override
public void println(float d) {
println(Float.toString(d));
}
@Override
public void println(double d) {
println(Double.toString(d));
}
@Override
public void println(boolean b) {
println(Boolean.toString(b));
}
@Override
public void println(Object s) {
println(Objects.toString(s));
}
@Override
public void write(int b) {
ensureByteEncoder().write(b);
}
@Override
public void write(byte[] b, int off, int len) {
ensureByteEncoder().write(b, off, len);
}
@Override
public void flush() {
if (flushAction != null) {
flushAction = null;
flushAction.run();
}
}
private ByteEncoder ensureByteEncoder() {
if (byteEncoder == null) {
byteEncoder = new ByteEncoder();
}
return byteEncoder;
}
@Override
public abstract void print(String s);
private class ByteEncoder {
private ByteBuffer buffer = ByteBuffer.wrap(new byte[32]);
private char[] outChars = new char[32];
private CharBuffer outBuffer = CharBuffer.wrap(outChars);
private CharsetDecoder decoder = StandardCharsets.UTF_8
.newDecoder()
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE);
void write(int b) {
postponeFlush();
if (!buffer.hasRemaining()) {
output();
}
buffer.put((byte) b);
}
void write(byte[] b, int off, int len) {
postponeFlush();
while (len > 0) {
var count = Math.min(len, buffer.remaining());
buffer.put(b, off, count);
len -= count;
output();
}
}
void postponeFlush() {
if (flushAction == null) {
flushAction = this::flush;
}
}
private void flush() {
if (buffer.position() > 0) {
output(true);
}
}
private void output() {
output(false);
}
private void output(boolean endOfInput) {
buffer.flip();
while (true) {
var result = decoder.decode(buffer, outBuffer, endOfInput);
print(new String(outChars, 0, outBuffer.position()));
outBuffer.rewind();
if (result != CoderResult.OVERFLOW) {
break;
}
}
buffer.compact();
}
}
}

View File

@ -24,6 +24,8 @@ import org.teavm.backend.c.runtime.fs.CFileSystem;
import org.teavm.backend.javascript.spi.GeneratedBy; import org.teavm.backend.javascript.spi.GeneratedBy;
import org.teavm.backend.wasm.runtime.WasmSupport; import org.teavm.backend.wasm.runtime.WasmSupport;
import org.teavm.classlib.PlatformDetector; import org.teavm.classlib.PlatformDetector;
import org.teavm.classlib.impl.console.JSStderrPrintStream;
import org.teavm.classlib.impl.console.JSStdoutPrintStream;
import org.teavm.classlib.impl.console.StderrOutputStream; import org.teavm.classlib.impl.console.StderrOutputStream;
import org.teavm.classlib.impl.console.StdoutOutputStream; import org.teavm.classlib.impl.console.StdoutOutputStream;
import org.teavm.classlib.java.io.TConsole; import org.teavm.classlib.java.io.TConsole;
@ -55,14 +57,22 @@ public final class TSystem extends TObject {
public static TPrintStream out() { public static TPrintStream out() {
if (outCache == null) { if (outCache == null) {
outCache = new TPrintStream((TOutputStream) (Object) StdoutOutputStream.INSTANCE, false); if (PlatformDetector.isJavaScript()) {
outCache = (TPrintStream) (Object) new JSStdoutPrintStream();
} else {
outCache = new TPrintStream((TOutputStream) (Object) StdoutOutputStream.INSTANCE, false);
}
} }
return outCache; return outCache;
} }
public static TPrintStream err() { public static TPrintStream err() {
if (errCache == null) { if (errCache == null) {
errCache = new TPrintStream((TOutputStream) (Object) StderrOutputStream.INSTANCE, false); if (PlatformDetector.isJavaScript()) {
errCache = (TPrintStream) (Object) new JSStderrPrintStream();
} else {
errCache = new TPrintStream((TOutputStream) (Object) StderrOutputStream.INSTANCE, false);
}
} }
return errCache; return errCache;
} }

View File

@ -397,55 +397,30 @@ let $rt_assertNotNaN = value => {
} }
return value; return value;
} }
let $rt_createOutputFunction = printFunction => {
let $rt_createOutputFunction = outputFunction => {
let buffer = ""; let buffer = "";
let utf8Buffer = 0; return msg => {
let utf8Remaining = 0; let index = 0;
while (true) {
let putCodePoint = ch =>{ let next = msg.indexOf('\n', index);
if (ch === 0xA) { if (next < 0) {
printFunction(buffer); break;
buffer = "";
} else if (ch < 0x10000) {
buffer += String.fromCharCode(ch);
} else {
ch = (ch - 0x10000) | 0;
let hi = (ch >> 10) + 0xD800;
let lo = (ch & 0x3FF) + 0xDC00;
buffer += String.fromCharCode(hi, lo);
}
}
return ch => {
if ((ch & 0x80) === 0) {
putCodePoint(ch);
} else if ((ch & 0xC0) === 0x80) {
if (utf8Buffer > 0) {
utf8Remaining <<= 6;
utf8Remaining |= ch & 0x3F;
if (--utf8Buffer === 0) {
putCodePoint(utf8Remaining);
}
} }
} else if ((ch & 0xE0) === 0xC0) { outputFunction(buffer + msg.substring(index, next));
utf8Remaining = ch & 0x1F; buffer = "";
utf8Buffer = 1; index = next + 1;
} else if ((ch & 0xF0) === 0xE0) {
utf8Remaining = ch & 0x0F;
utf8Buffer = 2;
} else if ((ch & 0xF8) === 0xF0) {
utf8Remaining = ch & 0x07;
utf8Buffer = 3;
} }
}; buffer += msg.substring(index);
}
} }
let $rt_putStdout = typeof teavm_globals.$rt_putStdoutCustom === "function" let $rt_putStdout = typeof teavm_globals.$rt_putStdoutCustom === "function"
? teavm_globals.$rt_putStdoutCustom ? teavm_globals.$rt_putStdoutCustom
: typeof console === "object" ? $rt_createOutputFunction(function(msg) { console.info(msg); }) : function() {}; : typeof console === "object" ? $rt_createOutputFunction(msg => console.info(msg)) : () => {};
let $rt_putStderr = typeof teavm_globals.$rt_putStderrCustom === "function" let $rt_putStderr = typeof teavm_globals.$rt_putStderrCustom === "function"
? teavm_globals.$rt_putStderrCustom ? teavm_globals.$rt_putStderrCustom
: typeof console === "object" ? $rt_createOutputFunction(function(msg) { console.error(msg); }) : function() {}; : typeof console === "object" ? $rt_createOutputFunction(msg => console.error(msg)) : () => {};
let $rt_packageData = null; let $rt_packageData = null;
let $rt_packages = data => { let $rt_packages = data => {

View File

@ -1,14 +1,18 @@
let $rt_stdoutBuffer = ""; let $rt_stdoutBuffer = "";
function $rt_putStdoutCustom(ch) { function $rt_putStdoutCustom(str) {
if (ch === 0xA) { let index = 0;
var lineElem = document.createElement("div"); while (true) {
var stdoutElem = document.getElementById("stdout"); let next = msg.indexOf('\n', index);
lineElem.appendChild(document.createTextNode($rt_stdoutBuffer)); if (next < 0) {
break;
}
let line = buffer + msg.substring(index, next);
let lineElem = document.createElement("div");
let stdoutElem = document.getElementById("stdout");
lineElem.appendChild(document.createTextNode(line));
stdoutElem.appendChild(lineElem); stdoutElem.appendChild(lineElem);
$rt_stdoutBuffer = ""; buffer = "";
stdoutElem.scrollTop = stdoutElem.scrollHeight; index = next + 1;
} else {
$rt_stdoutBuffer += String.fromCharCode(ch);
} }
} }
this.$rt_putStdoutCustom = $rt_putStdoutCustom; this.$rt_putStdoutCustom = $rt_putStdoutCustom;

View File

@ -188,43 +188,17 @@ let $rt_putStderrCustom = createOutputFunction(msg => {
function createOutputFunction(printFunction) { function createOutputFunction(printFunction) {
let buffer = ""; let buffer = "";
let utf8Buffer = 0; return msg => {
let utf8Remaining = 0; let index = 0;
while (true) {
function putCodePoint(ch) { let next = msg.indexOf('\n', index);
if (ch === 0xA) { if (next < 0) {
printFunction(buffer); break;
buffer = "";
} else if (ch < 0x10000) {
buffer += String.fromCharCode(ch);
} else {
ch = (ch - 0x10000) | 0;
var hi = (ch >> 10) + 0xD800;
var lo = (ch & 0x3FF) + 0xDC00;
buffer += String.fromCharCode(hi, lo);
}
}
return ch => {
if ((ch & 0x80) === 0) {
putCodePoint(ch);
} else if ((ch & 0xC0) === 0x80) {
if (utf8Buffer > 0) {
utf8Remaining <<= 6;
utf8Remaining |= ch & 0x3F;
if (--utf8Buffer === 0) {
putCodePoint(utf8Remaining);
}
} }
} else if ((ch & 0xE0) === 0xC0) { printFunction(buffer + msg.substring(index, next));
utf8Remaining = ch & 0x1F; buffer = "";
utf8Buffer = 1; index = next + 1;
} else if ((ch & 0xF0) === 0xE0) {
utf8Remaining = ch & 0x0F;
utf8Buffer = 2;
} else if ((ch & 0xF8) === 0xF0) {
utf8Remaining = ch & 0x07;
utf8Buffer = 3;
} }
}; buffer += msg.substring(index);
}
} }