diff --git a/core/src/main/java/org/teavm/backend/c/CTarget.java b/core/src/main/java/org/teavm/backend/c/CTarget.java index e3cd75426..0dce86f17 100644 --- a/core/src/main/java/org/teavm/backend/c/CTarget.java +++ b/core/src/main/java/org/teavm/backend/c/CTarget.java @@ -49,6 +49,7 @@ import org.teavm.backend.c.generators.ArrayGenerator; import org.teavm.backend.c.generators.Generator; import org.teavm.backend.c.intrinsic.AddressIntrinsic; import org.teavm.backend.c.intrinsic.AllocatorIntrinsic; +import org.teavm.backend.c.intrinsic.ConsoleIntrinsic; import org.teavm.backend.c.intrinsic.ExceptionHandlingIntrinsic; import org.teavm.backend.c.intrinsic.FunctionIntrinsic; import org.teavm.backend.c.intrinsic.GCIntrinsic; @@ -293,6 +294,7 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { intrinsics.add(new LongIntrinsic()); intrinsics.add(new IntegerIntrinsic()); intrinsics.add(new StringsIntrinsic()); + intrinsics.add(new ConsoleIntrinsic()); List generators = new ArrayList<>(); generators.add(new ArrayGenerator()); diff --git a/core/src/main/java/org/teavm/backend/c/intrinsic/ConsoleIntrinsic.java b/core/src/main/java/org/teavm/backend/c/intrinsic/ConsoleIntrinsic.java new file mode 100644 index 000000000..06f214403 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/c/intrinsic/ConsoleIntrinsic.java @@ -0,0 +1,63 @@ +/* + * Copyright 2019 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.backend.c.intrinsic; + +import org.teavm.ast.ConstantExpr; +import org.teavm.ast.Expr; +import org.teavm.ast.InvocationExpr; +import org.teavm.backend.c.generate.StringPoolGenerator; +import org.teavm.model.MethodReference; +import org.teavm.runtime.Console; + +public class ConsoleIntrinsic implements Intrinsic { + @Override + public boolean canHandle(MethodReference method) { + if (!method.getClassName().equals(Console.class.getName())) { + return false; + } + + return method.getName().equals("printString"); + } + + @Override + public void apply(IntrinsicContext context, InvocationExpr invocation) { + switch (invocation.getMethod().getName()) { + case "printString": { + context.writer().print("fprintf(stderr, \"%s\", "); + Expr arg = invocation.getArguments().get(0); + String literal = extractStringConstant(arg); + if (literal != null) { + StringPoolGenerator.generateSimpleStringLiteral(context.writer(), literal); + } else { + context.writer().print("teavm_stringToC("); + context.emit(arg); + context.writer().print(")"); + } + context.writer().print(")"); + break; + } + } + } + + private String extractStringConstant(Expr expr) { + if (!(expr instanceof ConstantExpr)) { + return null; + } + + Object value = ((ConstantExpr) expr).getValue(); + return value instanceof String ? (String) value : null; + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmRuntime.java b/core/src/main/java/org/teavm/backend/wasm/WasmRuntime.java index 21099791a..b3747188d 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmRuntime.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmRuntime.java @@ -83,6 +83,15 @@ public final class WasmRuntime { @Import(name = "print", module = "spectest") public static native void print(int a); + @Import(name = "logString", module = "teavm") + public static native void printString(String s); + + @Import(name = "logInt", module = "teavm") + public static native void printInt(int i); + + @Import(name = "logOutOfMemory", module = "teavm") + public static native void printOutOfMemory(); + public static void fillZero(Address address, int count) { int start = address.toInt(); diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java index e101dd2e7..b9d89a4ae 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java @@ -44,6 +44,7 @@ import org.teavm.backend.wasm.generators.WasmMethodGeneratorContext; import org.teavm.backend.wasm.intrinsics.AddressIntrinsic; import org.teavm.backend.wasm.intrinsics.AllocatorIntrinsic; import org.teavm.backend.wasm.intrinsics.ClassIntrinsic; +import org.teavm.backend.wasm.intrinsics.ConsoleIntrinsic; import org.teavm.backend.wasm.intrinsics.DoubleIntrinsic; import org.teavm.backend.wasm.intrinsics.ExceptionHandlingIntrinsic; import org.teavm.backend.wasm.intrinsics.FloatIntrinsic; @@ -269,6 +270,12 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { String[].class)).use(); dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "lookupResource", Address.class, String.class, Address.class)).use(); + dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "printString", + String.class, void.class)).use(); + dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "printInt", + int.class, void.class)).use(); + dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "printOutOfMemory", + void.class)).use(); dependencyAnalyzer.linkMethod(new MethodReference(Allocator.class, "allocate", RuntimeClass.class, Address.class)).use(); @@ -344,6 +351,7 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { context.addIntrinsic(new LongIntrinsic()); context.addIntrinsic(new IntegerIntrinsic()); context.addIntrinsic(new ObjectIntrinsic()); + context.addIntrinsic(new ConsoleIntrinsic()); context.addGenerator(new ArrayGenerator()); IntrinsicFactoryContext intrinsicFactoryContext = new IntrinsicFactoryContext(); diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/ConsoleIntrinsic.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/ConsoleIntrinsic.java new file mode 100644 index 000000000..bb591c578 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/ConsoleIntrinsic.java @@ -0,0 +1,66 @@ +/* + * Copyright 2019 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.backend.wasm.intrinsics; + +import org.teavm.ast.InvocationExpr; +import org.teavm.backend.wasm.WasmRuntime; +import org.teavm.backend.wasm.model.expression.WasmCall; +import org.teavm.backend.wasm.model.expression.WasmExpression; +import org.teavm.backend.wasm.model.expression.WasmUnreachable; +import org.teavm.model.MethodReference; +import org.teavm.runtime.Console; + +public class ConsoleIntrinsic implements WasmIntrinsic { + private static final MethodReference PRINT_STRING = new MethodReference(WasmRuntime.class, + "printString", String.class, void.class); + private static final MethodReference PRINT_INT = new MethodReference(WasmRuntime.class, + "printInt", int.class, void.class); + + @Override + public boolean isApplicable(MethodReference methodReference) { + if (!methodReference.getClassName().equals(Console.class.getName())) { + return false; + } + + switch (methodReference.getName()) { + case "printString": + case "printInt": + return true; + default: + return false; + } + } + + @Override + public WasmExpression apply(InvocationExpr invocation, WasmIntrinsicManager manager) { + switch (invocation.getMethod().getName()) { + case "printString": { + String name = manager.getNames().forMethod(PRINT_STRING); + WasmCall call = new WasmCall(name, true); + call.getArguments().add(manager.generate(invocation.getArguments().get(0))); + return call; + } + case "printInt": { + String name = manager.getNames().forMethod(PRINT_INT); + WasmCall call = new WasmCall(name, true); + call.getArguments().add(manager.generate(invocation.getArguments().get(0))); + return call; + } + default: + return new WasmUnreachable(); + } + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/GCIntrinsic.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/GCIntrinsic.java index ca75f222d..f73231a26 100644 --- a/core/src/main/java/org/teavm/backend/wasm/intrinsics/GCIntrinsic.java +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/GCIntrinsic.java @@ -18,13 +18,19 @@ package org.teavm.backend.wasm.intrinsics; import java.util.ArrayList; import java.util.List; import org.teavm.ast.InvocationExpr; +import org.teavm.backend.wasm.WasmRuntime; +import org.teavm.backend.wasm.model.expression.WasmBlock; +import org.teavm.backend.wasm.model.expression.WasmCall; import org.teavm.backend.wasm.model.expression.WasmExpression; import org.teavm.backend.wasm.model.expression.WasmInt32Constant; import org.teavm.backend.wasm.model.expression.WasmInt64Constant; +import org.teavm.backend.wasm.model.expression.WasmUnreachable; import org.teavm.model.MethodReference; import org.teavm.runtime.GC; public class GCIntrinsic implements WasmIntrinsic { + private static final MethodReference PRINT_OUT_OF_MEMORY = new MethodReference( + WasmRuntime.class, "printOutOfMemory", void.class); private List heapAddressExpressions = new ArrayList<>(); private List availableBytesExpressions = new ArrayList<>(); private List gcStorageAddressExpressions = new ArrayList<>(); @@ -89,6 +95,7 @@ public class GCIntrinsic implements WasmIntrinsic { case "regionsAddress": case "regionMaxCount": case "regionSize": + case "outOfMemory": return true; default: return false; @@ -122,6 +129,13 @@ public class GCIntrinsic implements WasmIntrinsic { availableBytesExpressions.add(constant); return constant; } + case "outOfMemory": { + WasmBlock block = new WasmBlock(false); + WasmCall call = new WasmCall(manager.getNames().forMethod(PRINT_OUT_OF_MEMORY), true); + block.getBody().add(call); + block.getBody().add(new WasmUnreachable()); + return block; + } default: throw new IllegalArgumentException(invocation.getMethod().toString()); } diff --git a/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java b/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java index 0ef320d54..4d65cef39 100644 --- a/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java +++ b/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java @@ -337,6 +337,9 @@ public class WasmBinaryRenderer { functionsSubsection.writeLEB(functions.size()); for (WasmFunction function : functions) { + if (function.getImportName() != null) { + continue; + } functionsSubsection.writeLEB(functionIndexes.get(function.getName())); functionsSubsection.writeAsciiString(function.getName()); } diff --git a/core/src/main/java/org/teavm/runtime/Console.java b/core/src/main/java/org/teavm/runtime/Console.java index 9be461b23..326abae2c 100644 --- a/core/src/main/java/org/teavm/runtime/Console.java +++ b/core/src/main/java/org/teavm/runtime/Console.java @@ -15,7 +15,6 @@ */ package org.teavm.runtime; -import org.teavm.interop.Address; import org.teavm.interop.Import; import org.teavm.interop.StaticInit; import org.teavm.interop.Unmanaged; @@ -26,8 +25,7 @@ public final class Console { private Console() { } - @Import(name = "teavm_printString") - public static native void printString(Address ptr); + public static native void printString(String s); @Import(name = "teavm_printInt") public static native void printInt(int n); diff --git a/core/src/main/java/org/teavm/runtime/ExceptionHandling.java b/core/src/main/java/org/teavm/runtime/ExceptionHandling.java index 1ff1d6f87..64b419a5d 100644 --- a/core/src/main/java/org/teavm/runtime/ExceptionHandling.java +++ b/core/src/main/java/org/teavm/runtime/ExceptionHandling.java @@ -18,7 +18,6 @@ package org.teavm.runtime; import org.teavm.interop.Address; import org.teavm.interop.Export; import org.teavm.interop.StaticInit; -import org.teavm.interop.Strings; import org.teavm.interop.Structure; import org.teavm.interop.Unmanaged; @@ -36,21 +35,21 @@ public final class ExceptionHandling { CallSite callSite = findCallSiteById(callSiteId); CallSiteLocation location = callSite.location; - Console.printString(Strings.toC(" at ")); + Console.printString(" at "); if (location.className == null || location.methodName == null) { - Console.printString(Strings.toC("(Unknown method)")); + Console.printString("(Unknown method)"); } else { - Console.printString(Strings.toC(location.className)); - Console.printString(Strings.toC(".")); - Console.printString(Strings.toC(location.methodName)); + Console.printString(location.className); + Console.printString("."); + Console.printString(location.methodName); } - Console.printString(Strings.toC("(")); + Console.printString("("); if (location.fileName != null && location.lineNumber >= 0) { - Console.printString(Strings.toC(location.fileName)); - Console.printString(Strings.toC(":")); + Console.printString(location.fileName); + Console.printString(":"); Console.printInt(location.lineNumber); } - Console.printString(Strings.toC(")\n")); + Console.printString(")\n"); stackFrame = ShadowStack.getNextStackFrame(stackFrame); } } diff --git a/core/src/main/resources/org/teavm/backend/wasm/wasm-runtime.js b/core/src/main/resources/org/teavm/backend/wasm/wasm-runtime.js index 40fce2274..e6ab79420 100644 --- a/core/src/main/resources/org/teavm/backend/wasm/wasm-runtime.js +++ b/core/src/main/resources/org/teavm/backend/wasm/wasm-runtime.js @@ -37,6 +37,17 @@ TeaVM.wasm = function() { function getNativeOffset(instant) { return new Date(instant).getTimezoneOffset(); } + function logString(string) { + var memory = new DataView(logString.memory.buffer); + var arrayPtr = memory.getUint32(string + 8, true); + var length = memory.getUint32(arrayPtr + 8, true); + for (var i = 0; i < length; ++i) { + putwchar(memory.getUint16(i * 2 + arrayPtr + 12, true)); + } + } + function logInt(i) { + lineBuffer += i.toString(); + } function importDefaults(obj) { obj.teavm = { @@ -48,7 +59,10 @@ TeaVM.wasm = function() { putwchar: putwchar, towlower: towlower, towupper: towupper, - getNativeOffset: getNativeOffset + getNativeOffset: getNativeOffset, + logString: logString, + logInt: logInt, + logOutOfMemory: function() { console.log("Out of memory") } }; obj.teavmMath = Math; @@ -79,6 +93,7 @@ TeaVM.wasm = function() { } WebAssembly.instantiate(response, importObj).then(function(resultObject) { + importObj.teavm.logString.memory = resultObject.instance.exports.memory; resultObject.instance.exports.main(); callback(resultObject); }).catch(function(error) {