mirror of
https://github.com/i-net-software/JWebAssembly.git
synced 2025-03-25 07:27:52 +01:00
More Unsafe stuff. First step for VarHandle in Java 9 and higher.
This commit is contained in:
parent
588c9563f6
commit
bc16b18083
@ -173,6 +173,7 @@ class FunctionManager {
|
|||||||
switch( name.className ) {
|
switch( name.className ) {
|
||||||
case UnsafeManager.UNSAFE_8:
|
case UnsafeManager.UNSAFE_8:
|
||||||
case UnsafeManager.UNSAFE_11:
|
case UnsafeManager.UNSAFE_11:
|
||||||
|
case UnsafeManager.VARHANDLE:
|
||||||
// Unsafe method call will be replaces by the UnsafeManager
|
// Unsafe method call will be replaces by the UnsafeManager
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,11 @@
|
|||||||
*/
|
*/
|
||||||
package de.inetsoftware.jwebassembly.module;
|
package de.inetsoftware.jwebassembly.module;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
@ -72,6 +75,9 @@ class UnsafeManager {
|
|||||||
/** Unsafe class bane in Java 11 */
|
/** Unsafe class bane in Java 11 */
|
||||||
static final String UNSAFE_11 = "jdk/internal/misc/Unsafe";
|
static final String UNSAFE_11 = "jdk/internal/misc/Unsafe";
|
||||||
|
|
||||||
|
/** VARHANDLE as modern replacement of Unsafe */
|
||||||
|
static final String VARHANDLE = "java/lang/invoke/VarHandle";
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
private final FunctionManager functions;
|
private final FunctionManager functions;
|
||||||
|
|
||||||
@ -106,6 +112,9 @@ class UnsafeManager {
|
|||||||
case UNSAFE_11:
|
case UNSAFE_11:
|
||||||
patch( instructions, i, callInst );
|
patch( instructions, i, callInst );
|
||||||
break;
|
break;
|
||||||
|
case VARHANDLE:
|
||||||
|
patchVarHandle( instructions, i, callInst );
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -145,14 +154,21 @@ class UnsafeManager {
|
|||||||
case "jdk/internal/misc/Unsafe.arrayIndexScale(Ljava/lang/Class;)I":
|
case "jdk/internal/misc/Unsafe.arrayIndexScale(Ljava/lang/Class;)I":
|
||||||
patch_arrayIndexScale( instructions, idx, callInst );
|
patch_arrayIndexScale( instructions, idx, callInst );
|
||||||
break;
|
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.getAndAddInt(Ljava/lang/Object;JI)I":
|
||||||
case "sun/misc/Unsafe.getAndSetInt(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.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.getAndAddLong(Ljava/lang/Object;JJ)J":
|
||||||
case "sun/misc/Unsafe.getAndSetLong(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.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.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 "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.getAndAddInt(Ljava/lang/Object;JI)I":
|
||||||
case "jdk/internal/misc/Unsafe.getAndSetInt(Ljava/lang/Object;JI)I":
|
case "jdk/internal/misc/Unsafe.getAndSetInt(Ljava/lang/Object;JI)I":
|
||||||
@ -190,8 +206,10 @@ class UnsafeManager {
|
|||||||
break;
|
break;
|
||||||
case "jdk/internal/misc/Unsafe.ensureClassInitialized(Ljava/lang/Class;)V":
|
case "jdk/internal/misc/Unsafe.ensureClassInitialized(Ljava/lang/Class;)V":
|
||||||
case "jdk/internal/misc/Unsafe.unpark(Ljava/lang/Object;)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 );
|
remove( instructions, idx, callInst, 2 );
|
||||||
break;
|
break;
|
||||||
|
case "sun/misc/Unsafe.park(ZJ)V":
|
||||||
case "jdk/internal/misc/Unsafe.park(ZJ)V":
|
case "jdk/internal/misc/Unsafe.park(ZJ)V":
|
||||||
remove( instructions, idx, callInst, 3 );
|
remove( instructions, idx, callInst, 3 );
|
||||||
break;
|
break;
|
||||||
@ -415,17 +433,44 @@ class UnsafeManager {
|
|||||||
* @param name
|
* @param name
|
||||||
* the calling function
|
* the calling function
|
||||||
* @param fieldNameParam
|
* @param fieldNameParam
|
||||||
* the function parameter with the field offset. This must be a long (Java signature "J"). The THIS
|
* 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.
|
||||||
* parameter has the index 0.
|
|
||||||
*/
|
*/
|
||||||
private void patchFieldFunction( List<WasmInstruction> instructions, int idx, final WasmCallInstruction callInst, FunctionName name, int fieldNameParam ) {
|
private void patchFieldFunction( List<WasmInstruction> instructions, int idx, final WasmCallInstruction callInst, FunctionName name, int fieldNameParam ) {
|
||||||
StackValue stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), fieldNameParam, callInst.getCodePosition() );
|
StackValue stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), fieldNameParam, callInst.getCodePosition() );
|
||||||
FunctionName fieldNameWithOffset = ((WasmGlobalInstruction)stackValue.instr).getFieldName();
|
WasmInstruction instr = stackValue.instr;
|
||||||
|
|
||||||
|
Set<FunctionName> 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 =
|
WatCodeSyntheticFunctionName func =
|
||||||
new WatCodeSyntheticFunctionName( fieldNameWithOffset.className, '.' + name.methodName, name.signature, "", (AnyType[])null ) {
|
new WatCodeSyntheticFunctionName( fieldNameWithOffset.className, '.' + name.methodName, name.signature, "", (AnyType[])null ) {
|
||||||
@Override
|
@Override
|
||||||
protected String getCode() {
|
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 ) {
|
if( state == null ) {
|
||||||
// we are in the scan phase. The static code was not scanned yet.
|
// we are in the scan phase. The static code was not scanned yet.
|
||||||
return "";
|
return "";
|
||||||
@ -476,8 +521,12 @@ class UnsafeManager {
|
|||||||
+ " return";
|
+ " return";
|
||||||
|
|
||||||
case "putOrderedInt":
|
case "putOrderedInt":
|
||||||
|
case "putInt":
|
||||||
case "putOrderedLong":
|
case "putOrderedLong":
|
||||||
|
case "putLong":
|
||||||
case "putOrderedObject":
|
case "putOrderedObject":
|
||||||
|
case "putObjectVolatile":
|
||||||
|
case "putObject":
|
||||||
return "local.get 0" // THIS
|
return "local.get 0" // THIS
|
||||||
+ " local.get 2" // x
|
+ " local.get 2" // x
|
||||||
+ " struct.set " + state.typeName + ' ' + state.fieldName;
|
+ " 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.
|
// 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-- ) {
|
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 ) {
|
if( instr.getType() == Type.DupThis && ((DupThis)instr).getValue() == callInst ) {
|
||||||
nop( instructions, i, i + 1 );
|
nop( instructions, i, i + 1 );
|
||||||
break;
|
break;
|
||||||
@ -588,6 +637,70 @@ class UnsafeManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void patchVarHandle( List<WasmInstruction> 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<WasmInstruction> 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
|
* Hold the state from declaring of Unsafe address
|
||||||
*/
|
*/
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -19,10 +19,13 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
import org.junit.ClassRule;
|
import org.junit.ClassRule;
|
||||||
import org.junit.runners.Parameterized.Parameters;
|
import org.junit.runners.Parameterized.Parameters;
|
||||||
|
|
||||||
|
import de.inetsoftware.jwebassembly.JWebAssembly;
|
||||||
import de.inetsoftware.jwebassembly.ScriptEngine;
|
import de.inetsoftware.jwebassembly.ScriptEngine;
|
||||||
import de.inetsoftware.jwebassembly.WasmRule;
|
import de.inetsoftware.jwebassembly.WasmRule;
|
||||||
import de.inetsoftware.jwebassembly.api.annotation.Export;
|
import de.inetsoftware.jwebassembly.api.annotation.Export;
|
||||||
@ -51,8 +54,14 @@ public class UnsafeTest extends AbstractBaseTest {
|
|||||||
addParam( list, script, "getAndAddLong" );
|
addParam( list, script, "getAndAddLong" );
|
||||||
addParam( list, script, "getAndSetLong" );
|
addParam( list, script, "getAndSetLong" );
|
||||||
addParam( list, script, "lazySetLong" );
|
addParam( list, script, "lazySetLong" );
|
||||||
|
addParam( list, script, "compareAndSwapReference" );
|
||||||
|
addParam( list, script, "getAndSetReference" );
|
||||||
|
addParam( list, script, "lazySetReference" );
|
||||||
}
|
}
|
||||||
rule.setTestParameters( list );
|
rule.setTestParameters( list );
|
||||||
|
JWebAssembly.LOGGER.setLevel( Level.FINE );
|
||||||
|
rule.setProperty( JWebAssembly.IGNORE_NATIVE, "true" );
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,5 +144,33 @@ public class UnsafeTest extends AbstractBaseTest {
|
|||||||
obj.lazySet( 42 );
|
obj.lazySet( 42 );
|
||||||
return obj.get();
|
return obj.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Export
|
||||||
|
static int compareAndSwapReference() {
|
||||||
|
AtomicReference<Integer> obj = new AtomicReference<>();
|
||||||
|
if( obj.compareAndSet( null, 25 ) ) {
|
||||||
|
return obj.get();
|
||||||
|
} else {
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Export
|
||||||
|
static int getAndSetReference() {
|
||||||
|
AtomicReference<Integer> obj = new AtomicReference<>();
|
||||||
|
obj.set( 13 );
|
||||||
|
if( obj.getAndSet( 25 ) == 13 ) {
|
||||||
|
return obj.get();
|
||||||
|
} else {
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Export
|
||||||
|
static int lazySetReference() {
|
||||||
|
AtomicReference<Integer> obj = new AtomicReference<>();
|
||||||
|
obj.lazySet( 42 );
|
||||||
|
return obj.get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user