diff --git a/src/coreclr/jit/codegen.h b/src/coreclr/jit/codegen.h index 9e9d6921c11200..028b98f2c39080 100644 --- a/src/coreclr/jit/codegen.h +++ b/src/coreclr/jit/codegen.h @@ -759,6 +759,10 @@ class CodeGen final : public CodeGenInterface void genCodeForBinary(GenTreeOp* treeNode); bool genIsSameLocalVar(GenTree* tree1, GenTree* tree2); +#if defined(TARGET_WASM) + void genCodeForConstant(GenTree* treeNode); +#endif + #if defined(TARGET_X86) void genCodeForLongUMod(GenTreeOp* node); #endif // TARGET_X86 diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 59c49cf45550ea..d54716280008e4 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -2213,6 +2213,11 @@ void CodeGen::genEmitMachineCode() // void CodeGen::genEmitUnwindDebugGCandEH() { +#ifdef TARGET_WASM + // TODO-WASM: Fix this phase causing an assertion failure even for methods with no GC locals or EH clauses + return; +#endif + /* Now that the code is issued, we can finalize and emit the unwind data */ compiler->unwindEmit(*codePtr, coldCodePtr); diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 7e0e2d567c4077..ba5d87b1f1cffb 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -269,6 +269,12 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) // Do nothing; this node is a marker for debug info. break; + case GT_CNS_INT: + case GT_CNS_LNG: + case GT_CNS_DBL: + genCodeForConstant(treeNode); + break; + default: #ifdef DEBUG NYIRAW(GenTree::OpName(treeNode->OperGet())); @@ -537,6 +543,60 @@ void CodeGen::genCodeForDivMod(GenTreeOp* treeNode) genProduceReg(treeNode); } +//------------------------------------------------------------------------ +// genCodeForConstant: Generate code for an integer or floating point constant +// +// Arguments: +// treeNode - The constant. +// +void CodeGen::genCodeForConstant(GenTree* treeNode) +{ + instruction ins; + cnsval_ssize_t bits; + var_types type = treeNode->TypeIs(TYP_REF, TYP_BYREF) ? TYP_I_IMPL : treeNode->TypeGet(); + static_assert(sizeof(cnsval_ssize_t) >= sizeof(double)); + + switch (type) + { + case TYP_INT: + { + ins = INS_i32_const; + GenTreeIntConCommon* con = treeNode->AsIntConCommon(); + bits = con->IntegralValue(); + break; + } + case TYP_LONG: + { + ins = INS_i64_const; + GenTreeIntConCommon* con = treeNode->AsIntConCommon(); + bits = con->IntegralValue(); + break; + } + case TYP_FLOAT: + { + ins = INS_f32_const; + GenTreeDblCon* con = treeNode->AsDblCon(); + double value = con->DconValue(); + memcpy(&bits, &value, sizeof(double)); + break; + } + case TYP_DOUBLE: + { + ins = INS_f64_const; + GenTreeDblCon* con = treeNode->AsDblCon(); + double value = con->DconValue(); + memcpy(&bits, &value, sizeof(double)); + break; + } + default: + unreached(); + } + + // The IF_ for the selected instruction, i.e. IF_F64, determines how these bits are emitted + GetEmitter()->emitIns_I(ins, emitTypeSize(treeNode), bits); + genProduceReg(treeNode); +} + //------------------------------------------------------------------------ // genCodeForShift: Generate code for a shift or rotate operator // diff --git a/src/coreclr/jit/emitfmtswasm.h b/src/coreclr/jit/emitfmtswasm.h index 1413c3cdfb1718..a9e3861ff16707 100644 --- a/src/coreclr/jit/emitfmtswasm.h +++ b/src/coreclr/jit/emitfmtswasm.h @@ -31,6 +31,9 @@ IF_DEF(OPCODE, IS_NONE, NONE) // IF_DEF(BLOCK, IS_NONE, NONE) // <0x40> IF_DEF(LABEL, IS_NONE, NONE) // IF_DEF(ULEB128, IS_NONE, NONE) // +IF_DEF(SLEB128, IS_NONE, NONE) // +IF_DEF(F32, IS_NONE, NONE) // +IF_DEF(F64, IS_NONE, NONE) // IF_DEF(MEMARG, IS_NONE, NONE) // ( ) #undef IF_DEF diff --git a/src/coreclr/jit/emitwasm.cpp b/src/coreclr/jit/emitwasm.cpp index c020eded146558..34fc086781d802 100644 --- a/src/coreclr/jit/emitwasm.cpp +++ b/src/coreclr/jit/emitwasm.cpp @@ -35,7 +35,7 @@ void emitter::emitIns(instruction ins) //------------------------------------------------------------------------ // emitIns_I: Emit an instruction with an immediate operand. // -void emitter::emitIns_I(instruction ins, emitAttr attr, target_ssize_t imm) +void emitter::emitIns_I(instruction ins, emitAttr attr, cnsval_ssize_t imm) { instrDesc* id = emitNewInstrSC(attr, imm); insFormat fmt = emitInsFormat(ins); @@ -65,7 +65,7 @@ void emitter::emitIns_R(instruction ins, emitAttr attr, regNumber reg) NYI_WASM("emitIns_R"); } -void emitter::emitIns_R_I(instruction ins, emitAttr attr, regNumber reg, ssize_t imm) +void emitter::emitIns_R_I(instruction ins, emitAttr attr, regNumber reg, cnsval_ssize_t imm) { NYI_WASM("emitIns_R_I"); } @@ -126,7 +126,13 @@ size_t emitter::emitSizeOfInsDsc(instrDesc* id) const return sizeof(instrDesc); } -static unsigned SizeOfULEB128(uint64_t value) +unsigned emitter::emitGetAlignHintLog2(const instrDesc* id) +{ + // FIXME + return 0; +} + +unsigned emitter::SizeOfULEB128(uint64_t value) { // bits_to_encode = (data != 0) ? 64 - CLZ(x) : 1 = 64 - CLZ(data | 1) // bytes = ceil(bits_to_encode / 7.0); = (6 + bits_to_encode) / 7 @@ -136,6 +142,13 @@ static unsigned SizeOfULEB128(uint64_t value) return (x * 37) >> 8; } +unsigned emitter::SizeOfSLEB128(int64_t value) +{ + // The same as SizeOfULEB128 calculation but we have to account for the sign bit. + unsigned x = 1 + 6 + 64 - (unsigned)BitOperations::LeadingZeroCount((uint64_t)(value ^ (value >> 63)) | 1UL); + return (x * 37) >> 8; +} + unsigned emitter::instrDesc::idCodeSize() const { #ifdef TARGET_WASM32 @@ -156,15 +169,28 @@ unsigned emitter::instrDesc::idCodeSize() const break; case IF_LABEL: assert(!idIsCnsReloc()); - size = SizeOfULEB128(static_cast(emitGetInsSC(this))); + size = SizeOfULEB128(emitGetInsSC(this)); break; case IF_ULEB128: - size += idIsCnsReloc() ? PADDED_RELOC_SIZE : SizeOfULEB128(static_cast(emitGetInsSC(this))); + size += idIsCnsReloc() ? PADDED_RELOC_SIZE : SizeOfULEB128(emitGetInsSC(this)); + break; + case IF_SLEB128: + size += idIsCnsReloc() ? PADDED_RELOC_SIZE : SizeOfSLEB128(emitGetInsSC(this)); + break; + case IF_F32: + size += 4; + break; + case IF_F64: + size += 8; break; case IF_MEMARG: - size += 1; // The alignment hint byte. - size += idIsCnsReloc() ? PADDED_RELOC_SIZE : SizeOfULEB128(static_cast(emitGetInsSC(this))); + { + uint64_t align = emitGetAlignHintLog2(this); + assert(align < 64); // spec says align > 2^6 produces a memidx for multiple memories. + size += SizeOfULEB128(align); + size += idIsCnsReloc() ? PADDED_RELOC_SIZE : SizeOfULEB128(emitGetInsSC(this)); break; + } default: unreached(); } @@ -176,6 +202,56 @@ void emitter::emitSetShortJump(instrDescJmp* id) NYI_WASM("emitSetShortJump"); } +size_t emitter::emitOutputULEB128(uint8_t* destination, uint64_t value) +{ + uint8_t* buffer = destination + writeableOffset; + if (value >= 0x80) + { + int pos = 0; + do + { + buffer[pos++] = (uint8_t)((value & 0x7F) | ((value >= 0x80) ? 0x80u : 0)); + value >>= 7; + } while (value > 0); + + return pos; + } + else + { + buffer[0] = (uint8_t)value; + return 1; + } +} + +size_t emitter::emitOutputSLEB128(uint8_t* destination, int64_t value) +{ + uint8_t* buffer = destination + writeableOffset; + bool cont = true; + int pos = 0; + while (cont) + { + uint8_t b = ((uint8_t)value & 0x7F); + value >>= 7; + bool isSignBitSet = (b & 0x40) != 0; + if ((value == 0 && !isSignBitSet) || (value == -1 && isSignBitSet)) + { + cont = false; + } + else + { + b |= 0x80; + } + buffer[pos++] = b; + } + return pos; +} + +size_t emitter::emitRawBytes(uint8_t* destination, const void* source, size_t count) +{ + memcpy(destination + writeableOffset, source, count); + return count; +} + size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp) { BYTE* dst = *dp; @@ -191,16 +267,61 @@ size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp) break; case IF_BLOCK: dst += emitOutputByte(dst, opcode); - dst += emitOutputByte(dst, 0x40); + dst += emitOutputByte(dst, 0x40 /* block type of void */); break; case IF_ULEB128: + { + dst += emitOutputByte(dst, opcode); + cnsval_ssize_t constant = emitGetInsSC(id); + dst += emitOutputULEB128(dst, (uint64_t)constant); + break; + } + case IF_SLEB128: + { + dst += emitOutputByte(dst, opcode); + cnsval_ssize_t constant = emitGetInsSC(id); + dst += emitOutputSLEB128(dst, (int64_t)constant); + break; + } + case IF_F32: + { + dst += emitOutputByte(dst, opcode); + // Reinterpret the bits as a double constant and then truncate it to f32, + // then finally copy the raw truncated f32 bits to the output. + cnsval_ssize_t bits = emitGetInsSC(id); + double value; + float truncated; + memcpy(&value, &bits, sizeof(double)); + truncated = FloatingPointUtils::convertToSingle(value); + dst += emitRawBytes(dst, &truncated, sizeof(float)); + break; + } + case IF_F64: + { dst += emitOutputByte(dst, opcode); - // TODO-WASM: emit uleb128 + // The int64 bits are actually a double constant we can copy directly + // to the output stream. + cnsval_ssize_t bits = emitGetInsSC(id); + dst += emitRawBytes(dst, &bits, sizeof(cnsval_ssize_t)); break; + } case IF_LABEL: - // TODO-WASM: emit uleb128 + NYI_WASM("emitOutputInstr IF_LABEL"); + break; + case IF_MEMARG: + { + dst += emitOutputByte(dst, opcode); + uint64_t align = emitGetAlignHintLog2(id); + uint64_t offset = emitGetInsSC(id); + assert(align <= UINT32_MAX); // spec says memarg alignment is u32 + assert(align < 64); // spec says align > 2^6 produces a memidx for multiple memories. + dst += emitOutputULEB128(dst, align); + dst += emitOutputULEB128(dst, offset); + break; + } default: NYI_WASM("emitOutputInstr"); + break; } #ifdef DEBUG @@ -303,17 +424,34 @@ void emitter::emitDispIns( case IF_LABEL: case IF_ULEB128: { - target_size_t imm = emitGetInsSC(id); - printf(" %u", imm); + cnsval_ssize_t imm = emitGetInsSC(id); + printf(" %llu", (uint64_t)imm); + } + break; + + case IF_SLEB128: + { + cnsval_ssize_t imm = emitGetInsSC(id); + printf(" %lli", (int64_t)imm); + } + break; + + case IF_F32: + case IF_F64: + { + cnsval_ssize_t bits = emitGetInsSC(id); + double value; + memcpy(&value, &bits, sizeof(double)); + printf(" %f", value); } break; case IF_MEMARG: { // TODO-WASM: decide what our strategy for alignment hints is and display these accordingly. - unsigned log2align = 1; - target_size_t offset = emitGetInsSC(id); - printf(" %u %u", log2align, offset); + unsigned log2align = emitGetAlignHintLog2(id) + 1; + cnsval_ssize_t offset = emitGetInsSC(id); + printf(" %u %llu", log2align, (uint64_t)offset); } break; diff --git a/src/coreclr/jit/emitwasm.h b/src/coreclr/jit/emitwasm.h index 890ab53c04197a..c3d4c04ddc0815 100644 --- a/src/coreclr/jit/emitwasm.h +++ b/src/coreclr/jit/emitwasm.h @@ -17,16 +17,21 @@ void emitDispInst(instruction ins); public: void emitIns(instruction ins); -void emitIns_I(instruction ins, emitAttr attr, target_ssize_t imm); +void emitIns_I(instruction ins, emitAttr attr, cnsval_ssize_t imm); void emitIns_S(instruction ins, emitAttr attr, int varx, int offs); void emitIns_R(instruction ins, emitAttr attr, regNumber reg); -void emitIns_R_I(instruction ins, emitAttr attr, regNumber reg, ssize_t imm); +void emitIns_R_I(instruction ins, emitAttr attr, regNumber reg, cnsval_ssize_t imm); void emitIns_Mov(instruction ins, emitAttr attr, regNumber dstReg, regNumber srcReg, bool canSkip); void emitIns_R_R(instruction ins, emitAttr attr, regNumber reg1, regNumber reg2); void emitIns_S_R(instruction ins, emitAttr attr, regNumber ireg, int varx, int offs); +static unsigned SizeOfULEB128(uint64_t value); +static unsigned SizeOfSLEB128(int64_t value); + +static unsigned emitGetAlignHintLog2(const instrDesc* id); + /************************************************************************/ /* Private members that deal with target-dependent instr. descriptors */ /************************************************************************/ @@ -51,3 +56,7 @@ instrDesc* emitNewInstrCallInd(int argCnt, bool emitInsIsStore(instruction ins); insFormat emitInsFormat(instruction ins); + +size_t emitOutputULEB128(uint8_t* destination, uint64_t value); +size_t emitOutputSLEB128(uint8_t* destination, int64_t value); +size_t emitRawBytes(uint8_t* destination, const void* source, size_t count); diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 9682ba4a42df8d..52e37c563f246d 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -5175,11 +5175,13 @@ unsigned Compiler::gtSetEvalOrder(GenTree* tree) case GT_CNS_LNG: case GT_CNS_INT: - // TODO-WASM: needs tuning based on the [S]LEB128 encoding size. - NYI_WASM("GT_CNS_LNG/GT_CNS_INT costing"); - costEx = 0; - costSz = 0; + { + GenTreeIntConCommon* con = tree->AsIntConCommon(); + int64_t imm = con->IntegralValue(); + costEx = 1; + costSz = 1 + (int)emitter::SizeOfSLEB128(imm); goto COMMON_CNS; + } #else case GT_CNS_STR: case GT_CNS_LNG: diff --git a/src/coreclr/jit/instrswasm.h b/src/coreclr/jit/instrswasm.h index b892290f1ad338..9a09ed70f7761f 100644 --- a/src/coreclr/jit/instrswasm.h +++ b/src/coreclr/jit/instrswasm.h @@ -46,11 +46,11 @@ INST(i64_load, "i64.load", 0, IF_MEMARG, 0x29) INST(f32_load, "f32.load", 0, IF_MEMARG, 0x2A) INST(f64_load, "f64.load", 0, IF_MEMARG, 0x2B) // 5.4.7 Numeric Instructions -// TODO-WASM: Constants -// INST(i32_const, "i32.const", 0, IF_LEB128, 0x41) -// INST(i64_const, "i64.const", 0, IF_LEB128, 0x42) -// INST(f32_const, "f32.const", 0, IF_F32, 0x43) -// INST(f64_const, "f64.const", 0, IF_F64, 0x44) +// Constants +INST(i32_const, "i32.const", 0, IF_SLEB128, 0x41) +INST(i64_const, "i64.const", 0, IF_SLEB128, 0x42) +INST(f32_const, "f32.const", 0, IF_F32, 0x43) +INST(f64_const, "f64.const", 0, IF_F64, 0x44) // Integer comparisons INST(i32_eqz, "i32.eqz", 0, IF_OPCODE, 0x45) INST(i32_eq, "i32.eq", 0, IF_OPCODE, 0x46) diff --git a/src/coreclr/jit/target.h b/src/coreclr/jit/target.h index 62112cfeca997b..bfa443dc1f4a5f 100644 --- a/src/coreclr/jit/target.h +++ b/src/coreclr/jit/target.h @@ -1186,6 +1186,11 @@ static_assert(sizeof(target_ssize_t) == TARGET_POINTER_SIZE); // to represent these pointers. typedef ssize_t cnsval_ssize_t; typedef size_t cnsval_size_t; +#elif defined(TARGET_WASM) +// WebAssembly has native support for 64-bit constants even in 32-bit mode, so we need the +// ability to store long constants regardless of pointer size on host or target. +typedef int64_t cnsval_ssize_t; +typedef uint64_t cnsval_size_t; #else typedef target_ssize_t cnsval_ssize_t; typedef target_size_t cnsval_size_t;