From 90e00f7eb4c2333dc69eb05a64daa4fd9d46a95a Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Mon, 1 Jul 2019 00:26:10 +0300 Subject: [PATCH] C: add option to support exceptions via setjmp/longjmp --- .../classlib/java/lang/reflect/TArray.java | 2 - .../teavm/ast/decompilation/Decompiler.java | 168 +++---- .../ast/decompilation/StatementGenerator.java | 45 +- .../ast/optimization/BreakEliminator.java | 29 +- .../java/org/teavm/backend/c/CTarget.java | 34 +- .../c/analyze/AstDefinitionUsageAnalysis.java | 420 ++++++++++++++++++ .../c/analyze/VolatileDefinitionFinder.java | 110 +++++ .../backend/c/generate/ClassGenerator.java | 47 +- .../c/generate/CodeGenerationVisitor.java | 310 +++++++++++-- .../backend/c/generate/CodeGenerator.java | 22 +- .../backend/c/generate/GenerationContext.java | 9 +- .../intrinsic/ExceptionHandlingIntrinsic.java | 20 + .../c/intrinsic/ShadowStackIntrinsic.java | 8 +- .../rendering/StatementRenderer.java | 3 +- .../org/teavm/backend/wasm/WasmTarget.java | 2 +- .../ExceptionHandlingIntrinsic.java | 34 +- .../main/java/org/teavm/common/RangeTree.java | 7 + ...ceptionHandlingShadowStackContributor.java | 19 +- .../lowlevel/ShadowStackTransformer.java | 57 ++- .../teavm/model/util/RegisterAllocator.java | 100 ++++- .../java/org/teavm/parsing/ProgramParser.java | 3 - .../java/org/teavm/runtime/Allocator.java | 1 - .../java/org/teavm/runtime/EventQueue.java | 2 +- .../org/teavm/runtime/ExceptionHandling.java | 33 +- .../java/org/teavm/runtime/MarkQueue.java | 4 + .../main/resources/org/teavm/backend/c/file.c | 1 + .../resources/org/teavm/backend/c/runtime.c | 2 +- .../resources/org/teavm/backend/c/runtime.h | 110 ++++- .../teavm/platform/PlatformClassMetadata.java | 1 + samples/benchmark/.gitignore | 4 - tests/compile-c-unix-fast.sh | 1 + .../classlib/java/nio/ByteBufferTest.java | 3 + tests/src/test/java/org/teavm/vm/VMTest.java | 20 +- .../c/incremental/IncrementalCBuilder.java | 6 + .../org/teavm/cli/TeaVMCBuilderRunner.java | 7 + .../main/java/org/teavm/cli/TeaVMRunner.java | 11 + .../java/org/teavm/tooling/TeaVMTool.java | 7 +- .../teavm/tooling/builder/BuildStrategy.java | 2 + .../builder/InProcessBuildStrategy.java | 7 + .../tooling/builder/RemoteBuildStrategy.java | 6 + .../org/teavm/tooling/daemon/BuildDaemon.java | 1 + .../tooling/daemon/RemoteBuildRequest.java | 1 + tools/devserver/pom.xml | 2 +- .../org/teavm/maven/TeaVMCompileMojo.java | 4 + 44 files changed, 1405 insertions(+), 280 deletions(-) create mode 100644 core/src/main/java/org/teavm/backend/c/analyze/AstDefinitionUsageAnalysis.java create mode 100644 core/src/main/java/org/teavm/backend/c/analyze/VolatileDefinitionFinder.java delete mode 100644 samples/benchmark/.gitignore diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TArray.java b/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TArray.java index 76825dda8..911b66df9 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TArray.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TArray.java @@ -25,7 +25,6 @@ import org.teavm.classlib.java.lang.TObject; import org.teavm.dependency.PluggableDependency; import org.teavm.interop.DelegateTo; import org.teavm.interop.NoSideEffects; -import org.teavm.interop.Unmanaged; import org.teavm.platform.PlatformClass; import org.teavm.runtime.Allocator; import org.teavm.runtime.RuntimeArray; @@ -69,7 +68,6 @@ public final class TArray extends TObject { private static native TObject newInstanceImpl(PlatformClass componentType, int length); @SuppressWarnings("unused") - @Unmanaged private static RuntimeObject newInstanceLowLevel(RuntimeClass cls, int length) { return Allocator.allocateArray(cls.arrayType, length).toStructure(); } diff --git a/core/src/main/java/org/teavm/ast/decompilation/Decompiler.java b/core/src/main/java/org/teavm/ast/decompilation/Decompiler.java index c719eb75a..5dddbb6c6 100644 --- a/core/src/main/java/org/teavm/ast/decompilation/Decompiler.java +++ b/core/src/main/java/org/teavm/ast/decompilation/Decompiler.java @@ -61,9 +61,7 @@ public class Decompiler { private Graph graph; private LoopGraph loopGraph; private GraphIndexer indexer; - private int[] loops; private int[] loopSuccessors; - private Block[] blockMap; private boolean[] exceptionHandlers; private int lastBlockId; private RangeTree codeTree; @@ -83,7 +81,6 @@ public class Decompiler { static class Block { Block parent; - int parentOffset; final IdentifiedStatement statement; final List body; final int end; @@ -100,24 +97,6 @@ public class Decompiler { this.start = start; this.end = end; } - - void installTo(int index, Block[] blockMap) { - if (nodeBackup == null) { - nodeToRestore = index; - nodeBackup = blockMap[index]; - } else { - nodeToRestore2 = index; - nodeBackup2 = blockMap[index]; - } - blockMap[index] = this; - } - - void removeFrom(Block[] blockMap) { - blockMap[nodeToRestore] = nodeBackup; - if (nodeToRestore2 >= 0) { - blockMap[nodeToRestore2] = nodeBackup2; - } - } } static class TryCatchBookmark { @@ -207,16 +186,14 @@ public class Decompiler { graph = indexer.getGraph(); loopGraph = new LoopGraph(this.graph); unflatCode(); - blockMap = new Block[program.basicBlockCount() * 2 + 1]; stack = new ArrayDeque<>(); this.program = program; BlockStatement rootStmt = new BlockStatement(); rootStmt.setId("root"); - stack.push(new Block(rootStmt, rootStmt.getBody(), -1, -1)); + stack.push(new Block(rootStmt, rootStmt.getBody(), -1, Integer.MAX_VALUE)); StatementGenerator generator = new StatementGenerator(); generator.classSource = classSource; generator.program = program; - generator.blockMap = blockMap; generator.indexer = indexer; parentNode = codeTree.getRoot(); currentNode = parentNode.getFirstChild(); @@ -224,18 +201,7 @@ public class Decompiler { fillExceptionHandlers(program); for (int i = 0; i < this.graph.size(); ++i) { int node = i < indexer.size() ? indexer.nodeAt(i) : -1; - int next = i + 1; - int head = loops[i]; - if (head != -1 && loopSuccessors[head] == next) { - next = head; - } - - if (node >= 0) { - generator.currentBlock = program.basicBlockAt(node); - int tmp = indexer.nodeAt(next); - generator.nextBlock = tmp >= 0 && next < indexer.size() ? program.basicBlockAt(tmp) : null; - } - + BasicBlock basicBlock = node >= 0 ? program.basicBlockAt(node) : null; Block block = stack.peek(); while (parentNode.getEnd() == i) { @@ -245,16 +211,18 @@ public class Decompiler { for (Block newBlock : createBlocks(i)) { block.body.add(newBlock.statement); newBlock.parent = block; - newBlock.parentOffset = block.body.size(); stack.push(newBlock); block = newBlock; } - createNewBookmarks(generator.currentBlock.getTryCatchBlocks()); + if (basicBlock != null) { + createNewBookmarks(basicBlock.getTryCatchBlocks()); + } + generator.currentBlock = block; - if (node >= 0) { + if (basicBlock != null) { generator.statements.clear(); TextLocation lastLocation = null; - for (Instruction insn : generator.currentBlock) { + for (Instruction insn : basicBlock) { if (insn.getLocation() != null && lastLocation != insn.getLocation()) { lastLocation = insn.getLocation(); } @@ -276,20 +244,17 @@ public class Decompiler { Block oldBlock = block; stack.pop(); block = stack.peek(); - if (block.start >= 0) { - int mappedStart = indexer.nodeAt(block.start); - if (blockMap[mappedStart] == oldBlock) { - blockMap[mappedStart] = block; - } - } - for (int j = 0; j < oldBlock.tryCatches.size(); ++j) { + for (int j = oldBlock.tryCatches.size() - 1; j >= 0; --j) { TryCatchBookmark bookmark = oldBlock.tryCatches.get(j); TryCatchStatement tryCatchStmt = new TryCatchStatement(); tryCatchStmt.setExceptionType(bookmark.exceptionType); tryCatchStmt.setExceptionVariable(bookmark.exceptionVariable); - tryCatchStmt.getHandler().add(generator.generateJumpStatement( - program.basicBlockAt(bookmark.exceptionHandler))); + Statement handlerStatement = generator.generateJumpStatement(block, + program.basicBlockAt(bookmark.exceptionHandler)); + if (handlerStatement != null) { + tryCatchStmt.getHandler().add(handlerStatement); + } List blockPart = oldBlock.body.subList(bookmark.offset, oldBlock.body.size()); tryCatchStmt.getProtectedBody().addAll(blockPart); blockPart.clear(); @@ -301,11 +266,16 @@ public class Decompiler { tryCatchBookmarks.subList(tryCatchBookmarks.size() - oldBlock.tryCatches.size(), tryCatchBookmarks.size()).clear(); oldBlock.tryCatches.clear(); - oldBlock.removeFrom(blockMap); } - if (generator.nextBlock != null && !isTrivialBlock(generator.nextBlock)) { - closeExpiredBookmarks(generator, generator.nextBlock.getTryCatchBlocks()); + if (i < this.graph.size() - 1) { + int nextNode = indexer.nodeAt(i + 1); + if (nextNode >= 0 && nextNode < program.basicBlockCount()) { + BasicBlock nextBlock = program.basicBlockAt(nextNode); + if (!isTrivialBlock(nextBlock)) { + closeExpiredBookmarks(generator, nextBlock.getTryCatchBlocks()); + } + } } } @@ -364,37 +334,42 @@ public class Decompiler { removedBookmarks.add(bookmark); } - Collections.reverse(removedBookmarks); - for (TryCatchBookmark bookmark : removedBookmarks) { + if (!removedBookmarks.isEmpty()) { + Collections.reverse(removedBookmarks); Block block = stack.peek(); - while (block != bookmark.block) { - if (block.body.size() > 1) { + + int lastBookmark = removedBookmarks.size() - 1; + boolean first = true; + while (lastBookmark >= 0) { + for (int j = lastBookmark; j >= 0; --j) { + TryCatchBookmark bookmark = removedBookmarks.get(j); + TryCatchStatement tryCatchStmt = new TryCatchStatement(); tryCatchStmt.setExceptionType(bookmark.exceptionType); tryCatchStmt.setExceptionVariable(bookmark.exceptionVariable); - tryCatchStmt.getHandler().add(generator.generateJumpStatement( - program.basicBlockAt(bookmark.exceptionHandler))); - List body = block.body.subList(0, block.body.size() - 1); + Statement handlerStatement = generator.generateJumpStatement(block, + program.basicBlockAt(bookmark.exceptionHandler)); + if (handlerStatement != null) { + tryCatchStmt.getHandler().add(handlerStatement); + } + + List body = first ? block.body : block.body.subList(0, block.body.size() - 1); + if (bookmark.block == block) { + body = body.subList(bookmark.offset, body.size()); + lastBookmark = j - 1; + } tryCatchStmt.getProtectedBody().addAll(body); - body.clear(); - body.add(tryCatchStmt); + if (!body.isEmpty()) { + body.clear(); + body.add(tryCatchStmt); + } } + + block.tryCatches.removeAll(removedBookmarks); + block = block.parent; + first = false; } - TryCatchStatement tryCatchStmt = new TryCatchStatement(); - tryCatchStmt.setExceptionType(bookmark.exceptionType); - tryCatchStmt.setExceptionVariable(bookmark.exceptionVariable); - Statement jumpToHandler = generator.generateJumpStatement(program.basicBlockAt(bookmark.exceptionHandler)); - if (jumpToHandler != null) { - tryCatchStmt.getHandler().add(jumpToHandler); - } - List blockPart = block.body.subList(bookmark.offset, block.body.size()); - tryCatchStmt.getProtectedBody().addAll(blockPart); - blockPart.clear(); - if (!tryCatchStmt.getProtectedBody().isEmpty()) { - blockPart.add(tryCatchStmt); - } - block.tryCatches.remove(bookmark); } tryCatchBookmarks.subList(start, tryCatchBookmarks.size()).clear(); @@ -402,16 +377,31 @@ public class Decompiler { private void createNewBookmarks(List tryCatchBlocks) { // Add new bookmarks + Block previousRoot = null; for (int i = tryCatchBookmarks.size(); i < tryCatchBlocks.size(); ++i) { TryCatchBlock tryCatch = tryCatchBlocks.get(tryCatchBlocks.size() - 1 - i); TryCatchBookmark bookmark = new TryCatchBookmark(); - bookmark.block = stack.peek(); - bookmark.offset = bookmark.block.body.size(); + + Block block = stack.peek(); + int offset = block.body.size(); + + if (offset == 0 && block != previousRoot) { + while (block.parent != previousRoot + && block.parent.start == block.start + && block.end < indexer.indexOf(tryCatch.getHandler().getIndex()) + && block.parent.body.size() == 1) { + block = block.parent; + } + } + previousRoot = block; + + bookmark.block = block; + bookmark.offset = offset; bookmark.exceptionHandler = tryCatch.getHandler().getIndex(); bookmark.exceptionType = tryCatch.getExceptionType(); bookmark.exceptionVariable = tryCatch.getHandler().getExceptionVariable() != null ? tryCatch.getHandler().getExceptionVariable().getIndex() : null; - bookmark.block.tryCatches.add(bookmark); + block.tryCatches.add(bookmark); tryCatchBookmarks.add(bookmark); } } @@ -421,26 +411,16 @@ public class Decompiler { while (currentNode != null && currentNode.getStart() == start) { Block block; IdentifiedStatement statement; - boolean loop = false; if (loopSuccessors[start] == currentNode.getEnd() || isSingleBlockLoop(start)) { WhileStatement whileStatement = new WhileStatement(); statement = whileStatement; block = new Block(statement, whileStatement.getBody(), start, currentNode.getEnd()); - loop = true; } else { BlockStatement blockStatement = new BlockStatement(); statement = blockStatement; block = new Block(statement, blockStatement.getBody(), start, currentNode.getEnd()); } result.add(block); - int mappedIndex = indexer.nodeAt(currentNode.getEnd()); - if (mappedIndex >= 0 && (blockMap[mappedIndex] == null - || !(blockMap[mappedIndex].statement instanceof WhileStatement))) { - block.installTo(mappedIndex, blockMap); - } - if (loop) { - block.installTo(indexer.nodeAt(start), blockMap); - } parentNode = currentNode; currentNode = currentNode.getFirstChild(); } @@ -477,17 +457,6 @@ public class Decompiler { // For each node find head of loop this node belongs to. // - int[] loops = new int[sz]; - Arrays.fill(loops, -1); - for (int head = 0; head < sz; ++head) { - int end = loopSuccessors[head]; - if (end > sz) { - continue; - } - for (int node = head + 1; node < end; ++node) { - loops[node] = head; - } - } List ranges = new ArrayList<>(); for (int node = 0; node < sz; ++node) { @@ -509,6 +478,5 @@ public class Decompiler { } codeTree = new RangeTree(sz + 1, ranges); this.loopSuccessors = loopSuccessors; - this.loops = loops; } } diff --git a/core/src/main/java/org/teavm/ast/decompilation/StatementGenerator.java b/core/src/main/java/org/teavm/ast/decompilation/StatementGenerator.java index 636c34913..101a8e823 100644 --- a/core/src/main/java/org/teavm/ast/decompilation/StatementGenerator.java +++ b/core/src/main/java/org/teavm/ast/decompilation/StatementGenerator.java @@ -40,6 +40,7 @@ import org.teavm.ast.SwitchStatement; import org.teavm.ast.ThrowStatement; import org.teavm.ast.UnaryOperation; import org.teavm.ast.UnwrapArrayExpr; +import org.teavm.ast.WhileStatement; import org.teavm.common.GraphIndexer; import org.teavm.model.BasicBlock; import org.teavm.model.ClassHolderSource; @@ -95,9 +96,7 @@ class StatementGenerator implements InstructionVisitor { private int lastSwitchId; final List statements = new ArrayList<>(); GraphIndexer indexer; - BasicBlock nextBlock; - BasicBlock currentBlock; - Decompiler.Block[] blockMap; + Decompiler.Block currentBlock; Program program; ClassHolderSource classSource; private TextLocation currentLocation; @@ -536,26 +535,32 @@ class StatementGenerator implements InstructionVisitor { throw new IllegalArgumentException(type.toString()); } - Statement generateJumpStatement(BasicBlock target) { - if (nextBlock == target && blockMap[target.getIndex()] == null) { + Statement generateJumpStatement(Decompiler.Block sourceBlock, BasicBlock target) { + Decompiler.Block targetBlock = getTargetBlock(sourceBlock, target); + if (targetBlock == null) { + int targetIndex = indexer.indexOf(target.getIndex()); + if (targetIndex >= sourceBlock.end) { + throw new IllegalStateException("Could not find block for basic block $" + target.getIndex()); + } return null; } - Decompiler.Block block = blockMap[target.getIndex()]; - if (block == null) { - throw new IllegalStateException("Could not find block for basic block $" + target.getIndex()); - } - if (target.getIndex() == indexer.nodeAt(block.end)) { + if (target.getIndex() == indexer.nodeAt(targetBlock.end)) { BreakStatement breakStmt = new BreakStatement(); breakStmt.setLocation(currentLocation); - breakStmt.setTarget(block.statement); + breakStmt.setTarget(targetBlock.statement); return breakStmt; } else { ContinueStatement contStmt = new ContinueStatement(); contStmt.setLocation(currentLocation); - contStmt.setTarget(block.statement); + contStmt.setTarget(targetBlock.statement); return contStmt; } } + + Statement generateJumpStatement(BasicBlock target) { + return generateJumpStatement(currentBlock, target); + } + private Statement generateJumpStatement(SwitchStatement stmt, int target) { Statement body = generateJumpStatement(program.basicBlockAt(target)); if (body == null) { @@ -566,6 +571,22 @@ class StatementGenerator implements InstructionVisitor { return body; } + private Decompiler.Block getTargetBlock(Decompiler.Block source, BasicBlock target) { + Decompiler.Block block = source; + int targetIndex = indexer.indexOf(target.getIndex()); + Decompiler.Block candidate = null; + while (block != null && (block.start >= targetIndex || block.end <= targetIndex)) { + if (block.statement instanceof WhileStatement && block.start == targetIndex) { + return block; + } + if (block.end == targetIndex) { + candidate = block; + } + block = block.parent; + } + return candidate; + } + private void branch(Expr condition, BasicBlock consequentBlock, BasicBlock alternativeBlock) { Statement consequent = generateJumpStatement(consequentBlock); Statement alternative = generateJumpStatement(alternativeBlock); diff --git a/core/src/main/java/org/teavm/ast/optimization/BreakEliminator.java b/core/src/main/java/org/teavm/ast/optimization/BreakEliminator.java index da9acbdd6..723a7371e 100644 --- a/core/src/main/java/org/teavm/ast/optimization/BreakEliminator.java +++ b/core/src/main/java/org/teavm/ast/optimization/BreakEliminator.java @@ -20,27 +20,22 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; -import org.teavm.ast.AssignmentStatement; +import org.teavm.ast.AbstractStatementVisitor; import org.teavm.ast.BlockStatement; import org.teavm.ast.BreakStatement; import org.teavm.ast.ConditionalStatement; import org.teavm.ast.ContinueStatement; -import org.teavm.ast.GotoPartStatement; import org.teavm.ast.IdentifiedStatement; -import org.teavm.ast.InitClassStatement; -import org.teavm.ast.MonitorEnterStatement; -import org.teavm.ast.MonitorExitStatement; import org.teavm.ast.ReturnStatement; import org.teavm.ast.SequentialStatement; import org.teavm.ast.Statement; -import org.teavm.ast.StatementVisitor; import org.teavm.ast.SwitchClause; import org.teavm.ast.SwitchStatement; import org.teavm.ast.ThrowStatement; import org.teavm.ast.TryCatchStatement; import org.teavm.ast.WhileStatement; -class BreakEliminator implements StatementVisitor { +class BreakEliminator extends AbstractStatementVisitor { private Map> blockSuccessors = new LinkedHashMap<>(); private Set outerStatements = new LinkedHashSet<>(); private List currentSequence; @@ -66,10 +61,6 @@ class BreakEliminator implements StatementVisitor { currentSequence = oldSequence; } - @Override - public void visit(AssignmentStatement statement) { - } - @Override public void visit(SequentialStatement statement) { if (currentSequence == null) { @@ -145,10 +136,6 @@ class BreakEliminator implements StatementVisitor { currentSequence.subList(currentIndex + 1, currentSequence.size()).clear(); } - @Override - public void visit(InitClassStatement statement) { - } - @Override public void visit(TryCatchStatement statement) { Map> oldBlockSuccessors = blockSuccessors; @@ -161,18 +148,6 @@ class BreakEliminator implements StatementVisitor { processSequence(statement.getHandler()); } - @Override - public void visit(GotoPartStatement statement) { - } - - @Override - public void visit(MonitorEnterStatement statement) { - } - - @Override - public void visit(MonitorExitStatement statement) { - } - private boolean escapes(List statements) { return new EscapingStatementFinder(usageCounter).check(statements); } 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 80d0312bb..69fd9d822 100644 --- a/core/src/main/java/org/teavm/backend/c/CTarget.java +++ b/core/src/main/java/org/teavm/backend/c/CTarget.java @@ -157,6 +157,8 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { private boolean incremental; private boolean lineNumbersGenerated; private SimpleStringPool stringPool; + private boolean longjmpUsed = true; + private List callSites = new ArrayList<>(); public void setMinHeapSize(int minHeapSize) { this.minHeapSize = minHeapSize; @@ -170,6 +172,10 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { this.lineNumbersGenerated = lineNumbersGenerated; } + public void setLongjmpUsed(boolean longjmpUsed) { + this.longjmpUsed = longjmpUsed; + } + public void setAstCache(MethodNodeCache astCache) { this.astCache = astCache; } @@ -195,7 +201,7 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { characteristics = new Characteristics(controller.getUnprocessedClassSource()); classInitializerEliminator = new ClassInitializerEliminator(controller.getUnprocessedClassSource()); classInitializerTransformer = new ClassInitializerTransformer(); - shadowStackTransformer = new ShadowStackTransformer(characteristics); + shadowStackTransformer = new ShadowStackTransformer(characteristics, !longjmpUsed); nullCheckInsertion = new NullCheckInsertion(characteristics); nullCheckTransformation = new NullCheckTransformation(); @@ -302,12 +308,14 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { public void afterOptimizations(Program program, MethodReader method) { classInitializerEliminator.apply(program); classInitializerTransformer.transform(program); - nullCheckTransformation.apply(program, method.getResultType()); + if (!longjmpUsed) { + nullCheckTransformation.apply(program, method.getResultType()); + } new CoroutineTransformation(controller.getUnprocessedClassSource(), asyncMethods, hasThreads) .apply(program, method.getReference()); ShadowStackTransformer shadowStackTransformer = !incremental ? this.shadowStackTransformer - : new ShadowStackTransformer(characteristics); + : new ShadowStackTransformer(characteristics, !longjmpUsed); shadowStackTransformer.apply(program, method); } @@ -350,7 +358,7 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { stringPool = new SimpleStringPool(); GenerationContext context = new GenerationContext(vtableProvider, characteristics, controller.getDependencyInfo(), stringPool, nameProvider, controller.getDiagnostics(), classes, - intrinsics, generators, asyncMethods::contains, buildTarget, incremental); + intrinsics, generators, asyncMethods::contains, buildTarget, incremental, longjmpUsed); BufferedCodeWriter runtimeWriter = new BufferedCodeWriter(false); BufferedCodeWriter runtimeHeaderWriter = new BufferedCodeWriter(false); @@ -358,13 +366,19 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { runtimeHeaderWriter.println("#pragma once"); if (incremental) { - runtimeHeaderWriter.println("#define TEAVM_INCREMENTAL true"); + runtimeHeaderWriter.println("#define TEAVM_INCREMENTAL 1"); + } + if (longjmpUsed) { + runtimeHeaderWriter.println("#define TEAVM_USE_SETJMP 1"); } emitResource(runtimeHeaderWriter, "runtime.h"); ClassGenerator classGenerator = new ClassGenerator(context, tagRegistry, decompiler, controller.getCacheStatus()); classGenerator.setAstCache(astCache); + if (context.isLongjmp() && !context.isIncremental()) { + classGenerator.setCallSites(callSites); + } IntrinsicFactoryContextImpl intrinsicFactoryContext = new IntrinsicFactoryContextImpl( controller.getUnprocessedClassSource(), controller.getClassLoader(), controller.getServices(), controller.getProperties()); @@ -515,14 +529,16 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { headerWriter.println("extern " + callSiteName + " teavm_callSites[];"); headerWriter.println("#define TEAVM_FIND_CALLSITE(id, frame) (teavm_callSites + id)"); - new CallSiteGenerator(context, writer, includes, "teavm_callSites") - .generate(CallSiteDescriptor.extract(context.getClassSource(), classNames)); + List callSites = context.isLongjmp() + ? this.callSites + : CallSiteDescriptor.extract(context.getClassSource(), classNames); + new CallSiteGenerator(context, writer, includes, "teavm_callSites").generate(callSites); } private void generateIncrementalCallSites(GenerationContext context, CodeWriter headerWriter) { String callSiteName = context.getNames().forClass(CallSiteGenerator.CALL_SITE); - headerWriter.println("#define TEAVM_FIND_CALLSITE(id, frame) (((" + callSiteName - + "*) ((void**) frame)[3]) + id)"); + headerWriter.println("#define TEAVM_FIND_CALLSITE(id, frame) ((" + callSiteName + + "*) ((TeaVM_StackFrame*) (frame))->callSites + id)"); } private void generateStrings(BuildTarget buildTarget, GenerationContext context) throws IOException { diff --git a/core/src/main/java/org/teavm/backend/c/analyze/AstDefinitionUsageAnalysis.java b/core/src/main/java/org/teavm/backend/c/analyze/AstDefinitionUsageAnalysis.java new file mode 100644 index 000000000..bdf4058d5 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/c/analyze/AstDefinitionUsageAnalysis.java @@ -0,0 +1,420 @@ +/* + * 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.analyze; + +import com.carrotsearch.hppc.IntArrayList; +import com.carrotsearch.hppc.IntHashSet; +import com.carrotsearch.hppc.IntIntHashMap; +import com.carrotsearch.hppc.IntIntMap; +import com.carrotsearch.hppc.IntObjectHashMap; +import com.carrotsearch.hppc.IntObjectMap; +import com.carrotsearch.hppc.IntSet; +import com.carrotsearch.hppc.IntStack; +import com.carrotsearch.hppc.LongHashSet; +import com.carrotsearch.hppc.LongSet; +import com.carrotsearch.hppc.ObjectIntHashMap; +import com.carrotsearch.hppc.ObjectIntMap; +import com.carrotsearch.hppc.cursors.IntCursor; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.teavm.ast.AssignmentStatement; +import org.teavm.ast.BlockStatement; +import org.teavm.ast.BreakStatement; +import org.teavm.ast.ConditionalStatement; +import org.teavm.ast.ContinueStatement; +import org.teavm.ast.Expr; +import org.teavm.ast.IdentifiedStatement; +import org.teavm.ast.RecursiveVisitor; +import org.teavm.ast.ReturnStatement; +import org.teavm.ast.Statement; +import org.teavm.ast.SwitchClause; +import org.teavm.ast.SwitchStatement; +import org.teavm.ast.ThrowStatement; +import org.teavm.ast.TryCatchStatement; +import org.teavm.ast.VariableExpr; +import org.teavm.ast.WhileStatement; +import org.teavm.common.Graph; +import org.teavm.common.GraphBuilder; + +public class AstDefinitionUsageAnalysis { + private GraphBuilder graphBuilder = new GraphBuilder(); + private GraphBuilder exceptionGraphBuilder = new GraphBuilder(); + private Graph cfg; + private Graph exceptionGraph; + private List nodes = new ArrayList<>(); + private int lastNode = -1; + private IdentifiedStatement defaultBreakTarget; + private IdentifiedStatement defaultContinueTarget; + private ObjectIntMap breakTargets = new ObjectIntHashMap<>(); + private ObjectIntMap continueTargets = new ObjectIntHashMap<>(); + private List definitions = new ArrayList<>(); + private List readonlyDefinitions = Collections.unmodifiableList(definitions); + private ObjectIntMap definitionIds = new ObjectIntHashMap<>(); + private List usages = new ArrayList<>(); + private IntStack stack = new IntStack(); + private IntStack exceptionHandlerStack = new IntStack(); + + public List getDefinitions() { + return readonlyDefinitions; + } + + public void analyze(Statement statement) { + prepare(statement); + propagate(); + cleanup(); + } + + private void prepare(Statement statement) { + lastNode = createNode(); + statement.acceptVisitor(visitor); + cfg = graphBuilder.build(); + exceptionGraph = exceptionGraphBuilder.build(); + graphBuilder = null; + exceptionGraphBuilder = null; + breakTargets = null; + continueTargets = null; + exceptionHandlerStack = null; + } + + private void connect(int from, int to) { + if (from >= 0 && to >= 0) { + graphBuilder.addEdge(from, to); + } + } + + private int createNode() { + int id = nodes.size(); + nodes.add(new Node()); + for (IntCursor cursor : exceptionHandlerStack) { + exceptionGraphBuilder.addEdge(id, cursor.value); + } + return id; + } + + private void propagate() { + while (!stack.isEmpty()) { + int exceptionHandlerId = stack.pop(); + int nodeId = stack.pop(); + int usageId = stack.pop(); + int variableId = usages.get(usageId).getIndex(); + + Node node = nodes.get(nodeId); + if (exceptionHandlerId < 0) { + if (!node.usages.add(usageId)) { + continue; + } + int definitionId = node.definitions.getOrDefault(variableId, -1); + if (definitionId >= 0) { + Definition definition = definitions.get(definitionId); + definition.usages.add(usages.get(usageId)); + continue; + } + if (variableId == node.exceptionVariable) { + continue; + } + } else { + if (!node.handlerUsages.add(pack(usageId, exceptionHandlerId))) { + continue; + } + IntArrayList definitionIds = node.handlerDefinitions.get(variableId); + if (definitionIds != null) { + TryCatchStatement handlerStatement = nodes.get(exceptionHandlerId).catchStatement; + Expr usage = usages.get(usageId); + for (IntCursor cursor : definitionIds) { + Definition definition = definitions.get(cursor.value); + definition.usages.add(usages.get(usageId)); + Set handlerUsages = definition.exceptionHandlingUsages.get(handlerStatement); + if (handlerUsages == null) { + handlerUsages = new LinkedHashSet<>(); + definition.exceptionHandlingUsages.put(handlerStatement, handlerUsages); + } + handlerUsages.add(usage); + } + } + } + + if (nodeId < cfg.size()) { + for (int predecessorId : cfg.incomingEdges(nodeId)) { + if (!nodes.get(predecessorId).usages.contains(usageId)) { + stack.push(usageId); + stack.push(predecessorId); + stack.push(-1); + } + } + } + + if (nodeId < exceptionGraph.size()) { + for (int predecessorId : exceptionGraph.incomingEdges(nodeId)) { + if (!nodes.get(predecessorId).handlerUsages.contains(usageId)) { + stack.push(usageId); + stack.push(predecessorId); + stack.push(nodeId); + } + } + } + } + } + + private void cleanup() { + cfg = null; + stack = null; + } + + private RecursiveVisitor visitor = new RecursiveVisitor() { + @Override + public void visit(ConditionalStatement statement) { + statement.getCondition().acceptVisitor(this); + int forkNode = createNode(); + int joinNode = createNode(); + + connect(lastNode, forkNode); + lastNode = forkNode; + visit(statement.getConsequent()); + connect(lastNode, joinNode); + + lastNode = forkNode; + visit(statement.getAlternative()); + connect(lastNode, joinNode); + + lastNode = joinNode; + } + + @Override + public void visit(SwitchStatement statement) { + IdentifiedStatement oldDefaultBreakTarget = defaultBreakTarget; + defaultBreakTarget = statement; + + statement.getValue().acceptVisitor(this); + int forkNode = createNode(); + int joinNode = createNode(); + + connect(lastNode, forkNode); + for (SwitchClause clause : statement.getClauses()) { + lastNode = forkNode; + visit(clause.getBody()); + connect(lastNode, joinNode); + } + + lastNode = forkNode; + visit(statement.getDefaultClause()); + connect(lastNode, joinNode); + + lastNode = joinNode; + defaultBreakTarget = oldDefaultBreakTarget; + } + + @Override + public void visit(WhileStatement statement) { + IdentifiedStatement oldDefaultBreakTarget = defaultBreakTarget; + IdentifiedStatement oldDefaultContinueTarget = defaultContinueTarget; + defaultBreakTarget = statement; + defaultContinueTarget = statement; + + int continueNode = createNode(); + int forkNode = createNode(); + int breakNode = createNode(); + breakTargets.put(statement, breakNode); + continueTargets.put(statement, continueNode); + + connect(lastNode, continueNode); + lastNode = continueNode; + if (statement.getCondition() != null) { + statement.getCondition().acceptVisitor(this); + } + + connect(lastNode, forkNode); + connect(forkNode, breakNode); + lastNode = forkNode; + visit(statement.getBody()); + connect(lastNode, continueNode); + lastNode = breakNode; + + breakTargets.remove(statement); + continueTargets.remove(statement); + defaultBreakTarget = oldDefaultBreakTarget; + defaultContinueTarget = oldDefaultContinueTarget; + } + + @Override + public void visit(BlockStatement statement) { + int breakNode = createNode(); + breakTargets.put(statement, breakNode); + visit(statement.getBody()); + connect(lastNode, breakNode); + lastNode = breakNode; + breakTargets.remove(statement); + } + + @Override + public void visit(TryCatchStatement statement) { + int handlerNode = createNode(); + Node handlerNodeData = nodes.get(handlerNode); + handlerNodeData.catchStatement = statement; + if (statement.getExceptionVariable() != null) { + handlerNodeData.exceptionVariable = statement.getExceptionVariable(); + } + exceptionHandlerStack.push(handlerNode); + visit(statement.getProtectedBody()); + exceptionHandlerStack.pop(); + + int node = lastNode; + lastNode = handlerNode; + visit(statement.getHandler()); + connect(lastNode, node); + + lastNode = node; + } + + @Override + public void visit(ReturnStatement statement) { + super.visit(statement); + lastNode = -1; + } + + @Override + public void visit(ThrowStatement statement) { + super.visit(statement); + lastNode = -1; + } + + @Override + public void visit(BreakStatement statement) { + IdentifiedStatement target = statement.getTarget(); + if (target == null) { + target = defaultBreakTarget; + } + connect(lastNode, breakTargets.getOrDefault(target, -1)); + lastNode = -1; + } + + @Override + public void visit(ContinueStatement statement) { + IdentifiedStatement target = statement.getTarget(); + if (target == null) { + target = defaultContinueTarget; + } + connect(lastNode, continueTargets.getOrDefault(target, -1)); + lastNode = -1; + } + + @Override + public void visit(AssignmentStatement statement) { + if (!processAssignment(statement)) { + super.visit(statement); + } + } + + private boolean processAssignment(AssignmentStatement statement) { + if (!(statement.getLeftValue() instanceof VariableExpr)) { + return false; + } + + int leftIndex = ((VariableExpr) statement.getLeftValue()).getIndex(); + statement.getRightValue().acceptVisitor(this); + Definition definition = new Definition(statement, definitions.size(), leftIndex); + definitions.add(definition); + definitionIds.put(definition, definition.id); + if (lastNode >= 0) { + Node node = nodes.get(lastNode); + node.definitions.put(definition.variableIndex, definition.id); + + IntArrayList handlerDefinitions = node.handlerDefinitions.get(leftIndex); + if (handlerDefinitions == null) { + handlerDefinitions = new IntArrayList(); + node.handlerDefinitions.put(leftIndex, handlerDefinitions); + } + handlerDefinitions.add(definition.id); + } + return true; + } + + @Override + public void visit(VariableExpr expr) { + if (lastNode < 0) { + return; + } + + Node node = nodes.get(lastNode); + int definitionId = node.definitions.getOrDefault(expr.getIndex(), -1); + if (definitionId >= 0) { + Definition definition = definitions.get(definitionId); + definition.usages.add(expr); + } else if (node.exceptionVariable != expr.getIndex()) { + int id = usages.size(); + usages.add(expr); + node.usages.add(id); + stack.push(id); + stack.push(lastNode); + stack.push(0); + } + } + }; + + static class Node { + IntIntMap definitions = new IntIntHashMap(); + IntObjectMap handlerDefinitions = new IntObjectHashMap<>(); + IntSet usages = new IntHashSet(); + LongSet handlerUsages = new LongHashSet(); + TryCatchStatement catchStatement; + int exceptionVariable = -1; + } + + private static long pack(int a, int b) { + return ((long) a << 32) | b; + } + + public static class Definition { + private AssignmentStatement statement; + private int id; + private int variableIndex; + final Set usages = new LinkedHashSet<>(); + private Set readonlyUsages = Collections.unmodifiableSet(usages); + final Map> exceptionHandlingUsages = new LinkedHashMap<>(); + private Map> readonlyExceptionHandlingUsages = + Collections.unmodifiableMap(exceptionHandlingUsages); + + Definition(AssignmentStatement statement, int id, int variableIndex) { + this.statement = statement; + this.id = id; + this.variableIndex = variableIndex; + } + + public AssignmentStatement getStatement() { + return statement; + } + + public int getId() { + return id; + } + + public int getVariableIndex() { + return variableIndex; + } + + public Set getUsages() { + return readonlyUsages; + } + + public Map> getExceptionHandlingUsages() { + return readonlyExceptionHandlingUsages; + } + } +} diff --git a/core/src/main/java/org/teavm/backend/c/analyze/VolatileDefinitionFinder.java b/core/src/main/java/org/teavm/backend/c/analyze/VolatileDefinitionFinder.java new file mode 100644 index 000000000..83cd8589e --- /dev/null +++ b/core/src/main/java/org/teavm/backend/c/analyze/VolatileDefinitionFinder.java @@ -0,0 +1,110 @@ +/* + * 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.analyze; + +import com.carrotsearch.hppc.IntHashSet; +import com.carrotsearch.hppc.IntSet; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.teavm.ast.AssignmentStatement; +import org.teavm.ast.RecursiveVisitor; +import org.teavm.ast.Statement; +import org.teavm.ast.TryCatchStatement; +import org.teavm.ast.VariableExpr; + +public class VolatileDefinitionFinder { + private static final int[] EMPTY_INT_ARRAY = new int[0]; + private Map defHandlers = new HashMap<>(); + private Set definitionsToBackup = new HashSet<>(); + private Map usagesToRestoreByHandler = new HashMap<>(); + + public void findVolatileDefinitions(Statement statement) { + AstDefinitionUsageAnalysis defuse = new AstDefinitionUsageAnalysis(); + defuse.analyze(statement); + statement.acceptVisitor(handlerAnalyzer); + + for (AstDefinitionUsageAnalysis.Definition definition : defuse.getDefinitions()) { + StackElement stack = defHandlers.get(definition.getStatement()); + if (stack == null || definition.getExceptionHandlingUsages().isEmpty()) { + continue; + } + + while (stack != null) { + if (definition.getExceptionHandlingUsages().get(stack.statement) != null) { + IntSet usagesToRestore = usagesToRestoreByHandler.get(stack.statement); + if (usagesToRestore == null) { + usagesToRestore = new IntHashSet(); + usagesToRestoreByHandler.put(stack.statement, usagesToRestore); + } + usagesToRestore.add(definition.getVariableIndex()); + definitionsToBackup.add(definition.getStatement()); + } + stack = stack.next; + } + } + + defHandlers = null; + } + + public boolean shouldBackup(AssignmentStatement statement) { + return definitionsToBackup.contains(statement); + } + + public int[] variablesToRestore(TryCatchStatement tryCatch) { + IntSet result = usagesToRestoreByHandler.get(tryCatch); + if (result == null) { + return EMPTY_INT_ARRAY; + } + int[] array = result.toArray(); + Arrays.sort(array); + return array; + } + + static class StackElement { + final TryCatchStatement statement; + final StackElement next; + + StackElement(TryCatchStatement statement, StackElement next) { + this.statement = statement; + this.next = next; + } + } + + private RecursiveVisitor handlerAnalyzer = new RecursiveVisitor() { + StackElement surroundingTryCatches; + StackElement handlingTryCatches; + + @Override + public void visit(TryCatchStatement statement) { + surroundingTryCatches = new StackElement(statement, surroundingTryCatches); + visit(statement.getProtectedBody()); + surroundingTryCatches = surroundingTryCatches.next; + + visit(statement.getHandler()); + } + + @Override + public void visit(AssignmentStatement statement) { + super.visit(statement); + if (statement.getLeftValue() instanceof VariableExpr && surroundingTryCatches != null) { + defHandlers.put(statement, surroundingTryCatches); + } + } + }; +} diff --git a/core/src/main/java/org/teavm/backend/c/generate/ClassGenerator.java b/core/src/main/java/org/teavm/backend/c/generate/ClassGenerator.java index 6ab02b08a..0ed683f7f 100644 --- a/core/src/main/java/org/teavm/backend/c/generate/ClassGenerator.java +++ b/core/src/main/java/org/teavm/backend/c/generate/ClassGenerator.java @@ -18,6 +18,7 @@ package org.teavm.backend.c.generate; import java.io.IOException; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; @@ -93,10 +94,12 @@ public class ClassGenerator { private CodeWriter codeWriter; private CodeWriter initWriter; private CodeWriter headerWriter; + private CodeWriter callSitesWriter; private IncludeManager includes; private IncludeManager headerIncludes; private MethodNodeCache astCache = EmptyMethodNodeCache.INSTANCE; private AstDependencyExtractor dependencyExtractor = new AstDependencyExtractor(); + private List callSites; public ClassGenerator(GenerationContext context, TagRegistry tagRegistry, Decompiler decompiler, CacheStatus cacheStatus) { @@ -110,6 +113,10 @@ public class ClassGenerator { this.astCache = astCache; } + public void setCallSites(List callSites) { + this.callSites = callSites; + } + public void prepare(ListableClassHolderSource classes) { for (String className : classes.getClassNames()) { ClassHolder cls = classes.get(className); @@ -196,7 +203,7 @@ public class ClassGenerator { } private void generateCallSites(List callSites, String callSitesName) { - CallSiteGenerator generator = new CallSiteGenerator(context, codeWriter, includes, callSitesName); + CallSiteGenerator generator = new CallSiteGenerator(context, callSitesWriter, includes, callSitesName); generator.setStatic(true); generator.generate(callSites); } @@ -224,6 +231,9 @@ public class ClassGenerator { headerIncludes.includePath("runtime.h"); codeGenerator = new CodeGenerator(context, codeWriter, includes); + if (context.isLongjmp() && !context.isIncremental()) { + codeGenerator.setCallSites(callSites); + } String sysInitializerName = context.getNames().forClassSystemInitializer(type); headerWriter.println("extern void " + sysInitializerName + "();"); @@ -279,17 +289,7 @@ public class ClassGenerator { } if (context.isIncremental()) { - String callSitesName; - List callSites = CallSiteDescriptor.extract(method.getProgram()); - if (!callSites.isEmpty()) { - callSitesName = "callsites_" + context.getNames().forMethod(method.getReference()); - includes.includeClass(CallSite.class.getName()); - generateCallSites(callSites, callSitesName); - } else { - callSitesName = "NULL"; - } - codeWriter.println("#define TEAVM_ALLOC_STACK(size) TEAVM_ALLOC_STACK_DEF(size, " - + callSitesName + ")"); + callSitesWriter = codeWriter.fragment(); } generateMethodForwardDeclaration(method); @@ -303,9 +303,19 @@ public class ClassGenerator { methodNode = entry.method; } + List callSites = null; + if (context.isLongjmp()) { + if (context.isIncremental()) { + callSites = new ArrayList<>(); + codeGenerator.setCallSites(callSites); + } + } + codeGenerator.generateMethod(methodNode); if (context.isIncremental()) { + generateCallSites(method.getReference(), + context.isLongjmp() ? callSites : CallSiteDescriptor.extract(method.getProgram())); codeWriter.println("#undef TEAVM_ALLOC_STACK"); } } @@ -318,6 +328,19 @@ public class ClassGenerator { headerWriter.println(";"); } + private void generateCallSites(MethodReference method, List callSites) { + String callSitesName; + if (!callSites.isEmpty()) { + callSitesName = "callsites_" + context.getNames().forMethod(method); + includes.includeClass(CallSite.class.getName()); + generateCallSites(callSites, callSitesName); + } else { + callSitesName = "NULL"; + } + callSitesWriter.println("#define TEAVM_ALLOC_STACK(size) TEAVM_ALLOC_STACK_DEF(size, " + + callSitesName + ")"); + } + private void generateInitializer(ClassHolder cls) { if (!needsInitializer(cls)) { return; diff --git a/core/src/main/java/org/teavm/backend/c/generate/CodeGenerationVisitor.java b/core/src/main/java/org/teavm/backend/c/generate/CodeGenerationVisitor.java index a5b46392a..b7d7a1a2b 100644 --- a/core/src/main/java/org/teavm/backend/c/generate/CodeGenerationVisitor.java +++ b/core/src/main/java/org/teavm/backend/c/generate/CodeGenerationVisitor.java @@ -15,6 +15,12 @@ */ package org.teavm.backend.c.generate; +import static org.teavm.model.lowlevel.ExceptionHandlingShadowStackContributor.isManagedMethodCall; +import com.carrotsearch.hppc.IntContainer; +import com.carrotsearch.hppc.IntHashSet; +import com.carrotsearch.hppc.IntSet; +import com.carrotsearch.hppc.ObjectIntHashMap; +import com.carrotsearch.hppc.ObjectIntMap; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.DoubleBuffer; @@ -24,10 +30,13 @@ import java.nio.LongBuffer; import java.nio.ShortBuffer; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Collections; import java.util.Deque; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.teavm.ast.ArrayType; import org.teavm.ast.AssignmentStatement; import org.teavm.ast.BinaryExpr; @@ -41,6 +50,7 @@ import org.teavm.ast.ContinueStatement; import org.teavm.ast.Expr; import org.teavm.ast.ExprVisitor; import org.teavm.ast.GotoPartStatement; +import org.teavm.ast.IdentifiedStatement; import org.teavm.ast.InitClassStatement; import org.teavm.ast.InstanceOfExpr; import org.teavm.ast.InvocationExpr; @@ -65,6 +75,7 @@ import org.teavm.ast.UnaryExpr; import org.teavm.ast.UnwrapArrayExpr; import org.teavm.ast.VariableExpr; import org.teavm.ast.WhileStatement; +import org.teavm.backend.c.analyze.VolatileDefinitionFinder; import org.teavm.backend.c.intrinsic.Intrinsic; import org.teavm.backend.c.intrinsic.IntrinsicContext; import org.teavm.backend.c.util.InteropUtil; @@ -83,9 +94,11 @@ import org.teavm.model.MethodReference; import org.teavm.model.TextLocation; import org.teavm.model.ValueType; import org.teavm.model.classes.VirtualTable; +import org.teavm.model.lowlevel.CallSiteDescriptor; +import org.teavm.model.lowlevel.CallSiteLocation; +import org.teavm.model.lowlevel.ExceptionHandlerDescriptor; import org.teavm.runtime.Allocator; import org.teavm.runtime.ExceptionHandling; -import org.teavm.runtime.Fiber; import org.teavm.runtime.RuntimeArray; import org.teavm.runtime.RuntimeClass; import org.teavm.runtime.RuntimeObject; @@ -107,19 +120,31 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { Object.class, void.class); private static final MethodReference MONITOR_EXIT_SYNC = new MethodReference(Object.class, "monitorExitSync", Object.class, void.class); + private static final MethodReference CATCH_EXCEPTION = new MethodReference(ExceptionHandling.class, + "catchException", Throwable.class); private static final Map BUFFER_TYPES = new HashMap<>(); private GenerationContext context; private NameProvider names; private CodeWriter writer; + private VolatileDefinitionFinder volatileDefinitions; private int[] temporaryVariableLevel = new int[5]; + private IntSet spilledVariables = new IntHashSet(); private int[] maxTemporaryVariableLevel = new int[5]; private MethodReference callingMethod; private IncludeManager includes; private boolean end; private boolean async; private final Deque locationStack = new ArrayDeque<>(); + private List callSites; + private List handlers = new ArrayList<>(); + private boolean managed; + private IdentifiedStatement defaultBreakTarget; + private IdentifiedStatement defaultContinueTarget; + private ObjectIntMap labelMap = new ObjectIntHashMap<>(); + private Set usedAsBreakTarget = new HashSet<>(); + private Set usedAsContinueTarget = new HashSet<>(); static { BUFFER_TYPES.put(ByteBuffer.class.getName(), "int8_t"); @@ -131,11 +156,14 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { BUFFER_TYPES.put(DoubleBuffer.class.getName(), "double"); } - public CodeGenerationVisitor(GenerationContext context, CodeWriter writer, IncludeManager includes) { + public CodeGenerationVisitor(GenerationContext context, CodeWriter writer, IncludeManager includes, + List callSites, VolatileDefinitionFinder volatileDefinitions) { this.context = context; this.writer = writer; this.names = context.getNames(); this.includes = includes; + this.callSites = callSites; + this.volatileDefinitions = volatileDefinitions; } public void setAsync(boolean async) { @@ -146,8 +174,13 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { return maxTemporaryVariableLevel; } + public IntContainer getSpilledVariables() { + return spilledVariables; + } + public void setCallingMethod(MethodReference callingMethod) { this.callingMethod = callingMethod; + this.managed = context.getCharacteristics().isManaged(callingMethod); } @Override @@ -308,7 +341,9 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { writer.print(")"); break; case NULL_CHECK: + writer.print("teavm_nullCheck("); expr.getOperand().acceptVisitor(this); + writer.print(")"); break; case INT_TO_BYTE: writer.print("TEAVM_TO_BYTE("); @@ -405,6 +440,10 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { } } + private boolean needsCallSiteId() { + return context.isLongjmp() && managed; + } + @Override public void visit(InvocationExpr expr) { ClassReader cls = context.getClassSource().get(expr.getMethod().getClassName()); @@ -416,15 +455,30 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { } } + boolean needParenthesis = false; + Intrinsic intrinsic = context.getIntrinsic(expr.getMethod()); if (intrinsic != null) { pushLocation(expr.getLocation()); + if (needsCallSiteId() && isManagedMethodCall(context.getCharacteristics(), expr.getMethod())) { + needParenthesis = true; + withCallSite(); + } intrinsic.apply(intrinsicContext, expr); popLocation(expr.getLocation()); + if (needParenthesis) { + writer.print(")"); + } return; } pushLocation(expr.getLocation()); + + if (needsCallSiteId() && context.getCharacteristics().isManaged(expr.getMethod())) { + needParenthesis = true; + withCallSite(); + } + switch (expr.getType()) { case CONSTRUCTOR: generateCallToConstructor(expr.getMethod(), expr.getArguments()); @@ -441,9 +495,34 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { } } + if (needParenthesis) { + writer.print(")"); + } + popLocation(expr.getLocation()); } + private void withCallSite() { + LocationStackEntry locationEntry = locationStack.peek(); + TextLocation location = locationEntry != null ? locationEntry.location : null; + String fileName = location != null ? location.getFileName() : null; + if (fileName != null) { + fileName = fileName.substring(fileName.lastIndexOf('/') + 1); + } + CallSiteLocation callSiteLocation = new CallSiteLocation( + fileName, + callingMethod.getClassName(), + callingMethod.getName(), + location != null ? location.getLine() : 0); + CallSiteDescriptor callSite = new CallSiteDescriptor(callSites.size(), callSiteLocation); + List reverseHandlers = new ArrayList<>(handlers); + Collections.reverse(reverseHandlers); + callSite.getHandlers().addAll(reverseHandlers); + callSites.add(callSite); + + writer.print("TEAVM_WITH_CALL_SITE_ID(").print(String.valueOf(callSite.getId())).print(", "); + } + private void generateCallToConstructor(MethodReference reference, List arguments) { String receiver = allocTemporaryVariable(CVariableType.PTR); writer.print("(" + receiver + " = "); @@ -751,9 +830,17 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { @Override public void visit(NewExpr expr) { pushLocation(expr.getLocation()); + boolean needParenthesis = false; + if (needsCallSiteId()) { + needParenthesis = true; + withCallSite(); + } includes.includeClass(expr.getConstructedClass()); includes.includeClass(ALLOC_METHOD.getClassName()); allocObject(expr.getConstructedClass()); + if (needParenthesis) { + writer.print(")"); + } popLocation(expr.getLocation()); } @@ -766,6 +853,13 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { @Override public void visit(NewArrayExpr expr) { pushLocation(expr.getLocation()); + + boolean needParenthesis = false; + if (needsCallSiteId()) { + needParenthesis = true; + withCallSite(); + } + ValueType type = ValueType.arrayOf(expr.getType()); writer.print(names.forMethod(ALLOC_ARRAY_METHOD)).print("(&") .print(names.forClassInstance(type)).print(", "); @@ -773,11 +867,24 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { includes.includeType(type); expr.getLength().acceptVisitor(this); writer.print(")"); + + if (needParenthesis) { + writer.print(")"); + } + popLocation(expr.getLocation()); } @Override public void visit(NewMultiArrayExpr expr) { + pushLocation(expr.getLocation()); + + boolean needParenthesis = false; + if (needsCallSiteId()) { + needParenthesis = true; + withCallSite(); + } + writer.print(names.forMethod(ALLOC_MULTI_ARRAY_METHOD)).print("(&") .print(names.forClassInstance(expr.getType())).print(", "); includes.includeClass(ALLOC_ARRAY_METHOD.getClassName()); @@ -791,6 +898,12 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { } writer.print("}, ").print(String.valueOf(expr.getDimensions().size())).print(")"); + + if (needParenthesis) { + writer.print(")"); + } + + popLocation(expr.getLocation()); } @Override @@ -813,11 +926,24 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { return; } } + pushLocation(expr.getLocation()); + + boolean needParenthesis = false; + if (needsCallSiteId()) { + needParenthesis = true; + withCallSite(); + } + writer.print("teavm_checkcast("); expr.getValue().acceptVisitor(this); includes.includeType(expr.getTarget()); writer.print(", ").print(names.forSupertypeFunction(expr.getTarget())).print(")"); + + if (needParenthesis) { + writer.print(")"); + } + popLocation(expr.getLocation()); } @@ -848,6 +974,7 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { @Override public void visit(AssignmentStatement statement) { pushLocation(statement.getLocation()); + if (statement.getLeftValue() != null) { if (statement.getLeftValue() instanceof QualificationExpr) { QualificationExpr qualification = (QualificationExpr) statement.getLeftValue(); @@ -871,8 +998,10 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { statement.getRightValue().acceptVisitor(this); writer.println(";"); - if (statement.isAsync()) { - emitSuspendChecker(); + if (volatileDefinitions.shouldBackup(statement)) { + VariableExpr lhs = (VariableExpr) statement.getLeftValue(); + spilledVariables.add(lhs.getIndex()); + writer.println("teavm_spill_" + lhs.getIndex() + " = " + getVariableName(lhs.getIndex()) + ";"); } popLocation(statement.getLocation()); @@ -929,6 +1058,12 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { @Override public void visit(SwitchStatement statement) { + IdentifiedStatement oldDefaultBreakTarget = defaultBreakTarget; + defaultBreakTarget = statement; + + int statementId = labelMap.size() + 1; + labelMap.put(statement, statementId); + pushLocation(statement.getValue().getLocation()); writer.print("switch ("); statement.getValue().acceptVisitor(this); @@ -958,13 +1093,23 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { writer.outdent().println("}"); - if (statement.getId() != null) { - writer.outdent().println("teavm_label_" + statement.getId() + ":;").indent(); + if (usedAsBreakTarget.contains(statement)) { + writer.outdent().println("teavm_label_" + statementId + ":;").indent(); } + + defaultBreakTarget = oldDefaultBreakTarget; } @Override public void visit(WhileStatement statement) { + IdentifiedStatement oldDefaultBreakTarget = defaultBreakTarget; + IdentifiedStatement oldDefaultContinueTarget = defaultContinueTarget; + defaultBreakTarget = statement; + defaultContinueTarget = statement; + + int statementId = labelMap.size() + 1; + labelMap.put(statement, statementId); + writer.print("while ("); if (statement.getCondition() != null) { statement.getCondition().acceptVisitor(this); @@ -980,44 +1125,54 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { } end = oldEnd; - if (statement.getId() != null) { - writer.outdent().println("teavm_cnt_" + statement.getId() + ":;").indent(); + if (usedAsContinueTarget.contains(statement)) { + writer.outdent().println("teavm_cnt_" + statementId + ":;").indent(); } writer.outdent().println("}"); - if (statement.getId() != null) { - writer.outdent().println("teavm_label_" + statement.getId() + ":;").indent(); + if (usedAsBreakTarget.contains(statement)) { + writer.outdent().println("teavm_label_" + statementId + ":;").indent(); } + + defaultContinueTarget = oldDefaultContinueTarget; + defaultBreakTarget = oldDefaultBreakTarget; } @Override public void visit(BlockStatement statement) { + int statementId = labelMap.size() + 1; + labelMap.put(statement, statementId); + visitMany(statement.getBody()); - if (statement.getId() != null) { - writer.outdent().println("teavm_label_" + statement.getId() + ":;").indent(); + if (usedAsBreakTarget.contains(statement)) { + writer.outdent().println("teavm_label_" + statementId + ":;").indent(); } } @Override public void visit(BreakStatement statement) { pushLocation(statement.getLocation()); - if (statement.getTarget() == null || statement.getTarget().getId() == null) { - writer.println("break;"); - } else { - writer.println("goto teavm_label_" + statement.getTarget().getId() + ";"); + IdentifiedStatement target = statement.getTarget(); + if (target == null) { + target = defaultBreakTarget; } + int id = labelMap.get(target); + writer.println("goto teavm_label_" + id + ";"); + usedAsBreakTarget.add(target); popLocation(statement.getLocation()); } @Override public void visit(ContinueStatement statement) { pushLocation(statement.getLocation()); - if (statement.getTarget() == null || statement.getTarget().getId() == null) { - writer.println("continue;"); - } else { - writer.println("goto teavm_cnt_" + statement.getTarget().getId() + ";"); + IdentifiedStatement target = statement.getTarget(); + if (target == null) { + target = defaultContinueTarget; } + int id = labelMap.get(target); + writer.println("goto teavm_cnt_" + id + ";"); + usedAsContinueTarget.add(target); popLocation(statement.getLocation()); } @@ -1036,23 +1191,99 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { @Override public void visit(ThrowStatement statement) { pushLocation(statement.getLocation()); + + boolean needParenthesis = false; + if (needsCallSiteId()) { + needParenthesis = true; + withCallSite(); + } + includes.includeClass(THROW_EXCEPTION_METHOD.getClassName()); writer.print(names.forMethod(THROW_EXCEPTION_METHOD)).print("("); statement.getException().acceptVisitor(this); - writer.println(");"); + writer.print(")"); + + if (needParenthesis) { + writer.print(")"); + } + writer.println(";"); + popLocation(statement.getLocation()); } @Override public void visit(InitClassStatement statement) { pushLocation(statement.getLocation()); + + boolean needParenthesis = false; + if (needsCallSiteId()) { + needParenthesis = true; + withCallSite(); + } + includes.includeClass(statement.getClassName()); - writer.println(names.forClassInitializer(statement.getClassName()) + "();"); + writer.print(names.forClassInitializer(statement.getClassName()) + "()"); + + if (needParenthesis) { + writer.print(")"); + } + writer.println(";"); + popLocation(statement.getLocation()); } @Override public void visit(TryCatchStatement statement) { + List tryCatchStatements = new ArrayList<>(); + List restoredVariablesByHandler = new ArrayList<>(); + while (true) { + if (statement.getProtectedBody().size() != 1) { + break; + } + Statement next = statement.getProtectedBody().get(0); + if (!(next instanceof TryCatchStatement)) { + break; + } + tryCatchStatements.add(statement); + restoredVariablesByHandler.add(volatileDefinitions.variablesToRestore(statement)); + statement = (TryCatchStatement) next; + } + tryCatchStatements.add(statement); + restoredVariablesByHandler.add(volatileDefinitions.variablesToRestore(statement)); + + int firstId = handlers.size(); + for (int i = 0; i < tryCatchStatements.size(); ++i) { + TryCatchStatement tryCatch = tryCatchStatements.get(i); + handlers.add(new ExceptionHandlerDescriptor(firstId + i + 1, tryCatch.getExceptionType())); + } + + writer.println("TEAVM_TRY").indent(); + visitMany(statement.getProtectedBody()); + writer.outdent().println("TEAVM_CATCH").indent(); + + for (int i = tryCatchStatements.size() - 1; i >= 0; --i) { + TryCatchStatement tryCatch = tryCatchStatements.get(i); + int[] variablesToRestore = restoredVariablesByHandler.get(i); + writer.println("// CATCH " + (tryCatch.getExceptionType() != null ? tryCatch.getExceptionType() : "any")); + writer.println("case " + (i + 1 + firstId) + ": {").indent(); + + for (int variableIndex : variablesToRestore) { + writer.println(getVariableName(variableIndex) + " = teavm_spill_" + variableIndex + ";"); + } + + if (tryCatch.getExceptionVariable() != null) { + writer.print(getVariableName(tryCatch.getExceptionVariable())).print(" = "); + writer.print(names.forMethod(CATCH_EXCEPTION)).println("();"); + } + visitMany(tryCatch.getHandler()); + + writer.println("break;"); + writer.outdent().println("}"); + } + + handlers.subList(firstId, handlers.size()).clear(); + + writer.outdent().println("TEAVM_END_TRY"); } @Override @@ -1062,26 +1293,47 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { @Override public void visit(MonitorEnterStatement statement) { pushLocation(statement.getLocation()); + + boolean needParenthesis = false; + if (needsCallSiteId()) { + needParenthesis = true; + withCallSite(); + } + includes.includeClass("java.lang.Object"); writer.print(names.forMethod(async ? MONITOR_ENTER : MONITOR_ENTER_SYNC)).print("("); statement.getObjectRef().acceptVisitor(this); - writer.println(");"); + writer.print(")"); + + if (needParenthesis) { + writer.print(")"); + } + writer.println(";"); + popLocation(statement.getLocation()); } @Override public void visit(MonitorExitStatement statement) { pushLocation(statement.getLocation()); + + boolean needParenthesis = false; + if (needsCallSiteId()) { + needParenthesis = true; + withCallSite(); + } + includes.includeClass("java.lang.Object"); writer.print(names.forMethod(async ? MONITOR_EXIT : MONITOR_EXIT_SYNC)).print("("); statement.getObjectRef().acceptVisitor(this); - writer.println(");"); - popLocation(statement.getLocation()); - } + writer.print(")"); - public void emitSuspendChecker() { - String suspendingName = names.forMethod(new MethodReference(Fiber.class, "isSuspending", boolean.class)); - writer.println("if (" + suspendingName + "(fiber)) goto teavm_exit_loop;"); + if (needParenthesis) { + writer.print(")"); + } + writer.println(";"); + + popLocation(statement.getLocation()); } private IntrinsicContext intrinsicContext = new IntrinsicContext() { diff --git a/core/src/main/java/org/teavm/backend/c/generate/CodeGenerator.java b/core/src/main/java/org/teavm/backend/c/generate/CodeGenerator.java index 45f74af75..dd6be74e7 100644 --- a/core/src/main/java/org/teavm/backend/c/generate/CodeGenerator.java +++ b/core/src/main/java/org/teavm/backend/c/generate/CodeGenerator.java @@ -15,12 +15,16 @@ */ package org.teavm.backend.c.generate; +import com.carrotsearch.hppc.IntContainer; +import java.util.List; import org.teavm.ast.MethodNode; import org.teavm.ast.RegularMethodNode; import org.teavm.ast.VariableNode; +import org.teavm.backend.c.analyze.VolatileDefinitionFinder; import org.teavm.model.ElementModifier; import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodReference; +import org.teavm.model.lowlevel.CallSiteDescriptor; public class CodeGenerator { private GenerationContext context; @@ -28,6 +32,7 @@ public class CodeGenerator { private CodeWriter localsWriter; private NameProvider names; private IncludeManager includes; + private List callSites; public CodeGenerator(GenerationContext context, CodeWriter writer, IncludeManager includes) { this.context = context; @@ -36,6 +41,10 @@ public class CodeGenerator { this.includes = includes; } + public void setCallSites(List callSites) { + this.callSites = callSites; + } + public void generateMethod(RegularMethodNode methodNode) { generateMethodSignature(writer, methodNode.getReference(), methodNode.getModifiers().contains(ElementModifier.STATIC), true); @@ -44,13 +53,16 @@ public class CodeGenerator { localsWriter = writer.fragment(); CodeGenerationVisitor visitor = generateMethodBody(methodNode); - generateLocals(methodNode, visitor.getTemporaries()); + generateLocals(methodNode, visitor.getTemporaries(), visitor.getSpilledVariables()); writer.outdent().println("}"); } private CodeGenerationVisitor generateMethodBody(RegularMethodNode methodNode) { - CodeGenerationVisitor visitor = new CodeGenerationVisitor(context, writer, includes); + VolatileDefinitionFinder volatileDefinitions = new VolatileDefinitionFinder(); + volatileDefinitions.findVolatileDefinitions(methodNode.getBody()); + CodeGenerationVisitor visitor = new CodeGenerationVisitor(context, writer, includes, callSites, + volatileDefinitions); visitor.setAsync(context.isAsync(methodNode.getReference())); visitor.setCallingMethod(methodNode.getReference()); methodNode.getBody().acceptVisitor(visitor); @@ -94,7 +106,7 @@ public class CodeGenerator { } } - private void generateLocals(MethodNode methodNode, int[] temporaryCount) { + private void generateLocals(MethodNode methodNode, int[] temporaryCount, IntContainer spilledVariables) { int start = methodNode.getReference().parameterCount() + 1; for (int i = start; i < methodNode.getVariables().size(); ++i) { VariableNode variableNode = methodNode.getVariables().get(i); @@ -103,6 +115,10 @@ public class CodeGenerator { } localsWriter.printType(variableNode.getType()).print(" teavm_local_").print(String.valueOf(i)) .println(";"); + if (spilledVariables.contains(i)) { + localsWriter.print("volatile ").printType(variableNode.getType()).print(" teavm_spill_") + .print(String.valueOf(i)).println(";"); + } } for (CVariableType type : CVariableType.values()) { diff --git a/core/src/main/java/org/teavm/backend/c/generate/GenerationContext.java b/core/src/main/java/org/teavm/backend/c/generate/GenerationContext.java index d572e424c..4c2a2b9d2 100644 --- a/core/src/main/java/org/teavm/backend/c/generate/GenerationContext.java +++ b/core/src/main/java/org/teavm/backend/c/generate/GenerationContext.java @@ -44,11 +44,13 @@ public class GenerationContext { private Predicate asyncMethods; private BuildTarget buildTarget; private boolean incremental; + private boolean longjmp; public GenerationContext(VirtualTableProvider virtualTableProvider, Characteristics characteristics, DependencyInfo dependencies, StringPool stringPool, NameProvider names, Diagnostics diagnostics, ClassReaderSource classSource, List intrinsics, List generators, - Predicate asyncMethods, BuildTarget buildTarget, boolean incremental) { + Predicate asyncMethods, BuildTarget buildTarget, boolean incremental, + boolean longjmp) { this.virtualTableProvider = virtualTableProvider; this.characteristics = characteristics; this.dependencies = dependencies; @@ -61,6 +63,7 @@ public class GenerationContext { this.asyncMethods = asyncMethods; this.buildTarget = buildTarget; this.incremental = incremental; + this.longjmp = longjmp; } public void addIntrinsic(Intrinsic intrinsic) { @@ -124,4 +127,8 @@ public class GenerationContext { public boolean isIncremental() { return incremental; } + + public boolean isLongjmp() { + return longjmp; + } } diff --git a/core/src/main/java/org/teavm/backend/c/intrinsic/ExceptionHandlingIntrinsic.java b/core/src/main/java/org/teavm/backend/c/intrinsic/ExceptionHandlingIntrinsic.java index 9070aca60..a5e38e59f 100644 --- a/core/src/main/java/org/teavm/backend/c/intrinsic/ExceptionHandlingIntrinsic.java +++ b/core/src/main/java/org/teavm/backend/c/intrinsic/ExceptionHandlingIntrinsic.java @@ -28,6 +28,9 @@ public class ExceptionHandlingIntrinsic implements Intrinsic { switch (method.getName()) { case "findCallSiteById": + case "isJumpSupported": + case "jumpToFrame": + case "abort": return true; default: return false; @@ -45,6 +48,23 @@ public class ExceptionHandlingIntrinsic implements Intrinsic { context.emit(invocation.getArguments().get(1)); context.writer().print(")"); break; + + case "isJumpSupported": + context.writer().print("TEAVM_JUMP_SUPPORTED"); + break; + + case "jumpToFrame": + context.writer().print("TEAVM_JUMP_TO_FRAME("); + context.emit(invocation.getArguments().get(0)); + context.writer().print(", "); + context.emit(invocation.getArguments().get(1)); + context.writer().print(")"); + break; + + case "abort": + context.includes().addInclude(""); + context.writer().print("abort();"); + break; } } } diff --git a/core/src/main/java/org/teavm/backend/c/intrinsic/ShadowStackIntrinsic.java b/core/src/main/java/org/teavm/backend/c/intrinsic/ShadowStackIntrinsic.java index b319ce003..61c0bdab3 100644 --- a/core/src/main/java/org/teavm/backend/c/intrinsic/ShadowStackIntrinsic.java +++ b/core/src/main/java/org/teavm/backend/c/intrinsic/ShadowStackIntrinsic.java @@ -73,9 +73,9 @@ public class ShadowStackIntrinsic implements Intrinsic { context.writer().print("teavm_stackTop"); return; case "getNextStackFrame": - context.writer().print("((void**) "); + context.writer().print("TEAVM_GET_NEXT_FRAME("); context.emit(invocation.getArguments().get(0)); - context.writer().print(")[0]"); + context.writer().print(")"); return; case "getStackRootCount": context.writer().print("TEAVM_GC_ROOTS_COUNT("); @@ -89,9 +89,9 @@ public class ShadowStackIntrinsic implements Intrinsic { return; } case "getCallSiteId": - context.writer().print("((int32_t) (intptr_t) ((void**) "); + context.writer().print("TEAVM_GET_CALL_SITE_ID("); context.emit(invocation.getArguments().get(0)); - context.writer().print(")[1])"); + context.writer().print(")"); return; } diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/StatementRenderer.java b/core/src/main/java/org/teavm/backend/javascript/rendering/StatementRenderer.java index 6008d04b8..ab81d00a2 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/StatementRenderer.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/StatementRenderer.java @@ -1447,7 +1447,8 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor { .softNewLine(); boolean first = true; boolean defaultHandlerOccurred = false; - for (TryCatchStatement catchClause : sequence) { + for (int i = sequence.size() - 1; i >= 0; --i) { + TryCatchStatement catchClause = sequence.get(i); if (!first) { writer.ws().append("else"); } 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 ec5d906b0..75ac0f72f 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java @@ -165,7 +165,7 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { controller.getUnprocessedClassSource()); classInitializerEliminator = new ClassInitializerEliminator(controller.getUnprocessedClassSource()); classInitializerTransformer = new ClassInitializerTransformer(); - shadowStackTransformer = new ShadowStackTransformer(managedMethodRepository); + shadowStackTransformer = new ShadowStackTransformer(managedMethodRepository, true); } @Override diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/ExceptionHandlingIntrinsic.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/ExceptionHandlingIntrinsic.java index 0c72887c5..61b80e6e1 100644 --- a/core/src/main/java/org/teavm/backend/wasm/intrinsics/ExceptionHandlingIntrinsic.java +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/ExceptionHandlingIntrinsic.java @@ -27,6 +27,7 @@ import org.teavm.backend.wasm.model.expression.WasmInt32Constant; import org.teavm.backend.wasm.model.expression.WasmIntBinary; import org.teavm.backend.wasm.model.expression.WasmIntBinaryOperation; import org.teavm.backend.wasm.model.expression.WasmIntType; +import org.teavm.backend.wasm.model.expression.WasmUnreachable; import org.teavm.model.MethodReference; import org.teavm.model.lowlevel.CallSiteDescriptor; import org.teavm.runtime.CallSite; @@ -50,6 +51,9 @@ public class ExceptionHandlingIntrinsic implements WasmIntrinsic { } switch (methodReference.getName()) { case "findCallSiteById": + case "isJumpSupported": + case "jumpToFrame": + case "abort": return true; } return false; @@ -64,15 +68,29 @@ public class ExceptionHandlingIntrinsic implements WasmIntrinsic { @Override public WasmExpression apply(InvocationExpr invocation, WasmIntrinsicManager manager) { - WasmInt32Constant constant = new WasmInt32Constant(0); - constant.setLocation(invocation.getLocation()); - constants.add(constant); + switch (invocation.getMethod().getName()) { + case "findCallSiteById": { + WasmInt32Constant constant = new WasmInt32Constant(0); + constant.setLocation(invocation.getLocation()); + constants.add(constant); - int callSiteSize = classGenerator.getClassSize(CallSite.class.getName()); - WasmExpression id = manager.generate(invocation.getArguments().get(0)); - WasmExpression offset = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.MUL, - id, new WasmInt32Constant(callSiteSize)); + int callSiteSize = classGenerator.getClassSize(CallSite.class.getName()); + WasmExpression id = manager.generate(invocation.getArguments().get(0)); + WasmExpression offset = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.MUL, + id, new WasmInt32Constant(callSiteSize)); - return new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.ADD, constant, offset); + return new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.ADD, constant, offset); + } + + case "isJumpSupported": + return new WasmInt32Constant(0); + + case "jumpToFrame": + case "abort": + return new WasmUnreachable(); + + default: + throw new IllegalArgumentException("Unknown method: " + invocation.getMethod()); + } } } diff --git a/core/src/main/java/org/teavm/common/RangeTree.java b/core/src/main/java/org/teavm/common/RangeTree.java index 57a6a4759..036b9719c 100644 --- a/core/src/main/java/org/teavm/common/RangeTree.java +++ b/core/src/main/java/org/teavm/common/RangeTree.java @@ -96,7 +96,13 @@ public class RangeTree { }); Deque stack = new ArrayDeque<>(); stack.push(root); + + Range lastRange = null; for (Range range : rangeList) { + if (lastRange != null && lastRange.left == range.left && lastRange.right == range.right) { + continue; + } + NodeImpl current = new NodeImpl(); current.start = range.left; current.end = range.right; @@ -114,6 +120,7 @@ public class RangeTree { ancestor.start = current.start; } stack.push(current); + lastRange = range; } } diff --git a/core/src/main/java/org/teavm/model/lowlevel/ExceptionHandlingShadowStackContributor.java b/core/src/main/java/org/teavm/model/lowlevel/ExceptionHandlingShadowStackContributor.java index 4ebe03a3c..feaa2f216 100644 --- a/core/src/main/java/org/teavm/model/lowlevel/ExceptionHandlingShadowStackContributor.java +++ b/core/src/main/java/org/teavm/model/lowlevel/ExceptionHandlingShadowStackContributor.java @@ -272,22 +272,29 @@ public class ExceptionHandlingShadowStackContributor { } private boolean isCallInstruction(Instruction insn) { + return isCallInstruction(characteristics, insn); + } + + public static boolean isCallInstruction(Characteristics characteristics, Instruction insn) { if (insn instanceof InitClassInstruction || insn instanceof ConstructInstruction || insn instanceof ConstructArrayInstruction || insn instanceof ConstructMultiArrayInstruction || insn instanceof CloneArrayInstruction || insn instanceof RaiseInstruction || insn instanceof MonitorEnterInstruction || insn instanceof MonitorExitInstruction) { return true; } else if (insn instanceof InvokeInstruction) { - MethodReference method = ((InvokeInstruction) insn).getMethod(); - if (characteristics.isManaged(method)) { - return true; - } - return method.getClassName().equals(ExceptionHandling.class.getName()) - && method.getName().startsWith("throw"); + return isManagedMethodCall(characteristics, ((InvokeInstruction) insn).getMethod()); } return false; } + public static boolean isManagedMethodCall(Characteristics characteristics, MethodReference method) { + if (characteristics.isManaged(method)) { + return true; + } + return method.getClassName().equals(ExceptionHandling.class.getName()) + && method.getName().startsWith("throw"); + } + private boolean isSpecialCallInstruction(Instruction insn) { if (!(insn instanceof InvokeInstruction)) { return false; diff --git a/core/src/main/java/org/teavm/model/lowlevel/ShadowStackTransformer.java b/core/src/main/java/org/teavm/model/lowlevel/ShadowStackTransformer.java index 0adb0e2c1..b8ce40792 100644 --- a/core/src/main/java/org/teavm/model/lowlevel/ShadowStackTransformer.java +++ b/core/src/main/java/org/teavm/model/lowlevel/ShadowStackTransformer.java @@ -24,22 +24,25 @@ import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; import org.teavm.model.Phi; import org.teavm.model.Program; +import org.teavm.model.TryCatchBlock; import org.teavm.model.Variable; import org.teavm.model.instructions.ExitInstruction; import org.teavm.model.instructions.IntegerConstantInstruction; import org.teavm.model.instructions.InvocationType; import org.teavm.model.instructions.InvokeInstruction; import org.teavm.model.instructions.JumpInstruction; +import org.teavm.model.util.BasicBlockMapper; import org.teavm.runtime.ShadowStack; public class ShadowStackTransformer { private Characteristics characteristics; private GCShadowStackContributor gcContributor; - private List callSites = new ArrayList<>(); + private boolean exceptionHandling; - public ShadowStackTransformer(Characteristics characteristics) { + public ShadowStackTransformer(Characteristics characteristics, boolean exceptionHandling) { gcContributor = new GCShadowStackContributor(characteristics); this.characteristics = characteristics; + this.exceptionHandling = exceptionHandling; } public void apply(Program program, MethodReader method) { @@ -48,11 +51,27 @@ public class ShadowStackTransformer { } int shadowStackSize = gcContributor.contribute(program, method); - int callSiteStartIndex = callSites.size(); - boolean exceptions = new ExceptionHandlingShadowStackContributor(characteristics, callSites, - method.getReference(), program).contribute(); - List programCallSites = callSites.subList(callSiteStartIndex, callSites.size()); - CallSiteDescriptor.save(programCallSites, program.getAnnotations()); + boolean exceptions; + if (exceptionHandling) { + List callSites = new ArrayList<>(); + exceptions = new ExceptionHandlingShadowStackContributor(characteristics, callSites, + method.getReference(), program).contribute(); + CallSiteDescriptor.save(callSites, program.getAnnotations()); + } else { + exceptions = false; + outer: for (BasicBlock block : program.getBasicBlocks()) { + if (!block.getTryCatchBlocks().isEmpty()) { + exceptions = true; + break; + } + for (Instruction insn : block) { + if (ExceptionHandlingShadowStackContributor.isCallInstruction(characteristics, insn)) { + exceptions = true; + break outer; + } + } + } + } if (shadowStackSize > 0 || exceptions) { addStackAllocation(program, shadowStackSize); @@ -62,6 +81,10 @@ public class ShadowStackTransformer { private void addStackAllocation(Program program, int maxDepth) { BasicBlock block = program.basicBlockAt(0); + if (!block.getTryCatchBlocks().isEmpty()) { + splitFirstBlock(program); + } + List instructionsToAdd = new ArrayList<>(); Variable sizeVariable = program.createVariable(); @@ -79,6 +102,26 @@ public class ShadowStackTransformer { block.addFirstAll(instructionsToAdd); } + private void splitFirstBlock(Program program) { + BasicBlock block = program.basicBlockAt(0); + BasicBlock split = program.createBasicBlock(); + while (block.getFirstInstruction() != null) { + Instruction instruction = block.getFirstInstruction(); + instruction.delete(); + split.add(instruction); + } + JumpInstruction jump = new JumpInstruction(); + jump.setLocation(split.getFirstInstruction().getLocation()); + jump.setTarget(split); + block.add(jump); + + List tryCatchBlocks = new ArrayList<>(block.getTryCatchBlocks()); + block.getTryCatchBlocks().clear(); + split.getTryCatchBlocks().addAll(tryCatchBlocks); + + new BasicBlockMapper((BasicBlock b) -> b == block ? split : b).transform(program); + } + private void addStackRelease(Program program, int maxDepth) { List blocks = new ArrayList<>(); boolean hasResult = false; diff --git a/core/src/main/java/org/teavm/model/util/RegisterAllocator.java b/core/src/main/java/org/teavm/model/util/RegisterAllocator.java index 3b0c33d63..172761c38 100644 --- a/core/src/main/java/org/teavm/model/util/RegisterAllocator.java +++ b/core/src/main/java/org/teavm/model/util/RegisterAllocator.java @@ -17,7 +17,9 @@ package org.teavm.model.util; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import org.teavm.common.DisjointSet; @@ -151,17 +153,103 @@ public class RegisterAllocator { } private void insertPhiArgumentsCopies(Program program) { - for (int i = 0; i < program.basicBlockCount(); ++i) { + List> catchIncomingsByVariable = new ArrayList<>( + Collections.nCopies(program.variableCount(), null)); + + int sz = program.basicBlockCount(); + for (int i = 0; i < sz; ++i) { + BasicBlock block = program.basicBlockAt(i); Map blockMap = new HashMap<>(); - for (Phi phi : program.basicBlockAt(i).getPhis()) { + List incomingsToRepeat = new ArrayList<>(); + for (Phi phi : block.getPhis()) { for (Incoming incoming : phi.getIncomings()) { - insertCopy(incoming, blockMap); + boolean fromTry = incoming.getSource().getTryCatchBlocks().stream() + .anyMatch(tryCatch -> tryCatch.getHandler() == incoming.getPhi().getBasicBlock()); + if (fromTry) { + int valueIndex = incoming.getValue().getIndex(); + List catchIncomings = catchIncomingsByVariable.get(valueIndex); + if (catchIncomings == null) { + catchIncomings = new ArrayList<>(1); + catchIncomingsByVariable.set(valueIndex, catchIncomings); + } + catchIncomings.add(incoming); + } else { + insertCopy(incoming, blockMap); + incomingsToRepeat.add(incoming); + } } } - for (Phi phi : program.basicBlockAt(i).getPhis()) { - for (Incoming incoming : phi.getIncomings()) { - insertCopy(incoming, blockMap); + + for (Incoming incoming : incomingsToRepeat) { + insertCopy(incoming, blockMap); + } + } + + DefinitionExtractor definitionExtractor = new DefinitionExtractor(); + List nextInstructions = new ArrayList<>(); + for (BasicBlock block : program.getBasicBlocks()) { + for (Instruction instruction : block) { + instruction.acceptVisitor(definitionExtractor); + Variable[] definedVariables = definitionExtractor.getDefinedVariables(); + for (Variable definedVariable : definedVariables) { + if (definedVariable.getIndex() >= catchIncomingsByVariable.size()) { + continue; + } + List catchIncomings = catchIncomingsByVariable.get(definedVariable.getIndex()); + if (catchIncomings == null) { + continue; + } + + Incoming incoming = null; + for (Iterator iter = catchIncomings.iterator(); iter.hasNext();) { + if (iter.next().getValue() == definedVariable) { + iter.remove(); + break; + } + } + if (incoming == null) { + continue; + } + + Variable copy = program.createVariable(); + copy.setLabel(incoming.getPhi().getReceiver().getLabel()); + copy.setDebugName(incoming.getPhi().getReceiver().getDebugName()); + + AssignInstruction copyInstruction = new AssignInstruction(); + copyInstruction.setReceiver(copy); + copyInstruction.setAssignee(incoming.getValue()); + copyInstruction.setLocation(instruction.getLocation()); + + incoming.setValue(copy); + + nextInstructions.add(copyInstruction); } + + if (!nextInstructions.isEmpty()) { + instruction.insertNextAll(nextInstructions); + nextInstructions.clear(); + } + } + } + + for (List remainingIncomings : catchIncomingsByVariable) { + if (remainingIncomings == null) { + continue; + } + for (Incoming incoming : remainingIncomings) { + BasicBlock block = incoming.getSource(); + + Variable copy = program.createVariable(); + copy.setLabel(incoming.getPhi().getReceiver().getLabel()); + copy.setDebugName(incoming.getPhi().getReceiver().getDebugName()); + incoming.setValue(copy); + + AssignInstruction copyInstruction = new AssignInstruction(); + copyInstruction.setReceiver(copy); + copyInstruction.setAssignee(incoming.getValue()); + copyInstruction.setLocation(block.getFirstInstruction().getLocation()); + + block.addFirst(copyInstruction); } } } diff --git a/core/src/main/java/org/teavm/parsing/ProgramParser.java b/core/src/main/java/org/teavm/parsing/ProgramParser.java index 4647de2b7..4cf178a7c 100644 --- a/core/src/main/java/org/teavm/parsing/ProgramParser.java +++ b/core/src/main/java/org/teavm/parsing/ProgramParser.java @@ -93,7 +93,6 @@ import org.teavm.model.instructions.StringConstantInstruction; import org.teavm.model.instructions.SwitchInstruction; import org.teavm.model.instructions.SwitchTableEntry; import org.teavm.model.instructions.UnwrapArrayInstruction; -import org.teavm.model.util.ProgramUtils; import org.teavm.model.util.TransitionExtractor; public class ProgramParser { @@ -185,8 +184,6 @@ public class ProgramParser { while (program.variableCount() <= signatureVars) { program.createVariable(); } - program.basicBlockAt(0).getTryCatchBlocks().addAll(ProgramUtils.copyTryCatches( - program.basicBlockAt(1), program)); return program; } diff --git a/core/src/main/java/org/teavm/runtime/Allocator.java b/core/src/main/java/org/teavm/runtime/Allocator.java index 5e49084fe..0a14e6c9c 100644 --- a/core/src/main/java/org/teavm/runtime/Allocator.java +++ b/core/src/main/java/org/teavm/runtime/Allocator.java @@ -47,7 +47,6 @@ public final class Allocator { return result; } - @Unmanaged public static RuntimeArray allocateMultiArray(RuntimeClass tag, Address dimensions, int dimensionCount) { int size = dimensions.getInt(); RuntimeArray array = allocateArray(tag, dimensions.getInt()).toStructure(); diff --git a/core/src/main/java/org/teavm/runtime/EventQueue.java b/core/src/main/java/org/teavm/runtime/EventQueue.java index c1a7711d0..a4b715ad7 100644 --- a/core/src/main/java/org/teavm/runtime/EventQueue.java +++ b/core/src/main/java/org/teavm/runtime/EventQueue.java @@ -55,7 +55,7 @@ public final class EventQueue { } public static void kill(int id) { - for (int i = 0; i < data.length; ++i) { + for (int i = 0; i < size; ++i) { if (data[i].id == id) { remove(i); break; diff --git a/core/src/main/java/org/teavm/runtime/ExceptionHandling.java b/core/src/main/java/org/teavm/runtime/ExceptionHandling.java index fe8c22b26..9695164de 100644 --- a/core/src/main/java/org/teavm/runtime/ExceptionHandling.java +++ b/core/src/main/java/org/teavm/runtime/ExceptionHandling.java @@ -25,15 +25,26 @@ public final class ExceptionHandling { private ExceptionHandling() { } + @Unmanaged public static native CallSite findCallSiteById(int id, Address frame); + @Unmanaged + public static native boolean isJumpSupported(); + + @Unmanaged + public static native void jumpToFrame(Address frame, int id); + + @Unmanaged + public static native void abort(); + + @Unmanaged public static void printStack() { Address stackFrame = ShadowStack.getStackTop(); while (stackFrame != null) { int callSiteId = ShadowStack.getCallSiteId(stackFrame); CallSite callSite = findCallSiteById(callSiteId, stackFrame); CallSiteLocation location = callSite.location; - MethodLocation methodLocation = location.method; + MethodLocation methodLocation = location != null ? location.method : null; Console.printString(" at "); if (methodLocation.className == null || methodLocation.methodName == null) { @@ -56,7 +67,7 @@ public final class ExceptionHandling { private static Throwable thrownException; - @Export(name = "sys_catchException") + @Export(name = "teavm_catchException") @Unmanaged public static Throwable catchException() { Throwable exception = thrownException; @@ -72,6 +83,7 @@ public final class ExceptionHandling { RuntimeClass exceptionClass = RuntimeClass.getClass(exceptionPtr); Address stackFrame = ShadowStack.getStackTop(); + int handlerId = 0; stackLoop: while (stackFrame != null) { int callSiteId = ShadowStack.getCallSiteId(stackFrame); CallSite callSite = findCallSiteById(callSiteId, stackFrame); @@ -79,6 +91,7 @@ public final class ExceptionHandling { while (handler != null) { if (handler.exceptionClass == null || handler.exceptionClass.isSupertypeOf.apply(exceptionClass)) { + handlerId = handler.id; ShadowStack.setExceptionHandlerId(stackFrame, handler.id); break stackLoop; } @@ -88,6 +101,13 @@ public final class ExceptionHandling { ShadowStack.setExceptionHandlerId(stackFrame, callSiteId - 1); stackFrame = ShadowStack.getNextStackFrame(stackFrame); } + + if (stackFrame == null) { + printStack(); + abort(); + } else if (isJumpSupported()) { + jumpToFrame(stackFrame, handlerId); + } } @Unmanaged @@ -96,6 +116,7 @@ public final class ExceptionHandling { } @Unmanaged + @Export(name = "teavm_throwNullPointerException") public static void throwNullPointerException() { throw new NullPointerException(); } @@ -119,11 +140,11 @@ public final class ExceptionHandling { int callSiteId = ShadowStack.getCallSiteId(stackFrame); CallSite callSite = findCallSiteById(callSiteId, stackFrame); CallSiteLocation location = callSite.location; - MethodLocation methodLocation = location.method; + MethodLocation methodLocation = location != null ? location.method : null; StackTraceElement element = createElement( - location != null && methodLocation.className != null ? methodLocation.className.value : "", - location != null && methodLocation.methodName != null ? methodLocation.methodName.value : "", - location != null && methodLocation.fileName != null ? methodLocation.fileName.value : null, + methodLocation != null && methodLocation.className != null ? methodLocation.className.value : "", + methodLocation != null && methodLocation.methodName != null ? methodLocation.methodName.value : "", + methodLocation != null && methodLocation.fileName != null ? methodLocation.fileName.value : null, location != null ? location.lineNumber : -1); target[index++] = element; stackFrame = ShadowStack.getNextStackFrame(stackFrame); diff --git a/core/src/main/java/org/teavm/runtime/MarkQueue.java b/core/src/main/java/org/teavm/runtime/MarkQueue.java index 4148c244b..1f37b4c71 100644 --- a/core/src/main/java/org/teavm/runtime/MarkQueue.java +++ b/core/src/main/java/org/teavm/runtime/MarkQueue.java @@ -16,7 +16,11 @@ package org.teavm.runtime; import org.teavm.interop.Address; +import org.teavm.interop.StaticInit; +import org.teavm.interop.Unmanaged; +@Unmanaged +@StaticInit final class MarkQueue { private MarkQueue() { } diff --git a/core/src/main/resources/org/teavm/backend/c/file.c b/core/src/main/resources/org/teavm/backend/c/file.c index 108fd0f1a..7ec9d62f4 100644 --- a/core/src/main/resources/org/teavm/backend/c/file.c +++ b/core/src/main/resources/org/teavm/backend/c/file.c @@ -11,6 +11,7 @@ #include #include #include +#include int32_t teavm_file_homeDirectory(char16_t** result) { struct passwd *pw = getpwuid(getuid()); diff --git a/core/src/main/resources/org/teavm/backend/c/runtime.c b/core/src/main/resources/org/teavm/backend/c/runtime.c index b27e8d5d1..5101bb93e 100644 --- a/core/src/main/resources/org/teavm/backend/c/runtime.c +++ b/core/src/main/resources/org/teavm/backend/c/runtime.c @@ -24,7 +24,7 @@ void* teavm_gc_heapAddress = NULL; -void** teavm_stackTop; +TeaVM_StackFrame* teavm_stackTop = NULL; void* teavm_gc_gcStorageAddress = NULL; int32_t teavm_gc_gcStorageSize = INT32_C(0); diff --git a/core/src/main/resources/org/teavm/backend/c/runtime.h b/core/src/main/resources/org/teavm/backend/c/runtime.h index 3e29f3026..2bbc0b3d7 100644 --- a/core/src/main/resources/org/teavm/backend/c/runtime.h +++ b/core/src/main/resources/org/teavm/backend/c/runtime.h @@ -4,6 +4,10 @@ #include #include +#ifdef TEAVM_USE_SETJMP +#include +#endif + #ifdef __GNUC__ #include #include @@ -58,6 +62,18 @@ typedef struct TeaVM_String { int32_t hashCode; } TeaVM_String; +typedef struct TeaVM_StackFrame { + struct TeaVM_StackFrame* next; + #ifdef TEAVM_INCREMENTAL + void* callSites; + #endif + #ifdef TEAVM_USE_SETJMP + jmp_buf* jmpTarget; + #endif + int32_t size; + int32_t callSiteId; +} TeaVM_StackFrame; + extern void* teavm_gc_heapAddress; extern char *teavm_beforeClasses; @@ -87,10 +103,10 @@ static inline int32_t teavm_compare_i64(int64_t a, int64_t b) { return a > b ? INT32_C(1) : a < b ? INT32_C(-1) : INT32_C(0); } static inline int32_t teavm_compare_float(float a, float b) { - return a > b ? INT32_C(1) : a < b ? INT32_C(-1) : INT32_C(0); + return a > b ? INT32_C(1) : a < b ? INT32_C(-1) : a == b ? INT32_C(0) : INT32_C(1); } static inline int32_t teavm_compare_double(double a, double b) { - return a > b ? INT32_C(1) : a < b ? INT32_C(-1) : INT32_C(0); + return a > b ? INT32_C(1) : a < b ? INT32_C(-1) : a == b ? INT32_C(0) : INT32_C(1); } #define TEAVM_ALIGN(addr, alignment) ((void*) (((uintptr_t) (addr) + ((alignment) - 1)) / (alignment) * (alignment))) @@ -110,36 +126,39 @@ static inline void* teavm_checkcast(void* obj, int32_t (*cls)(TeaVM_Class*)) { #ifdef TEAVM_INCREMENTAL - #define TEAVM_ALLOC_STACK_DEF(size, callSites) \ - void* teavm__shadowStack__[(size) + 4]; \ - teavm__shadowStack__[0] = teavm_stackTop; \ - teavm__shadowStack__[2] = (void*) size; \ - teavm__shadowStack__[3] = (void*) (callSites); \ - teavm_stackTop = teavm__shadowStack__ + #define TEAVM_ALLOC_STACK_DEF(sz, cs) \ + struct { TeaVM_StackFrame header; void* data[(sz)]; } teavm_shadowStack; \ + teavm_shadowStack.header.next = teavm_stackTop; \ + teavm_shadowStack.header.callSites = (cs); \ + teavm_shadowStack.header.size = (sz); \ + teavm_stackTop = &teavm_shadowStack.header #define TEAVM_STACK_HEADER_ADD_SIZE 1 #else - #define TEAVM_ALLOC_STACK(size) \ - void* teavm__shadowStack__[(size) + 3]; \ - teavm__shadowStack__[0] = teavm_stackTop; \ - teavm__shadowStack__[2] = (void*) size; \ - teavm_stackTop = teavm__shadowStack__ + #define TEAVM_ALLOC_STACK(sz) \ + struct { TeaVM_StackFrame header; void* data[(sz)]; } teavm_shadowStack; \ + teavm_shadowStack.header.next = teavm_stackTop; \ + teavm_shadowStack.header.size = (sz); \ + teavm_stackTop = &teavm_shadowStack.header #define TEAVM_STACK_HEADER_ADD_SIZE 0 #endif -#define TEAVM_RELEASE_STACK teavm_stackTop = teavm__shadowStack__[0] -#define TEAVM_GC_ROOT(index, ptr) teavm__shadowStack__[3 + TEAVM_STACK_HEADER_ADD_SIZE + (index)] = ptr -#define TEAVM_GC_ROOT_RELEASE(index) teavm__shadowStack__[3 + TEAVM_STACK_HEADER_ADD_SIZE + (index)] = NULL -#define TEAVM_GC_ROOTS_COUNT(ptr) ((int32_t) (intptr_t) ((void**) (ptr))[2]) -#define TEAVM_GET_GC_ROOTS(ptr) (((void**) (ptr)) + 3 + TEAVM_STACK_HEADER_ADD_SIZE) -#define TEAVM_CALL_SITE(id) (teavm__shadowStack__[1] = (void*) (id)) -#define TEAVM_EXCEPTION_HANDLER ((int32_t) (intptr_t) (teavm__shadowStack__[1])) -#define TEAVM_SET_EXCEPTION_HANDLER(frame, id) (((void**) (frame))[1] = (void*) (intptr_t) (id)) +#define TEAVM_RELEASE_STACK (teavm_stackTop = teavm_shadowStack.header.next) +#define TEAVM_GC_ROOT(index, ptr) teavm_shadowStack.data[index] = ptr +#define TEAVM_GC_ROOT_RELEASE(index) teavm_shadowStack.data[index] = NULL +#define TEAVM_GC_ROOTS_COUNT(ptr) (((TeaVM_StackFrame*) (ptr))->size); +#define TEAVM_GET_GC_ROOTS(ptr) &((struct { TeaVM_StackFrame header; void* data[1]; }*) (ptr))->data; +#define TEAVM_CALL_SITE(id) (teavm_shadowStack.header.callSiteId = (id)) +#define TEAVM_WITH_CALL_SITE_ID(id, expr) (TEAVM_CALL_SITE(id), (expr)) +#define TEAVM_EXCEPTION_HANDLER (teavm_shadowStack.header.callSiteId) +#define TEAVM_SET_EXCEPTION_HANDLER(frame, id) (((TeaVM_StackFrame*) (frame))->callSiteId = (id)) +#define TEAVM_GET_NEXT_FRAME(frame) (((TeaVM_StackFrame*) (frame))->next) +#define TEAVM_GET_CALL_SITE_ID(frame) (((TeaVM_StackFrame*) (frame))->callSiteId) #define TEAVM_ADDRESS_ADD(address, offset) ((char *) (address) + (offset)) #define TEAVM_STRUCTURE_ADD(structure, address, offset) (((structure*) (address)) + offset) @@ -160,7 +179,7 @@ static inline void* teavm_checkcast(void* obj, int32_t (*cls)(TeaVM_Class*)) { .hashCode = INT32_C(hash) \ } -extern void** teavm_stackTop; +extern TeaVM_StackFrame* teavm_stackTop; extern void* teavm_gc_gcStorageAddress; extern int32_t teavm_gc_gcStorageSize; @@ -359,4 +378,49 @@ extern int32_t teavm_file_canonicalize(char16_t*, int32_t, char16_t**); extern int64_t teavm_unixTimeOffset; #endif -extern void teavm_logchar(int32_t); \ No newline at end of file +extern void teavm_logchar(int32_t); + +#ifdef TEAVM_USE_SETJMP +#define TEAVM_JUMP_SUPPORTED 1 +#define TEAVM_TRY \ + do { \ + jmp_buf teavm_tryBuffer; \ + jmp_buf* teavm_oldTryBuffer = teavm_shadowStack.header.jmpTarget; \ + teavm_shadowStack.header.jmpTarget = &teavm_tryBuffer; \ + int teavm_exceptionHandler = setjmp(teavm_tryBuffer); \ + switch (teavm_exceptionHandler) { \ + case 0: { +#define TEAVM_CATCH \ + break; \ + } \ + default: { \ + longjmp(*teavm_oldTryBuffer, teavm_exceptionHandler); \ + break; \ + } +#define TEAVM_END_TRY \ + } \ + teavm_shadowStack.header.jmpTarget = teavm_oldTryBuffer; \ + } while (0); + +#define TEAVM_JUMP_TO_FRAME(frame, id) \ + teavm_stackTop = (TeaVM_StackFrame*) (frame); \ + longjmp(*teavm_stackTop->jmpTarget, id) +extern void teavm_throwNullPointerException(); +inline static void* teavm_nullCheck(void* o) { + if (o == NULL) { + teavm_throwNullPointerException(); + #ifdef __GNUC__ + __builtin_unreachable(); + #endif + #ifdef _MSC_VER + __assume(0); + #endif + } + return o; +} +#else +#define TEAVM_JUMP_SUPPORTED 0 +#define TEAVM_JUMP_TO_FRAME(frame, id) +#endif + +extern void* teavm_catchException(); \ No newline at end of file diff --git a/platform/src/main/java/org/teavm/platform/PlatformClassMetadata.java b/platform/src/main/java/org/teavm/platform/PlatformClassMetadata.java index de7c1d193..51739473c 100644 --- a/platform/src/main/java/org/teavm/platform/PlatformClassMetadata.java +++ b/platform/src/main/java/org/teavm/platform/PlatformClassMetadata.java @@ -32,6 +32,7 @@ public interface PlatformClassMetadata extends JSObject { PlatformClass getSuperclass(); @JSProperty + @Unmanaged String getName(); @JSProperty diff --git a/samples/benchmark/.gitignore b/samples/benchmark/.gitignore deleted file mode 100644 index aa057d63a..000000000 --- a/samples/benchmark/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/CMakeFiles -/CMakeCache.txt -/Makefile -/cmake_install.cmake \ No newline at end of file diff --git a/tests/compile-c-unix-fast.sh b/tests/compile-c-unix-fast.sh index 9d2dbeeea..ad4371da6 100755 --- a/tests/compile-c-unix-fast.sh +++ b/tests/compile-c-unix-fast.sh @@ -1,2 +1,3 @@ +export LC_ALL=C SOURCE_DIR=$(pwd) gcc -g -O0 -lrt -lm all.c -o run_test \ No newline at end of file diff --git a/tests/src/test/java/org/teavm/classlib/java/nio/ByteBufferTest.java b/tests/src/test/java/org/teavm/classlib/java/nio/ByteBufferTest.java index 248d77fdd..4289e5509 100644 --- a/tests/src/test/java/org/teavm/classlib/java/nio/ByteBufferTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/nio/ByteBufferTest.java @@ -335,18 +335,21 @@ public class ByteBufferTest { byte[] receiver = new byte[4]; try { buffer.get(receiver, 0, 5); + fail("Error expected"); } catch (IndexOutOfBoundsException e) { assertThat(receiver, is(new byte[4])); assertThat(buffer.position(), is(0)); } try { buffer.get(receiver, -1, 3); + fail("Error expected"); } catch (IndexOutOfBoundsException e) { assertThat(receiver, is(new byte[4])); assertThat(buffer.position(), is(0)); } try { buffer.get(receiver, 6, 3); + fail("Error expected"); } catch (IndexOutOfBoundsException e) { assertThat(receiver, is(new byte[4])); assertThat(buffer.position(), is(0)); diff --git a/tests/src/test/java/org/teavm/vm/VMTest.java b/tests/src/test/java/org/teavm/vm/VMTest.java index 309b52290..68d3b4ec5 100644 --- a/tests/src/test/java/org/teavm/vm/VMTest.java +++ b/tests/src/test/java/org/teavm/vm/VMTest.java @@ -20,6 +20,8 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.util.Arrays; +import java.util.List; import java.util.function.Supplier; import org.junit.Test; import org.junit.runner.RunWith; @@ -116,19 +118,19 @@ public class VMTest { @Test public void catchFinally() { StringBuilder sb = new StringBuilder(); + List a = Arrays.asList("a", "b"); + if (a.isEmpty()) { + return; + } try { - if (Integer.parseInt("invalid") > 0) { - sb.append("err1;"); - } else { - sb.append("err2;"); + for (String b : a) { + if (b.length() < 3) { + sb.append(b); + } } - sb.append("err3"); - } catch (NumberFormatException e) { - sb.append("catch;"); } finally { sb.append("finally;"); } - assertEquals("catch;finally;", sb.toString()); } @Test @@ -195,7 +197,7 @@ public class VMTest { n += foo() * 5; } catch (RuntimeException e) { assertEquals(RuntimeException.class, e.getClass()); - assertEquals(n, 22); + assertEquals(22, n); } } diff --git a/tools/c-incremental/src/main/java/org/teavm/tooling/c/incremental/IncrementalCBuilder.java b/tools/c-incremental/src/main/java/org/teavm/tooling/c/incremental/IncrementalCBuilder.java index e2c3f0f89..168e55e47 100644 --- a/tools/c-incremental/src/main/java/org/teavm/tooling/c/incremental/IncrementalCBuilder.java +++ b/tools/c-incremental/src/main/java/org/teavm/tooling/c/incremental/IncrementalCBuilder.java @@ -63,6 +63,7 @@ public class IncrementalCBuilder { private String mainClass; private String[] classPath; private int minHeapSize = 32; + private boolean longjmpSupported = true; private boolean lineNumbersGenerated; private String targetPath; private String externalTool; @@ -131,6 +132,10 @@ public class IncrementalCBuilder { this.mainFunctionName = mainFunctionName; } + public void setLongjmpSupported(boolean longjmpSupported) { + this.longjmpSupported = longjmpSupported; + } + public void addProgressHandler(ProgressHandler handler) { synchronized (progressHandlers) { progressHandlers.add(handler); @@ -327,6 +332,7 @@ public class IncrementalCBuilder { cTarget.setIncremental(true); cTarget.setMinHeapSize(minHeapSize * 1024 * 1024); cTarget.setLineNumbersGenerated(lineNumbersGenerated); + cTarget.setLongjmpUsed(longjmpSupported); vm.setOptimizationLevel(TeaVMOptimizationLevel.SIMPLE); vm.setCacheStatus(classSource); vm.addVirtualMethods(m -> true); diff --git a/tools/cli/src/main/java/org/teavm/cli/TeaVMCBuilderRunner.java b/tools/cli/src/main/java/org/teavm/cli/TeaVMCBuilderRunner.java index 0bfd85f3e..029975fa1 100644 --- a/tools/cli/src/main/java/org/teavm/cli/TeaVMCBuilderRunner.java +++ b/tools/cli/src/main/java/org/teavm/cli/TeaVMCBuilderRunner.java @@ -62,6 +62,10 @@ public class TeaVMCBuilderRunner { .hasArg() .withDescription("Minimum heap size in bytes") .create()); + options.addOption(OptionBuilder + .withLongOpt("no-longjmp") + .withDescription("Don't use setjmp/longjmp functions to emulate exception handling") + .create()); options.addOption(OptionBuilder .withLongOpt("entry-point") .withArgName("name") @@ -117,6 +121,9 @@ public class TeaVMCBuilderRunner { if (commandLine.hasOption('e')) { builder.setMainFunctionName(commandLine.getOptionValue('e')); } + if (commandLine.hasOption("no-longjmp")) { + builder.setLongjmpSupported(false); + } String[] args = commandLine.getArgs(); if (args.length != 1) { diff --git a/tools/cli/src/main/java/org/teavm/cli/TeaVMRunner.java b/tools/cli/src/main/java/org/teavm/cli/TeaVMRunner.java index cb553ded2..0666555b7 100644 --- a/tools/cli/src/main/java/org/teavm/cli/TeaVMRunner.java +++ b/tools/cli/src/main/java/org/teavm/cli/TeaVMRunner.java @@ -141,6 +141,10 @@ public final class TeaVMRunner { .withDescription("Maximum number of names kept in top-level scope (" + "other will be put in a separate object. 10000 by default.") .create()); + options.addOption(OptionBuilder + .withLongOpt("no-longjmp") + .withDescription("Don't use setjmp/longjmp functions to emulate exceptions (C target)") + .create()); } private TeaVMRunner(CommandLine commandLine) { @@ -177,6 +181,7 @@ public final class TeaVMRunner { parseIncrementalOptions(); parseJavaScriptOptions(); parseWasmOptions(); + parseCOptions(); parseHeap(); if (commandLine.hasOption("e")) { @@ -313,6 +318,12 @@ public final class TeaVMRunner { } } + private void parseCOptions() { + if (commandLine.hasOption("no-longjmp")) { + tool.setLongjmpSupported(false); + } + } + private void parseHeap() { if (commandLine.hasOption("min-heap")) { int size; diff --git a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java index 027f599cc..6975e5cdd 100644 --- a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java +++ b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java @@ -103,7 +103,7 @@ public class TeaVMTool { private Set generatedFiles = new HashSet<>(); private int minHeapSize = 32 * (1 << 20); private ReferenceCache referenceCache; - private String mainFunctionName; + private boolean longjmpSupported = true; public File getTargetDirectory() { return targetDirectory; @@ -249,8 +249,8 @@ public class TeaVMTool { this.wasmVersion = wasmVersion; } - public void setMainFunctionName(String mainFunctionName) { - this.mainFunctionName = mainFunctionName; + public void setLongjmpSupported(boolean longjmpSupported) { + this.longjmpSupported = longjmpSupported; } public void setProgressListener(TeaVMProgressListener progressListener) { @@ -327,6 +327,7 @@ public class TeaVMTool { cTarget = new CTarget(); cTarget.setMinHeapSize(minHeapSize); cTarget.setLineNumbersGenerated(debugInformationGenerated); + cTarget.setLongjmpUsed(longjmpSupported); return cTarget; } diff --git a/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java b/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java index 2461f279d..38af51eda 100644 --- a/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java +++ b/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java @@ -74,5 +74,7 @@ public interface BuildStrategy { void setHeapSize(int heapSize); + void setLongjmpSupported(boolean value); + BuildResult build() throws BuildException; } diff --git a/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java b/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java index f1a38243e..1c1bcd2f9 100644 --- a/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java +++ b/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java @@ -61,6 +61,7 @@ public class InProcessBuildStrategy implements BuildStrategy { private WasmBinaryVersion wasmVersion = WasmBinaryVersion.V_0x1; private int heapSize = 32; private final List sourceFileProviders = new ArrayList<>(); + private boolean longjmpSupported = true; private TeaVMProgressListener progressListener; private Properties properties = new Properties(); private TeaVMToolLog log = new EmptyTeaVMToolLog(); @@ -196,6 +197,11 @@ public class InProcessBuildStrategy implements BuildStrategy { this.heapSize = heapSize; } + @Override + public void setLongjmpSupported(boolean longjmpSupported) { + this.longjmpSupported = longjmpSupported; + } + @Override public BuildResult build() throws BuildException { TeaVMTool tool = new TeaVMTool(); @@ -222,6 +228,7 @@ public class InProcessBuildStrategy implements BuildStrategy { tool.setCacheDirectory(cacheDirectory != null ? new File(cacheDirectory) : null); tool.setWasmVersion(wasmVersion); tool.setMinHeapSize(heapSize); + tool.setLongjmpSupported(longjmpSupported); tool.getProperties().putAll(properties); diff --git a/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java b/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java index f95bc7d17..b04acc5ed 100644 --- a/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java +++ b/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java @@ -51,6 +51,7 @@ public class RemoteBuildStrategy implements BuildStrategy { request = new RemoteBuildRequest(); request.optimizationLevel = TeaVMOptimizationLevel.ADVANCED; request.wasmVersion = WasmBinaryVersion.V_0x1; + request.longjmpSupported = true; } @Override @@ -174,6 +175,11 @@ public class RemoteBuildStrategy implements BuildStrategy { request.heapSize = heapSize; } + @Override + public void setLongjmpSupported(boolean value) { + request.longjmpSupported = value; + } + @Override public BuildResult build() throws BuildException { RemoteBuildResponse response; diff --git a/tools/core/src/main/java/org/teavm/tooling/daemon/BuildDaemon.java b/tools/core/src/main/java/org/teavm/tooling/daemon/BuildDaemon.java index 7e2cdda4e..e5371e1d9 100644 --- a/tools/core/src/main/java/org/teavm/tooling/daemon/BuildDaemon.java +++ b/tools/core/src/main/java/org/teavm/tooling/daemon/BuildDaemon.java @@ -159,6 +159,7 @@ public class BuildDaemon extends UnicastRemoteObject implements RemoteBuildServi tool.setMaxTopLevelNames(request.maxTopLevelNames); tool.setWasmVersion(request.wasmVersion); tool.setMinHeapSize(request.heapSize); + tool.setLongjmpSupported(request.longjmpSupported); for (String sourceDirectory : request.sourceDirectories) { tool.addSourceFileProvider(new DirectorySourceFileProvider(new File(sourceDirectory))); diff --git a/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java b/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java index 4cf32b9b4..ae008f59c 100644 --- a/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java +++ b/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java @@ -46,4 +46,5 @@ public class RemoteBuildRequest implements Serializable { public boolean fastDependencyAnalysis; public WasmBinaryVersion wasmVersion; public int heapSize; + public boolean longjmpSupported; } diff --git a/tools/devserver/pom.xml b/tools/devserver/pom.xml index 8e0f7ee48..e7f290042 100644 --- a/tools/devserver/pom.xml +++ b/tools/devserver/pom.xml @@ -130,7 +130,7 @@ process-classes - ${build.directory}/classes/teavm/devserver + ${project.build.directory}/classes/teavm/devserver deobfuscator.js true ADVANCED diff --git a/tools/maven/plugin/src/main/java/org/teavm/maven/TeaVMCompileMojo.java b/tools/maven/plugin/src/main/java/org/teavm/maven/TeaVMCompileMojo.java index 69b2de494..5f8a71eb4 100644 --- a/tools/maven/plugin/src/main/java/org/teavm/maven/TeaVMCompileMojo.java +++ b/tools/maven/plugin/src/main/java/org/teavm/maven/TeaVMCompileMojo.java @@ -146,6 +146,9 @@ public class TeaVMCompileMojo extends AbstractMojo { @Parameter(property = "teavm.processMemory", defaultValue = "512") private int processMemory; + @Parameter(property = "teavm.longjmpSupported", defaultValue = "true") + private boolean longjmpSupported; + private void setupBuilder(BuildStrategy builder) throws MojoExecutionException { builder.setLog(new MavenTeaVMToolLog(getLog())); try { @@ -277,6 +280,7 @@ public class TeaVMCompileMojo extends AbstractMojo { builder.setCacheDirectory(cacheDirectory.getAbsolutePath()); builder.setTargetType(targetType); builder.setWasmVersion(wasmVersion); + builder.setLongjmpSupported(longjmpSupported); BuildResult result; result = builder.build(); TeaVMProblemRenderer.describeProblems(result.getCallGraph(), result.getProblems(), toolLog);