Skip to content

Commit

Permalink
wasm gc: prevent null constant instructions that are used more that once
Browse files Browse the repository at this point in the history
Rationale: since Wasm GC is statically typed IR, with even nulls having some type, sharing null constants between expressions can cause unsolvable contradictions in types
  • Loading branch information
konsoletyper committed Feb 10, 2025
1 parent 54d6537 commit 2958e63
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,10 @@ public WasmGCVariableCategoryProvider(ClassHierarchy hierarchy) {
public Object[] getCategories(Program program, MethodReference method) {
var inference = new PreciseTypeInference(program, method, hierarchy);
inference.setPhisSkipped(false);
inference.setBackPropagation(true);
var result = new Object[program.variableCount()];
for (int i = 0; i < program.variableCount(); ++i) {
var type = inference.typeOf(program.variableAt(i));
result[i] = type != null ? type : PreciseTypeInference.OBJECT_TYPE;
result[i] = type != null ? type : new Object();
}
return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.Queue;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import org.teavm.ast.RegularMethodNode;
import org.teavm.ast.decompilation.Decompiler;
import org.teavm.backend.wasm.BaseWasmFunctionRepository;
Expand Down Expand Up @@ -54,14 +55,21 @@
import org.teavm.model.CallLocation;
import org.teavm.model.ClassHierarchy;
import org.teavm.model.ElementModifier;
import org.teavm.model.Instruction;
import org.teavm.model.ListableClassHolderSource;
import org.teavm.model.ListableClassReaderSource;
import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference;
import org.teavm.model.Program;
import org.teavm.model.TextLocation;
import org.teavm.model.ValueType;
import org.teavm.model.Variable;
import org.teavm.model.analysis.ClassInitializerInfo;
import org.teavm.model.instructions.NullConstantInstruction;
import org.teavm.model.util.InstructionVariableMapper;
import org.teavm.model.util.RegisterAllocator;
import org.teavm.model.util.UsageExtractor;

public class WasmGCMethodGenerator implements BaseWasmFunctionRepository {
private WasmModule module;
Expand Down Expand Up @@ -238,6 +246,7 @@ private void generateCustomMethodBody(WasmGCCustomGenerator customGenerator, Met

private void generateRegularMethodBody(MethodHolder method, WasmFunction function) {
Objects.requireNonNull(method.getProgram());
eliminateMultipleNullConstantUsages(method.getProgram());
var decompiler = getDecompiler();
var categoryProvider = new WasmGCVariableCategoryProvider(hierarchy);
var allocator = new RegisterAllocator(categoryProvider);
Expand Down Expand Up @@ -311,6 +320,80 @@ private void generateRegularMethodBody(MethodHolder method, WasmFunction functio
visitor.generate(ast.getBody(), function.getBody());
}

private void eliminateMultipleNullConstantUsages(Program program) {
var nulls = new boolean[program.variableCount()];
var usageCount = new int[program.variableCount()];
var locations = new TextLocation[program.variableCount()];
var usageExtractor = new UsageExtractor();
for (var block : program.getBasicBlocks()) {
for (var insn : block) {
insn.acceptVisitor(usageExtractor);
var usedVars = usageExtractor.getUsedVariables();
if (usedVars != null) {
for (var usedVar : usedVars) {
usageCount[usedVar.getIndex()]++;
}
}
if (insn instanceof NullConstantInstruction) {
var index = ((NullConstantInstruction) insn).getReceiver().getIndex();
nulls[index] = true;
locations[index] = insn.getLocation();
}
}
for (var phi : block.getPhis()) {
for (var input : phi.getIncomings()) {
usageCount[input.getValue().getIndex()]++;
}
}
}

for (var i = 0; i < program.variableCount(); ++i) {
if (nulls[i]) {
if (usageCount[i] <= 1) {
nulls[i] = false;
}
} else {
usageCount[i] = 0;
}
}

var mapFunction = new Function<Variable, Variable>() {
Instruction instruction;

@Override
public Variable apply(Variable variable) {
if (variable.getIndex() >= nulls.length || !nulls[variable.getIndex()]
|| usageCount[variable.getIndex()]++ == 0) {
return variable;
}
var nullConstant = new NullConstantInstruction();
nullConstant.setReceiver(program.createVariable());
nullConstant.setLocation(locations[variable.getIndex()]);
instruction.insertPrevious(nullConstant);
return nullConstant.getReceiver();
}
};
var mapper = new InstructionVariableMapper(mapFunction);
for (var block : program.getBasicBlocks()) {
for (var insn : block) {
mapFunction.instruction = insn;
insn.acceptVisitor(mapper);
}
for (var phi : block.getPhis()) {
for (var input : phi.getIncomings()) {
var index = input.getValue().getIndex();
if (index < nulls.length && nulls[index] && usageCount[index]++ > 0) {
var nullConstant = new NullConstantInstruction();
nullConstant.setReceiver(program.createVariable());
nullConstant.setLocation(locations[index]);
input.setValue(nullConstant.getReceiver());
input.getSource().getLastInstruction().insertPrevious(nullConstant);
}
}
}
}
}

private void calculateNonNullableVars(boolean[] nonNullVars, RegularMethodNode ast) {
var calculator = new NonNullVarsCalculator(nonNullVars);
ast.getBody().acceptVisitor(calculator);
Expand Down

0 comments on commit 2958e63

Please sign in to comment.