From bc16b180831fe087fd21c1e32c98244dc7468d19 Mon Sep 17 00:00:00 2001 From: Volker Berlin Date: Sun, 12 Feb 2023 21:46:05 +0100 Subject: [PATCH] More Unsafe stuff. First step for VarHandle in Java 9 and higher. --- .../jwebassembly/module/FunctionManager.java | 1 + .../jwebassembly/module/UnsafeManager.java | 125 +++++++++++++++++- .../ReplacementForMethodHandles.java | 46 +++++++ .../jwebassembly/runtime/UnsafeTest.java | 37 ++++++ 4 files changed, 203 insertions(+), 6 deletions(-) create mode 100644 src/de/inetsoftware/jwebassembly/module/nativecode/ReplacementForMethodHandles.java diff --git a/src/de/inetsoftware/jwebassembly/module/FunctionManager.java b/src/de/inetsoftware/jwebassembly/module/FunctionManager.java index e7e25a2..6ac7e4e 100644 --- a/src/de/inetsoftware/jwebassembly/module/FunctionManager.java +++ b/src/de/inetsoftware/jwebassembly/module/FunctionManager.java @@ -173,6 +173,7 @@ class FunctionManager { switch( name.className ) { case UnsafeManager.UNSAFE_8: case UnsafeManager.UNSAFE_11: + case UnsafeManager.VARHANDLE: // Unsafe method call will be replaces by the UnsafeManager return name; } diff --git a/src/de/inetsoftware/jwebassembly/module/UnsafeManager.java b/src/de/inetsoftware/jwebassembly/module/UnsafeManager.java index e5003d1..81e1213 100644 --- a/src/de/inetsoftware/jwebassembly/module/UnsafeManager.java +++ b/src/de/inetsoftware/jwebassembly/module/UnsafeManager.java @@ -15,8 +15,11 @@ */ package de.inetsoftware.jwebassembly.module; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Set; import javax.annotation.Nonnull; @@ -72,6 +75,9 @@ class UnsafeManager { /** Unsafe class bane in Java 11 */ static final String UNSAFE_11 = "jdk/internal/misc/Unsafe"; + /** VARHANDLE as modern replacement of Unsafe */ + static final String VARHANDLE = "java/lang/invoke/VarHandle"; + @Nonnull private final FunctionManager functions; @@ -106,6 +112,9 @@ class UnsafeManager { case UNSAFE_11: patch( instructions, i, callInst ); break; + case VARHANDLE: + patchVarHandle( instructions, i, callInst ); + break; } break; default: @@ -145,14 +154,21 @@ class UnsafeManager { case "jdk/internal/misc/Unsafe.arrayIndexScale(Ljava/lang/Class;)I": patch_arrayIndexScale( instructions, idx, callInst ); break; + case "sun/misc/Unsafe.getObjectVolatile(Ljava/lang/Object;J)Ljava/lang/Object;": + case "sun/misc/Unsafe.getInt(Ljava/lang/Object;J)I": + patchFieldFunction( instructions, idx, callInst, name, 1 ); + break; case "sun/misc/Unsafe.getAndAddInt(Ljava/lang/Object;JI)I": case "sun/misc/Unsafe.getAndSetInt(Ljava/lang/Object;JI)I": case "sun/misc/Unsafe.putOrderedInt(Ljava/lang/Object;JI)V": + case "sun/misc/Unsafe.putInt(Ljava/lang/Object;JI)V": case "sun/misc/Unsafe.getAndAddLong(Ljava/lang/Object;JJ)J": case "sun/misc/Unsafe.getAndSetLong(Ljava/lang/Object;JJ)J": case "sun/misc/Unsafe.putOrderedLong(Ljava/lang/Object;JJ)V": - case "sun/misc/Unsafe.getObjectVolatile(Ljava/lang/Object;J)Ljava/lang/Object;": + case "sun/misc/Unsafe.putLong(Ljava/lang/Object;JJ)V": case "sun/misc/Unsafe.putOrderedObject(Ljava/lang/Object;JLjava/lang/Object;)V": + case "sun/misc/Unsafe.putObjectVolatile(Ljava/lang/Object;JLjava/lang/Object;)V": + case "sun/misc/Unsafe.putObject(Ljava/lang/Object;JLjava/lang/Object;)V": case "sun/misc/Unsafe.getAndSetObject(Ljava/lang/Object;JLjava/lang/Object;)Ljava/lang/Object;": case "jdk/internal/misc/Unsafe.getAndAddInt(Ljava/lang/Object;JI)I": case "jdk/internal/misc/Unsafe.getAndSetInt(Ljava/lang/Object;JI)I": @@ -190,8 +206,10 @@ class UnsafeManager { break; case "jdk/internal/misc/Unsafe.ensureClassInitialized(Ljava/lang/Class;)V": case "jdk/internal/misc/Unsafe.unpark(Ljava/lang/Object;)V": + case "sun/misc/Unsafe.unpark(Ljava/lang/Object;)V": remove( instructions, idx, callInst, 2 ); break; + case "sun/misc/Unsafe.park(ZJ)V": case "jdk/internal/misc/Unsafe.park(ZJ)V": remove( instructions, idx, callInst, 3 ); break; @@ -415,17 +433,44 @@ class UnsafeManager { * @param name * the calling function * @param fieldNameParam - * the function parameter with the field offset. This must be a long (Java signature "J"). The THIS - * parameter has the index 0. + * the function parameter on the stack with the field offset on the stack. This must be a long (Java signature "J") for Unsafe. This is the parameter count from right. */ private void patchFieldFunction( List instructions, int idx, final WasmCallInstruction callInst, FunctionName name, int fieldNameParam ) { StackValue stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), fieldNameParam, callInst.getCodePosition() ); - FunctionName fieldNameWithOffset = ((WasmGlobalInstruction)stackValue.instr).getFieldName(); + WasmInstruction instr = stackValue.instr; + + Set fieldNames; + FunctionName fieldNameWithOffset = null; + if( instr.getType() == Type.Global ) { + fieldNameWithOffset = ((WasmGlobalInstruction)instr).getFieldName(); + fieldNames = Collections.singleton( fieldNameWithOffset ); + } else { + // java.util.concurrent.ConcurrentHashMap.tabAt() calculate a value with the field + fieldNames = new HashSet<>(); + int pos2 = stackValue.idx; + stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), fieldNameParam + 1, callInst.getCodePosition() ); + int i = stackValue.idx; + for( ; i < pos2; i++ ) { + instr = instructions.get( i ); + if( instr.getType() != Type.Global ) { + continue; + } + fieldNameWithOffset = ((WasmGlobalInstruction)instr).getFieldName(); + fieldNames.add( fieldNameWithOffset ); + } + } + WatCodeSyntheticFunctionName func = new WatCodeSyntheticFunctionName( fieldNameWithOffset.className, '.' + name.methodName, name.signature, "", (AnyType[])null ) { @Override protected String getCode() { - UnsafeState state = unsafes.get( fieldNameWithOffset ); + UnsafeState state = null; + for(FunctionName fieldNameWithOffset : fieldNames ) { + state = unsafes.get( fieldNameWithOffset ); + if( state != null ) { + break; + } + } if( state == null ) { // we are in the scan phase. The static code was not scanned yet. return ""; @@ -476,8 +521,12 @@ class UnsafeManager { + " return"; case "putOrderedInt": + case "putInt": case "putOrderedLong": + case "putLong": case "putOrderedObject": + case "putObjectVolatile": + case "putObject": return "local.get 0" // THIS + " local.get 2" // x + " struct.set " + state.typeName + ' ' + state.fieldName; @@ -492,7 +541,7 @@ class UnsafeManager { // a virtual method call has also a DUP of this because we need for virtual method dispatch the parameter 2 times. for( int i = idx; i >= 0; i-- ) { - WasmInstruction instr = instructions.get( i ); + instr = instructions.get( i ); if( instr.getType() == Type.DupThis && ((DupThis)instr).getValue() == callInst ) { nop( instructions, i, i + 1 ); break; @@ -588,6 +637,70 @@ class UnsafeManager { } } + private void patchVarHandle( List instructions, int idx, WasmCallInstruction callInst ) { + FunctionName name = callInst.getFunctionName(); + switch( name.methodName ) { + case "getAndSet": + patchVarHandleFieldFunction( instructions, idx, callInst, name, 3 ); + break; + } + } + + /** + * Patch an unsafe function that access a field + * + * @param instructions + * the instruction list + * @param idx + * the index in the instructions + * @param callInst + * the method call to Unsafe + * @param name + * the calling function + * @param fieldNameParam + * the function parameter with the field offset on the stack. This must be a long (Java signature "J") for Unsafe. + */ + private void patchVarHandleFieldFunction( List instructions, int idx, final WasmCallInstruction callInst, FunctionName name, int fieldNameParam ) { + StackValue stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), fieldNameParam, callInst.getCodePosition() ); + FunctionName fieldNameWithOffset = ((WasmGlobalInstruction)stackValue.instr).getFieldName(); + WatCodeSyntheticFunctionName func = + new WatCodeSyntheticFunctionName( fieldNameWithOffset.className, '.' + name.methodName, name.signature, "", (AnyType[])null ) { + @Override + protected String getCode() { + UnsafeState state = unsafes.get( fieldNameWithOffset ); + if( state == null ) { + // we are in the scan phase. The static code was not scanned yet. + return ""; + } + AnyType[] paramTypes = callInst.getPopValueTypes(); + switch( name.methodName ) { + case "getAndSet": + return "local.get 1" // THIS + + " struct.get " + state.typeName + ' ' + state.fieldName // + + " local.get 1" // THIS + + " local.get 2" // newValue + + " struct.set " + state.typeName + ' ' + state.fieldName // + + " return"; + + } + + throw new RuntimeException( name.signatureName ); + } + }; + functions.markAsNeeded( func, false ); + WasmCallInstruction call = new WasmCallInstruction( func, callInst.getCodePosition(), callInst.getLineNumber(), callInst.getTypeManager(), false ); + instructions.set( idx, call ); + + // a virtual method call has also a DUP of this because we need for virtual method dispatch the parameter 2 times. + for( int i = idx; i >= 0; i-- ) { + WasmInstruction instr = instructions.get( i ); + if( instr.getType() == Type.DupThis && ((DupThis)instr).getValue() == callInst ) { + nop( instructions, i, i + 1 ); + break; + } + } + } + /** * Hold the state from declaring of Unsafe address */ diff --git a/src/de/inetsoftware/jwebassembly/module/nativecode/ReplacementForMethodHandles.java b/src/de/inetsoftware/jwebassembly/module/nativecode/ReplacementForMethodHandles.java new file mode 100644 index 0000000..ae52eca --- /dev/null +++ b/src/de/inetsoftware/jwebassembly/module/nativecode/ReplacementForMethodHandles.java @@ -0,0 +1,46 @@ +/* + Copyright 2023 Volker Berlin (i-net software) + + 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 de.inetsoftware.jwebassembly.module.nativecode; + +import java.lang.invoke.MethodHandles.Lookup; + +import de.inetsoftware.jwebassembly.api.annotation.Replace; + +/** + * Replacement for java.lang.invoke.MethodHandles + * + * @author Volker Berlin + */ +public class ReplacementForMethodHandles { + + /** + * Replacement for static lookup(). + */ + @Replace( "java/lang/invoke/MethodHandles.lookup()Ljava/lang/invoke/MethodHandles$Lookup;" ) + static Lookup lookup() { + return null; + } + + /** + * Replacement for static lookup(). + */ + @Replace( "java/lang/invoke/MethodHandles$Lookup.findVarHandle(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/invoke/VarHandle;" ) + Object findVarHandle(Class recv, String name, Class type) { + return null; + } + +} diff --git a/test/de/inetsoftware/jwebassembly/runtime/UnsafeTest.java b/test/de/inetsoftware/jwebassembly/runtime/UnsafeTest.java index ee3ff4e..cd612bd 100644 --- a/test/de/inetsoftware/jwebassembly/runtime/UnsafeTest.java +++ b/test/de/inetsoftware/jwebassembly/runtime/UnsafeTest.java @@ -19,10 +19,13 @@ import java.util.ArrayList; import java.util.Collection; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; import org.junit.ClassRule; import org.junit.runners.Parameterized.Parameters; +import de.inetsoftware.jwebassembly.JWebAssembly; import de.inetsoftware.jwebassembly.ScriptEngine; import de.inetsoftware.jwebassembly.WasmRule; import de.inetsoftware.jwebassembly.api.annotation.Export; @@ -51,8 +54,14 @@ public class UnsafeTest extends AbstractBaseTest { addParam( list, script, "getAndAddLong" ); addParam( list, script, "getAndSetLong" ); addParam( list, script, "lazySetLong" ); + addParam( list, script, "compareAndSwapReference" ); + addParam( list, script, "getAndSetReference" ); + addParam( list, script, "lazySetReference" ); } rule.setTestParameters( list ); + JWebAssembly.LOGGER.setLevel( Level.FINE ); + rule.setProperty( JWebAssembly.IGNORE_NATIVE, "true" ); + return list; } @@ -135,5 +144,33 @@ public class UnsafeTest extends AbstractBaseTest { obj.lazySet( 42 ); return obj.get(); } + + @Export + static int compareAndSwapReference() { + AtomicReference obj = new AtomicReference<>(); + if( obj.compareAndSet( null, 25 ) ) { + return obj.get(); + } else { + return 42; + } + } + + @Export + static int getAndSetReference() { + AtomicReference obj = new AtomicReference<>(); + obj.set( 13 ); + if( obj.getAndSet( 25 ) == 13 ) { + return obj.get(); + } else { + return 42; + } + } + + @Export + static int lazySetReference() { + AtomicReference obj = new AtomicReference<>(); + obj.lazySet( 42 ); + return obj.get(); + } } }