diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Interlocked.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Interlocked.cs index f22109a0e11f25..159e8f7e29071b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Interlocked.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Interlocked.cs @@ -84,7 +84,7 @@ public static short Exchange(ref short location1, short value) => [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe byte Exchange(ref byte location1, byte value) { -#if !MONO && (TARGET_X86 || TARGET_AMD64 || TARGET_ARM64) +#if (MONO && (TARGET_AMD64 || TARGET_ARM64 || TARGET_WASM)) || (!MONO && (TARGET_X86 || TARGET_AMD64 || TARGET_ARM64)) return Exchange(ref location1, value); // Must expand intrinsic #else // this relies on GC keeping 4B alignment for refs and on subtracting to such alignment being in the same object @@ -123,7 +123,7 @@ public static unsafe byte Exchange(ref byte location1, byte value) [CLSCompliant(false)] public static unsafe ushort Exchange(ref ushort location1, ushort value) { -#if !MONO && (TARGET_X86 || TARGET_AMD64 || TARGET_ARM64) +#if ((MONO && (TARGET_AMD64 || TARGET_ARM64 || TARGET_WASM)) || !MONO && (TARGET_X86 || TARGET_AMD64 || TARGET_ARM64)) return Exchange(ref location1, value); // Must expand intrinsic #else // this relies on GC keeping 4B alignment for refs and on subtracting to such alignment being in the same object @@ -322,7 +322,7 @@ public static short CompareExchange(ref short location1, short value, short comp [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe byte CompareExchange(ref byte location1, byte value, byte comparand) { -#if !MONO && (TARGET_X86 || TARGET_AMD64 || TARGET_ARM64) +#if (MONO && (TARGET_ARM64 || TARGET_AMD64 || TARGET_WASM)) || (!MONO && (TARGET_X86 || TARGET_AMD64 || TARGET_ARM64)) return CompareExchange(ref location1, value, comparand); // Must expand intrinsic #else // this relies on GC keeping 4B alignment for refs and on subtracting to such alignment being in the same object @@ -365,7 +365,7 @@ public static unsafe byte CompareExchange(ref byte location1, byte value, byte c [CLSCompliant(false)] public static unsafe ushort CompareExchange(ref ushort location1, ushort value, ushort comparand) { -#if !MONO && (TARGET_X86 || TARGET_AMD64 || TARGET_ARM64) +#if (MONO && (TARGET_ARM64 || TARGET_AMD64 || TARGET_WASM)) || (!MONO && (TARGET_X86 || TARGET_AMD64 || TARGET_ARM64)) return CompareExchange(ref location1, value, comparand); // Must expand intrinsic #else // this relies on GC keeping 4B alignment for refs and on subtracting to such alignment being in the same object diff --git a/src/mono/browser/runtime/jiterpreter-opcodes.ts b/src/mono/browser/runtime/jiterpreter-opcodes.ts index d535070df2ae8b..974bbaf5233c45 100644 --- a/src/mono/browser/runtime/jiterpreter-opcodes.ts +++ b/src/mono/browser/runtime/jiterpreter-opcodes.ts @@ -511,3 +511,73 @@ export const enum WasmSimdOpcode { i32x4_extadd_pairwise_i16x8_s = 0x7e, i32x4_extadd_pairwise_i16x8_u = 0x7f, } + +export const enum WasmAtomicOpcode { + memory_atomic_notify = 0x00, + memory_atomic_wait32 = 0x01, + memory_atomic_wait64 = 0x02, + atomic_fence = 0x03, + i32_atomic_load = 0x10, + i64_atomic_load = 0x11, + i32_atomic_load8_u = 0x12, + i32_atomic_load16_u = 0x13, + i64_atomic_load8_u = 0x14, + i64_atomic_load16_u = 0x15, + i64_atomic_load32_u = 0x16, + i32_atomic_store = 0x17, + i64_atomic_store = 0x18, + i32_atomic_store8 = 0x19, + i32_atomic_store16 = 0x1A, + i64_atomic_store8 = 0x1B, + i64_atomic_store16 = 0x1C, + i64_atomic_store32 = 0x1D, + i32_atomic_rmw_add = 0x1E, + i64_atomic_rmw_add = 0x1F, + i32_atomic_rmw8_add_u = 0x20, + i32_atomic_rmw16_add_u = 0x21, + i64_atomic_rmw8_add_u = 0x22, + i64_atomic_rmw16_add_u = 0x23, + i64_atomic_rmw32_add_u = 0x24, + i32_atomic_rmw_sub = 0x25, + i64_atomic_rmw_sub = 0x26, + i32_atomic_rmw8_sub_u = 0x27, + i32_atomic_rmw16_sub_u = 0x28, + i64_atomic_rmw8_sub_u = 0x29, + i64_atomic_rmw16_sub_u = 0x2A, + i64_atomic_rmw32_sub_u = 0x2B, + i32_atomic_rmw_and = 0x2C, + i64_atomic_rmw_and = 0x2D, + i32_atomic_rmw8_and_u = 0x2E, + i32_atomic_rmw16_and_u = 0x2F, + i64_atomic_rmw8_and_u = 0x30, + i64_atomic_rmw16_and_u = 0x31, + i64_atomic_rmw32_and_u = 0x32, + i32_atomic_rmw_or = 0x33, + i64_atomic_rmw_or = 0x34, + i32_atomic_rmw8_or_u = 0x35, + i32_atomic_rmw16_or_u = 0x36, + i64_atomic_rmw8_or_u = 0x37, + i64_atomic_rmw16_or_u = 0x38, + i64_atomic_rmw32_or_u = 0x39, + i32_atomic_rmw_xor = 0x3A, + i64_atomic_rmw_xor = 0x3B, + i32_atomic_rmw8_xor_u = 0x3C, + i32_atomic_rmw16_xor_u = 0x3D, + i64_atomic_rmw8_xor_u = 0x3E, + i64_atomic_rmw16_xor_u = 0x3F, + i64_atomic_rmw32_xor_u = 0x40, + i32_atomic_rmw_xchg = 0x41, + i64_atomic_rmw_xchg = 0x42, + i32_atomic_rmw8_xchg_u = 0x43, + i32_atomic_rmw16_xchg_u = 0x44, + i64_atomic_rmw8_xchg_u = 0x45, + i64_atomic_rmw16_xchg_u = 0x46, + i64_atomic_rmw32_xchg_u = 0x47, + i32_atomic_rmw_cmpxchg = 0x48, + i64_atomic_rmw_cmpxchg = 0x49, + i32_atomic_rmw8_cmpxchg_u = 0x4A, + i32_atomic_rmw16_cmpxchg_u = 0x4B, + i64_atomic_rmw8_cmpxchg_u = 0x4C, + i64_atomic_rmw16_cmpxchg_u = 0x4D, + i64_atomic_rmw32_cmpxchg_u = 0x4E, +} diff --git a/src/mono/browser/runtime/jiterpreter-support.ts b/src/mono/browser/runtime/jiterpreter-support.ts index 8b3370edfe75b1..e89b829c9df326 100644 --- a/src/mono/browser/runtime/jiterpreter-support.ts +++ b/src/mono/browser/runtime/jiterpreter-support.ts @@ -4,7 +4,7 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import { NativePointer, ManagedPointer, VoidPtr } from "./types/emscripten"; import { Module, mono_assert, runtimeHelpers } from "./globals"; -import { WasmOpcode, WasmSimdOpcode, WasmValtype } from "./jiterpreter-opcodes"; +import { WasmOpcode, WasmSimdOpcode, WasmAtomicOpcode, WasmValtype } from "./jiterpreter-opcodes"; import { MintOpcode } from "./mintops"; import cwraps from "./cwraps"; import { mono_log_error, mono_log_info } from "./logging"; @@ -105,6 +105,9 @@ export class WasmBuilder { nextConstantSlot = 0; backBranchTraceLevel = 0; + containsSimd!: boolean; + containsAtomics!: boolean; + compressImportNames = false; lockImports = false; @@ -153,6 +156,9 @@ export class WasmBuilder { this.callHandlerReturnAddresses.length = 0; this.allowNullCheckOptimization = this.options.eliminateNullChecks; + + this.containsSimd = false; + this.containsAtomics = false; } _push () { @@ -257,11 +263,18 @@ export class WasmBuilder { appendSimd (value: WasmSimdOpcode, allowLoad?: boolean) { this.current.appendU8(WasmOpcode.PREFIX_simd); - // Yes that's right. We're using LEB128 to encode 8-bit opcodes. Why? I don't know mono_assert(((value | 0) !== 0) || ((value === WasmSimdOpcode.v128_load) && (allowLoad === true)), "Expected non-v128_load simd opcode or allowLoad==true"); + // Yes that's right. We're using LEB128 to encode 8-bit opcodes. Why? I don't know return this.current.appendULeb(value); } + appendAtomic (value: WasmAtomicOpcode, allowNotify?: boolean) { + this.current.appendU8(WasmOpcode.PREFIX_atomic); + mono_assert(((value | 0) !== 0) || ((value === WasmAtomicOpcode.memory_atomic_notify) && (allowNotify === true)), "Expected non-notify atomic opcode or allowNotify==true"); + // Unlike SIMD, the spec appears to say that atomic opcodes are just two sequential bytes with explicit values. + return this.current.appendU8(value); + } + appendU32 (value: number) { return this.current.appendU32(value); } @@ -517,7 +530,7 @@ export class WasmBuilder { // memtype (limits = 0x03 n:u32 m:u32 => {min n, max m, shared}) this.appendU8(0x02); this.appendU8(0x03); - // emcc seems to generate this min/max by default + // HACK: emcc seems to generate this min/max by default this.appendULeb(256); this.appendULeb(32768); } else { @@ -1900,6 +1913,7 @@ export type JiterpreterOptions = { enableCallResume: boolean; enableWasmEh: boolean; enableSimd: boolean; + enableAtomics: boolean; zeroPageOptimization: boolean; cprop: boolean; // For locations where the jiterpreter heuristic says we will be unable to generate @@ -1946,6 +1960,7 @@ const optionNames: { [jsName: string]: string } = { "enableCallResume": "jiterpreter-call-resume-enabled", "enableWasmEh": "jiterpreter-wasm-eh-enabled", "enableSimd": "jiterpreter-simd-enabled", + "enableAtomics": "jiterpreter-atomics-enabled", "zeroPageOptimization": "jiterpreter-zero-page-optimization", "cprop": "jiterpreter-constant-propagation", "enableStats": "jiterpreter-stats-enabled", diff --git a/src/mono/browser/runtime/jiterpreter-tables.ts b/src/mono/browser/runtime/jiterpreter-tables.ts index a345844e9d9048..90b599a8bb553e 100644 --- a/src/mono/browser/runtime/jiterpreter-tables.ts +++ b/src/mono/browser/runtime/jiterpreter-tables.ts @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import { - WasmOpcode, WasmSimdOpcode, JiterpSpecialOpcode + WasmOpcode, WasmSimdOpcode, WasmAtomicOpcode, JiterpSpecialOpcode } from "./jiterpreter-opcodes"; import { MintOpcode, SimdIntrinsic2, SimdIntrinsic3, SimdIntrinsic4 @@ -325,6 +325,24 @@ export const mathIntrinsicTable: { [opcode: number]: [isUnary: boolean, isF32: b [MintOpcode.MINT_REM_R4]: [false, true, "fmodf"], }; +export const xchgTable: { [opcode: number]: [wasmOpcode: WasmAtomicOpcode, resultFixupOpcode: WasmOpcode, alignmentPower: number] } = { + [MintOpcode.MINT_MONO_EXCHANGE_U1]: [WasmAtomicOpcode.i32_atomic_rmw8_xchg_u, WasmOpcode.unreachable, 0], + [MintOpcode.MINT_MONO_EXCHANGE_I1]: [WasmAtomicOpcode.i32_atomic_rmw8_xchg_u, WasmOpcode.i32_extend_8_s, 0], + [MintOpcode.MINT_MONO_EXCHANGE_U2]: [WasmAtomicOpcode.i32_atomic_rmw16_xchg_u, WasmOpcode.unreachable, 1], + [MintOpcode.MINT_MONO_EXCHANGE_I2]: [WasmAtomicOpcode.i32_atomic_rmw16_xchg_u, WasmOpcode.i32_extend_16_s, 1], + [MintOpcode.MINT_MONO_EXCHANGE_I4]: [WasmAtomicOpcode.i32_atomic_rmw_xchg, WasmOpcode.unreachable, 2], + [MintOpcode.MINT_MONO_EXCHANGE_I8]: [WasmAtomicOpcode.i64_atomic_rmw_xchg, WasmOpcode.unreachable, 3], +}; + +export const cmpxchgTable: { [opcode: number]: [wasmOpcode: WasmAtomicOpcode, resultFixupOpcode: WasmOpcode, alignmentPower: number] } = { + [MintOpcode.MINT_MONO_CMPXCHG_U1]: [WasmAtomicOpcode.i32_atomic_rmw8_cmpxchg_u, WasmOpcode.unreachable, 0], + [MintOpcode.MINT_MONO_CMPXCHG_I1]: [WasmAtomicOpcode.i32_atomic_rmw8_cmpxchg_u, WasmOpcode.i32_extend_8_s, 0], + [MintOpcode.MINT_MONO_CMPXCHG_U2]: [WasmAtomicOpcode.i32_atomic_rmw16_cmpxchg_u, WasmOpcode.unreachable, 1], + [MintOpcode.MINT_MONO_CMPXCHG_I2]: [WasmAtomicOpcode.i32_atomic_rmw16_cmpxchg_u, WasmOpcode.i32_extend_16_s, 1], + [MintOpcode.MINT_MONO_CMPXCHG_I4]: [WasmAtomicOpcode.i32_atomic_rmw_cmpxchg, WasmOpcode.unreachable, 2], + [MintOpcode.MINT_MONO_CMPXCHG_I8]: [WasmAtomicOpcode.i64_atomic_rmw_cmpxchg, WasmOpcode.unreachable, 3], +}; + export const simdCreateSizes = { [MintOpcode.MINT_SIMD_V128_I1_CREATE]: 1, [MintOpcode.MINT_SIMD_V128_I2_CREATE]: 2, diff --git a/src/mono/browser/runtime/jiterpreter-trace-generator.ts b/src/mono/browser/runtime/jiterpreter-trace-generator.ts index e81c9b6683cbcf..4db54e579e5b21 100644 --- a/src/mono/browser/runtime/jiterpreter-trace-generator.ts +++ b/src/mono/browser/runtime/jiterpreter-trace-generator.ts @@ -54,6 +54,7 @@ import { bitmaskTable, createScalarTable, simdExtractTable, simdReplaceTable, simdLoadTable, simdStoreTable, + xchgTable, cmpxchgTable, } from "./jiterpreter-tables"; import { mono_log_error, mono_log_info } from "./logging"; import { mono_assert, runtimeHelpers } from "./globals"; @@ -244,7 +245,6 @@ export function generateWasmBody ( ): number { const abort = 0; let isFirstInstruction = true, isConditionallyExecuted = false, - containsSimd = false, pruneOpcodes = false, hasEmittedUnreachable = false; let result = 0, prologueOpcodeCounter = 0, @@ -1465,26 +1465,6 @@ export function generateWasmBody ( break; } - case MintOpcode.MINT_MONO_CMPXCHG_I4: - builder.local("pLocals"); - append_ldloc(builder, getArgU16(ip, 2), WasmOpcode.i32_load); // dest - append_ldloc(builder, getArgU16(ip, 3), WasmOpcode.i32_load); // newVal - append_ldloc(builder, getArgU16(ip, 4), WasmOpcode.i32_load); // expected - builder.callImport("cmpxchg_i32"); - append_stloc_tail(builder, getArgU16(ip, 1), WasmOpcode.i32_store); - break; - case MintOpcode.MINT_MONO_CMPXCHG_I8: - // because i64 values can't pass through JS cleanly (c.f getRawCwrap and - // EMSCRIPTEN_KEEPALIVE), we pass addresses of newVal, expected and the return value - // to the helper function. The "dest" for the compare-exchange is already a - // pointer, so load it normally - append_ldloc(builder, getArgU16(ip, 2), WasmOpcode.i32_load); // dest - append_ldloca(builder, getArgU16(ip, 3), 0); // newVal - append_ldloca(builder, getArgU16(ip, 4), 0); // expected - append_ldloca(builder, getArgU16(ip, 1), 8); // oldVal - builder.callImport("cmpxchg_i64"); - break; - case MintOpcode.MINT_LOG2_I4: case MintOpcode.MINT_LOG2_I8: { const isI64 = (opcode === MintOpcode.MINT_LOG2_I8); @@ -1647,13 +1627,19 @@ export function generateWasmBody ( (opcode >= MintOpcode.MINT_SIMD_V128_LDC) && (opcode <= MintOpcode.MINT_SIMD_INTRINS_P_PPP) ) { + builder.containsSimd = true; if (!emit_simd(builder, ip, opcode, opname, simdIntrinsArgCount, simdIntrinsIndex)) ip = abort; - else { - containsSimd = true; + else // We need to do dreg invalidation differently for simd, especially to handle ldc skipDregInvalidation = true; - } + } else if ( + (opcode >= MintOpcode.MINT_MONO_MEMORY_BARRIER) && + (opcode <= MintOpcode.MINT_MONO_CMPXCHG_I8) + ) { + builder.containsAtomics = true; + if (!emit_atomics(builder, ip, opcode)) + ip = abort; } else if (opcodeValue === 0) { // This means it was explicitly marked as no-value in the opcode value table // so we can just skip over it. This is done for things like nops. @@ -1740,7 +1726,7 @@ export function generateWasmBody ( // HACK: Traces containing simd will be *much* shorter than non-simd traces, // which will cause both the heuristic and our length requirement outside // to reject them. For now, just add a big constant to the length - if (containsSimd) + if (builder.containsSimd) result += 10240; return result; } @@ -3963,3 +3949,53 @@ function emit_simd_4 (builder: WasmBuilder, ip: MintOpcodePtr, index: SimdIntrin return false; } } + +function emit_atomics ( + builder: WasmBuilder, ip: MintOpcodePtr, opcode: number +) { + if (!builder.options.enableAtomics) + return false; + + // FIXME: memory barrier might be worthwhile to implement + // FIXME: We could probably unify most of the xchg/cmpxchg implementation into one implementation + + const xchg = xchgTable[opcode]; + if (xchg) { + const is64 = xchg[2] > 2; + // TODO: Generate alignment check to produce a better runtime error when address is not aligned? + builder.local("pLocals"); // stloc head + append_ldloc_cknull(builder, getArgU16(ip, 2), ip, true); // address + append_ldloc(builder, getArgU16(ip, 3), is64 ? WasmOpcode.i64_load : WasmOpcode.i32_load); // replacement + builder.appendAtomic(xchg[0], false); + builder.appendMemarg(0, xchg[2]); + // Fixup the result if necessary + if (xchg[1] !== WasmOpcode.unreachable) + builder.appendU8(xchg[1]); + // store old value + append_stloc_tail(builder, getArgU16(ip, 1), is64 ? WasmOpcode.i64_store : WasmOpcode.i32_store); + return true; + } + + const cmpxchg = cmpxchgTable[opcode]; + if (cmpxchg) { + const is64 = cmpxchg[2] > 2; + // TODO: Generate alignment check to produce a better runtime error when address is not aligned? + builder.local("pLocals"); // stloc head + append_ldloc_cknull(builder, getArgU16(ip, 2), ip, true); // address + // FIXME: Do these loads need to be sized? I think it's well-defined even if there are garbage bytes in the i32, + // based on language from the spec that looks like this: 'expected wrapped from i32 to i8, 8-bit compare equal' + append_ldloc(builder, getArgU16(ip, 4), is64 ? WasmOpcode.i64_load : WasmOpcode.i32_load); // expected + append_ldloc(builder, getArgU16(ip, 3), is64 ? WasmOpcode.i64_load : WasmOpcode.i32_load); // replacement + builder.appendAtomic(cmpxchg[0], false); + builder.appendMemarg(0, cmpxchg[2]); + // Fixup the result if necessary + if (cmpxchg[1] !== WasmOpcode.unreachable) + builder.appendU8(cmpxchg[1]); + // store old value + append_stloc_tail(builder, getArgU16(ip, 1), is64 ? WasmOpcode.i64_store : WasmOpcode.i32_store); + return true; + } + + return false; +} + diff --git a/src/mono/browser/runtime/jiterpreter.ts b/src/mono/browser/runtime/jiterpreter.ts index 9fc538c03f62d3..6b934b879c320f 100644 --- a/src/mono/browser/runtime/jiterpreter.ts +++ b/src/mono/browser/runtime/jiterpreter.ts @@ -283,8 +283,6 @@ function getTraceImports () { importDef("array_rank", getRawCwrap("mono_jiterp_get_array_rank")), ["a_elesize", "array_rank", getRawCwrap("mono_jiterp_get_array_element_size")], importDef("stfld_o", getRawCwrap("mono_jiterp_set_object_field")), - importDef("cmpxchg_i32", getRawCwrap("mono_jiterp_cas_i32")), - importDef("cmpxchg_i64", getRawCwrap("mono_jiterp_cas_i64")), ["stelemr_tc", "stelemr", getRawCwrap("mono_jiterp_stelem_ref")], importDef("fma", getRawCwrap("fma")), importDef("fmaf", getRawCwrap("fmaf")), @@ -629,25 +627,6 @@ function initialize_builder (builder: WasmBuilder) { }, WasmValtype.void, true ); - builder.defineType( - "cmpxchg_i32", - { - "dest": WasmValtype.i32, - "newVal": WasmValtype.i32, - "expected": WasmValtype.i32, - }, - WasmValtype.i32, true - ); - builder.defineType( - "cmpxchg_i64", - { - "dest": WasmValtype.i32, - "newVal": WasmValtype.i32, - "expected": WasmValtype.i32, - "oldVal": WasmValtype.i32, - }, - WasmValtype.void, true - ); builder.defineType( "stelemr", { @@ -896,7 +875,12 @@ function generate_wasm ( } catch (exc: any) { threw = true; rejected = false; - mono_log_error(`${methodFullName || traceName} code generation failed: ${exc} ${exc.stack}`); + let desc = builder.containsSimd + ? " (simd)" + : ""; + if (builder.containsAtomics) + desc += " (atomics)"; + mono_log_error(`${methodFullName || traceName}${desc} code generation failed: ${exc} ${exc.stack}`); recordFailure(); return 0; } finally { diff --git a/src/mono/mono/arch/amd64/amd64-codegen.h b/src/mono/mono/arch/amd64/amd64-codegen.h index e20e43357cee6e..f4d048e3c4dc9a 100644 --- a/src/mono/mono/arch/amd64/amd64-codegen.h +++ b/src/mono/mono/arch/amd64/amd64-codegen.h @@ -1216,12 +1216,42 @@ typedef union { #define amd64_prefix_size(inst,p,size) do { x86_prefix((inst), p); } while (0) #define amd64_rdtsc_size(inst,size) do { amd64_codegen_pre(inst); amd64_emit_rex ((inst),(size),0,0,0); x86_rdtsc(inst); amd64_codegen_post(inst); } while (0) +// FIXME: wrong for size == 1 or 2 #define amd64_cmpxchg_reg_reg_size(inst,dreg,reg,size) do { amd64_codegen_pre(inst); amd64_emit_rex ((inst),(size),(dreg),0,(reg)); x86_cmpxchg_reg_reg((inst),((dreg)&0x7),((reg)&0x7)); amd64_codegen_post(inst); } while (0) +// FIXME: wrong for size == 1 or 2 #define amd64_cmpxchg_mem_reg_size(inst,mem,reg,size) do { amd64_codegen_pre(inst); amd64_emit_rex ((inst),(size),0,0,(reg)); x86_cmpxchg_mem_reg((inst),(mem),((reg)&0x7)); amd64_codegen_post(inst); } while (0) -#define amd64_cmpxchg_membase_reg_size(inst,basereg,disp,reg,size) do { amd64_codegen_pre(inst); amd64_emit_rex ((inst),(size),(reg),0,(basereg)); x86_cmpxchg_membase_reg((inst),((basereg)&0x7),(disp),((reg)&0x7)); amd64_codegen_post(inst); } while (0) + +#define amd64_cmpxchg_membase_reg_size(inst,basereg,disp,reg,size) \ + do { \ + amd64_codegen_pre(inst); \ + if ((size) == 2) \ + x86_prefix((inst), X86_OPERAND_PREFIX); \ + amd64_emit_rex ((inst),(size),(reg),0,(basereg)); \ + switch ((size)) { \ + case 1: x86_byte((inst), 0x0f); x86_byte((inst), 0xb0); break; \ + case 2: case 4: case 8: x86_byte((inst), 0x0f); x86_byte((inst), 0xb1); break; \ + default: assert (0); \ + }\ + x86_membase_emit((inst),((reg)&0x7),((basereg)&0x7),(disp)); \ + amd64_codegen_post(inst); \ + } while (0) #define amd64_xchg_reg_reg_size(inst,dreg,reg,size) do { amd64_codegen_pre(inst); amd64_emit_rex ((inst),(size),(dreg),0,(reg)); x86_xchg_reg_reg((inst),((dreg)&0x7),((reg)&0x7),(size) == 8 ? 4 : (size)); amd64_codegen_post(inst); } while (0) #define amd64_xchg_mem_reg_size(inst,mem,reg,size) do { amd64_codegen_pre(inst); amd64_emit_rex ((inst),(size),0,0,(reg)); x86_xchg_mem_reg((inst),(mem),((reg)&0x7),(size) == 8 ? 4 : (size)); amd64_codegen_post(inst); } while (0) -#define amd64_xchg_membase_reg_size(inst,basereg,disp,reg,size) do { amd64_codegen_pre(inst); amd64_emit_rex ((inst),(size),(reg),0,(basereg)); x86_xchg_membase_reg((inst),((basereg)&0x7),(disp),((reg)&0x7),(size) == 8 ? 4 : (size)); amd64_codegen_post(inst); } while (0) +#define amd64_xchg_membase_reg_size(inst,basereg,disp,reg,size) \ + do { \ + amd64_codegen_pre(inst); \ + if ((size) == 2) \ + x86_prefix((inst), X86_OPERAND_PREFIX); \ + amd64_emit_rex ((inst),(size),(reg),0,(basereg)); \ + switch ((size)) { \ + case 1: x86_byte ((inst), 0x86); break; \ + case 2: case 4: case 8: x86_byte ((inst), 0x87); break; \ + default: assert (0); \ + } \ + x86_membase_emit ((inst), ((reg)&0x7), ((basereg)&0x7), (disp)); \ + amd64_codegen_post(inst); \ + } while (0) + #define amd64_inc_mem_size(inst,mem,size) do { amd64_codegen_pre(inst); amd64_emit_rex ((inst),(size),0,0,0); x86_inc_mem((inst),(mem)); amd64_codegen_post(inst); } while (0) #define amd64_inc_membase_size(inst,basereg,disp,size) do { amd64_codegen_pre(inst); amd64_emit_rex ((inst),(size),0,0,(basereg)); x86_inc_membase((inst),((basereg)&0x7),(disp)); amd64_codegen_post(inst); } while (0) //#define amd64_inc_reg_size(inst,reg,size) do { amd64_codegen_pre(inst); amd64_emit_rex ((inst),(size),0,0,(reg)); x86_inc_reg((inst),((reg)&0x7)); amd64_codegen_post(inst); } while (0) diff --git a/src/mono/mono/arch/arm64/arm64-codegen.h b/src/mono/mono/arch/arm64/arm64-codegen.h index f749f5be8eff1f..67af335893d539 100644 --- a/src/mono/mono/arch/arm64/arm64-codegen.h +++ b/src/mono/mono/arch/arm64/arm64-codegen.h @@ -454,6 +454,8 @@ MONO_RESTORE_WARNING #define arm_stlxrx(p, rs, rt, rn) arm_format_stlxr ((p), 0x3, (rs), (rn), (rt)) #define arm_stlxrw(p, rs, rt, rn) arm_format_stlxr ((p), 0x2, (rs), (rn), (rt)) +#define arm_stlxrh(p, rs, rt, rn) arm_format_stlxr ((p), 0x1, (rs), (rn), (rt)) +#define arm_stlxrb(p, rs, rt, rn) arm_format_stlxr ((p), 0x0, (rs), (rn), (rt)) /* Load/Store SIMD&FP */ diff --git a/src/mono/mono/mini/cpu-amd64.mdesc b/src/mono/mono/mini/cpu-amd64.mdesc index 66e16bb5232015..c5df012d8eb396 100644 --- a/src/mono/mono/mini/cpu-amd64.mdesc +++ b/src/mono/mono/mini/cpu-amd64.mdesc @@ -363,8 +363,12 @@ tls_get: dest:i len:32 tls_set: src1:i len:16 atomic_add_i4: src1:b src2:i dest:i len:32 atomic_add_i8: src1:b src2:i dest:i len:32 +atomic_exchange_u1: src1:b src2:i dest:i len:12 +atomic_exchange_u2: src1:b src2:i dest:i len:13 atomic_exchange_i4: src1:b src2:i dest:i len:12 atomic_exchange_i8: src1:b src2:i dest:i len:12 +atomic_cas_u1: src1:b src2:i src3:a dest:a len:24 +atomic_cas_u2: src1:b src2:i src3:a dest:a len:25 atomic_cas_i4: src1:b src2:i src3:a dest:a len:24 atomic_cas_i8: src1:b src2:i src3:a dest:a len:24 memory_barrier: len:3 diff --git a/src/mono/mono/mini/cpu-arm64.mdesc b/src/mono/mono/mini/cpu-arm64.mdesc index 49f235c5e4cb7c..5378b4d7241f74 100644 --- a/src/mono/mono/mini/cpu-arm64.mdesc +++ b/src/mono/mono/mini/cpu-arm64.mdesc @@ -482,8 +482,12 @@ arm64_hint: len:4 atomic_add_i4: dest:i src1:i src2:i len:32 atomic_add_i8: dest:i src1:i src2:i len:32 +atomic_exchange_u1: dest:i src1:i src2:i len:32 +atomic_exchange_u2: dest:i src1:i src2:i len:32 atomic_exchange_i4: dest:i src1:i src2:i len:32 atomic_exchange_i8: dest:i src1:i src2:i len:32 +atomic_cas_u1: dest:i src1:i src2:i src3:i len:32 +atomic_cas_u2: dest:i src1:i src2:i src3:i len:32 atomic_cas_i4: dest:i src1:i src2:i src3:i len:32 atomic_cas_i8: dest:i src1:i src2:i src3:i len:32 memory_barrier: len:8 clob:a diff --git a/src/mono/mono/mini/interp/interp.c b/src/mono/mono/mini/interp/interp.c index 61e1ff55d2f9b7..12f2cdd50350ee 100644 --- a/src/mono/mono/mini/interp/interp.c +++ b/src/mono/mono/mini/interp/interp.c @@ -7188,6 +7188,46 @@ MINT_IN_CASE(MINT_BRTRUE_I8_SP) ZEROP_SP(gint64, !=); MINT_IN_BREAK; mono_memory_barrier (); MINT_IN_BREAK; } + MINT_IN_CASE(MINT_MONO_EXCHANGE_U1) { + guint8 *dest = LOCAL_VAR (ip [2], guint8*); + guint8 exch = LOCAL_VAR (ip[3], guint8); + NULL_CHECK(dest); + LOCAL_VAR(ip[1], guint32) = (guint32)mono_atomic_xchg_u8(dest, exch); + ip += 4; + MINT_IN_BREAK; + } + MINT_IN_CASE(MINT_MONO_EXCHANGE_I1) { + gint8 *dest = LOCAL_VAR (ip [2], gint8*); + gint8 exch = LOCAL_VAR (ip[3], gint8); + NULL_CHECK(dest); + LOCAL_VAR(ip[1], gint32) = (gint32)(gint8)mono_atomic_xchg_u8((guint8*)dest, exch); + ip += 4; + MINT_IN_BREAK; + } + MINT_IN_CASE(MINT_MONO_EXCHANGE_U2) { + guint16 *dest = LOCAL_VAR (ip [2], guint16*); + guint16 exch = LOCAL_VAR (ip[3], guint16); + NULL_CHECK(dest); + LOCAL_VAR(ip[1], guint32) = (guint32)mono_atomic_xchg_u16(dest, exch); + ip += 4; + MINT_IN_BREAK; + } + MINT_IN_CASE(MINT_MONO_EXCHANGE_I2) { + gint16 *dest = LOCAL_VAR (ip [2], gint16*); + gint16 exch = LOCAL_VAR (ip[3], gint16); + NULL_CHECK(dest); + LOCAL_VAR(ip[1], gint32) = (gint32)(gint16)mono_atomic_xchg_u16((guint16*)dest, exch); + ip += 4; + MINT_IN_BREAK; + } + MINT_IN_CASE(MINT_MONO_EXCHANGE_I4) { + gint32 *dest = LOCAL_VAR (ip [2], gint32*); + gint32 exch = LOCAL_VAR (ip[3], gint32); + NULL_CHECK(dest); + LOCAL_VAR(ip[1], gint32) = mono_atomic_xchg_i32(dest, exch); + ip += 4; + MINT_IN_BREAK; + } MINT_IN_CASE(MINT_MONO_EXCHANGE_I8) { gboolean flag = FALSE; gint64 *dest = LOCAL_VAR (ip [2], gint64*); @@ -7209,6 +7249,46 @@ MINT_IN_CASE(MINT_BRTRUE_I8_SP) ZEROP_SP(gint64, !=); MINT_IN_BREAK; ip += 4; MINT_IN_BREAK; } + MINT_IN_CASE(MINT_MONO_CMPXCHG_U1) { + guint8 *dest = LOCAL_VAR(ip[2], guint8*); + guint8 value = LOCAL_VAR(ip[3], guint8); + guint8 comparand = LOCAL_VAR(ip[4], guint8); + NULL_CHECK(dest); + + LOCAL_VAR(ip[1], guint32) = (guint32)mono_atomic_cas_u8(dest, value, comparand); + ip += 5; + MINT_IN_BREAK; + } + MINT_IN_CASE(MINT_MONO_CMPXCHG_I1) { + gint8 *dest = LOCAL_VAR(ip[2], gint8*); + gint8 value = LOCAL_VAR(ip[3], gint8); + gint8 comparand = LOCAL_VAR(ip[4], gint8); + NULL_CHECK(dest); + + LOCAL_VAR(ip[1], gint32) = (gint32)(gint8)mono_atomic_cas_u8((guint8*)dest, value, comparand); + ip += 5; + MINT_IN_BREAK; + } + MINT_IN_CASE(MINT_MONO_CMPXCHG_U2) { + guint16 *dest = LOCAL_VAR(ip[2], guint16*); + guint16 value = LOCAL_VAR(ip[3], guint16); + guint16 comparand = LOCAL_VAR(ip[4], guint16); + NULL_CHECK(dest); + + LOCAL_VAR(ip[1], guint32) = (guint32)mono_atomic_cas_u16(dest, value, comparand); + ip += 5; + MINT_IN_BREAK; + } + MINT_IN_CASE(MINT_MONO_CMPXCHG_I2) { + gint16 *dest = LOCAL_VAR(ip[2], gint16*); + gint16 value = LOCAL_VAR(ip[3], gint16); + gint16 comparand = LOCAL_VAR(ip[4], gint16); + NULL_CHECK(dest); + + LOCAL_VAR(ip[1], gint32) = (gint32)(gint16)mono_atomic_cas_u16((guint16*)dest, value, comparand); + ip += 5; + MINT_IN_BREAK; + } MINT_IN_CASE(MINT_MONO_CMPXCHG_I4) { gint32 *dest = LOCAL_VAR(ip[2], gint32*); gint32 value = LOCAL_VAR(ip[3], gint32); diff --git a/src/mono/mono/mini/interp/jiterpreter-opcode-values.h b/src/mono/mono/mini/interp/jiterpreter-opcode-values.h index c3926ccb4c4791..b0b7ac2492f571 100644 --- a/src/mono/mono/mini/interp/jiterpreter-opcode-values.h +++ b/src/mono/mono/mini/interp/jiterpreter-opcode-values.h @@ -183,5 +183,8 @@ OP(MINT_THROW, ABORT_OUTSIDE_BRANCH_BLOCK_NONE) OP(MINT_MOV_SRC_OFF, NORMAL) OP(MINT_MOV_DST_OFF, NORMAL) +OPRANGE(MINT_MONO_EXCHANGE_U1, MINT_MONO_EXCHANGE_I8, HIGH) +OPRANGE(MINT_MONO_CMPXCHG_U1, MINT_MONO_CMPXCHG_I8, HIGH) + // FIXME: Not implemented individual opcodes OP(MINT_CONV_U4_R8, ABORT) diff --git a/src/mono/mono/mini/interp/jiterpreter.c b/src/mono/mono/mini/interp/jiterpreter.c index 43b71e70cd3d50..13d337b10c9fe0 100644 --- a/src/mono/mono/mini/interp/jiterpreter.c +++ b/src/mono/mono/mini/interp/jiterpreter.c @@ -561,18 +561,6 @@ mono_jiterp_interp_entry_prologue (JiterpEntryData *data, void *this_arg) return sp_args; } -EMSCRIPTEN_KEEPALIVE int32_t -mono_jiterp_cas_i32 (volatile int32_t *addr, int32_t newVal, int32_t expected) -{ - return mono_atomic_cas_i32 (addr, newVal, expected); -} - -EMSCRIPTEN_KEEPALIVE void -mono_jiterp_cas_i64 (volatile int64_t *addr, int64_t *newVal, int64_t *expected, int64_t *oldVal) -{ - *oldVal = mono_atomic_cas_i64 (addr, *newVal, *expected); -} - static int opcode_value_table [MINT_LASTOP] = { 0 }; static gboolean opcode_value_table_initialized = FALSE; @@ -1517,7 +1505,11 @@ EMSCRIPTEN_KEEPALIVE int mono_jiterp_allocate_table_entry (int type) { g_assert ((type >= 0) && (type <= JITERPRETER_TABLE_LAST)); JiterpreterTableInfo *table = &tables[type]; - g_assert (table->first_index > 0); + // Handle unlikely condition where the jiterpreter is engaged before initialize_table runs at all (i.e. tiering disabled) + if (table->first_index <= 0) { + g_printf ("MONO_WASM: Jiterpreter table %d is not yet initialized\n", type); + return 0; + } // Handle extremely unlikely race condition (allocate_table_entry called while another thread is in initialize_table) #ifdef DISABLE_THREADS diff --git a/src/mono/mono/mini/interp/mintops.def b/src/mono/mono/mini/interp/mintops.def index 7edb4f53e3fb70..11e65fb97ade69 100644 --- a/src/mono/mono/mini/interp/mintops.def +++ b/src/mono/mono/mini/interp/mintops.def @@ -709,7 +709,16 @@ OPDEF(MINT_LDPTR, "mono_ldptr", 3, 1, 0, MintOpShortInt) OPDEF(MINT_MONO_NEWOBJ, "mono_newobj", 3, 1, 0, MintOpClassToken) OPDEF(MINT_MONO_RETOBJ, "mono_retobj", 2, 0, 1, MintOpNoArgs) OPDEF(MINT_MONO_MEMORY_BARRIER, "mono_memory_barrier", 1, 0, 0, MintOpNoArgs) +OPDEF(MINT_MONO_EXCHANGE_U1, "mono_interlocked.xchg.u1", 4, 1, 2, MintOpNoArgs) +OPDEF(MINT_MONO_EXCHANGE_I1, "mono_interlocked.xchg.i1", 4, 1, 2, MintOpNoArgs) +OPDEF(MINT_MONO_EXCHANGE_U2, "mono_interlocked.xchg.u2", 4, 1, 2, MintOpNoArgs) +OPDEF(MINT_MONO_EXCHANGE_I2, "mono_interlocked.xchg.i2", 4, 1, 2, MintOpNoArgs) +OPDEF(MINT_MONO_EXCHANGE_I4, "mono_interlocked.xchg.i4", 4, 1, 2, MintOpNoArgs) OPDEF(MINT_MONO_EXCHANGE_I8, "mono_interlocked.xchg.i8", 4, 1, 2, MintOpNoArgs) +OPDEF(MINT_MONO_CMPXCHG_U1, "mono_interlocked.cmpxchg.u1", 5, 1, 3, MintOpNoArgs) +OPDEF(MINT_MONO_CMPXCHG_I1, "mono_interlocked.cmpxchg.i1", 5, 1, 3, MintOpNoArgs) +OPDEF(MINT_MONO_CMPXCHG_U2, "mono_interlocked.cmpxchg.u2", 5, 1, 3, MintOpNoArgs) +OPDEF(MINT_MONO_CMPXCHG_I2, "mono_interlocked.cmpxchg.i2", 5, 1, 3, MintOpNoArgs) OPDEF(MINT_MONO_CMPXCHG_I4, "mono_interlocked.cmpxchg.i4", 5, 1, 3, MintOpNoArgs) OPDEF(MINT_MONO_CMPXCHG_I8, "mono_interlocked.cmpxchg.i8", 5, 1, 3, MintOpNoArgs) OPDEF(MINT_MONO_LDDOMAIN, "mono_lddomain", 2, 1, 0, MintOpNoArgs) diff --git a/src/mono/mono/mini/interp/transform.c b/src/mono/mono/mini/interp/transform.c index 1af7c2d514b234..4b1865b8310b53 100644 --- a/src/mono/mono/mini/interp/transform.c +++ b/src/mono/mono/mini/interp/transform.c @@ -2457,15 +2457,57 @@ interp_handle_intrinsics (TransformData *td, MonoMethod *target_method, MonoClas } else if (in_corlib && !strcmp (klass_name_space, "System.Threading") && !strcmp (klass_name, "Interlocked")) { if (!strcmp (tm, "MemoryBarrier") && csignature->param_count == 0) *op = MINT_MONO_MEMORY_BARRIER; - else if (!strcmp (tm, "Exchange") && csignature->param_count == 2 && csignature->params [0]->type == MONO_TYPE_I8 && csignature->params [1]->type == MONO_TYPE_I8) - *op = MINT_MONO_EXCHANGE_I8; - else if (!strcmp (tm, "CompareExchange") && csignature->param_count == 3 && - (csignature->params[1]->type == MONO_TYPE_I4 || - csignature->params[1]->type == MONO_TYPE_I8)) { - if (csignature->params[1]->type == MONO_TYPE_I4) + else if (!strcmp (tm, "Exchange") && csignature->param_count == 2 && m_type_is_byref (csignature->params[0])) { + MonoType *t = mini_get_underlying_type (csignature->params[1]); + switch (t->type) { + case MONO_TYPE_I1: + *op = MINT_MONO_EXCHANGE_I1; + break; + case MONO_TYPE_U1: + *op = MINT_MONO_EXCHANGE_U1; + break; + case MONO_TYPE_I2: + *op = MINT_MONO_EXCHANGE_I2; + break; + case MONO_TYPE_U2: + *op = MINT_MONO_EXCHANGE_U2; + break; + case MONO_TYPE_I4: + *op = MINT_MONO_EXCHANGE_I4; + break; + case MONO_TYPE_I8: + *op = MINT_MONO_EXCHANGE_I8; + break; + default: + // no intrinsic + break; + } + } + else if (!strcmp (tm, "CompareExchange") && csignature->param_count == 3) { + MonoType *t = mini_get_underlying_type (csignature->params[1]); + switch (t->type) { + case MONO_TYPE_U1: + *op = MINT_MONO_CMPXCHG_U1; + break; + case MONO_TYPE_I1: + *op = MINT_MONO_CMPXCHG_I1; + break; + case MONO_TYPE_U2: + *op = MINT_MONO_CMPXCHG_U2; + break; + case MONO_TYPE_I2: + *op = MINT_MONO_CMPXCHG_I2; + break; + case MONO_TYPE_I4: *op = MINT_MONO_CMPXCHG_I4; - else + break; + case MONO_TYPE_I8: *op = MINT_MONO_CMPXCHG_I8; + break; + default: + /* no intrinsic */ + break; + } } } else if (in_corlib && !strcmp (klass_name_space, "System.Threading") && !strcmp (klass_name, "Thread")) { if (!strcmp (tm, "MemoryBarrier") && csignature->param_count == 0) diff --git a/src/mono/mono/mini/intrinsics.c b/src/mono/mono/mini/intrinsics.c index 402d58b3c7651c..ce6636b322094f 100644 --- a/src/mono/mono/mini/intrinsics.c +++ b/src/mono/mono/mini/intrinsics.c @@ -1522,11 +1522,34 @@ mini_emit_inst_for_method (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSign else if (strcmp (cmethod->name, "Exchange") == 0 && fsig->param_count == 2 && m_type_is_byref (fsig->params [0])) { MonoInst *f2i = NULL, *i2f; guint32 opcode, f2i_opcode = 0, i2f_opcode = 0; - gboolean is_ref = byref_arg_is_reference (fsig->params [0]); - gboolean is_float = fsig->params [0]->type == MONO_TYPE_R4 || fsig->params [0]->type == MONO_TYPE_R8; - - if (fsig->params [0]->type == MONO_TYPE_I4 || - fsig->params [0]->type == MONO_TYPE_R4) { + // params[1] is byval, use it to decide what kind of op to do + // get the underlying type so enums and bool work too. + MonoType *param_type = mini_get_underlying_type (fsig->params[1]); + gboolean is_ref = mini_type_is_reference (param_type); + gboolean is_float = param_type->type == MONO_TYPE_R4 || param_type->type == MONO_TYPE_R8; + guint32 u2i_result_opcode = 0; + MonoInst *u2i_result = NULL; + + // For small types .NET stack temps are always i4, so we need to zext or + // sext the output + if (param_type->type == MONO_TYPE_I1) { + opcode = OP_ATOMIC_EXCHANGE_U1; + u2i_result_opcode = OP_ICONV_TO_I1; + } + else if (param_type->type == MONO_TYPE_U1) { + opcode = OP_ATOMIC_EXCHANGE_U1; + u2i_result_opcode = OP_ICONV_TO_U1; + } + else if (param_type->type == MONO_TYPE_I2) { + opcode = OP_ATOMIC_EXCHANGE_U2; + u2i_result_opcode = OP_ICONV_TO_I2; + } + else if (param_type->type == MONO_TYPE_U2) { + opcode = OP_ATOMIC_EXCHANGE_U2; + u2i_result_opcode = OP_ICONV_TO_U2; + } + else if (param_type->type == MONO_TYPE_I4 || + param_type->type == MONO_TYPE_R4) { opcode = OP_ATOMIC_EXCHANGE_I4; f2i_opcode = OP_MOVE_F_TO_I4; i2f_opcode = OP_MOVE_I4_TO_F; @@ -1577,7 +1600,11 @@ mini_emit_inst_for_method (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSign ins->sreg2 = is_float ? f2i->dreg : args [1]->dreg; MONO_ADD_INS (cfg->cbb, ins); - switch (fsig->params [0]->type) { + switch (param_type->type) { + case MONO_TYPE_U1: + case MONO_TYPE_I1: + case MONO_TYPE_U2: + case MONO_TYPE_I2: case MONO_TYPE_I4: ins->type = STACK_I4; break; @@ -1611,6 +1638,13 @@ mini_emit_inst_for_method (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSign MONO_ADD_INS (cfg->cbb, i2f); ins = i2f; + } else if (u2i_result_opcode) { + MONO_INST_NEW (cfg, u2i_result, u2i_result_opcode); + u2i_result->dreg = mono_alloc_ireg (cfg); + u2i_result->sreg1 = ins->dreg; + MONO_ADD_INS (cfg->cbb, u2i_result); + + ins = u2i_result; } if (cfg->gen_write_barriers && is_ref) @@ -1619,11 +1653,43 @@ mini_emit_inst_for_method (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSign else if ((strcmp (cmethod->name, "CompareExchange") == 0) && fsig->param_count == 3) { MonoInst *f2i_new = NULL, *f2i_cmp = NULL, *i2f; guint32 opcode, f2i_opcode = 0, i2f_opcode = 0; - gboolean is_ref = mini_type_is_reference (fsig->params [1]); - gboolean is_float = fsig->params [1]->type == MONO_TYPE_R4 || fsig->params [1]->type == MONO_TYPE_R8; - - if (fsig->params [1]->type == MONO_TYPE_I4 || - fsig->params [1]->type == MONO_TYPE_R4) { + MonoType *param1_type = mini_get_underlying_type (fsig->params[1]); + gboolean is_ref = mini_type_is_reference (param1_type); + gboolean is_float = param1_type->type == MONO_TYPE_R4 || param1_type->type == MONO_TYPE_R8; + guint32 i2u_cmp_opcode = 0, u2i_result_opcode = 0; + MonoInst *i2u_cmp = NULL, *u2i_result = NULL; + + // For small types the "compare" part of CAS is done on zero extended. For + // the result, .NET stack temps are always i4, so we need to zext or sext + // the output + if (param1_type->type == MONO_TYPE_U1) { + opcode = OP_ATOMIC_CAS_U1; + i2u_cmp_opcode = 0; + // zext the result + u2i_result_opcode = OP_ICONV_TO_U1; + } + else if (param1_type->type == MONO_TYPE_I1) { + opcode = OP_ATOMIC_CAS_U1; + // zero extend expected comparand + i2u_cmp_opcode = OP_ICONV_TO_U1; + // sign extend result + u2i_result_opcode = OP_ICONV_TO_I1; + } + else if (param1_type->type == MONO_TYPE_U2) { + opcode = OP_ATOMIC_CAS_U2; + i2u_cmp_opcode = 0; + // zext the result + u2i_result_opcode = OP_ICONV_TO_U2; + } + else if (param1_type->type == MONO_TYPE_I2) { + opcode = OP_ATOMIC_CAS_U2; + // zero extend expected comparand + i2u_cmp_opcode = OP_ICONV_TO_U2; + // sign extend result + u2i_result_opcode = OP_ICONV_TO_I2; + } + else if (param1_type->type == MONO_TYPE_I4 || + param1_type->type == MONO_TYPE_R4) { opcode = OP_ATOMIC_CAS_I4; f2i_opcode = OP_MOVE_F_TO_I4; i2f_opcode = OP_MOVE_I4_TO_F; @@ -1631,15 +1697,15 @@ mini_emit_inst_for_method (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSign } #if SIZEOF_REGISTER == 8 else if (is_ref || - fsig->params [1]->type == MONO_TYPE_I8 || - fsig->params [1]->type == MONO_TYPE_R8 || - fsig->params [1]->type == MONO_TYPE_I) { + param1_type->type == MONO_TYPE_I8 || + param1_type->type == MONO_TYPE_R8 || + param1_type->type == MONO_TYPE_I) { opcode = OP_ATOMIC_CAS_I8; f2i_opcode = OP_MOVE_F_TO_I8; i2f_opcode = OP_MOVE_I8_TO_F; } #else - else if (is_ref || fsig->params [1]->type == MONO_TYPE_I) { + else if (is_ref || param1_type->type == MONO_TYPE_I) { opcode = OP_ATOMIC_CAS_I4; cfg->has_atomic_cas_i4 = TRUE; } @@ -1670,6 +1736,13 @@ mini_emit_inst_for_method (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSign MONO_ADD_INS (cfg->cbb, f2i_cmp); } + if (i2u_cmp_opcode) { + MONO_INST_NEW (cfg, i2u_cmp, i2u_cmp_opcode); + i2u_cmp->dreg = mono_alloc_ireg (cfg); + i2u_cmp->sreg1 = args[2]->dreg; + MONO_ADD_INS (cfg->cbb, i2u_cmp); + } + if (is_ref && !mini_debug_options.weak_memory_model) mini_emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_REL); @@ -1677,11 +1750,25 @@ mini_emit_inst_for_method (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSign MONO_INST_NEW (cfg, ins, opcode); ins->dreg = is_ref ? alloc_ireg_ref (cfg) : alloc_ireg (cfg); ins->sreg1 = args [0]->dreg; - ins->sreg2 = is_float ? f2i_new->dreg : args [1]->dreg; - ins->sreg3 = is_float ? f2i_cmp->dreg : args [2]->dreg; + if (is_float) { + ins->sreg2 = f2i_new->dreg; + ins->sreg3 = f2i_cmp->dreg; + } else if (i2u_cmp_opcode) { + ins->sreg2 = args[1]->dreg; + ins->sreg3 = i2u_cmp->dreg; + } else { + ins->sreg2 = args[1]->dreg; + ins->sreg3 = args[2]->dreg; + } MONO_ADD_INS (cfg->cbb, ins); - switch (fsig->params [1]->type) { + switch (param1_type->type) { + case MONO_TYPE_U1: + case MONO_TYPE_I1: + case MONO_TYPE_U2: + case MONO_TYPE_I2: + ins->type = STACK_I4; + break; case MONO_TYPE_I4: ins->type = STACK_I4; break; @@ -1702,7 +1789,7 @@ mini_emit_inst_for_method (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSign ins->type = STACK_R8; break; default: - g_assert (mini_type_is_reference (fsig->params [1])); + g_assert (mini_type_is_reference (param1_type)); ins->type = STACK_OBJ; break; } @@ -1718,6 +1805,14 @@ mini_emit_inst_for_method (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSign ins = i2f; } + if (u2i_result_opcode) { + MONO_INST_NEW (cfg, u2i_result, u2i_result_opcode); + u2i_result->dreg = mono_alloc_ireg (cfg); + u2i_result->sreg1 = ins->dreg; + MONO_ADD_INS (cfg->cbb, u2i_result); + + ins = u2i_result; + } if (cfg->gen_write_barriers && is_ref) mini_emit_write_barrier (cfg, args [0], args [1]); diff --git a/src/mono/mono/mini/mini-amd64.c b/src/mono/mono/mini/mini-amd64.c index 12c4929d6b56a6..329c83a1138e47 100644 --- a/src/mono/mono/mini/mini-amd64.c +++ b/src/mono/mono/mini/mini-amd64.c @@ -6905,9 +6905,27 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) break; } + case OP_ATOMIC_EXCHANGE_U1: + case OP_ATOMIC_EXCHANGE_U2: case OP_ATOMIC_EXCHANGE_I4: case OP_ATOMIC_EXCHANGE_I8: { - guint32 size = ins->opcode == OP_ATOMIC_EXCHANGE_I4 ? 4 : 8; + guint32 size; + switch (ins->opcode) { + case OP_ATOMIC_EXCHANGE_U1: + size = 1; + break; + case OP_ATOMIC_EXCHANGE_U2: + size = 2; + break; + case OP_ATOMIC_EXCHANGE_I4: + size = 4; + break; + case OP_ATOMIC_EXCHANGE_I8: + size = 8; + break; + default: + g_assert_not_reached (); + } /* LOCK prefix is implied. */ amd64_mov_reg_reg (code, GP_SCRATCH_REG, ins->sreg2, size); @@ -6915,14 +6933,28 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) amd64_mov_reg_reg (code, ins->dreg, GP_SCRATCH_REG, size); break; } + case OP_ATOMIC_CAS_U1: + case OP_ATOMIC_CAS_U2: case OP_ATOMIC_CAS_I4: case OP_ATOMIC_CAS_I8: { guint32 size; - if (ins->opcode == OP_ATOMIC_CAS_I8) - size = 8; - else + switch (ins->opcode) { + case OP_ATOMIC_CAS_U1: + size = 1; + break; + case OP_ATOMIC_CAS_U2: + size = 2; + break; + case OP_ATOMIC_CAS_I4: size = 4; + break; + case OP_ATOMIC_CAS_I8: + size = 8; + break; + default: + g_assert_not_reached (); + } /* * See http://msdn.microsoft.com/en-us/magazine/cc302329.aspx for @@ -9823,8 +9855,12 @@ mono_arch_opcode_supported (int opcode) switch (opcode) { case OP_ATOMIC_ADD_I4: case OP_ATOMIC_ADD_I8: + case OP_ATOMIC_EXCHANGE_U1: + case OP_ATOMIC_EXCHANGE_U2: case OP_ATOMIC_EXCHANGE_I4: case OP_ATOMIC_EXCHANGE_I8: + case OP_ATOMIC_CAS_U1: + case OP_ATOMIC_CAS_U2: case OP_ATOMIC_CAS_I4: case OP_ATOMIC_CAS_I8: case OP_ATOMIC_LOAD_I1: diff --git a/src/mono/mono/mini/mini-arm64.c b/src/mono/mono/mini/mini-arm64.c index 36ee5a2411131e..c42684ce693527 100644 --- a/src/mono/mono/mini/mini-arm64.c +++ b/src/mono/mono/mini/mini-arm64.c @@ -5086,6 +5086,30 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) arm_movx (code, dreg, ARMREG_IP0); break; } + case OP_ATOMIC_EXCHANGE_U1: { + guint8 *buf [16]; + + buf [0] = code; + arm_ldxrb (code, ARMREG_IP0, sreg1); + arm_stlxrb (code, ARMREG_IP1, sreg2, sreg1); + arm_cbnzw (code, ARMREG_IP1, buf [0]); + + arm_dmb (code, ARM_DMB_ISH); + arm_movx (code, dreg, ARMREG_IP0); + break; + } + case OP_ATOMIC_EXCHANGE_U2: { + guint8 *buf [16]; + + buf [0] = code; + arm_ldxrh (code, ARMREG_IP0, sreg1); + arm_stlxrh (code, ARMREG_IP1, sreg2, sreg1); + arm_cbnzw (code, ARMREG_IP1, buf [0]); + + arm_dmb (code, ARM_DMB_ISH); + arm_movx (code, dreg, ARMREG_IP0); + break; + } case OP_ATOMIC_EXCHANGE_I4: { guint8 *buf [16]; @@ -5110,6 +5134,34 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) arm_movx (code, dreg, ARMREG_IP0); break; } + case OP_ATOMIC_CAS_U1: { + guint8 *buf [16]; + buf [0] = code; + arm_ldxrb (code, ARMREG_IP0, sreg1); + arm_cmpw (code, ARMREG_IP0, ins->sreg3); + buf [1] = code; + arm_bcc (code, ARMCOND_NE, 0); + arm_stlxrb(code, ARMREG_IP1, sreg2, sreg1); + arm_cbnzw (code, ARMREG_IP1, buf [0]); + arm_patch_rel (buf [1], code, MONO_R_ARM64_BCC); + arm_dmb (code, ARM_DMB_ISH); + arm_movx (code, dreg, ARMREG_IP0); + break; + } + case OP_ATOMIC_CAS_U2: { + guint8 *buf [16]; + buf [0] = code; + arm_ldxrh (code, ARMREG_IP0, sreg1); + arm_cmpw (code, ARMREG_IP0, ins->sreg3); + buf [1] = code; + arm_bcc (code, ARMCOND_NE, 0); + arm_stlxrh(code, ARMREG_IP1, sreg2, sreg1); + arm_cbnzw (code, ARMREG_IP1, buf [0]); + arm_patch_rel (buf [1], code, MONO_R_ARM64_BCC); + arm_dmb (code, ARM_DMB_ISH); + arm_movx (code, dreg, ARMREG_IP0); + break; + } case OP_ATOMIC_CAS_I4: { guint8 *buf [16]; @@ -6935,8 +6987,12 @@ mono_arch_opcode_supported (int opcode) switch (opcode) { case OP_ATOMIC_ADD_I4: case OP_ATOMIC_ADD_I8: + case OP_ATOMIC_EXCHANGE_U1: + case OP_ATOMIC_EXCHANGE_U2: case OP_ATOMIC_EXCHANGE_I4: case OP_ATOMIC_EXCHANGE_I8: + case OP_ATOMIC_CAS_U1: + case OP_ATOMIC_CAS_U2: case OP_ATOMIC_CAS_I4: case OP_ATOMIC_CAS_I8: case OP_ATOMIC_LOAD_I1: diff --git a/src/mono/mono/mini/mini-llvm.c b/src/mono/mono/mini/mini-llvm.c index b85d6b562e837b..b52f42a5054e24 100644 --- a/src/mono/mono/mini/mini-llvm.c +++ b/src/mono/mono/mini/mini-llvm.c @@ -7565,15 +7565,29 @@ MONO_RESTORE_WARNING #define ARM64_ATOMIC_FENCE_FIX #endif + case OP_ATOMIC_EXCHANGE_U1: + case OP_ATOMIC_EXCHANGE_U2: case OP_ATOMIC_EXCHANGE_I4: case OP_ATOMIC_EXCHANGE_I8: { LLVMValueRef args [2]; LLVMTypeRef t; - if (ins->opcode == OP_ATOMIC_EXCHANGE_I4) + switch (ins->opcode) { + case OP_ATOMIC_EXCHANGE_U1: + t = LLVMInt8Type (); + break; + case OP_ATOMIC_EXCHANGE_U2: + t = LLVMInt16Type (); + break; + case OP_ATOMIC_EXCHANGE_I4: t = LLVMInt32Type (); - else + break; + case OP_ATOMIC_EXCHANGE_I8: t = LLVMInt64Type (); + break; + default: + g_assert_not_reached (); + } g_assert (ins->inst_offset == 0); @@ -7617,15 +7631,29 @@ MONO_RESTORE_WARNING ARM64_ATOMIC_FENCE_FIX; break; } + case OP_ATOMIC_CAS_U1: + case OP_ATOMIC_CAS_U2: case OP_ATOMIC_CAS_I4: case OP_ATOMIC_CAS_I8: { LLVMValueRef args [3], val; LLVMTypeRef t; - if (ins->opcode == OP_ATOMIC_CAS_I4) + switch (ins->opcode) { + case OP_ATOMIC_CAS_U1: + t = LLVMInt8Type (); + break; + case OP_ATOMIC_CAS_U2: + t = LLVMInt16Type (); + break; + case OP_ATOMIC_CAS_I4: t = LLVMInt32Type (); - else + break; + case OP_ATOMIC_CAS_I8: t = LLVMInt64Type (); + break; + default: + g_assert_not_reached (); + } args [0] = convert (ctx, lhs, pointer_type (t)); /* comparand */ diff --git a/src/mono/mono/mini/mini-ops.h b/src/mono/mono/mini/mini-ops.h index 3020fe0d53599e..76a1c49c15e20e 100644 --- a/src/mono/mono/mini/mini-ops.h +++ b/src/mono/mono/mini/mini-ops.h @@ -1274,9 +1274,13 @@ MINI_OP(OP_ATOMIC_AND_I8, "atomic_and_i8", IREG, IREG, IREG) MINI_OP(OP_ATOMIC_OR_I4, "atomic_or_i4", IREG, IREG, IREG) MINI_OP(OP_ATOMIC_OR_I8, "atomic_or_i8", IREG, IREG, IREG) +MINI_OP(OP_ATOMIC_EXCHANGE_U1, "atomic_exchange_u1", IREG, IREG, IREG) +MINI_OP(OP_ATOMIC_EXCHANGE_U2, "atomic_exchange_u2", IREG, IREG, IREG) MINI_OP(OP_ATOMIC_EXCHANGE_I4, "atomic_exchange_i4", IREG, IREG, IREG) MINI_OP(OP_ATOMIC_EXCHANGE_I8, "atomic_exchange_i8", IREG, IREG, IREG) +MINI_OP3(OP_ATOMIC_CAS_U1, "atomic_cas_u1", IREG, IREG, IREG, IREG) +MINI_OP3(OP_ATOMIC_CAS_U2, "atomic_cas_u2", IREG, IREG, IREG, IREG) MINI_OP3(OP_ATOMIC_CAS_I4, "atomic_cas_i4", IREG, IREG, IREG, IREG) MINI_OP3(OP_ATOMIC_CAS_I8, "atomic_cas_i8", IREG, IREG, IREG, IREG) diff --git a/src/mono/mono/mini/mini-wasm.c b/src/mono/mono/mini/mini-wasm.c index db7c8b2de39a60..4f094b9246810e 100644 --- a/src/mono/mono/mini/mini-wasm.c +++ b/src/mono/mono/mini/mini-wasm.c @@ -175,8 +175,12 @@ mono_arch_opcode_supported (int opcode) switch (opcode) { case OP_ATOMIC_ADD_I4: case OP_ATOMIC_ADD_I8: + case OP_ATOMIC_EXCHANGE_U1: + case OP_ATOMIC_EXCHANGE_U2: case OP_ATOMIC_EXCHANGE_I4: case OP_ATOMIC_EXCHANGE_I8: + case OP_ATOMIC_CAS_U1: + case OP_ATOMIC_CAS_U2: case OP_ATOMIC_CAS_I4: case OP_ATOMIC_CAS_I8: case OP_ATOMIC_LOAD_I1: diff --git a/src/mono/mono/utils/atomic.h b/src/mono/mono/utils/atomic.h index 7d1d127f60c5ce..3f0a01e6beb428 100644 --- a/src/mono/mono/utils/atomic.h +++ b/src/mono/mono/utils/atomic.h @@ -95,6 +95,22 @@ Apple targets have historically being problematic, xcode 4.6 would miscompile th #include +static inline guint8 +mono_atomic_cas_u8 (volatile guint8 *dest, guint8 exch, guint8 comp) +{ + g_static_assert (sizeof (atomic_char) == sizeof (*dest) && ATOMIC_CHAR_LOCK_FREE == 2); + (void)atomic_compare_exchange_strong ((volatile atomic_char *)dest, (char*)&comp, exch); + return comp; +} + +static inline guint16 +mono_atomic_cas_u16 (volatile guint16 *dest, guint16 exch, guint16 comp) +{ + g_static_assert (sizeof (atomic_short) == sizeof (*dest) && ATOMIC_SHORT_LOCK_FREE == 2); + (void)atomic_compare_exchange_strong ((volatile atomic_short *)dest, (short*)&comp, exch); + return comp; +} + static inline gint32 mono_atomic_cas_i32 (volatile gint32 *dest, gint32 exch, gint32 comp) { @@ -171,6 +187,20 @@ mono_atomic_dec_i64 (volatile gint64 *dest) return mono_atomic_add_i64 (dest, -1); } +static inline guint8 +mono_atomic_xchg_u8 (volatile guint8 *dest, guint8 exch) +{ + g_static_assert (sizeof (atomic_char) == sizeof (*dest) && ATOMIC_CHAR_LOCK_FREE == 2); + return atomic_exchange ((volatile atomic_char *)dest, exch); +} + +static inline guint16 +mono_atomic_xchg_u16 (volatile guint16 *dest, guint16 exch) +{ + g_static_assert (sizeof (atomic_short) == sizeof (*dest) && ATOMIC_SHORT_LOCK_FREE == 2); + return atomic_exchange ((volatile atomic_short *)dest, exch); +} + static inline gint32 mono_atomic_xchg_i32 (volatile gint32 *dest, gint32 exch) { @@ -311,6 +341,18 @@ mono_atomic_store_ptr (volatile gpointer *dst, gpointer val) #include #include +static inline guint8 +mono_atomic_cas_u8 (volatile guint8 *dest, guint8 exch, guint8 comp) +{ + return _InterlockedCompareExchange8 ((char volatile *)dest, (char)exch, (char)comp); +} + +static inline guint16 +mono_atomic_cas_u16 (volatile guint16 *dest, guint16 exch, guint16 comp) +{ + return _InterlockedCompareExchange16 ((short volatile *)dest, (short)exch, (short)comp); +} + static inline gint32 mono_atomic_cas_i32 (volatile gint32 *dest, gint32 exch, gint32 comp) { @@ -365,6 +407,18 @@ mono_atomic_dec_i64 (volatile gint64 *dest) return InterlockedDecrement64 ((LONG64 volatile *)dest); } +static inline guint8 +mono_atomic_xchg_u8 (volatile guint8 *dest, guint8 exch) +{ + return _InterlockedExchange8 ((char volatile *)dest, (char)exch); +} + +static inline guint16 +mono_atomic_xchg_u16 (volatile guint16 *dest, guint16 exch) +{ + return _InterlockedExchange16 ((short volatile *)dest, (short)exch); +} + static inline gint32 mono_atomic_xchg_i32 (volatile gint32 *dest, gint32 exch) { @@ -508,6 +562,18 @@ mono_atomic_store_ptr (volatile gpointer *dst, gpointer val) #define gcc_sync_fetch_and_add(a, b) __sync_fetch_and_add (a, b) #endif +static inline guint8 mono_atomic_cas_u8(volatile guint8 *dest, + guint8 exch, guint8 comp) +{ + return gcc_sync_val_compare_and_swap (dest, comp, exch); +} + +static inline guint16 mono_atomic_cas_u16(volatile guint16 *dest, + guint16 exch, guint16 comp) +{ + return gcc_sync_val_compare_and_swap (dest, comp, exch); +} + static inline gint32 mono_atomic_cas_i32(volatile gint32 *dest, gint32 exch, gint32 comp) { @@ -534,6 +600,24 @@ static inline gint32 mono_atomic_dec_i32(volatile gint32 *val) return gcc_sync_sub_and_fetch (val, 1); } +static inline guint8 mono_atomic_xchg_u8(volatile guint8 *val, guint8 new_val) +{ + guint8 old_val; + do { + old_val = *val; + } while (gcc_sync_val_compare_and_swap (val, old_val, new_val) != old_val); + return old_val; +} + +static inline guint16 mono_atomic_xchg_u16(volatile guint16 *val, guint16 new_val) +{ + guint16 old_val; + do { + old_val = *val; + } while (gcc_sync_val_compare_and_swap (val, old_val, new_val) != old_val); + return old_val; +} + static inline gint32 mono_atomic_xchg_i32(volatile gint32 *val, gint32 new_val) { gint32 old_val; @@ -726,6 +810,8 @@ static inline void mono_atomic_store_i64(volatile gint64 *dst, gint64 val) #define WAPI_NO_ATOMIC_ASM /* Fallbacks seem to not be used anymore, they should be removed. */ +/* extern guint8 mono_atomic_cas_u8(volatile guint8 *dest, guint8 exch, guint8 comp); */ +/* extern guint16 mono_atomic_cas_u16(volatile guint16 *dest, guint16 exch, guint16 comp); */ extern gint32 mono_atomic_cas_i32(volatile gint32 *dest, gint32 exch, gint32 comp); extern gint64 mono_atomic_cas_i64(volatile gint64 *dest, gint64 exch, gint64 comp); extern gpointer mono_atomic_cas_ptr(volatile gpointer *dest, gpointer exch, gpointer comp); @@ -735,6 +821,8 @@ extern gint32 mono_atomic_inc_i32(volatile gint32 *dest); extern gint64 mono_atomic_inc_i64(volatile gint64 *dest); extern gint32 mono_atomic_dec_i32(volatile gint32 *dest); extern gint64 mono_atomic_dec_i64(volatile gint64 *dest); +/*extern guint8 mono_atomic_xchg_u8(volatile guint8 *dest, guint8 exch); */ +/*extern guint16 mono_atomic_xchg_u16(volatile guint16 *dest, guint16 exch); */ extern gint32 mono_atomic_xchg_i32(volatile gint32 *dest, gint32 exch); extern gint64 mono_atomic_xchg_i64(volatile gint64 *dest, gint64 exch); extern gpointer mono_atomic_xchg_ptr(volatile gpointer *dest, gpointer exch); diff --git a/src/mono/mono/utils/options-def.h b/src/mono/mono/utils/options-def.h index bd33aea9e23b71..7c1b2c3ff06ee8 100644 --- a/src/mono/mono/utils/options-def.h +++ b/src/mono/mono/utils/options-def.h @@ -129,6 +129,8 @@ DEFINE_BOOL(jiterpreter_eliminate_null_checks, "jiterpreter-eliminate-null-check DEFINE_BOOL(jiterpreter_backward_branches_enabled, "jiterpreter-backward-branches-enabled", TRUE, "Enable performing backward branches without exiting traces") // Attempt to use WASM v128 opcodes to implement SIMD interpreter opcodes DEFINE_BOOL(jiterpreter_enable_simd, "jiterpreter-simd-enabled", TRUE, "Attempt to use WebAssembly SIMD support") +// Attempt to use WASM atomics opcodes to implement interpreter atomics opcodes +DEFINE_BOOL(jiterpreter_enable_atomics, "jiterpreter-atomics-enabled", TRUE, "Attempt to use WebAssembly atomics support") // Since the zero page is unallocated, loading array/string/span lengths from null ptrs will yield zero DEFINE_BOOL(jiterpreter_zero_page_optimization, "jiterpreter-zero-page-optimization", TRUE, "Exploit the zero page being unallocated to optimize out null checks") // We can produce higher quality code by embedding known constants directly into traces instead of loading diff --git a/src/mono/sample/HelloWorld/HelloWorld.csproj b/src/mono/sample/HelloWorld/HelloWorld.csproj index 4f897f0fc5627d..a35c1bf2859671 100644 --- a/src/mono/sample/HelloWorld/HelloWorld.csproj +++ b/src/mono/sample/HelloWorld/HelloWorld.csproj @@ -9,6 +9,7 @@ true normal full + false + + + +