Repository: tetratelabs/wazero
Pull request #490 (2022-04-20 12:40:51)

Adds support for nontrapping float to int conversion proposal
This commit adds support for "nontrapping float to int conversion" proposal
which is one of the finished proposals at this point and is included in the 2.0 draft.

https://github.com/WebAssembly/spec/blob/main/proposals/nontrapping-float-to-int-conversion/Overview.md

mathetake: done with interpreter including test.. next is JIT! 
mathetake: ok the left is arm64
mathetake: done (surprisingly only one line change was necessary:D)
mathetake: ok waiting for @codefromthecrypt to do post 1.0 spectest refactoring! 
codefromthecrypt: on this now
codefromthecrypt: I'll start with adding ops into the text decoder so I can more easily backfill wazeroir tests.. if that becomes too tricky, I'll add wazeroir using manually generated wasm.
codefromthecrypt: @mathetake so i started the spec tests which fail on interpreter but pass on JIT. Also some comments/questions how to test the values not obviously representable in Go. Can you push a commit to fix the things and answer those? I'll continue after!
mathetake: Test fixed! On the comment about NaNs, there are not representative (as far as i know though) in Go, and so that's the reason why I added the perl magic in Makefile https://github.com/tetratelabs/wazero/blob/main/Makefile#L36-L40
mathetake: glad to know that now passes spectests 😄 
codefromthecrypt: thanks for the help. I'll finish up the spectests!
codefromthecrypt: @mathetake done. merge when ready!

Patch:
diff --git a/README.md b/README.md
index 5a9e46b9..49f06b7d 100644
--- a/README.md
+++ b/README.md
@@ -211,7 +211,7 @@ default. However, the following status covers what's currently possible with
 | Feature                               | Status |
 |:-------------------------------------:|:------:|
 | mutable-global                        |   ✅   |
-| nontrapping-float-to-int-conversions  |   ❌   |
+| nontrapping-float-to-int-conversions  |   ✅   |
 | sign-extension-ops                    |   ✅   |
 | multi-value                           |   ✅   |
 | JS-BigInt-integration                 |  N/A   |
diff --git a/config.go b/config.go
index 1b0bf299..ebf6e442 100644
--- a/config.go
+++ b/config.go
@@ -93,7 +93,7 @@ func (c *RuntimeConfig) WithFeatureMutableGlobal(enabled bool) *RuntimeConfig {
 }
 
 // WithFeatureSignExtensionOps enables sign extension instructions ("sign-extension-ops"). This defaults to false as the
-// feature was not finished in WebAssembly 1.0 (20191205).
+// feature was not in WebAssembly 1.0 (20191205).
 //
 // This has the following effects:
 // * Adds instructions `i32.extend8_s`, `i32.extend16_s`, `i64.extend8_s`, `i64.extend16_s` and `i64.extend32_s`
@@ -119,6 +119,26 @@ func (c *RuntimeConfig) WithFeatureMultiValue(enabled bool) *RuntimeConfig {
 	return ret
 }
 
+// WithFeatureNonTrappingFloatToIntConversion enables non-trapping float-to-int conversions.
+// ("nontrapping-float-to-int-conversion"). This defaults to false as the feature was not in WebAssembly 1.0 (20191205).
+//
+// The only effect of enabling this is allowing the following instructions, which return 0 on NaN instead of panicking.
+// * `i32.trunc_sat_f32_s`
+// * `i32.trunc_sat_f32_u`
+// * `i32.trunc_sat_f64_s`
+// * `i32.trunc_sat_f64_u`
+// * `i64.trunc_sat_f32_s`
+// * `i64.trunc_sat_f32_u`
+// * `i64.trunc_sat_f64_s`
+// * `i64.trunc_sat_f64_u`
+//
+// See https://github.com/WebAssembly/spec/blob/main/proposals/nontrapping-float-to-int-conversion/Overview.md
+func (c *RuntimeConfig) WithFeatureNonTrappingFloatToIntConversion(enabled bool) *RuntimeConfig {
+	ret := c.clone()
+	ret.enabledFeatures = ret.enabledFeatures.Set(wasm.FeatureNonTrappingFloatToIntConversion, enabled)
+	return ret
+}
+
 // CompiledCode is a WebAssembly 1.0 (20191205) module ready to be instantiated (Runtime.InstantiateModule) as an\
 // api.Module.
 //
diff --git a/internal/asm/amd64/impl.go b/internal/asm/amd64/impl.go
index c2f51498..2dd3b606 100644
--- a/internal/asm/amd64/impl.go
+++ b/internal/asm/amd64/impl.go
@@ -870,6 +870,7 @@ var relativeJumpOpcodes = map[asm.Instruction]relativeJumpOpcode{
 	JLS: {short: []byte{0x76}, long: []byte{0x0f, 0x86}},
 	JLT: {short: []byte{0x7c}, long: []byte{0x0f, 0x8c}},
 	JMI: {short: []byte{0x78}, long: []byte{0x0f, 0x88}},
+	JPL: {short: []byte{0x79}, long: []byte{0x0f, 0x89}},
 	JNE: {short: []byte{0x75}, long: []byte{0x0f, 0x85}},
 	JPC: {short: []byte{0x7b}, long: []byte{0x0f, 0x8b}},
 	JPS: {short: []byte{0x7a}, long: []byte{0x0f, 0x8a}},
diff --git a/internal/wasm/features.go b/internal/wasm/features.go
index 33dd6d10..afa49c4f 100644
--- a/internal/wasm/features.go
+++ b/internal/wasm/features.go
@@ -44,6 +44,20 @@ const (
 	//
 	// See https://github.com/WebAssembly/spec/blob/main/proposals/multi-value/Overview.md
 	FeatureMultiValue
+
+	// FeatureNonTrappingFloatToIntConversion decides if parsing should succeed on the following instructions:
+	//
+	// * [OpcodeMiscPrefix, OpcodeMiscI32TruncSatF32S]
+	// * [OpcodeMiscPrefix, OpcodeMiscI32TruncSatF32U]
+	// * [OpcodeMiscPrefix, OpcodeMiscI64TruncSatF32S]
+	// * [OpcodeMiscPrefix, OpcodeMiscI64TruncSatF32U]
+	// * [OpcodeMiscPrefix, OpcodeMiscI32TruncSatF64S]
+	// * [OpcodeMiscPrefix, OpcodeMiscI32TruncSatF64U]
+	// * [OpcodeMiscPrefix, OpcodeMiscI64TruncSatF64S]
+	// * [OpcodeMiscPrefix, OpcodeMiscI64TruncSatF64U]
+	//
+	// See https://github.com/WebAssembly/spec/blob/main/proposals/nontrapping-float-to-int-conversion/Overview.md
+	FeatureNonTrappingFloatToIntConversion
 )
 
 // Set assigns the value for the given feature.
@@ -94,6 +108,9 @@ func featureName(f Features) string {
 	case FeatureMultiValue:
 		// match https://github.com/WebAssembly/spec/blob/main/proposals/multi-value/Overview.md
 		return "multi-value"
+	case FeatureNonTrappingFloatToIntConversion:
+		// match https://github.com/WebAssembly/spec/blob/main/proposals/nontrapping-float-to-int-conversion/Overview.md
+		return "nontrapping-float-to-int-conversion"
 	}
 	return ""
 }
diff --git a/internal/wasm/func_validation.go b/internal/wasm/func_validation.go
index bea11d33..14fd28d8 100644
--- a/internal/wasm/func_validation.go
+++ b/internal/wasm/func_validation.go
@@ -515,7 +515,7 @@ func (m *Module) validateFunctionWithMaxStackValues(
 			for _, exp := range funcType.Results {
 				valueTypeStack.push(exp)
 			}
-		} else if OpcodeI32Eqz <= op && op <= LastOpcode {
+		} else if OpcodeI32Eqz <= op && op <= OpcodeI64Extend32S {
 			switch op {
 			case OpcodeI32Eqz:
 				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
@@ -728,6 +728,31 @@ func (m *Module) validateFunctionWithMaxStackValues(
 			default:
 				return fmt.Errorf("invalid numeric instruction 0x%x", op)
 			}
+		} else if op == OpcodeMiscPrefix {
+			pc++
+			// Miscellaneous instructions come with two bytes which starts with OpcodeMiscPrefix,
+			// and the second byte determines the actual instruction.
+			miscOpcode := body[pc]
+			if miscOpcode >= OpcodeMiscI32TruncSatF32S && miscOpcode <= OpcodeMiscI64TruncSatF64U {
+				if err := enabledFeatures.Require(FeatureNonTrappingFloatToIntConversion); err != nil {
+					return fmt.Errorf("%s invalid as %v", miscInstructionNames[miscOpcode], err)
+				}
+				var inType, outType ValueType
+				switch miscOpcode {
+				case OpcodeMiscI32TruncSatF32S, OpcodeMiscI32TruncSatF32U:
+					inType, outType = ValueTypeF32, ValueTypeI32
+				case OpcodeMiscI32TruncSatF64S, OpcodeMiscI32TruncSatF64U:
+					inType, outType = ValueTypeF64, ValueTypeI32
+				case OpcodeMiscI64TruncSatF32S, OpcodeMiscI64TruncSatF32U:
+					inType, outType = ValueTypeF32, ValueTypeI64
+				case OpcodeMiscI64TruncSatF64S, OpcodeMiscI64TruncSatF64U:
+					inType, outType = ValueTypeF64, ValueTypeI64
+				}
+				if err := valueTypeStack.popAndVerifyType(inType); err != nil {
+					return fmt.Errorf("cannot pop the operand for %s: %v", miscInstructionNames[miscOpcode], err)
+				}
+				valueTypeStack.push(outType)
+			}
 		} else if op == OpcodeBlock {
 			bt, num, err := DecodeBlockType(types, bytes.NewReader(body[pc+1:]), enabledFeatures)
 			if err != nil {
diff --git a/internal/wasm/instruction.go b/internal/wasm/instruction.go
index ddc2cad5..221446b2 100644
--- a/internal/wasm/instruction.go
+++ b/internal/wasm/instruction.go
@@ -251,7 +251,28 @@ const (
 	// Note: This is dependent on the flag FeatureSignExtensionOps
 	OpcodeI64Extend32S Opcode = 0xc4
 
-	LastOpcode = OpcodeI64Extend32S
+	// OpcodeMiscPrefix is the prefix of various multi-byte opcodes.
+	// Introduced in nontrapping float to int conversion proposal.
+	// https://github.com/WebAssembly/spec/blob/ce4b6c4d47eb06098cc7ab2e81f24748da822f20/proposals/nontrapping-float-to-int-conversion/Overview.md
+	OpcodeMiscPrefix Opcode = 0xfc
+)
+
+// OpcodeMisc represents opcodes of the miscellaneous operations.
+// Such an operations has multi-byte encoding which is prefixed by OpcodeMiscPrefix.
+type OpcodeMisc = byte
+
+const (
+	// Below are toggled with FeatureNonTrappingFloatToIntConversion.
+	// https://github.com/WebAssembly/spec/blob/ce4b6c4d47eb06098cc7ab2e81f24748da822f20/proposals/nontrapping-float-to-int-conversion/Overview.md
+
+	OpcodeMiscI32TruncSatF32S OpcodeMisc = 0x00
+	OpcodeMiscI32TruncSatF32U OpcodeMisc = 0x01
+	OpcodeMiscI32TruncSatF64S OpcodeMisc = 0x02
+	OpcodeMiscI32TruncSatF64U OpcodeMisc = 0x03
+	OpcodeMiscI64TruncSatF32S OpcodeMisc = 0x04
+	OpcodeMiscI64TruncSatF32U OpcodeMisc = 0x05
+	OpcodeMiscI64TruncSatF64S OpcodeMisc = 0x06
+	OpcodeMiscI64TruncSatF64U OpcodeMisc = 0x07
 )
 
 const (
@@ -435,6 +456,8 @@ const (
 	OpcodeI64Extend8SName  = "i64.extend8_s"
 	OpcodeI64Extend16SName = "i64.extend16_s"
 	OpcodeI64Extend32SName = "i64.extend32_s"
+
+	OpcodeMiscPrefixName = "misc_prefix"
 )
 
 var instructionNames = [256]string{
@@ -624,3 +647,30 @@ var instructionNames = [256]string{
 func InstructionName(oc Opcode) string {
 	return instructionNames[oc]
 }
+
+const (
+	OpcodeI32TruncSatF32SName = "i32.trunc_sat_f32_s"
+	OpcodeI32TruncSatF32UName = "i32.trunc_sat_f32_u"
+	OpcodeI32TruncSatF64SName = "i32.trunc_sat_f64_s"
+	OpcodeI32TruncSatF64UName = "i32.trunc_sat_f64_u"
+	OpcodeI64TruncSatF32SName = "i64.trunc_sat_f32_s"
+	OpcodeI64TruncSatF32UName = "i64.trunc_sat_f32_u"
+	OpcodeI64TruncSatF64SName = "i64.trunc_sat_f64_s"
+	OpcodeI64TruncSatF64UName = "i64.trunc_sat_f64_u"
+)
+
+var miscInstructionNames = [256]string{
+	OpcodeMiscI32TruncSatF32S: OpcodeI32TruncSatF32SName,
+	OpcodeMiscI32TruncSatF32U: OpcodeI32TruncSatF32UName,
+	OpcodeMiscI32TruncSatF64S: OpcodeI32TruncSatF64SName,
+	OpcodeMiscI32TruncSatF64U: OpcodeI32TruncSatF64UName,
+	OpcodeMiscI64TruncSatF32S: OpcodeI64TruncSatF32SName,
+	OpcodeMiscI64TruncSatF32U: OpcodeI64TruncSatF32UName,
+	OpcodeMiscI64TruncSatF64S: OpcodeI64TruncSatF64SName,
+	OpcodeMiscI64TruncSatF64U: OpcodeI64TruncSatF64UName,
+}
+
+// MiscInstructionName returns the instruction corresponding to this miscellaneous Opcode.
+func MiscInstructionName(oc OpcodeMisc) string {
+	return miscInstructionNames[oc]
+}
diff --git a/internal/wasm/interpreter/interpreter.go b/internal/wasm/interpreter/interpreter.go
index e473ff19..912dfd76 100644
--- a/internal/wasm/interpreter/interpreter.go
+++ b/internal/wasm/interpreter/interpreter.go
@@ -168,6 +168,7 @@ func (c *code) instantiate(f *wasm.FunctionInstance) *function {
 type interpreterOp struct {
 	kind   wazeroir.OperationKind
 	b1, b2 byte
+	b3     bool
 	us     []uint64
 	rs     []*wazeroir.InclusiveRange
 }
@@ -472,6 +473,7 @@ func (e *engine) lowerIR(ir *wazeroir.CompilationResult) (*code, error) {
 		case *wazeroir.OperationITruncFromF:
 			op.b1 = byte(o.InputType)
 			op.b2 = byte(o.OutputType)
+			op.b3 = o.NonTrapping
 		case *wazeroir.OperationFConvertFromI:
 			op.b1 = byte(o.InputType)
 			op.b2 = byte(o.OutputType)
@@ -1369,39 +1371,95 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, callCtx *wasm.CallCont
 					case wazeroir.SignedInt32:
 						v := math.Trunc(float64(math.Float32frombits(uint32(ce.popValue()))))
 						if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN
-							panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
+							if op.b3 {
+								// non-trapping conversion must cast nan to zero.
+								v = 0
+							} else {
+								panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
+							}
 						} else if v < math.MinInt32 || v > math.MaxInt32 {
-							panic(wasmruntime.ErrRuntimeIntegerOverflow)
+							if op.b3 {
+								// non-trapping conversion must "saturate" the value for overflowing sources.
+								if v < 0 {
+									v = math.MinInt32
+								} else {
+									v = math.MaxInt32
+								}
+							} else {
+								panic(wasmruntime.ErrRuntimeIntegerOverflow)
+							}
 						}
-						ce.pushValue(uint64(int32(v)))
+						ce.pushValue(uint64(uint32(int32(v))))
 					case wazeroir.SignedInt64:
 						v := math.Trunc(float64(math.Float32frombits(uint32(ce.popValue()))))
 						res := int64(v)
 						if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN
-							panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
+							if op.b3 {
+								// non-trapping conversion must cast nan to zero.
+								res = 0
+							} else {
+								panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
+							}
 						} else if v < math.MinInt64 || v >= math.MaxInt64 {
 							// Note: math.MaxInt64 is rounded up to math.MaxInt64+1 in 64-bit float representation,
 							// and that's why we use '>=' not '>' to check overflow.
-							panic(wasmruntime.ErrRuntimeIntegerOverflow)
+							if op.b3 {
+								// non-trapping conversion must "saturate" the value for overflowing sources.
+								if v < 0 {
+									res = math.MinInt64
+								} else {
+									res = math.MaxInt64
+								}
+							} else {
+								panic(wasmruntime.ErrRuntimeIntegerOverflow)
+							}
 						}
 						ce.pushValue(uint64(res))
 					case wazeroir.SignedUint32:
 						v := math.Trunc(float64(math.Float32frombits(uint32(ce.popValue()))))
 						if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN
-							panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
+							if op.b3 {
+								// non-trapping conversion must cast nan to zero.
+								v = 0
+							} else {
+								panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
+							}
 						} else if v < 0 || v > math.MaxUint32 {
-							panic(wasmruntime.ErrRuntimeIntegerOverflow)
+							if op.b3 {
+								// non-trapping conversion must "saturate" the value for overflowing source.
+								if v < 0 {
+									v = 0
+								} else {
+									v = math.MaxUint32
+								}
+							} else {
+								panic(wasmruntime.ErrRuntimeIntegerOverflow)
+							}
 						}
 						ce.pushValue(uint64(uint32(v)))
 					case wazeroir.SignedUint64:
 						v := math.Trunc(float64(math.Float32frombits(uint32(ce.popValue()))))
 						res := uint64(v)
 						if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN
-							panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
+							if op.b3 {
+								// non-trapping conversion must cast nan to zero.
+								res = 0
+							} else {
+								panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
+							}
 						} else if v < 0 || v >= math.MaxUint64 {
 							// Note: math.MaxUint64 is rounded up to math.MaxUint64+1 in 64-bit float representation,
 							// and that's why we use '>=' not '>' to check overflow.
-							panic(wasmruntime.ErrRuntimeIntegerOverflow)
+							if op.b3 {
+								// non-trapping conversion must "saturate" the value for overflowing source.
+								if v < 0 {
+									res = 0
+								} else {
+									res = math.MaxUint64
+								}
+							} else {
+								panic(wasmruntime.ErrRuntimeIntegerOverflow)
+							}
 						}
 						ce.pushValue(res)
 					}
@@ -1411,39 +1469,95 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, callCtx *wasm.CallCont
 					case wazeroir.SignedInt32:
 						v := math.Trunc(math.Float64frombits(ce.popValue()))
 						if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN
-							panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
+							if op.b3 {
+								// non-trapping conversion must cast nan to zero.
+								v = 0
+							} else {
+								panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
+							}
 						} else if v < math.MinInt32 || v > math.MaxInt32 {
-							panic(wasmruntime.ErrRuntimeIntegerOverflow)
+							if op.b3 {
+								// non-trapping conversion must "saturate" the value for overflowing source.
+								if v < 0 {
+									v = math.MinInt32
+								} else {
+									v = math.MaxInt32
+								}
+							} else {
+								panic(wasmruntime.ErrRuntimeIntegerOverflow)
+							}
 						}
-						ce.pushValue(uint64(int32(v)))
+						ce.pushValue(uint64(uint32(int32(v))))
 					case wazeroir.SignedInt64:
 						v := math.Trunc(math.Float64frombits(ce.popValue()))
 						res := int64(v)
 						if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN
-							panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
+							if op.b3 {
+								// non-trapping conversion must cast nan to zero.
+								res = 0
+							} else {
+								panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
+							}
 						} else if v < math.MinInt64 || v >= math.MaxInt64 {
 							// Note: math.MaxInt64 is rounded up to math.MaxInt64+1 in 64-bit float representation,
 							// and that's why we use '>=' not '>' to check overflow.
-							panic(wasmruntime.ErrRuntimeIntegerOverflow)
+							if op.b3 {
+								// non-trapping conversion must "saturate" the value for overflowing source.
+								if v < 0 {
+									res = math.MinInt64
+								} else {
+									res = math.MaxInt64
+								}
+							} else {
+								panic(wasmruntime.ErrRuntimeIntegerOverflow)
+							}
 						}
 						ce.pushValue(uint64(res))
 					case wazeroir.SignedUint32:
 						v := math.Trunc(math.Float64frombits(ce.popValue()))
 						if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN
-							panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
+							if op.b3 {
+								// non-trapping conversion must cast nan to zero.
+								v = 0
+							} else {
+								panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
+							}
 						} else if v < 0 || v > math.MaxUint32 {
-							panic(wasmruntime.ErrRuntimeIntegerOverflow)
+							if op.b3 {
+								// non-trapping conversion must "saturate" the value for overflowing source.
+								if v < 0 {
+									v = 0
+								} else {
+									v = math.MaxUint32
+								}
+							} else {
+								panic(wasmruntime.ErrRuntimeIntegerOverflow)
+							}
 						}
 						ce.pushValue(uint64(uint32(v)))
 					case wazeroir.SignedUint64:
 						v := math.Trunc(math.Float64frombits(ce.popValue()))
 						res := uint64(v)
 						if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN
-							panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
+							if op.b3 {
+								// non-trapping conversion must cast nan to zero.
+								res = 0
+							} else {
+								panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
+							}
 						} else if v < 0 || v >= math.MaxUint64 {
 							// Note: math.MaxUint64 is rounded up to math.MaxUint64+1 in 64-bit float representation,
 							// and that's why we use '>=' not '>' to check overflow.
-							panic(wasmruntime.ErrRuntimeIntegerOverflow)
+							if op.b3 {
+								// non-trapping conversion must "saturate" the value for overflowing source.
+								if v < 0 {
+									res = 0
+								} else {
+									res = math.MaxUint64
+								}
+							} else {
+								panic(wasmruntime.ErrRuntimeIntegerOverflow)
+							}
 						}
 						ce.pushValue(res)
 					}
diff --git a/internal/wasm/jit/jit_impl_amd64.go b/internal/wasm/jit/jit_impl_amd64.go
index 9eec53d3..aef6ae6b 100644
--- a/internal/wasm/jit/jit_impl_amd64.go
+++ b/internal/wasm/jit/jit_impl_amd64.go
@@ -24,8 +24,16 @@ var (
 	zero64BitAddress                              uintptr
 	minimum32BitSignedInt                         int32 = math.MinInt32
 	minimum32BitSignedIntAddress                  uintptr
+	maximum32BitSignedInt                         int32 = math.MaxInt32
+	maximum32BitSignedIntAddress                  uintptr
+	maximum32BitUnsignedInt                       uint32 = math.MaxUint32
+	maximum32BitUnsignedIntAddress                uintptr
 	minimum64BitSignedInt                         int64 = math.MinInt64
 	minimum64BitSignedIntAddress                  uintptr
+	maximum64BitSignedInt                         int64 = math.MaxInt64
+	maximum64BitSignedIntAddress                  uintptr
+	maximum64BitUnsignedInt                       uint64 = math.MaxUint64
+	maximum64BitUnsignedIntAddress                uintptr
 	float32SignBitMask                            uint32 = 1 << 31
 	float32RestBitMask                            uint32 = ^float32SignBitMask
 	float32SignBitMaskAddress                     uintptr
@@ -59,7 +67,11 @@ func init() {
 	// https://stackoverflow.com/questions/31853189/x86-64-assembly-why-displacement-not-64-bits
 	zero64BitAddress = uintptr(unsafe.Pointer(&zero64Bit))
 	minimum32BitSignedIntAddress = uintptr(unsafe.Pointer(&minimum32BitSignedInt))
+	maximum32BitSignedIntAddress = uintptr(unsafe.Pointer(&maximum32BitSignedInt))
+	maximum32BitUnsignedIntAddress = uintptr(unsafe.Pointer(&maximum32BitUnsignedInt))
 	minimum64BitSignedIntAddress = uintptr(unsafe.Pointer(&minimum64BitSignedInt))
+	maximum64BitSignedIntAddress = uintptr(unsafe.Pointer(&maximum64BitSignedInt))
+	maximum64BitUnsignedIntAddress = uintptr(unsafe.Pointer(&maximum64BitUnsignedInt))
 	float32SignBitMaskAddress = uintptr(unsafe.Pointer(&float32SignBitMask))
 	float32RestBitMaskAddress = uintptr(unsafe.Pointer(&float32RestBitMask))
 	float64SignBitMaskAddress = uintptr(unsafe.Pointer(&float64SignBitMask))
@@ -2013,27 +2025,27 @@ func (c *amd64Compiler) compileI32WrapFromI64() error {
 // [2] https://xem.github.io/minix86/manual/intel-x86-and-64-manual-vol1/o_7281d5ea06a5b67a-268.html
 func (c *amd64Compiler) compileITruncFromF(o *wazeroir.OperationITruncFromF) (err error) {
 	if o.InputType == wazeroir.Float32 && o.OutputType == wazeroir.SignedInt32 {
-		err = c.emitSignedI32TruncFromFloat(true)
+		err = c.emitSignedI32TruncFromFloat(true, o.NonTrapping)
 	} else if o.InputType == wazeroir.Float32 && o.OutputType == wazeroir.SignedInt64 {
-		err = c.emitSignedI64TruncFromFloat(true)
+		err = c.emitSignedI64TruncFromFloat(true, o.NonTrapping)
 	} else if o.InputType == wazeroir.Float64 && o.OutputType == wazeroir.SignedInt32 {
-		err = c.emitSignedI32TruncFromFloat(false)
+		err = c.emitSignedI32TruncFromFloat(false, o.NonTrapping)
 	} else if o.InputType == wazeroir.Float64 && o.OutputType == wazeroir.SignedInt64 {
-		err = c.emitSignedI64TruncFromFloat(false)
+		err = c.emitSignedI64TruncFromFloat(false, o.NonTrapping)
 	} else if o.InputType == wazeroir.Float32 && o.OutputType == wazeroir.SignedUint32 {
-		err = c.emitUnsignedI32TruncFromFloat(true)
+		err = c.emitUnsignedI32TruncFromFloat(true, o.NonTrapping)
 	} else if o.InputType == wazeroir.Float32 && o.OutputType == wazeroir.SignedUint64 {
-		err = c.emitUnsignedI64TruncFromFloat(true)
+		err = c.emitUnsignedI64TruncFromFloat(true, o.NonTrapping)
 	} else if o.InputType == wazeroir.Float64 && o.OutputType == wazeroir.SignedUint32 {
-		err = c.emitUnsignedI32TruncFromFloat(false)
+		err = c.emitUnsignedI32TruncFromFloat(false, o.NonTrapping)
 	} else if o.InputType == wazeroir.Float64 && o.OutputType == wazeroir.SignedUint64 {
-		err = c.emitUnsignedI64TruncFromFloat(false)
+		err = c.emitUnsignedI64TruncFromFloat(false, o.NonTrapping)
 	}
 	return
 }
 
 // emitUnsignedI32TruncFromFloat implements compileITruncFromF when the destination type is a 32-bit unsigned integer.
-func (c *amd64Compiler) emitUnsignedI32TruncFromFloat(isFloat32Bit bool) error {
+func (c *amd64Compiler) emitUnsignedI32TruncFromFloat(isFloat32Bit, nonTrapping bool) error {
 	source := c.locationStack.pop()
 	if err := c.compileEnsureOnGeneralPurposeRegister(source); err != nil {
 		return err
@@ -2052,7 +2064,19 @@ func (c *amd64Compiler) emitUnsignedI32TruncFromFloat(isFloat32Bit bool) error {
 	}
 
 	// Check the parity flag (set when the value is NaN), and if it is set, we should raise an exception.
-	jmpIfNaN := c.assembler.CompileJump(amd64.JPS) // jump if parity is set.
+	jmpIfNotNaN := c.assembler.CompileJump(amd64.JPC) // jump if parity is not set.
+
+	var nonTrappingNaNJump asm.Node
+	if !nonTrapping {
+		c.compileExitFromNativeCode(jitCallStatusCodeInvalidFloatToIntConversion)
+	} else {
+		// In non trapping case, NaN is casted as zero.
+		// Zero out the result register by XOR itsself.
+		c.assembler.CompileRegisterToRegister(amd64.XORL, result, result)
+		nonTrappingNaNJump = c.assembler.CompileJump(amd64.JMP)
+	}
+
+	c.assembler.SetJumpTargetOnNext(jmpIfNotNaN)
 
 	// Jump if the source float value is above or equal math.MaxInt32+1.
 	jmpAboveOrEqualMaxIn32PlusOne := c.assembler.CompileJump(amd64.JCC)
@@ -2066,8 +2090,19 @@ func (c *amd64Compiler) emitUnsignedI32TruncFromFloat(isFloat32Bit bool) error {
 
 	// Then if the result is minus, it is invalid conversion from minus float (incl. -Inf).
 	c.assembler.CompileRegisterToRegister(amd64.TESTL, result, result)
+	jmpIfNotMinusOrMinusInf := c.assembler.CompileJump(amd64.JPL)
+
+	var nonTrappingMinusJump asm.Node
+	if !nonTrapping {
+		c.compileExitFromNativeCode(jitCallStatusIntegerOverflow)
+	} else {
+		// In non trapping case, the minus value is casted as zero.
+		// Zero out the result register by XOR itsself.
+		c.assembler.CompileRegisterToRegister(amd64.XORL, result, result)
+		nonTrappingMinusJump = c.assembler.CompileJump(amd64.JMP)
+	}
 
-	jmpIfMinusOrMinusInf := c.assembler.CompileJump(amd64.JMI)
+	c.assembler.SetJumpTargetOnNext(jmpIfNotMinusOrMinusInf)
 
 	// Otherwise, the values is valid.
 	okJmpForLessThanMaxInt32PlusOne := c.assembler.CompileJump(amd64.JMP)
@@ -2103,14 +2138,18 @@ func (c *amd64Compiler) emitUnsignedI32TruncFromFloat(isFloat32Bit bool) error {
 
 	okJmpForAboveOrEqualMaxInt32PlusOne := c.assembler.CompileJump(amd64.JMP)
 
-	c.assembler.SetJumpTargetOnNext(jmpIfNaN)
-	c.compileExitFromNativeCode(jitCallStatusCodeInvalidFloatToIntConversion)
-
-	c.assembler.SetJumpTargetOnNext(jmpIfMinusOrMinusInf, jmpIfPlusInf)
-	c.compileExitFromNativeCode(jitCallStatusIntegerOverflow)
+	c.assembler.SetJumpTargetOnNext(jmpIfPlusInf)
+	if !nonTrapping {
+		c.compileExitFromNativeCode(jitCallStatusIntegerOverflow)
+	} else {
+		c.assembler.CompileMemoryToRegister(amd64.MOVL, asm.NilRegister, int64(maximum32BitUnsignedIntAddress), result)
+	}
 
 	// We jump to the next instructions for valid cases.
 	c.assembler.SetJumpTargetOnNext(okJmpForLessThanMaxInt32PlusOne, okJmpForAboveOrEqualMaxInt32PlusOne)
+	if nonTrapping {
+		c.assembler.SetJumpTargetOnNext(nonTrappingMinusJump, nonTrappingNaNJump)
+	}
 
 	// We consumed the source's register and placed the conversion result
 	// in the result register.
@@ -2121,7 +2160,7 @@ func (c *amd64Compiler) emitUnsignedI32TruncFromFloat(isFloat32Bit bool) error {
 }
 
 // emitUnsignedI32TruncFromFloat implements compileITruncFromF when the destination type is a 64-bit unsigned integer.
-func (c *amd64Compiler) emitUnsignedI64TruncFromFloat(isFloat32Bit bool) error {
+func (c *amd64Compiler) emitUnsignedI64TruncFromFloat(isFloat32Bit, nonTrapping bool) error {
 	source := c.locationStack.pop()
 	if err := c.compileEnsureOnGeneralPurposeRegister(source); err != nil {
 		return err
@@ -2140,7 +2179,19 @@ func (c *amd64Compiler) emitUnsignedI64TruncFromFloat(isFloat32Bit bool) error {
 	}
 
 	// Check the parity flag (set when the value is NaN), and if it is set, we should raise an exception.
-	jmpIfNaN := c.assembler.CompileJump(amd64.JPS) // jump if parity is set.
+	jmpIfNotNaN := c.assembler.CompileJump(amd64.JPC) // jump if parity is not set.
+
+	var nonTrappingNaNJump asm.Node
+	if !nonTrapping {
+		c.compileExitFromNativeCode(jitCallStatusCodeInvalidFloatToIntConversion)
+	} else {
+		// In non trapping case, NaN is casted as zero.
+		// Zero out the result register by XOR itsself.
+		c.assembler.CompileRegisterToRegister(amd64.XORQ, result, result)
+		nonTrappingNaNJump = c.assembler.CompileJump(amd64.JMP)
+	}
+
+	c.assembler.SetJumpTargetOnNext(jmpIfNotNaN)
 
 	// Jump if the source float values is above or equal math.MaxInt64+1.
 	jmpAboveOrEqualMaxIn32PlusOne := c.assembler.CompileJump(amd64.JCC)
@@ -2154,7 +2205,19 @@ func (c *amd64Compiler) emitUnsignedI64TruncFromFloat(isFloat32Bit bool) error {
 
 	// Then if the result is minus, it is invalid conversion from minus float (incl. -Inf).
 	c.assembler.CompileRegisterToRegister(amd64.TESTQ, result, result)
-	jmpIfMinusOrMinusInf := c.assembler.CompileJump(amd64.JMI)
+	jmpIfNotMinusOrMinusInf := c.assembler.CompileJump(amd64.JPL)
+
+	var nonTrappingMinusJump asm.Node
+	if !nonTrapping {
+		c.compileExitFromNativeCode(jitCallStatusIntegerOverflow)
+	} else {
+		// In non trapping case, the minus value is casted as zero.
+		// Zero out the result register by XOR itsself.
+		c.assembler.CompileRegisterToRegister(amd64.XORQ, result, result)
+		nonTrappingMinusJump = c.assembler.CompileJump(amd64.JMP)
+	}
+
+	c.assembler.SetJumpTargetOnNext(jmpIfNotMinusOrMinusInf)
 
 	// Otherwise, the values is valid.
 	okJmpForLessThanMaxInt64PlusOne := c.assembler.CompileJump(amd64.JMP)
@@ -2190,14 +2253,18 @@ func (c *amd64Compiler) emitUnsignedI64TruncFromFloat(isFloat32Bit bool) error {
 
 	okJmpForAboveOrEqualMaxInt64PlusOne := c.assembler.CompileJump(amd64.JMP)
 
-	c.assembler.SetJumpTargetOnNext(jmpIfNaN)
-	c.compileExitFromNativeCode(jitCallStatusCodeInvalidFloatToIntConversion)
-
-	c.assembler.SetJumpTargetOnNext(jmpIfMinusOrMinusInf, jmpIfPlusInf)
-	c.compileExitFromNativeCode(jitCallStatusIntegerOverflow)
+	c.assembler.SetJumpTargetOnNext(jmpIfPlusInf)
+	if !nonTrapping {
+		c.compileExitFromNativeCode(jitCallStatusIntegerOverflow)
+	} else {
+		c.assembler.CompileMemoryToRegister(amd64.MOVQ, asm.NilRegister, int64(maximum64BitUnsignedIntAddress), result)
+	}
 
 	// We jump to the next instructions for valid cases.
 	c.assembler.SetJumpTargetOnNext(okJmpForLessThanMaxInt64PlusOne, okJmpForAboveOrEqualMaxInt64PlusOne)
+	if nonTrapping {
+		c.assembler.SetJumpTargetOnNext(nonTrappingMinusJump, nonTrappingNaNJump)
+	}
 
 	// We consumed the source's register and placed the conversion result
 	// in the result register.
@@ -2208,7 +2275,7 @@ func (c *amd64Compiler) emitUnsignedI64TruncFromFloat(isFloat32Bit bool) error {
 }
 
 // emitSignedI32TruncFromFloat implements compileITruncFromF when the destination type is a 32-bit signed integer.
-func (c *amd64Compiler) emitSignedI32TruncFromFloat(isFloat32Bit bool) error {
+func (c *amd64Compiler) emitSignedI32TruncFromFloat(isFloat32Bit, nonTrapping bool) error {
 	source := c.locationStack.pop()
 	if err := c.compileEnsureOnGeneralPurposeRegister(source); err != nil {
 		return err
@@ -2246,8 +2313,16 @@ func (c *amd64Compiler) emitSignedI32TruncFromFloat(isFloat32Bit bool) error {
 	// Check the parity flag (set when the value is NaN), and if it is set, we should raise an exception.
 	jmpIfNotNaN := c.assembler.CompileJump(amd64.JPC) // jump if parity is not set.
 
-	// If the value is NaN, we return the function with jitCallStatusCodeInvalidFloatToIntConversion.
-	c.compileExitFromNativeCode(jitCallStatusCodeInvalidFloatToIntConversion)
+	var nontrappingNanJump asm.Node
+	if !nonTrapping {
+		// If the value is NaN, we return the function with jitCallStatusCodeInvalidFloatToIntConversion.
+		c.compileExitFromNativeCode(jitCallStatusCodeInvalidFloatToIntConversion)
+	} else {
+		// In non trapping case, NaN is casted as zero.
+		// Zero out the result register by XOR itsself.
+		c.assembler.CompileRegisterToRegister(amd64.XORL, result, result)
+		nontrappingNanJump = c.assembler.CompileJump(amd64.JMP)
+	}
 
 	// Check if the value is larger than or equal the minimum 32-bit integer value,
 	// meaning that the value exceeds the lower bound of 32-bit signed integer range.
@@ -2258,40 +2333,67 @@ func (c *amd64Compiler) emitSignedI32TruncFromFloat(isFloat32Bit bool) error {
 		c.assembler.CompileMemoryToRegister(amd64.UCOMISD, asm.NilRegister, int64(float64ForMinimumSigned32bitIntegerAddress), source.register)
 	}
 
-	// Jump if the value exceeds the lower bound.
-	var jmpIfExceedsLowerBound asm.Node
-	if isFloat32Bit {
-		jmpIfExceedsLowerBound = c.assembler.CompileJump(amd64.JCS)
-	} else {
-		jmpIfExceedsLowerBound = c.assembler.CompileJump(amd64.JLS)
-	}
+	if !nonTrapping {
+		// Jump if the value exceeds the lower bound.
+		var jmpIfExceedsLowerBound asm.Node
+		if isFloat32Bit {
+			jmpIfExceedsLowerBound = c.assembler.CompileJump(amd64.JCS)
+		} else {
+			jmpIfExceedsLowerBound = c.assembler.CompileJump(amd64.JLS)
+		}
 
-	// At this point, the value is the minimum signed 32-bit int (=-2147483648.000000) or larger than 32-bit maximum.
-	// So, check if the value equals the minimum signed 32-bit int.
-	if isFloat32Bit {
-		c.assembler.CompileMemoryToRegister(amd64.UCOMISS, asm.NilRegister, int64(zero64BitAddress), source.register)
+		// At this point, the value is the minimum signed 32-bit int (=-2147483648.000000) or larger than 32-bit maximum.
+		// So, check if the value equals the minimum signed 32-bit int.
+		if isFloat32Bit {
+			c.assembler.CompileMemoryToRegister(amd64.UCOMISS, asm.NilRegister, int64(zero64BitAddress), source.register)
+		} else {
+			c.assembler.CompileMemoryToRegister(amd64.UCOMISD, asm.NilRegister, int64(zero64BitAddress), source.register)
+		}
+
+		jmpIfMinimumSignedInt := c.assembler.CompileJump(amd64.JCS) // jump if the value is minus (= the minimum signed 32-bit int).
+
+		c.assembler.SetJumpTargetOnNext(jmpIfExceedsLowerBound)
+		c.compileExitFromNativeCode(jitCallStatusIntegerOverflow)
+
+		// We jump to the next instructions for valid cases.
+		c.assembler.SetJumpTargetOnNext(okJmp, jmpIfMinimumSignedInt)
 	} else {
-		c.assembler.CompileMemoryToRegister(amd64.UCOMISD, asm.NilRegister, int64(zero64BitAddress), source.register)
-	}
+		// Jump if the value does not exceeds the lower bound.
+		var jmpIfNotExceedsLowerBound asm.Node
+		if isFloat32Bit {
+			jmpIfNotExceedsLowerBound = c.assembler.CompileJump(amd64.JCC)
+		} else {
+			jmpIfNotExceedsLowerBound = c.assembler.CompileJump(amd64.JHI)
+		}
 
-	jmpIfMinimumSignedInt := c.assembler.CompileJump(amd64.JCS) // jump if the value is minus (= the minimum signed 32-bit int).
+		// If the value exceeds the lower bound, we "saturate" it to the minimum.
+		c.assembler.CompileMemoryToRegister(amd64.MOVL, asm.NilRegister, int64(minimum32BitSignedIntAddress), result)
+		nonTrappingSaturatedMinimumJump := c.assembler.CompileJump(amd64.JMP)
 
-	c.assembler.SetJumpTargetOnNext(jmpIfExceedsLowerBound)
-	c.compileExitFromNativeCode(jitCallStatusIntegerOverflow)
+		// Otherwise, the value is the minimum signed 32-bit int (=-2147483648.000000) or larger than 32-bit maximum.
+		c.assembler.SetJumpTargetOnNext(jmpIfNotExceedsLowerBound)
+		if isFloat32Bit {
+			c.assembler.CompileMemoryToRegister(amd64.UCOMISS, asm.NilRegister, int64(zero64BitAddress), source.register)
+		} else {
+			c.assembler.CompileMemoryToRegister(amd64.UCOMISD, asm.NilRegister, int64(zero64BitAddress), source.register)
+		}
+		jmpIfMinimumSignedInt := c.assembler.CompileJump(amd64.JCS) // jump if the value is minus (= the minimum signed 32-bit int).
 
-	// We jump to the next instructions for valid cases.
-	c.assembler.SetJumpTargetOnNext(okJmp, jmpIfMinimumSignedInt)
+		// If the value exceeds signed 32-bit maximum, we saturate it to the maximum.
+		c.assembler.CompileMemoryToRegister(amd64.MOVL, asm.NilRegister, int64(maximum32BitSignedIntAddress), result)
+
+		c.assembler.SetJumpTargetOnNext(okJmp, nontrappingNanJump, nonTrappingSaturatedMinimumJump, jmpIfMinimumSignedInt)
+	}
 
 	// We consumed the source's register and placed the conversion result
 	// in the result register.
 	c.locationStack.markRegisterUnused(source.register)
-	loc := c.pushValueLocationOnRegister(result)
-	loc.setRegisterType(generalPurposeRegisterTypeInt)
+	c.pushValueLocationOnRegister(result)
 	return nil
 }
 
 // emitSignedI64TruncFromFloat implements compileITruncFromF when the destination type is a 64-bit signed integer.
-func (c *amd64Compiler) emitSignedI64TruncFromFloat(isFloat32Bit bool) error {
+func (c *amd64Compiler) emitSignedI64TruncFromFloat(isFloat32Bit, nonTrapping bool) error {
 	source := c.locationStack.pop()
 	if err := c.compileEnsureOnGeneralPurposeRegister(source); err != nil {
 		return err
@@ -2329,7 +2431,15 @@ func (c *amd64Compiler) emitSignedI64TruncFromFloat(isFloat32Bit bool) error {
 	// Check the parity flag (set when the value is NaN), and if it is set, we should raise an exception.
 	jmpIfNotNaN := c.assembler.CompileJump(amd64.JPC) // jump if parity is not set.
 
-	c.compileExitFromNativeCode(jitCallStatusCodeInvalidFloatToIntConversion)
+	var nontrappingNanJump asm.Node
+	if !nonTrapping {
+		c.compileExitFromNativeCode(jitCallStatusCodeInvalidFloatToIntConversion)
+	} else {
+		// In non trapping case, NaN is casted as zero.
+		// Zero out the result register by XOR itsself.
+		c.assembler.CompileRegisterToRegister(amd64.XORQ, result, result)
+		nontrappingNanJump = c.assembler.CompileJump(amd64.JMP)
+	}
 
 	// Check if the value is larger than or equal the minimum 64-bit integer value,
 	// meaning that the value exceeds the lower bound of 64-bit signed integer range.
@@ -2340,24 +2450,49 @@ func (c *amd64Compiler) emitSignedI64TruncFromFloat(isFloat32Bit bool) error {
 		c.assembler.CompileMemoryToRegister(amd64.UCOMISD, asm.NilRegister, int64(float64ForMinimumSigned64bitIntegerAddress), source.register)
 	}
 
-	// Jump if the value is -Inf.
-	jmpIfExceedsLowerBound := c.assembler.CompileJump(amd64.JCS)
+	if !nonTrapping {
+		// Jump if the value is -Inf.
+		jmpIfExceedsLowerBound := c.assembler.CompileJump(amd64.JCS)
 
-	// At this point, the value is the minimum signed 64-bit int (=-9223372036854775808.0) or larger than 64-bit maximum.
-	// So, check if the value equals the minimum signed 64-bit int.
-	if isFloat32Bit {
-		c.assembler.CompileMemoryToRegister(amd64.UCOMISS, asm.NilRegister, int64(zero64BitAddress), source.register)
+		// At this point, the value is the minimum signed 64-bit int (=-9223372036854775808.0) or larger than 64-bit maximum.
+		// So, check if the value equals the minimum signed 64-bit int.
+		if isFloat32Bit {
+			c.assembler.CompileMemoryToRegister(amd64.UCOMISS, asm.NilRegister, int64(zero64BitAddress), source.register)
+		} else {
+			c.assembler.CompileMemoryToRegister(amd64.UCOMISD, asm.NilRegister, int64(zero64BitAddress), source.register)
+		}
+
+		jmpIfMinimumSignedInt := c.assembler.CompileJump(amd64.JCS) // jump if the value is minus (= the minimum signed 64-bit int).
+
+		c.assembler.SetJumpTargetOnNext(jmpIfExceedsLowerBound)
+		c.compileExitFromNativeCode(jitCallStatusIntegerOverflow)
+
+		// We jump to the next instructions for valid cases.
+		c.assembler.SetJumpTargetOnNext(okJmp, jmpIfMinimumSignedInt)
 	} else {
-		c.assembler.CompileMemoryToRegister(amd64.UCOMISD, asm.NilRegister, int64(zero64BitAddress), source.register)
-	}
+		// Jump if the value is not -Inf.
+		jmpIfNotExceedsLowerBound := c.assembler.CompileJump(amd64.JCC)
+
+		// If the value exceeds the lower bound, we "saturate" it to the minimum.
+		c.assembler.CompileMemoryToRegister(amd64.MOVQ, asm.NilRegister, int64(minimum64BitSignedIntAddress), result)
+		nonTrappingSaturatedMinimumJump := c.assembler.CompileJump(amd64.JMP)
+
+		// Otherwise, the value is the minimum signed 64-bit int (=-9223372036854775808.0) or larger than 64-bit maximum.
+		// So, check if the value equals the minimum signed 64-bit int.
+		c.assembler.SetJumpTargetOnNext(jmpIfNotExceedsLowerBound)
+		if isFloat32Bit {
+			c.assembler.CompileMemoryToRegister(amd64.UCOMISS, asm.NilRegister, int64(zero64BitAddress), source.register)
+		} else {
+			c.assembler.CompileMemoryToRegister(amd64.UCOMISD, asm.NilRegister, int64(zero64BitAddress), source.register)
+		}
 
-	jmpIfMinimumSignedInt := c.assembler.CompileJump(amd64.JCS) // jump if the value is minus (= the minimum signed 64-bit int).
+		jmpIfMinimumSignedInt := c.assembler.CompileJump(amd64.JCS) // jump if the value is minus (= the minimum signed 64-bit int).
 
-	c.assembler.SetJumpTargetOnNext(jmpIfExceedsLowerBound)
-	c.compileExitFromNativeCode(jitCallStatusIntegerOverflow)
+		// If the value exceeds signed 64-bit maximum, we saturate it to the maximum.
+		c.assembler.CompileMemoryToRegister(amd64.MOVQ, asm.NilRegister, int64(maximum64BitSignedIntAddress), result)
 
-	// We jump to the next instructions for valid cases.
-	c.assembler.SetJumpTargetOnNext(okJmp, jmpIfMinimumSignedInt)
+		c.assembler.SetJumpTargetOnNext(okJmp, jmpIfMinimumSignedInt, nonTrappingSaturatedMinimumJump, nontrappingNanJump)
+	}
 
 	// We consumed the source's register and placed the conversion result
 	// in the result register.
diff --git a/internal/wasm/jit/jit_impl_arm64.go b/internal/wasm/jit/jit_impl_arm64.go
index 1c6994e0..ff475222 100644
--- a/internal/wasm/jit/jit_impl_arm64.go
+++ b/internal/wasm/jit/jit_impl_arm64.go
@@ -2090,17 +2090,17 @@ func (c *arm64Compiler) compileITruncFromF(o *wazeroir.OperationITruncFromF) err
 	c.assembler.CompileRegisterToRegister(convinst, source.register, destinationReg)
 	c.pushValueLocationOnRegister(destinationReg)
 
-	// Obtain the floating point status register value into the general purpose register,
-	// so that we can check if the conversion resulted in undefined behavior.
-	c.assembler.CompileRegisterToRegister(arm64.MRS, arm64.REG_FPSR, arm64ReservedRegisterForTemporary)
-	// Check if the conversion was undefined by comparing the status with 1.
-	// See https://developer.arm.com/documentation/ddi0595/2020-12/AArch64-Registers/FPSR--Floating-point-Status-Register
-	c.assembler.CompileRegisterAndConstToNone(arm64.CMP, arm64ReservedRegisterForTemporary, 1)
+	if !o.NonTrapping {
+		// Obtain the floating point status register value into the general purpose register,
+		// so that we can check if the conversion resulted in undefined behavior.
+		c.assembler.CompileRegisterToRegister(arm64.MRS, arm64.REG_FPSR, arm64ReservedRegisterForTemporary)
+		// Check if the conversion was undefined by comparing the status with 1.
+		// See https://developer.arm.com/documentation/ddi0595/2020-12/AArch64-Registers/FPSR--Floating-point-Status-Register
+		c.assembler.CompileRegisterAndConstToNone(arm64.CMP, arm64ReservedRegisterForTemporary, 1)
 
-	brOK := c.assembler.CompileJump(arm64.BNE)
+		brOK := c.assembler.CompileJump(arm64.BNE)
 
-	// If so, exit the execution with errors depending on whether or not the source value is NaN.
-	{
+		// If so, exit the execution with errors depending on whether or not the source value is NaN.
 		var floatcmp asm.Instruction
 		if is32bitFloat {
 			floatcmp = arm64.FCMPS
@@ -2118,10 +2118,10 @@ func (c *arm64Compiler) compileITruncFromF(o *wazeroir.OperationITruncFromF) err
 		// Otherwise, the operation was invalid as this is trying to convert NaN to integer.
 		c.assembler.SetJumpTargetOnNext(brIfSourceNaN)
 		c.compileExitFromNativeCode(jitCallStatusCodeInvalidFloatToIntConversion)
-	}
 
-	// Otherwise, we branch into the next instruction.
-	c.assembler.SetJumpTargetOnNext(brOK)
+		// Otherwise, we branch into the next instruction.
+		c.assembler.SetJumpTargetOnNext(brOK)
+	}
 	return nil
 }
 
diff --git a/internal/wasm/text/func_parser.go b/internal/wasm/text/func_parser.go
index 76726bc3..25d4834d 100644
--- a/internal/wasm/text/func_parser.go
+++ b/internal/wasm/text/func_parser.go
@@ -4,7 +4,9 @@ import (
 	"errors"
 	"fmt"
 
+	"github.com/tetratelabs/wazero/api"
 	"github.com/tetratelabs/wazero/internal/leb128"
+	"github.com/tetratelabs/wazero/internal/u64"
 	"github.com/tetratelabs/wazero/internal/wasm"
 )
 
@@ -149,6 +151,14 @@ func (p *funcParser) beginInstruction(tokenBytes []byte) (next tokenParser, err
 	case wasm.OpcodeDropName: // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#-hrefsyntax-instr-parametricmathsfdrop
 		opCode = wasm.OpcodeDrop
 		next = p.beginFieldOrInstruction
+
+	case wasm.OpcodeF32ConstName: // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-instr-numeric
+		opCode = wasm.OpcodeF32Const
+		next = p.parseF32
+	case wasm.OpcodeF64ConstName: // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-instr-numeric
+		opCode = wasm.OpcodeF64Const
+		next = p.parseF64
+
 	case wasm.OpcodeI32AddName: // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-instr-numeric
 		opCode = wasm.OpcodeI32Add
 		next = p.beginFieldOrInstruction
@@ -197,6 +207,35 @@ func (p *funcParser) beginInstruction(tokenBytes []byte) (next tokenParser, err
 	case wasm.OpcodeI64Extend32SName:
 		opCode = wasm.OpcodeI64Extend32S
 		next = p.beginFieldOrInstruction
+
+		// Next are nontrapping-float-to-int-conversion
+		// See https://github.com/WebAssembly/spec/blob/main/proposals/nontrapping-float-to-int-conversion/Overview.md
+
+	case wasm.OpcodeI32TruncSatF32SName:
+		opCode = wasm.OpcodeMiscPrefix
+		p.currentBody = append(p.currentBody, opCode, wasm.OpcodeMiscI32TruncSatF32S)
+	case wasm.OpcodeI32TruncSatF32UName:
+		opCode = wasm.OpcodeMiscPrefix
+		p.currentBody = append(p.currentBody, opCode, wasm.OpcodeMiscI32TruncSatF32U)
+	case wasm.OpcodeI32TruncSatF64SName:
+		opCode = wasm.OpcodeMiscPrefix
+		p.currentBody = append(p.currentBody, opCode, wasm.OpcodeMiscI32TruncSatF64S)
+	case wasm.OpcodeI32TruncSatF64UName:
+		opCode = wasm.OpcodeMiscPrefix
+		p.currentBody = append(p.currentBody, opCode, wasm.OpcodeMiscI32TruncSatF64U)
+	case wasm.OpcodeI64TruncSatF32SName:
+		opCode = wasm.OpcodeMiscPrefix
+		p.currentBody = append(p.currentBody, opCode, wasm.OpcodeMiscI64TruncSatF32S)
+	case wasm.OpcodeI64TruncSatF32UName:
+		opCode = wasm.OpcodeMiscPrefix
+		p.currentBody = append(p.currentBody, opCode, wasm.OpcodeMiscI64TruncSatF32U)
+	case wasm.OpcodeI64TruncSatF64SName:
+		opCode = wasm.OpcodeMiscPrefix
+		p.currentBody = append(p.currentBody, opCode, wasm.OpcodeMiscI64TruncSatF64S)
+	case wasm.OpcodeI64TruncSatF64UName:
+		opCode = wasm.OpcodeMiscPrefix
+		p.currentBody = append(p.currentBody, opCode, wasm.OpcodeMiscI64TruncSatF64U)
+
 	default:
 		return nil, fmt.Errorf("unsupported instruction: %s", tokenBytes)
 	}
@@ -208,6 +247,14 @@ func (p *funcParser) beginInstruction(tokenBytes []byte) (next tokenParser, err
 		}
 	}
 
+	// Guard >1.0 feature nontrapping-float-to-int-conversion
+	if opCode == wasm.OpcodeMiscPrefix {
+		if err = p.enabledFeatures.Require(wasm.FeatureNonTrappingFloatToIntConversion); err != nil {
+			return nil, fmt.Errorf("%s invalid as %v", tokenBytes, err)
+		}
+		return p.beginFieldOrInstruction, nil
+	}
+
 	p.currentBody = append(p.currentBody, opCode)
 	return next, nil
 }
@@ -236,8 +283,36 @@ func (p *funcParser) end() (tokenParser, error) {
 	return p.onFunc(p.currentTypeIdx, code, p.currentName, p.currentParamNames)
 }
 
+// parseF32 parses a wasm.ValueTypeF32 and appends it to the currentBody.
+// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#floating-point%E2%91%A4
+func (p *funcParser) parseF32(tok tokenType, tokenBytes []byte, _, _ uint32) (tokenParser, error) {
+	if tok != tokenUN {
+		return nil, unexpectedToken(tok, tokenBytes)
+	}
+	if i, overflow := decodeUint32(tokenBytes); overflow { // TODO: negative hex nan inf and actual float!
+		return nil, fmt.Errorf("f32 outside range of uint32: %s", tokenBytes)
+	} else {
+		p.currentBody = append(p.currentBody, u64.LeBytes(api.EncodeF32(float32(i)))...)
+	}
+	return p.beginFieldOrInstruction, nil
+}
+
+// parseF64 parses a wasm.ValueTypeF64 and appends it to the currentBody.
+// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#floating-point%E2%91%A4
+func (p *funcParser) parseF64(tok tokenType, tokenBytes []byte, _, _ uint32) (tokenParser, error) {
+	if tok != tokenUN {
+		return nil, unexpectedToken(tok, tokenBytes)
+	}
+	if i, overflow := decodeUint64(tokenBytes); overflow { // TODO: negative hex nan inf and actual float!
+		return nil, fmt.Errorf("f64 outside range of uint64: %s", tokenBytes)
+	} else {
+		p.currentBody = append(p.currentBody, u64.LeBytes(api.EncodeF64(float64(i)))...)
+	}
+	return p.beginFieldOrInstruction, nil
+}
+
 // parseI32 parses a wasm.ValueTypeI32 and appends it to the currentBody.
-func (p *funcParser) parseI32(tok tokenType, tokenBytes []byte, line, col uint32) (tokenParser, error) {
+func (p *funcParser) parseI32(tok tokenType, tokenBytes []byte, _, _ uint32) (tokenParser, error) {
 	if tok != tokenUN {
 		return nil, unexpectedToken(tok, tokenBytes)
 	}
@@ -250,7 +325,7 @@ func (p *funcParser) parseI32(tok tokenType, tokenBytes []byte, line, col uint32
 }
 
 // parseI64 parses a wasm.ValueTypeI64 and appends it to the currentBody.
-func (p *funcParser) parseI64(tok tokenType, tokenBytes []byte, line, col uint32) (tokenParser, error) {
+func (p *funcParser) parseI64(tok tokenType, tokenBytes []byte, _, _ uint32) (tokenParser, error) {
 	if tok != tokenUN {
 		return nil, unexpectedToken(tok, tokenBytes)
 	}
diff --git a/internal/wazeroir/compiler.go b/internal/wazeroir/compiler.go
index 30b3bf07..11a08c39 100644
--- a/internal/wazeroir/compiler.go
+++ b/internal/wazeroir/compiler.go
@@ -1454,6 +1454,44 @@ operatorSwitch:
 		c.emit(
 			&OperationSignExtend64From32{},
 		)
+	case wasm.OpcodeMiscPrefix:
+		c.pc++
+		switch miscOp := c.body[c.pc]; miscOp {
+		case wasm.OpcodeMiscI32TruncSatF32S:
+			c.emit(
+				&OperationITruncFromF{InputType: Float32, OutputType: SignedInt32, NonTrapping: true},
+			)
+		case wasm.OpcodeMiscI32TruncSatF32U:
+			c.emit(
+				&OperationITruncFromF{InputType: Float32, OutputType: SignedUint32, NonTrapping: true},
+			)
+		case wasm.OpcodeMiscI32TruncSatF64S:
+			c.emit(
+				&OperationITruncFromF{InputType: Float64, OutputType: SignedInt32, NonTrapping: true},
+			)
+		case wasm.OpcodeMiscI32TruncSatF64U:
+			c.emit(
+				&OperationITruncFromF{InputType: Float64, OutputType: SignedUint32, NonTrapping: true},
+			)
+		case wasm.OpcodeMiscI64TruncSatF32S:
+			c.emit(
+				&OperationITruncFromF{InputType: Float32, OutputType: SignedInt64, NonTrapping: true},
+			)
+		case wasm.OpcodeMiscI64TruncSatF32U:
+			c.emit(
+				&OperationITruncFromF{InputType: Float32, OutputType: SignedUint64, NonTrapping: true},
+			)
+		case wasm.OpcodeMiscI64TruncSatF64S:
+			c.emit(
+				&OperationITruncFromF{InputType: Float64, OutputType: SignedInt64, NonTrapping: true},
+			)
+		case wasm.OpcodeMiscI64TruncSatF64U:
+			c.emit(
+				&OperationITruncFromF{InputType: Float64, OutputType: SignedUint64, NonTrapping: true},
+			)
+		default:
+			return fmt.Errorf("unsupported misc instruction in wazeroir: 0x%x", op)
+		}
 	default:
 		return fmt.Errorf("unsupported instruction in wazeroir: 0x%x", op)
 	}
diff --git a/internal/wazeroir/operations.go b/internal/wazeroir/operations.go
index 29fd707c..b2d0d7c5 100644
--- a/internal/wazeroir/operations.go
+++ b/internal/wazeroir/operations.go
@@ -831,6 +831,9 @@ func (o *OperationI32WrapFromI64) Kind() OperationKind {
 type OperationITruncFromF struct {
 	InputType  Float
 	OutputType SignedInt
+	// NonTrapping true if this conversion is "nontrapping" in the sense of the
+	// https://github.com/WebAssembly/spec/blob/ce4b6c4d47eb06098cc7ab2e81f24748da822f20/proposals/nontrapping-float-to-int-conversion/Overview.md
+	NonTrapping bool
 }
 
 func (o *OperationITruncFromF) Kind() OperationKind {
diff --git a/internal/wazeroir/signature.go b/internal/wazeroir/signature.go
index b1fee8bb..adc22ca0 100644
--- a/internal/wazeroir/signature.go
+++ b/internal/wazeroir/signature.go
@@ -350,6 +350,19 @@ func (c *compiler) wasmOpcodeSignature(op wasm.Opcode, index uint32) (*signature
 		return signature_I32_I32, nil
 	case wasm.OpcodeI64Extend8S, wasm.OpcodeI64Extend16S, wasm.OpcodeI64Extend32S:
 		return signature_I64_I64, nil
+	case wasm.OpcodeMiscPrefix:
+		switch miscOp := c.body[c.pc+1]; miscOp {
+		case wasm.OpcodeMiscI32TruncSatF32S, wasm.OpcodeMiscI32TruncSatF32U:
+			return signature_F32_I32, nil
+		case wasm.OpcodeMiscI32TruncSatF64S, wasm.OpcodeMiscI32TruncSatF64U:
+			return signature_F64_I32, nil
+		case wasm.OpcodeMiscI64TruncSatF32S, wasm.OpcodeMiscI64TruncSatF32U:
+			return signature_F32_I64, nil
+		case wasm.OpcodeMiscI64TruncSatF64S, wasm.OpcodeMiscI64TruncSatF64U:
+			return signature_F64_I64, nil
+		default:
+			return nil, fmt.Errorf("unsupported misc instruction in wazeroir: 0x%x", op)
+		}
 	default:
 		return nil, fmt.Errorf("unsupported instruction in wazeroir: 0x%x", op)
 	}


Test patch:
diff --git a/config_test.go b/config_test.go
index 2111dcc6..887f8568 100644
--- a/config_test.go
+++ b/config_test.go
@@ -43,6 +43,15 @@ func TestRuntimeConfig(t *testing.T) {
 				enabledFeatures: wasm.FeatureSignExtensionOps,
 			},
 		},
+		{
+			name: "nontrapping-float-to-int-conversion",
+			with: func(c *RuntimeConfig) *RuntimeConfig {
+				return c.WithFeatureNonTrappingFloatToIntConversion(true)
+			},
+			expected: &RuntimeConfig{
+				enabledFeatures: wasm.FeatureNonTrappingFloatToIntConversion,
+			},
+		},
 	}
 	for _, tt := range tests {
 		tc := tt
@@ -80,6 +89,14 @@ func TestRuntimeConfig_FeatureToggle(t *testing.T) {
 				return c.WithFeatureSignExtensionOps(v)
 			},
 		},
+		{
+			name:          "nontrapping-float-to-int-conversion",
+			feature:       wasm.FeatureNonTrappingFloatToIntConversion,
+			expectDefault: false,
+			setFeature: func(c *RuntimeConfig, v bool) *RuntimeConfig {
+				return c.WithFeatureNonTrappingFloatToIntConversion(v)
+			},
+		},
 	}
 
 	for _, tt := range tests {
diff --git a/internal/integration_test/asm/amd64_debug/impl_test.go b/internal/integration_test/asm/amd64_debug/impl_test.go
index 8ffcdd52..c9f87b33 100644
--- a/internal/integration_test/asm/amd64_debug/impl_test.go
+++ b/internal/integration_test/asm/amd64_debug/impl_test.go
@@ -620,7 +620,7 @@ func TestAssemblerImpl_encodeNoneToBranch(t *testing.T) {
 			t.Run(fmt.Sprintf("is_short_jump=%v", isShortJump), func(t *testing.T) {
 				for _, inst := range []asm.Instruction{
 					amd64.JCC, amd64.JCS, amd64.JEQ, amd64.JGE, amd64.JGT, amd64.JHI, amd64.JLE, amd64.JLS, amd64.JLT, amd64.JMI,
-					amd64.JMP, amd64.JNE, amd64.JPC, amd64.JPS,
+					amd64.JMP, amd64.JNE, amd64.JPC, amd64.JPS, amd64.JPL,
 				} {
 					inst := inst
 					t.Run(amd64.InstructionName(inst), func(t *testing.T) {
@@ -658,7 +658,7 @@ func TestAssemblerImpl_encodeNoneToBranch(t *testing.T) {
 			t.Run(fmt.Sprintf("is_short_jump=%v", isShortJump), func(t *testing.T) {
 				for _, inst := range []asm.Instruction{
 					amd64.JCC, amd64.JCS, amd64.JEQ, amd64.JGE, amd64.JGT, amd64.JHI, amd64.JLE, amd64.JLS, amd64.JLT,
-					amd64.JMI, amd64.JMP, amd64.JNE, amd64.JPC, amd64.JPS,
+					amd64.JMI, amd64.JMP, amd64.JNE, amd64.JPC, amd64.JPS, amd64.JPL,
 				} {
 					inst := inst
 					t.Run(amd64.InstructionName(inst), func(t *testing.T) {
diff --git a/internal/integration_test/post1_0/multi-value/multi_value_test.go b/internal/integration_test/post1_0/multi-value/spec_test.go
similarity index 100%
rename from internal/integration_test/post1_0/multi-value/multi_value_test.go
rename to internal/integration_test/post1_0/multi-value/spec_test.go
diff --git a/internal/integration_test/post1_0/nontrapping-float-to-int-conversion/spec_test.go b/internal/integration_test/post1_0/nontrapping-float-to-int-conversion/spec_test.go
new file mode 100644
index 00000000..44a43d56
--- /dev/null
+++ b/internal/integration_test/post1_0/nontrapping-float-to-int-conversion/spec_test.go
@@ -0,0 +1,248 @@
+package nontrapping_float_to_int_conversion
+
+import (
+	"context"
+	_ "embed"
+	"math"
+	"testing"
+
+	"github.com/tetratelabs/wazero"
+	"github.com/tetratelabs/wazero/api"
+	"github.com/tetratelabs/wazero/internal/testing/require"
+)
+
+// testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
+var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
+
+func TestNonTrappingFloatToIntConversion_JIT(t *testing.T) {
+	if !wazero.JITSupported {
+		t.Skip()
+	}
+	testNonTrappingFloatToIntConversion(t, wazero.NewRuntimeConfigJIT)
+}
+
+func TestNonTrappingFloatToIntConversion_Interpreter(t *testing.T) {
+	testNonTrappingFloatToIntConversion(t, wazero.NewRuntimeConfigInterpreter)
+}
+
+// conversions includes changes to test/core/conversions.wast from the commit that added
+// "nontrapping-float-to-int-conversion" support.
+//
+// See https://github.com/WebAssembly/spec/commit/c8fd933fa51eb0b511bce027b573aef7ee373726
+var nonTrappingFloatToIntConversion = []byte(`(module $conversions.wast
+  (func $i32.trunc_sat_f32_s (param f32) (result i32) local.get 0 i32.trunc_sat_f32_s)
+  (export "i32.trunc_sat_f32_s" (func $i32.trunc_sat_f32_s))
+
+  (func $i32.trunc_sat_f32_u (param f32) (result i32) local.get 0 i32.trunc_sat_f32_u)
+  (export "i32.trunc_sat_f32_u" (func $i32.trunc_sat_f32_u))
+
+  (func $i32.trunc_sat_f64_s (param f64) (result i32) local.get 0 i32.trunc_sat_f64_s)
+  (export "i32.trunc_sat_f64_s" (func $i32.trunc_sat_f64_s))
+
+  (func $i32.trunc_sat_f64_u (param f64) (result i32) local.get 0 i32.trunc_sat_f64_u)
+  (export "i32.trunc_sat_f64_u" (func $i32.trunc_sat_f64_u))
+
+  (func $i64.trunc_sat_f32_s (param f32) (result i64) local.get 0 i64.trunc_sat_f32_s)
+  (export "i64.trunc_sat_f32_s" (func $i64.trunc_sat_f32_s))
+
+  (func $i64.trunc_sat_f32_u (param f32) (result i64) local.get 0 i64.trunc_sat_f32_u)
+  (export "i64.trunc_sat_f32_u" (func $i64.trunc_sat_f32_u))
+
+  (func $i64.trunc_sat_f64_s (param f64) (result i64) local.get 0 i64.trunc_sat_f64_s)
+  (export "i64.trunc_sat_f64_s" (func $i64.trunc_sat_f64_s))
+
+  (func $i64.trunc_sat_f64_u (param f64) (result i64) local.get 0 i64.trunc_sat_f64_u)
+  (export "i64.trunc_sat_f64_u" (func $i64.trunc_sat_f64_u))
+)
+`)
+
+func testNonTrappingFloatToIntConversion(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
+	t.Run("disabled", func(t *testing.T) {
+		// Non-trapping Float-to-int Conversions are disabled by default.
+		r := wazero.NewRuntimeWithConfig(newRuntimeConfig())
+		_, err := r.InstantiateModuleFromCode(testCtx, nonTrappingFloatToIntConversion)
+		require.Error(t, err)
+	})
+	t.Run("enabled", func(t *testing.T) {
+		r := wazero.NewRuntimeWithConfig(newRuntimeConfig().WithFeatureNonTrappingFloatToIntConversion(true))
+		module, err := r.InstantiateModuleFromCode(testCtx, nonTrappingFloatToIntConversion)
+		require.NoError(t, err)
+
+		// https://github.com/WebAssembly/spec/commit/c8fd933fa51eb0b511bce027b573aef7ee373726#diff-68f5d3026030825a35400ba547214701a409a89ce4b1bdb525d5eb98a5e03a38R259-R447
+		// Skip -0.0 due to SA4026: in Go, the floating-point literal '-0.0' is the same as '0.0'
+		// Skip nan:0x200000, -nan, -nan:0x200000 as there is no representation of these in Go.
+		testFunctions(t, module, []funcTest{
+			{name: "i32.trunc_sat_f32_s", param: 0, expected: 0},
+			{name: "i32.trunc_sat_f32_s", param: api.EncodeF32(0x1p-149), expected: 0},
+			{name: "i32.trunc_sat_f32_s", param: api.EncodeF32(-0x1p-149), expected: 0},
+			{name: "i32.trunc_sat_f32_s", param: api.EncodeF32(1.0), expected: 1},
+			{name: "i32.trunc_sat_f32_s", param: api.EncodeF32(0x1.19999ap+0), expected: 1},
+			{name: "i32.trunc_sat_f32_s", param: api.EncodeF32(1.5), expected: 1},
+			{name: "i32.trunc_sat_f32_s", param: api.EncodeF32(-1.0), expected: api.EncodeI32(-1)},
+			{name: "i32.trunc_sat_f32_s", param: api.EncodeF32(-0x1.19999ap+0), expected: api.EncodeI32(-1)},
+			{name: "i32.trunc_sat_f32_s", param: api.EncodeF32(-1.5), expected: api.EncodeI32(-1)},
+			{name: "i32.trunc_sat_f32_s", param: api.EncodeF32(-1.9), expected: api.EncodeI32(-1)},
+			{name: "i32.trunc_sat_f32_s", param: api.EncodeF32(-2.0), expected: api.EncodeI32(-2)},
+			{name: "i32.trunc_sat_f32_s", param: api.EncodeF32(2147483520.0), expected: 2147483520},
+			{name: "i32.trunc_sat_f32_s", param: api.EncodeF32(-2147483648.0), expected: api.EncodeI32(-2147483648)},
+			{name: "i32.trunc_sat_f32_s", param: api.EncodeF32(2147483648.0), expected: 0x7fffffff},
+			{name: "i32.trunc_sat_f32_s", param: api.EncodeF32(-2147483904.0), expected: 0x80000000},
+			{name: "i32.trunc_sat_f32_s", param: api.EncodeF32(float32(math.Inf(1))), expected: 0x7fffffff},
+			{name: "i32.trunc_sat_f32_s", param: api.EncodeF32(float32(math.Inf(-11))), expected: 0x80000000},
+			{name: "i32.trunc_sat_f32_s", param: api.EncodeF32(float32(math.NaN())), expected: 0},
+
+			{name: "i32.trunc_sat_f32_u", param: api.EncodeF32(0.0), expected: 0},
+			{name: "i32.trunc_sat_f32_u", param: api.EncodeF32(0x1p-149), expected: 0},
+			{name: "i32.trunc_sat_f32_u", param: api.EncodeF32(-0x1p-149), expected: 0},
+			{name: "i32.trunc_sat_f32_u", param: api.EncodeF32(1.0), expected: 1},
+			{name: "i32.trunc_sat_f32_u", param: api.EncodeF32(0x1.19999ap+0), expected: 1},
+			{name: "i32.trunc_sat_f32_u", param: api.EncodeF32(1.5), expected: 1},
+			{name: "i32.trunc_sat_f32_u", param: api.EncodeF32(1.9), expected: 1},
+			{name: "i32.trunc_sat_f32_u", param: api.EncodeF32(2.0), expected: 2},
+			{name: "i32.trunc_sat_f32_u", param: api.EncodeF32(2147483648), expected: api.EncodeI32(-2147483648)}, // 0x1.00000p+31 -> 8000 0000
+			{name: "i32.trunc_sat_f32_u", param: api.EncodeF32(4294967040.0), expected: api.EncodeI32(-256)},
+			{name: "i32.trunc_sat_f32_u", param: api.EncodeF32(-0x1.ccccccp-1), expected: 0},
+			{name: "i32.trunc_sat_f32_u", param: api.EncodeF32(-0x1.fffffep-1), expected: 0},
+			{name: "i32.trunc_sat_f32_u", param: api.EncodeF32(4294967296.0), expected: 0xffffffff},
+			{name: "i32.trunc_sat_f32_u", param: api.EncodeF32(-1.0), expected: 0x00000000},
+			{name: "i32.trunc_sat_f32_u", param: api.EncodeF32(float32(math.Inf(1))), expected: 0xffffffff},
+			{name: "i32.trunc_sat_f32_u", param: api.EncodeF32(float32(math.Inf(-1))), expected: 0x00000000},
+			{name: "i32.trunc_sat_f32_u", param: api.EncodeF32(float32(math.NaN())), expected: 0},
+
+			{name: "i32.trunc_sat_f64_s", param: api.EncodeF64(0.0), expected: 0},
+			{name: "i32.trunc_sat_f64_s", param: api.EncodeF64(0x0.0000000000001p-1022), expected: 0},
+			{name: "i32.trunc_sat_f64_s", param: api.EncodeF64(-0x0.0000000000001p-1022), expected: 0},
+			{name: "i32.trunc_sat_f64_s", param: api.EncodeF64(1.0), expected: 1},
+			{name: "i32.trunc_sat_f64_s", param: api.EncodeF64(0x1.199999999999ap+0), expected: 1},
+			{name: "i32.trunc_sat_f64_s", param: api.EncodeF64(1.5), expected: 1},
+			{name: "i32.trunc_sat_f64_s", param: api.EncodeF64(-1.0), expected: api.EncodeI32(-1)},
+			{name: "i32.trunc_sat_f64_s", param: api.EncodeF64(-0x1.199999999999ap+0), expected: api.EncodeI32(-1)},
+			{name: "i32.trunc_sat_f64_s", param: api.EncodeF64(-1.5), expected: api.EncodeI32(-1)},
+			{name: "i32.trunc_sat_f64_s", param: api.EncodeF64(-1.9), expected: api.EncodeI32(-1)},
+			{name: "i32.trunc_sat_f64_s", param: api.EncodeF64(-2.0), expected: api.EncodeI32(-2)},
+			{name: "i32.trunc_sat_f64_s", param: api.EncodeF64(2147483647.0), expected: 2147483647},
+			{name: "i32.trunc_sat_f64_s", param: api.EncodeF64(-2147483648.0), expected: api.EncodeI32(-2147483648)},
+			{name: "i32.trunc_sat_f64_s", param: api.EncodeF64(2147483648.0), expected: 0x7fffffff},
+			{name: "i32.trunc_sat_f64_s", param: api.EncodeF64(-2147483649.0), expected: 0x80000000},
+			{name: "i32.trunc_sat_f64_s", param: api.EncodeF64(math.Inf(1)), expected: 0x7fffffff},
+			{name: "i32.trunc_sat_f64_s", param: api.EncodeF64(math.Inf(-1)), expected: 0x80000000},
+			{name: "i32.trunc_sat_f64_s", param: api.EncodeF64(math.NaN()), expected: 0},
+
+			{name: "i32.trunc_sat_f64_u", param: api.EncodeF64(0.0), expected: 0},
+			{name: "i32.trunc_sat_f64_u", param: api.EncodeF64(0x0.0000000000001p-1022), expected: 0},
+			{name: "i32.trunc_sat_f64_u", param: api.EncodeF64(-0x0.0000000000001p-1022), expected: 0},
+			{name: "i32.trunc_sat_f64_u", param: api.EncodeF64(1.0), expected: 1},
+			{name: "i32.trunc_sat_f64_u", param: api.EncodeF64(0x1.199999999999ap+0), expected: 1},
+			{name: "i32.trunc_sat_f64_u", param: api.EncodeF64(1.5), expected: 1},
+			{name: "i32.trunc_sat_f64_u", param: api.EncodeF64(1.9), expected: 1},
+			{name: "i32.trunc_sat_f64_u", param: api.EncodeF64(2.0), expected: 2},
+			{name: "i32.trunc_sat_f64_u", param: api.EncodeF64(2147483648), expected: api.EncodeI32(-2147483648)}, // 0x1.00000p+31 -> 8000 0000
+			{name: "i32.trunc_sat_f64_u", param: api.EncodeF64(4294967295.0), expected: api.EncodeI32(-1)},
+			{name: "i32.trunc_sat_f64_u", param: api.EncodeF64(-0x1.ccccccccccccdp-1), expected: 0},
+			{name: "i32.trunc_sat_f64_u", param: api.EncodeF64(-0x1.fffffffffffffp-1), expected: 0},
+			{name: "i32.trunc_sat_f64_u", param: api.EncodeF64(1e8), expected: 100000000},
+			{name: "i32.trunc_sat_f64_u", param: api.EncodeF64(4294967296.0), expected: 0xffffffff},
+			{name: "i32.trunc_sat_f64_u", param: api.EncodeF64(-1.0), expected: 0x00000000},
+			{name: "i32.trunc_sat_f64_u", param: api.EncodeF64(1e16), expected: 0xffffffff},
+			{name: "i32.trunc_sat_f64_u", param: api.EncodeF64(1e30), expected: 0xffffffff},
+			{name: "i32.trunc_sat_f64_u", param: api.EncodeF64(9223372036854775808), expected: 0xffffffff},
+			{name: "i32.trunc_sat_f64_u", param: api.EncodeF64(math.Inf(1)), expected: 0xffffffff},
+			{name: "i32.trunc_sat_f64_u", param: api.EncodeF64(math.Inf(-1)), expected: 0x00000000},
+			{name: "i32.trunc_sat_f64_u", param: api.EncodeF64(math.NaN()), expected: 0},
+
+			{name: "i64.trunc_sat_f32_s", param: api.EncodeF32(0.0), expected: 0},
+			{name: "i64.trunc_sat_f32_s", param: api.EncodeF32(0x1p-149), expected: 0},
+			{name: "i64.trunc_sat_f32_s", param: api.EncodeF32(-0x1p-149), expected: 0},
+			{name: "i64.trunc_sat_f32_s", param: api.EncodeF32(1.0), expected: 1},
+			{name: "i64.trunc_sat_f32_s", param: api.EncodeF32(0x1.19999ap+0), expected: 1},
+			{name: "i64.trunc_sat_f32_s", param: api.EncodeF32(1.5), expected: 1},
+			{name: "i64.trunc_sat_f32_s", param: api.EncodeF32(-1.0), expected: api.EncodeI64(-1)},
+			{name: "i64.trunc_sat_f32_s", param: api.EncodeF32(-0x1.19999ap+0), expected: api.EncodeI64(-1)},
+			{name: "i64.trunc_sat_f32_s", param: api.EncodeF32(-1.5), expected: api.EncodeI64(-1)},
+			{name: "i64.trunc_sat_f32_s", param: api.EncodeF32(-1.9), expected: api.EncodeI64(-1)},
+			{name: "i64.trunc_sat_f32_s", param: api.EncodeF32(-2.0), expected: api.EncodeI64(-2)},
+			{name: "i64.trunc_sat_f32_s", param: api.EncodeF32(4294967296), expected: 4294967296},                  // 0x1.00000p+32 -> 1 0000 0000
+			{name: "i64.trunc_sat_f32_s", param: api.EncodeF32(-4294967296), expected: api.EncodeI64(-4294967296)}, // -0x1.00000p+32 -> ffff ffff 0000 0000
+			{name: "i64.trunc_sat_f32_s", param: api.EncodeF32(9223371487098961920.0), expected: 9223371487098961920},
+			{name: "i64.trunc_sat_f32_s", param: api.EncodeF32(-9223372036854775808.0), expected: api.EncodeI64(-9223372036854775808)},
+			{name: "i64.trunc_sat_f32_s", param: api.EncodeF32(9223372036854775808.0), expected: 0x7fffffffffffffff},
+			{name: "i64.trunc_sat_f32_s", param: api.EncodeF32(-9223373136366403584.0), expected: 0x8000000000000000},
+			{name: "i64.trunc_sat_f32_s", param: api.EncodeF32(float32(math.Inf(1))), expected: 0x7fffffffffffffff},
+			{name: "i64.trunc_sat_f32_s", param: api.EncodeF32(float32(math.Inf(-1))), expected: 0x8000000000000000},
+			{name: "i64.trunc_sat_f32_s", param: api.EncodeF32(float32(math.NaN())), expected: 0},
+
+			{name: "i64.trunc_sat_f32_u", param: api.EncodeF32(0.0), expected: 0},
+			{name: "i64.trunc_sat_f32_u", param: api.EncodeF32(0x1p-149), expected: 0},
+			{name: "i64.trunc_sat_f32_u", param: api.EncodeF32(-0x1p-149), expected: 0},
+			{name: "i64.trunc_sat_f32_u", param: api.EncodeF32(1.0), expected: 1},
+			{name: "i64.trunc_sat_f32_u", param: api.EncodeF32(0x1.19999ap+0), expected: 1},
+			{name: "i64.trunc_sat_f32_u", param: api.EncodeF32(1.5), expected: 1},
+			{name: "i64.trunc_sat_f32_u", param: api.EncodeF32(4294967296), expected: 4294967296},
+			{name: "i64.trunc_sat_f32_u", param: api.EncodeF32(18446742974197923840.0), expected: api.EncodeI64(-1099511627776)},
+			{name: "i64.trunc_sat_f32_u", param: api.EncodeF32(-0x1.ccccccp-1), expected: 0},
+			{name: "i64.trunc_sat_f32_u", param: api.EncodeF32(-0x1.fffffep-1), expected: 0},
+			{name: "i64.trunc_sat_f32_u", param: api.EncodeF32(18446744073709551616.0), expected: 0xffffffffffffffff},
+			{name: "i64.trunc_sat_f32_u", param: api.EncodeF32(-1.0), expected: 0x0000000000000000},
+			{name: "i64.trunc_sat_f32_u", param: api.EncodeF32(float32(math.Inf(1))), expected: 0xffffffffffffffff},
+			{name: "i64.trunc_sat_f32_u", param: api.EncodeF32(float32(math.Inf(-1))), expected: 0x0000000000000000},
+			{name: "i64.trunc_sat_f32_u", param: api.EncodeF32(float32(math.NaN())), expected: 0},
+
+			{name: "i64.trunc_sat_f64_s", param: api.EncodeF64(0.0), expected: 0},
+			{name: "i64.trunc_sat_f64_s", param: api.EncodeF64(0x0.0000000000001p-1022), expected: 0},
+			{name: "i64.trunc_sat_f64_s", param: api.EncodeF64(-0x0.0000000000001p-1022), expected: 0},
+			{name: "i64.trunc_sat_f64_s", param: api.EncodeF64(1.0), expected: 1},
+			{name: "i64.trunc_sat_f64_s", param: api.EncodeF64(0x1.199999999999ap+0), expected: 1},
+			{name: "i64.trunc_sat_f64_s", param: api.EncodeF64(1.5), expected: 1},
+			{name: "i64.trunc_sat_f64_s", param: api.EncodeF64(-1.0), expected: api.EncodeI64(-1)},
+			{name: "i64.trunc_sat_f64_s", param: api.EncodeF64(-0x1.199999999999ap+0), expected: api.EncodeI64(-1)},
+			{name: "i64.trunc_sat_f64_s", param: api.EncodeF64(-1.5), expected: api.EncodeI64(-1)},
+			{name: "i64.trunc_sat_f64_s", param: api.EncodeF64(-1.9), expected: api.EncodeI64(-1)},
+			{name: "i64.trunc_sat_f64_s", param: api.EncodeF64(-2.0), expected: api.EncodeI64(-2)},
+			{name: "i64.trunc_sat_f64_s", param: api.EncodeF64(4294967296), expected: 4294967296},                  // 0x1.00000p+32 -> 1 0000 0000
+			{name: "i64.trunc_sat_f64_s", param: api.EncodeF64(-4294967296), expected: api.EncodeI64(-4294967296)}, // -0x1.00000p+32 -> ffff ffff 0000 0000
+			{name: "i64.trunc_sat_f64_s", param: api.EncodeF64(9223372036854774784.0), expected: 9223372036854774784},
+			{name: "i64.trunc_sat_f64_s", param: api.EncodeF64(-9223372036854775808.0), expected: api.EncodeI64(-9223372036854775808)},
+			{name: "i64.trunc_sat_f64_s", param: api.EncodeF64(9223372036854775808.0), expected: 0x7fffffffffffffff},
+			{name: "i64.trunc_sat_f64_s", param: api.EncodeF64(-9223372036854777856.0), expected: 0x8000000000000000},
+			{name: "i64.trunc_sat_f64_s", param: api.EncodeF64(math.Inf(1)), expected: 0x7fffffffffffffff},
+			{name: "i64.trunc_sat_f64_s", param: api.EncodeF64(math.Inf(-1)), expected: 0x8000000000000000},
+			{name: "i64.trunc_sat_f64_s", param: api.EncodeF64(math.NaN()), expected: 0},
+
+			{name: "i64.trunc_sat_f64_u", param: api.EncodeF64(0.0), expected: 0},
+			{name: "i64.trunc_sat_f64_u", param: api.EncodeF64(0x0.0000000000001p-1022), expected: 0},
+			{name: "i64.trunc_sat_f64_u", param: api.EncodeF64(-0x0.0000000000001p-1022), expected: 0},
+			{name: "i64.trunc_sat_f64_u", param: api.EncodeF64(1.0), expected: 1},
+			{name: "i64.trunc_sat_f64_u", param: api.EncodeF64(0x1.199999999999ap+0), expected: 1},
+			{name: "i64.trunc_sat_f64_u", param: api.EncodeF64(1.5), expected: 1},
+			{name: "i64.trunc_sat_f64_u", param: api.EncodeF64(4294967295), expected: 0xffffffff},
+			{name: "i64.trunc_sat_f64_u", param: api.EncodeF64(4294967296), expected: 0x100000000},
+			{name: "i64.trunc_sat_f64_u", param: api.EncodeF64(18446744073709549568.0), expected: api.EncodeI64(-2048)},
+			{name: "i64.trunc_sat_f64_u", param: api.EncodeF64(-0x1.ccccccccccccdp-1), expected: 0},
+			{name: "i64.trunc_sat_f64_u", param: api.EncodeF64(-0x1.fffffffffffffp-1), expected: 0},
+			{name: "i64.trunc_sat_f64_u", param: api.EncodeF64(1e8), expected: 100000000},
+			{name: "i64.trunc_sat_f64_u", param: api.EncodeF64(1e16), expected: 10000000000000000},
+			{name: "i64.trunc_sat_f64_u", param: api.EncodeF64(9223372036854775808), expected: api.EncodeI64(-9223372036854775808)},
+			{name: "i64.trunc_sat_f64_u", param: api.EncodeF64(18446744073709551616.0), expected: 0xffffffffffffffff},
+			{name: "i64.trunc_sat_f64_u", param: api.EncodeF64(-1.0), expected: 0x0000000000000000},
+			{name: "i64.trunc_sat_f64_u", param: api.EncodeF64(math.Inf(1)), expected: 0xffffffffffffffff},
+			{name: "i64.trunc_sat_f64_u", param: api.EncodeF64(math.Inf(-1)), expected: 0x0000000000000000},
+			{name: "i64.trunc_sat_f64_u", param: api.EncodeF64(math.NaN()), expected: 0},
+		})
+	})
+}
+
+type funcTest struct {
+	name            string
+	param, expected uint64
+}
+
+func testFunctions(t *testing.T, module api.Module, tests []funcTest) {
+	for _, tt := range tests {
+		tc := tt
+		t.Run(tc.name, func(t *testing.T) {
+			results, err := module.ExportedFunction(tc.name).Call(testCtx, tc.param)
+			require.NoError(t, err)
+			require.Equal(t, tc.expected, results[0])
+		})
+	}
+}
diff --git a/internal/integration_test/post1_0/sign-extension-ops/sign_extension_ops_test.go b/internal/integration_test/post1_0/sign-extension-ops/spec_test.go
similarity index 100%
rename from internal/integration_test/post1_0/sign-extension-ops/sign_extension_ops_test.go
rename to internal/integration_test/post1_0/sign-extension-ops/spec_test.go
diff --git a/internal/integration_test/vs/codec_test.go b/internal/integration_test/vs/codec_test.go
index d145a002..c0fe7cb8 100644
--- a/internal/integration_test/vs/codec_test.go
+++ b/internal/integration_test/vs/codec_test.go
@@ -31,13 +31,14 @@ var exampleBinary = binary.EncodeModule(example)
 
 func newExample() *wasm.Module {
 	three := wasm.Index(3)
-	i32, i64 := wasm.ValueTypeI32, wasm.ValueTypeI64
+	f32, i32, i64 := wasm.ValueTypeF32, wasm.ValueTypeI32, wasm.ValueTypeI64
 	return &wasm.Module{
 		TypeSection: []*wasm.FunctionType{
 			{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}},
 			{},
 			{Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}},
 			{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i64}},
+			{Params: []wasm.ValueType{f32}, Results: []wasm.ValueType{i32}},
 			{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32, i32}},
 		},
 		ImportSection: []*wasm.Import{
@@ -51,12 +52,17 @@ func newExample() *wasm.Module {
 				DescFunc: 2,
 			},
 		},
-		FunctionSection: []wasm.Index{wasm.Index(1), wasm.Index(1), wasm.Index(0), wasm.Index(3), wasm.Index(4)},
+		FunctionSection: []wasm.Index{wasm.Index(1), wasm.Index(1), wasm.Index(0), wasm.Index(3), wasm.Index(4), wasm.Index(5)},
 		CodeSection: []*wasm.Code{
 			{Body: []byte{wasm.OpcodeCall, 3, wasm.OpcodeEnd}},
 			{Body: []byte{wasm.OpcodeEnd}},
 			{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeLocalGet, 1, wasm.OpcodeI32Add, wasm.OpcodeEnd}},
 			{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeI64Extend16S, wasm.OpcodeEnd}},
+			{Body: []byte{
+				wasm.OpcodeLocalGet, 0x00,
+				wasm.OpcodeMiscPrefix, wasm.OpcodeMiscI32TruncSatF32S,
+				wasm.OpcodeEnd,
+			}},
 			{Body: []byte{wasm.OpcodeLocalGet, 1, wasm.OpcodeLocalGet, 0, wasm.OpcodeEnd}},
 		},
 		MemorySection: &wasm.Memory{Min: 1, Max: three, IsMaxEncoded: true},
@@ -64,7 +70,7 @@ func newExample() *wasm.Module {
 			{Name: "AddInt", Type: wasm.ExternTypeFunc, Index: wasm.Index(4)},
 			{Name: "", Type: wasm.ExternTypeFunc, Index: wasm.Index(3)},
 			{Name: "mem", Type: wasm.ExternTypeMemory, Index: wasm.Index(0)},
-			{Name: "swap", Type: wasm.ExternTypeFunc, Index: wasm.Index(6)},
+			{Name: "swap", Type: wasm.ExternTypeFunc, Index: wasm.Index(7)},
 		},
 		StartSection: &three,
 		NameSection: &wasm.NameSection{
@@ -75,7 +81,7 @@ func newExample() *wasm.Module {
 				{Index: wasm.Index(2), Name: "call_hello"},
 				{Index: wasm.Index(3), Name: "hello"},
 				{Index: wasm.Index(4), Name: "addInt"},
-				{Index: wasm.Index(6), Name: "swap"},
+				{Index: wasm.Index(7), Name: "swap"},
 			},
 			LocalNames: wasm.IndirectNameMap{
 				{Index: wasm.Index(1), NameMap: wasm.NameMap{
diff --git a/internal/integration_test/vs/testdata/example.wat b/internal/integration_test/vs/testdata/example.wat
index 4d4145b6..f4fa1d41 100644
--- a/internal/integration_test/vs/testdata/example.wat
+++ b/internal/integration_test/vs/testdata/example.wat
@@ -42,6 +42,10 @@
     ;; https://github.com/WebAssembly/spec/blob/main/proposals/sign-extension-ops/Overview.md
     (func (param i64) (result i64) local.get 0 i64.extend16_s)
 
+    ;; add function using "nontrapping-float-to-int-conversion"
+    ;; https://github.com/WebAssembly/spec/blob/main/proposals/nontrapping-float-to-int-conversion/Overview.md
+    (func (param f32) (result i32) local.get 0 i32.trunc_sat_f32_s)
+
     ;; add function using "multi-value"
     ;; https://github.com/WebAssembly/spec/blob/main/proposals/multi-value/Overview.md
     (func $swap (param i32 i32) (result i32 i32) local.get 1 local.get 0)
diff --git a/internal/wasm/features_test.go b/internal/wasm/features_test.go
index 31e6cbbd..3690e5a8 100644
--- a/internal/wasm/features_test.go
+++ b/internal/wasm/features_test.go
@@ -62,7 +62,7 @@ func TestFeatures_String(t *testing.T) {
 		{name: "multi-value", feature: FeatureMultiValue, expected: "multi-value"},
 		{name: "features", feature: FeatureMutableGlobal | FeatureMultiValue, expected: "mutable-global|multi-value"},
 		{name: "undefined", feature: 1 << 63, expected: ""},
-		{name: "all", feature: FeaturesFinished, expected: "mutable-global|sign-extension-ops|multi-value"},
+		{name: "all", feature: FeaturesFinished, expected: "mutable-global|sign-extension-ops|multi-value|nontrapping-float-to-int-conversion"},
 	}
 
 	for _, tt := range tests {
diff --git a/internal/wasm/func_validation_test.go b/internal/wasm/func_validation_test.go
index 2481d46d..7e13fdfc 100644
--- a/internal/wasm/func_validation_test.go
+++ b/internal/wasm/func_validation_test.go
@@ -103,6 +103,79 @@ func TestModule_ValidateFunction_SignExtensionOps(t *testing.T) {
 	}
 }
 
+func TestModule_ValidateFunction_NonTrappingFloatToIntConversion(t *testing.T) {
+	tests := []struct {
+		input                Opcode
+		expectedErrOnDisable string
+	}{
+		{
+			input:                OpcodeMiscI32TruncSatF32S,
+			expectedErrOnDisable: "i32.trunc_sat_f32_s invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
+		},
+		{
+			input:                OpcodeMiscI32TruncSatF32U,
+			expectedErrOnDisable: "i32.trunc_sat_f32_u invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
+		},
+		{
+			input:                OpcodeMiscI32TruncSatF64S,
+			expectedErrOnDisable: "i32.trunc_sat_f64_s invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
+		},
+		{
+			input:                OpcodeMiscI32TruncSatF64U,
+			expectedErrOnDisable: "i32.trunc_sat_f64_u invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
+		},
+		{
+			input:                OpcodeMiscI64TruncSatF32S,
+			expectedErrOnDisable: "i64.trunc_sat_f32_s invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
+		},
+		{
+			input:                OpcodeMiscI64TruncSatF32U,
+			expectedErrOnDisable: "i64.trunc_sat_f32_u invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
+		},
+		{
+			input:                OpcodeMiscI64TruncSatF64S,
+			expectedErrOnDisable: "i64.trunc_sat_f64_s invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
+		},
+		{
+			input:                OpcodeMiscI64TruncSatF64U,
+			expectedErrOnDisable: "i64.trunc_sat_f64_u invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
+		},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+		t.Run(InstructionName(tc.input), func(t *testing.T) {
+			t.Run("disabled", func(t *testing.T) {
+				m := &Module{
+					TypeSection:     []*FunctionType{v_v},
+					FunctionSection: []Index{0},
+					CodeSection:     []*Code{{Body: []byte{OpcodeMiscPrefix, tc.input}}},
+				}
+				err := m.validateFunction(Features20191205, 0, []Index{0}, nil, nil, nil)
+				require.EqualError(t, err, tc.expectedErrOnDisable)
+			})
+			t.Run("enabled", func(t *testing.T) {
+				var body []byte
+				switch tc.input {
+				case OpcodeMiscI32TruncSatF32S, OpcodeMiscI32TruncSatF32U, OpcodeMiscI64TruncSatF32S, OpcodeMiscI64TruncSatF32U:
+					body = []byte{OpcodeF32Const, 1, 2, 3, 4}
+				case OpcodeMiscI32TruncSatF64S, OpcodeMiscI32TruncSatF64U, OpcodeMiscI64TruncSatF64S, OpcodeMiscI64TruncSatF64U:
+					body = []byte{OpcodeF64Const, 1, 2, 3, 4, 5, 6, 7, 8}
+				}
+				body = append(body, OpcodeMiscPrefix, tc.input, OpcodeDrop, OpcodeEnd)
+
+				m := &Module{
+					TypeSection:     []*FunctionType{v_v},
+					FunctionSection: []Index{0},
+					CodeSection:     []*Code{{Body: body}},
+				}
+				err := m.validateFunction(FeatureNonTrappingFloatToIntConversion, 0, []Index{0}, nil, nil, nil)
+				require.NoError(t, err)
+			})
+		})
+	}
+}
+
 // TestModule_ValidateFunction_MultiValue only tests what can't yet be detected during compilation. These examples are
 // from test/core/if.wast from the commit that added "multi-value" support.
 //
diff --git a/internal/wasm/interpreter/interpreter_test.go b/internal/wasm/interpreter/interpreter_test.go
index 51e0f656..8b75a3a4 100644
--- a/internal/wasm/interpreter/interpreter_test.go
+++ b/internal/wasm/interpreter/interpreter_test.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"math"
+	"strconv"
 	"testing"
 
 	"github.com/tetratelabs/wazero/internal/buildoptions"
@@ -88,6 +89,201 @@ func TestInterpreter_ModuleEngine_Call_Errors(t *testing.T) {
 	enginetest.RunTestModuleEngine_Call_Errors(t, et)
 }
 
+func TestInterpreter_NonTrappingFloatToIntConversion(t *testing.T) {
+	_0x80000000 := uint32(0x80000000)
+	_0xffffffff := uint32(0xffffffff)
+	_0x8000000000000000 := uint64(0x8000000000000000)
+	_0xffffffffffffffff := uint64(0xffffffffffffffff)
+
+	for _, tc := range []struct {
+		op            wasm.OpcodeMisc
+		inputType     wazeroir.Float
+		outputType    wazeroir.SignedInt
+		input32bit    []float32
+		input64bit    []float64
+		expected32bit []int32
+		expected64bit []int64
+	}{
+		{
+			// https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L261-L282
+			op:         wasm.OpcodeMiscI32TruncSatF32S,
+			inputType:  wazeroir.Float32,
+			outputType: wazeroir.SignedInt32,
+			input32bit: []float32{
+				0.0, 0.0, 0x1p-149, -0x1p-149, 1.0, 0x1.19999ap+0, 1.5, -1.0, -0x1.19999ap+0,
+				-1.5, -1.9, -2.0, 2147483520.0, -2147483648.0, 2147483648.0, -2147483904.0,
+				float32(math.Inf(1)), float32(math.Inf(-1)), float32(math.NaN()), float32(math.NaN()),
+				float32(math.NaN()), float32(math.NaN()),
+			},
+			expected32bit: []int32{
+				0, 0, 0, 0, 1, 1, 1, -1, -1, -1, -1, -2, 2147483520, -2147483648, 0x7fffffff,
+				int32(_0x80000000), 0x7fffffff, int32(_0x80000000), 0, 0, 0, 0,
+			},
+		},
+		{
+			// https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L284-L304
+			op:         wasm.OpcodeMiscI32TruncSatF32U,
+			inputType:  wazeroir.Float32,
+			outputType: wazeroir.SignedUint32,
+			input32bit: []float32{
+				0.0, 0.0, 0x1p-149, -0x1p-149, 1.0, 0x1.19999ap+0, 1.5, 1.9, 2.0, 2147483648, 4294967040.0,
+				-0x1.ccccccp-1, -0x1.fffffep-1, 4294967296.0, -1.0, float32(math.Inf(1)), float32(math.Inf(-1)),
+				float32(math.NaN()), float32(math.NaN()), float32(math.NaN()), float32(math.NaN()),
+			},
+			expected32bit: []int32{
+				0, 0, 0, 0, 1, 1, 1, 1, 2, -2147483648, -256, 0, 0, int32(_0xffffffff), 0x00000000,
+				int32(_0xffffffff), 0x00000000, 0, 0, 0, 0,
+			},
+		},
+		{
+			// https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L355-L378
+			op:         wasm.OpcodeMiscI64TruncSatF32S,
+			inputType:  wazeroir.Float32,
+			outputType: wazeroir.SignedInt64,
+			input32bit: []float32{
+				0.0, 0.0, 0x1p-149, -0x1p-149, 1.0, 0x1.19999ap+0, 1.5, -1.0, -0x1.19999ap+0, -1.5, -1.9, -2.0, 4294967296,
+				-4294967296, 9223371487098961920.0, -9223372036854775808.0, 9223372036854775808.0, -9223373136366403584.0,
+				float32(math.Inf(1)), float32(math.Inf(-1)), float32(math.NaN()), float32(math.NaN()), float32(math.NaN()),
+				float32(math.NaN()),
+			},
+			expected64bit: []int64{
+				0, 0, 0, 0, 1, 1, 1, -1, -1, -1, -1, -2, 4294967296, -4294967296, 9223371487098961920, -9223372036854775808,
+				0x7fffffffffffffff, int64(_0x8000000000000000), 0x7fffffffffffffff, int64(_0x8000000000000000), 0, 0, 0, 0,
+			},
+		},
+		{
+			// https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L380-L398
+			op:         wasm.OpcodeMiscI64TruncSatF32U,
+			inputType:  wazeroir.Float32,
+			outputType: wazeroir.SignedUint64,
+			input32bit: []float32{
+				0.0, 0.0, 0x1p-149, -0x1p-149, 1.0, 0x1.19999ap+0, 1.5, 4294967296,
+				18446742974197923840.0, -0x1.ccccccp-1, -0x1.fffffep-1, 18446744073709551616.0, -1.0,
+				float32(math.Inf(1)), float32(math.Inf(-1)), float32(math.NaN()), float32(math.NaN()),
+				float32(math.NaN()), float32(math.NaN()),
+			},
+			expected64bit: []int64{
+				0, 0, 0, 0, 1, 1, 1,
+				4294967296, -1099511627776, 0, 0, int64(_0xffffffffffffffff), 0x0000000000000000,
+				int64(_0xffffffffffffffff), 0x0000000000000000, 0, 0, 0, 0,
+			},
+		},
+		{
+			// https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L306-L327
+			op:         wasm.OpcodeMiscI32TruncSatF64S,
+			inputType:  wazeroir.Float64,
+			outputType: wazeroir.SignedInt32,
+			input64bit: []float64{
+				0.0, 0.0, 0x0.0000000000001p-1022, -0x0.0000000000001p-1022, 1.0, 0x1.199999999999ap+0, 1.5, -1.0,
+				-0x1.199999999999ap+0, -1.5, -1.9, -2.0, 2147483647.0, -2147483648.0, 2147483648.0,
+				-2147483649.0, math.Inf(1), math.Inf(-1), math.NaN(), math.NaN(), math.NaN(), math.NaN(),
+			},
+			expected32bit: []int32{
+				0, 0, 0, 0, 1, 1, 1, -1, -1, -1, -1, -2,
+				2147483647, -2147483648, 0x7fffffff, int32(_0x80000000), 0x7fffffff, int32(_0x80000000), 0,
+				0, 0, 0,
+			},
+		},
+		{
+			// https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L329-L353
+			op:         wasm.OpcodeMiscI32TruncSatF64U,
+			inputType:  wazeroir.Float64,
+			outputType: wazeroir.SignedUint32,
+			input64bit: []float64{
+				0.0, 0.0, 0x0.0000000000001p-1022, -0x0.0000000000001p-1022, 1.0, 0x1.199999999999ap+0, 1.5, 1.9, 2.0,
+				2147483648, 4294967295.0, -0x1.ccccccccccccdp-1, -0x1.fffffffffffffp-1, 1e8, 4294967296.0, -1.0, 1e16, 1e30,
+				9223372036854775808, math.Inf(1), math.Inf(-1), math.NaN(), math.NaN(), math.NaN(), math.NaN(),
+			},
+			expected32bit: []int32{
+				0, 0, 0, 0, 1, 1, 1, 1, 2, -2147483648, -1,
+				0, 0, 100000000, int32(_0xffffffff), 0x00000000, int32(_0xffffffff), int32(_0xffffffff), int32(_0xffffffff),
+				int32(_0xffffffff), 0x00000000, 0, 0, 0, 0,
+			},
+		},
+		{
+			// https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L400-L423
+			op:         wasm.OpcodeMiscI64TruncSatF64S,
+			inputType:  wazeroir.Float64,
+			outputType: wazeroir.SignedInt64,
+			input64bit: []float64{
+				0.0, 0.0, 0x0.0000000000001p-1022, -0x0.0000000000001p-1022, 1.0, 0x1.199999999999ap+0, 1.5, -1.0,
+				-0x1.199999999999ap+0, -1.5, -1.9, -2.0, 4294967296, -4294967296, 9223372036854774784.0, -9223372036854775808.0,
+				9223372036854775808.0, -9223372036854777856.0, math.Inf(1), math.Inf(-1), math.NaN(), math.NaN(), math.NaN(),
+				math.NaN(),
+			},
+			expected64bit: []int64{
+				0, 0, 0, 0, 1, 1, 1, -1, -1, -1, -1, -2,
+				4294967296, -4294967296, 9223372036854774784, -9223372036854775808, 0x7fffffffffffffff,
+				int64(_0x8000000000000000), 0x7fffffffffffffff, int64(_0x8000000000000000), 0, 0, 0, 0,
+			},
+		},
+		{
+			// https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L425-L447
+			op:         wasm.OpcodeMiscI64TruncSatF64U,
+			inputType:  wazeroir.Float64,
+			outputType: wazeroir.SignedUint64,
+			input64bit: []float64{
+				0.0, 0.0, 0x0.0000000000001p-1022, -0x0.0000000000001p-1022, 1.0, 0x1.199999999999ap+0, 1.5, 4294967295, 4294967296,
+				18446744073709549568.0, -0x1.ccccccccccccdp-1, -0x1.fffffffffffffp-1, 1e8, 1e16, 9223372036854775808,
+				18446744073709551616.0, -1.0, math.Inf(1), math.Inf(-1), math.NaN(), math.NaN(), math.NaN(), math.NaN(),
+			},
+			expected64bit: []int64{
+				0, 0, 0, 0, 1, 1, 1, 0xffffffff, 0x100000000, -2048, 0, 0, 100000000, 10000000000000000,
+				-9223372036854775808, int64(_0xffffffffffffffff), 0x0000000000000000, int64(_0xffffffffffffffff),
+				0x0000000000000000, 0, 0, 0, 0,
+			},
+		},
+	} {
+		tc := tc
+		t.Run(wasm.MiscInstructionName(tc.op), func(t *testing.T) {
+			in32bit := len(tc.input32bit) > 0
+			casenum := len(tc.input32bit)
+			if !in32bit {
+				casenum = len(tc.input64bit)
+			}
+			for i := 0; i < casenum; i++ {
+				i := i
+				t.Run(strconv.Itoa(i), func(t *testing.T) {
+					var body []*interpreterOp
+					if in32bit {
+						body = append(body, &interpreterOp{kind: wazeroir.OperationKindConstF32,
+							us: []uint64{uint64(math.Float32bits(tc.input32bit[i]))}})
+					} else {
+						body = append(body, &interpreterOp{kind: wazeroir.OperationKindConstF64,
+							us: []uint64{uint64(math.Float64bits(tc.input64bit[i]))}})
+					}
+
+					body = append(body, &interpreterOp{
+						kind: wazeroir.OperationKindITruncFromF,
+						b1:   byte(tc.inputType),
+						b2:   byte(tc.outputType),
+						b3:   true, // NonTrapping = true.
+					})
+
+					// Return from function.
+					body = append(body,
+						&interpreterOp{kind: wazeroir.OperationKindBr, us: []uint64{math.MaxUint64}},
+					)
+
+					ce := &callEngine{}
+					f := &function{
+						source: &wasm.FunctionInstance{Module: &wasm.ModuleInstance{Engine: &moduleEngine{}}},
+						body:   body,
+					}
+					ce.callNativeFunc(testCtx, &wasm.CallContext{}, f)
+
+					if len(tc.expected32bit) > 0 {
+						require.Equal(t, tc.expected32bit[i], int32(uint32(ce.popValue())))
+					} else {
+						require.Equal(t, tc.expected64bit[i], int64((ce.popValue())))
+					}
+				})
+			}
+		})
+
+	}
+}
+
 func TestInterpreter_CallEngine_callNativeFunc_signExtend(t *testing.T) {
 	translateToIROperationKind := func(op wasm.Opcode) (kind wazeroir.OperationKind) {
 		switch op {
diff --git a/internal/wasm/jit/jit_conversion_test.go b/internal/wasm/jit/jit_conversion_test.go
index dbc2f58b..66d91992 100644
--- a/internal/wasm/jit/jit_conversion_test.go
+++ b/internal/wasm/jit/jit_conversion_test.go
@@ -141,8 +141,9 @@ func TestCompiler_compileExtend(t *testing.T) {
 
 func TestCompiler_compileITruncFromF(t *testing.T) {
 	for _, tc := range []struct {
-		outputType wazeroir.SignedInt
-		inputType  wazeroir.Float
+		outputType  wazeroir.SignedInt
+		inputType   wazeroir.Float
+		nonTrapping bool
 	}{
 		{outputType: wazeroir.SignedInt32, inputType: wazeroir.Float32},
 		{outputType: wazeroir.SignedInt32, inputType: wazeroir.Float64},
@@ -152,12 +153,21 @@ func TestCompiler_compileITruncFromF(t *testing.T) {
 		{outputType: wazeroir.SignedUint32, inputType: wazeroir.Float64},
 		{outputType: wazeroir.SignedUint64, inputType: wazeroir.Float32},
 		{outputType: wazeroir.SignedUint64, inputType: wazeroir.Float64},
+		{outputType: wazeroir.SignedInt32, inputType: wazeroir.Float32, nonTrapping: true},
+		{outputType: wazeroir.SignedInt32, inputType: wazeroir.Float64, nonTrapping: true},
+		{outputType: wazeroir.SignedInt64, inputType: wazeroir.Float32, nonTrapping: true},
+		{outputType: wazeroir.SignedInt64, inputType: wazeroir.Float64, nonTrapping: true},
+		{outputType: wazeroir.SignedUint32, inputType: wazeroir.Float32, nonTrapping: true},
+		{outputType: wazeroir.SignedUint32, inputType: wazeroir.Float64, nonTrapping: true},
+		{outputType: wazeroir.SignedUint64, inputType: wazeroir.Float32, nonTrapping: true},
+		{outputType: wazeroir.SignedUint64, inputType: wazeroir.Float64, nonTrapping: true},
 	} {
 		tc := tc
-		t.Run(fmt.Sprintf("%s from %s", tc.outputType, tc.inputType), func(t *testing.T) {
+		t.Run(fmt.Sprintf("%s from %s (non-trapping=%v)", tc.outputType, tc.inputType, tc.nonTrapping), func(t *testing.T) {
 			for _, v := range []float64{
 				1.0, 100, -100, 1, -1, 100.01234124, -100.01234124, 200.12315,
-				6.8719476736e+10 /* = 1 << 36 */, -6.8719476736e+10, 1.37438953472e+11, /* = 1 << 37 */
+				6.8719476736e+10,                     /* = 1 << 36 */
+				-6.8719476736e+10, 1.37438953472e+11, /* = 1 << 37 */
 				-1.37438953472e+11, -2147483649.0, 2147483648.0, math.MinInt32,
 				math.MaxInt32, math.MaxUint32, math.MinInt64, math.MaxInt64,
 				math.MaxUint64, math.MaxFloat32, math.SmallestNonzeroFloat32, math.MaxFloat64,
@@ -195,7 +205,7 @@ func TestCompiler_compileITruncFromF(t *testing.T) {
 					require.NoError(t, err)
 
 					err = compiler.compileITruncFromF(&wazeroir.OperationITruncFromF{
-						InputType: tc.inputType, OutputType: tc.outputType,
+						InputType: tc.inputType, OutputType: tc.outputType, NonTrapping: tc.nonTrapping,
 					})
 					require.NoError(t, err)
 
@@ -210,67 +220,142 @@ func TestCompiler_compileITruncFromF(t *testing.T) {
 					// Check the result.
 					expStatus := jitCallStatusCodeReturned
 					if math.IsNaN(v) {
-						expStatus = jitCallStatusCodeInvalidFloatToIntConversion
+						if tc.nonTrapping {
+							v = 0
+						} else {
+							expStatus = jitCallStatusCodeInvalidFloatToIntConversion
+						}
 					}
 					if tc.inputType == wazeroir.Float32 && tc.outputType == wazeroir.SignedInt32 {
 						f32 := float32(v)
+						exp := int32(math.Trunc(float64(f32)))
 						if f32 < math.MinInt32 || f32 >= math.MaxInt32 {
-							expStatus = jitCallStatusIntegerOverflow
+							if tc.nonTrapping {
+								if f32 < 0 {
+									exp = math.MinInt32
+								} else {
+									exp = math.MaxInt32
+								}
+							} else {
+								expStatus = jitCallStatusIntegerOverflow
+							}
 						}
 						if expStatus == jitCallStatusCodeReturned {
-							require.Equal(t, int32(math.Trunc(float64(f32))), env.stackTopAsInt32())
+							require.Equal(t, exp, env.stackTopAsInt32())
 						}
 					} else if tc.inputType == wazeroir.Float32 && tc.outputType == wazeroir.SignedInt64 {
 						f32 := float32(v)
+						exp := int64(math.Trunc(float64(f32)))
 						if f32 < math.MinInt64 || f32 >= math.MaxInt64 {
-							expStatus = jitCallStatusIntegerOverflow
+							if tc.nonTrapping {
+								if f32 < 0 {
+									exp = math.MinInt64
+								} else {
+									exp = math.MaxInt64
+								}
+							} else {
+								expStatus = jitCallStatusIntegerOverflow
+							}
 						}
 						if expStatus == jitCallStatusCodeReturned {
-							require.Equal(t, int64(math.Trunc(float64(f32))), env.stackTopAsInt64())
+							require.Equal(t, exp, env.stackTopAsInt64())
 						}
 					} else if tc.inputType == wazeroir.Float64 && tc.outputType == wazeroir.SignedInt32 {
 						if v < math.MinInt32 || v > math.MaxInt32 {
-							expStatus = jitCallStatusIntegerOverflow
+							if tc.nonTrapping {
+								if v < 0 {
+									v = math.MinInt32
+								} else {
+									v = math.MaxInt32
+								}
+							} else {
+								expStatus = jitCallStatusIntegerOverflow
+							}
 						}
 						if expStatus == jitCallStatusCodeReturned {
 							require.Equal(t, int32(math.Trunc(v)), env.stackTopAsInt32())
 						}
 					} else if tc.inputType == wazeroir.Float64 && tc.outputType == wazeroir.SignedInt64 {
+						exp := int64(math.Trunc(v))
 						if v < math.MinInt64 || v >= math.MaxInt64 {
-							expStatus = jitCallStatusIntegerOverflow
+							if tc.nonTrapping {
+								if v < 0 {
+									exp = math.MinInt64
+								} else {
+									exp = math.MaxInt64
+								}
+							} else {
+								expStatus = jitCallStatusIntegerOverflow
+							}
 						}
 						if expStatus == jitCallStatusCodeReturned {
-							require.Equal(t, int64(math.Trunc(v)), env.stackTopAsInt64())
+							require.Equal(t, exp, env.stackTopAsInt64())
 						}
 					} else if tc.inputType == wazeroir.Float32 && tc.outputType == wazeroir.SignedUint32 {
 						f32 := float32(v)
+						exp := uint32(math.Trunc(float64(f32)))
 						if f32 < 0 || f32 >= math.MaxUint32 {
-							expStatus = jitCallStatusIntegerOverflow
+							if tc.nonTrapping {
+								if v < 0 {
+									exp = 0
+								} else {
+									exp = math.MaxUint32
+								}
+							} else {
+								expStatus = jitCallStatusIntegerOverflow
+							}
 						}
 						if expStatus == jitCallStatusCodeReturned {
-							require.Equal(t, uint32(math.Trunc(float64(f32))), env.stackTopAsUint32())
+							require.Equal(t, exp, env.stackTopAsUint32())
 						}
 					} else if tc.inputType == wazeroir.Float64 && tc.outputType == wazeroir.SignedUint32 {
+						exp := uint32(math.Trunc(v))
 						if v < 0 || v > math.MaxUint32 {
-							expStatus = jitCallStatusIntegerOverflow
+							if tc.nonTrapping {
+								if v < 0 {
+									exp = 0
+								} else {
+									exp = math.MaxUint32
+								}
+							} else {
+								expStatus = jitCallStatusIntegerOverflow
+							}
 						}
 						if expStatus == jitCallStatusCodeReturned {
-							require.Equal(t, uint32(math.Trunc(v)), env.stackTopAsUint32())
+							require.Equal(t, exp, env.stackTopAsUint32())
 						}
 					} else if tc.inputType == wazeroir.Float32 && tc.outputType == wazeroir.SignedUint64 {
 						f32 := float32(v)
+						exp := uint64(math.Trunc(float64(f32)))
 						if f32 < 0 || f32 >= math.MaxUint64 {
-							expStatus = jitCallStatusIntegerOverflow
+							if tc.nonTrapping {
+								if v < 0 {
+									exp = 0
+								} else {
+									exp = math.MaxUint64
+								}
+							} else {
+								expStatus = jitCallStatusIntegerOverflow
+							}
 						}
 						if expStatus == jitCallStatusCodeReturned {
-							require.Equal(t, uint64(math.Trunc(float64(f32))), env.stackTopAsUint64())
+							require.Equal(t, exp, env.stackTopAsUint64())
 						}
 					} else if tc.inputType == wazeroir.Float64 && tc.outputType == wazeroir.SignedUint64 {
+						exp := uint64(math.Trunc(v))
 						if v < 0 || v >= math.MaxUint64 {
-							expStatus = jitCallStatusIntegerOverflow
+							if tc.nonTrapping {
+								if v < 0 {
+									exp = 0
+								} else {
+									exp = math.MaxUint64
+								}
+							} else {
+								expStatus = jitCallStatusIntegerOverflow
+							}
 						}
 						if expStatus == jitCallStatusCodeReturned {
-							require.Equal(t, uint64(math.Trunc(v)), env.stackTopAsUint64())
+							require.Equal(t, exp, env.stackTopAsUint64())
 						}
 					}
 					require.Equal(t, expStatus, env.jitStatus())
diff --git a/internal/wasm/text/decoder_test.go b/internal/wasm/text/decoder_test.go
index a7b3fa88..149a01d9 100644
--- a/internal/wasm/text/decoder_test.go
+++ b/internal/wasm/text/decoder_test.go
@@ -232,6 +232,21 @@ func TestDecodeModule(t *testing.T) {
 				},
 			},
 		},
+		{
+			name: "func nontrapping-float-to-int-conversions",
+			input: `(module
+			(func (param f32) (result i32) local.get 0 i32.trunc_sat_f32_s)
+		)`,
+			expected: &wasm.Module{
+				TypeSection:     []*wasm.FunctionType{f32_i32},
+				FunctionSection: []wasm.Index{0},
+				CodeSection: []*wasm.Code{{Body: []byte{
+					wasm.OpcodeLocalGet, 0x00,
+					wasm.OpcodeMiscPrefix, wasm.OpcodeMiscI32TruncSatF32S,
+					wasm.OpcodeEnd,
+				}}},
+			},
+		},
 		{
 			// Spec says expand abbreviations first. It doesn't explicitly say you can't mix forms.
 			// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#abbreviations%E2%91%A0
@@ -1975,6 +1990,13 @@ func TestParseModule_Errors(t *testing.T) {
 		)`,
 			expectedErr: "2:47: i64.extend16_s invalid as feature \"sign-extension-ops\" is disabled in module.func[0]",
 		},
+		{
+			name: "func nontrapping-float-to-int-conversions disabled",
+			input: `(module
+			(func (param f32) (result i32) local.get 0 i32.trunc_sat_f32_s)
+		)`,
+			expectedErr: "2:47: i32.trunc_sat_f32_s invalid as feature \"nontrapping-float-to-int-conversion\" is disabled in module.func[0]",
+		},
 		{
 			name:           "memory over max",
 			input:          "(module (memory 1 4))",
diff --git a/internal/wasm/text/func_parser_test.go b/internal/wasm/text/func_parser_test.go
index 31dfe12c..56a68e83 100644
--- a/internal/wasm/text/func_parser_test.go
+++ b/internal/wasm/text/func_parser_test.go
@@ -43,6 +43,16 @@ func TestFuncParser(t *testing.T) {
 				wasm.OpcodeEnd,
 			}},
 		},
+		{
+			name:     "f32.const",
+			source:   "(func f32.const 306)",
+			expected: &wasm.Code{Body: []byte{wasm.OpcodeF32Const, 0x0, 0x0, 0x99, 0x43, 0x0, 0x0, 0x0, 0x0, wasm.OpcodeEnd}},
+		},
+		{
+			name:     "f64.const",
+			source:   "(func f64.const 356)",
+			expected: &wasm.Code{Body: []byte{wasm.OpcodeF64Const, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x76, 0x40, wasm.OpcodeEnd}},
+		},
 		{
 			name:     "i32.const",
 			source:   "(func i32.const 306)",
@@ -173,6 +183,82 @@ func TestFuncParser(t *testing.T) {
 				wasm.OpcodeEnd,
 			}},
 		},
+
+		// Below are changes to test/core/conversions.wast from the commit that added "nontrapping-float-to-int-conversions" support.
+		// See https://github.com/WebAssembly/spec/commit/c8fd933fa51eb0b511bce027b573aef7ee373726
+
+		{
+			name:   "i32.trunc_sat_f32_s",
+			source: "(func (param f32) (result i32) local.get 0 i32.trunc_sat_f32_s)",
+			expected: &wasm.Code{Body: []byte{
+				wasm.OpcodeLocalGet, 0x00,
+				wasm.OpcodeMiscPrefix, wasm.OpcodeMiscI32TruncSatF32S,
+				wasm.OpcodeEnd,
+			}},
+		},
+		{
+			name:   "i32.trunc_sat_f32_u",
+			source: "(func (param f32) (result i32) local.get 0 i32.trunc_sat_f32_u)",
+			expected: &wasm.Code{Body: []byte{
+				wasm.OpcodeLocalGet, 0x00,
+				wasm.OpcodeMiscPrefix, wasm.OpcodeMiscI32TruncSatF32U,
+				wasm.OpcodeEnd,
+			}},
+		},
+		{
+			name:   "i32.trunc_sat_f64_s",
+			source: "(func (param f64) (result i32) local.get 0 i32.trunc_sat_f64_s)",
+			expected: &wasm.Code{Body: []byte{
+				wasm.OpcodeLocalGet, 0x00,
+				wasm.OpcodeMiscPrefix, wasm.OpcodeMiscI32TruncSatF64S,
+				wasm.OpcodeEnd,
+			}},
+		},
+		{
+			name:   "i32.trunc_sat_f64_u",
+			source: "(func (param f64) (result i32) local.get 0 i32.trunc_sat_f64_u)",
+			expected: &wasm.Code{Body: []byte{
+				wasm.OpcodeLocalGet, 0x00,
+				wasm.OpcodeMiscPrefix, wasm.OpcodeMiscI32TruncSatF64U,
+				wasm.OpcodeEnd,
+			}},
+		},
+		{
+			name:   "i64.trunc_sat_f32_s",
+			source: "(func (param f32) (result i64) local.get 0 i64.trunc_sat_f32_s)",
+			expected: &wasm.Code{Body: []byte{
+				wasm.OpcodeLocalGet, 0x00,
+				wasm.OpcodeMiscPrefix, wasm.OpcodeMiscI64TruncSatF32S,
+				wasm.OpcodeEnd,
+			}},
+		},
+		{
+			name:   "i64.trunc_sat_f32_u",
+			source: "(func (param f32) (result i64) local.get 0 i64.trunc_sat_f32_u)",
+			expected: &wasm.Code{Body: []byte{
+				wasm.OpcodeLocalGet, 0x00,
+				wasm.OpcodeMiscPrefix, wasm.OpcodeMiscI64TruncSatF32U,
+				wasm.OpcodeEnd,
+			}},
+		},
+		{
+			name:   "i64.trunc_sat_f64_s",
+			source: "(func (param f64) (result i64) local.get 0 i64.trunc_sat_f64_s)",
+			expected: &wasm.Code{Body: []byte{
+				wasm.OpcodeLocalGet, 0x00,
+				wasm.OpcodeMiscPrefix, wasm.OpcodeMiscI64TruncSatF64S,
+				wasm.OpcodeEnd,
+			}},
+		},
+		{
+			name:   "i64.trunc_sat_f64_u",
+			source: "(func (param f64) (result i64) local.get 0 i64.trunc_sat_f64_u)",
+			expected: &wasm.Code{Body: []byte{
+				wasm.OpcodeLocalGet, 0x00,
+				wasm.OpcodeMiscPrefix, wasm.OpcodeMiscI64TruncSatF64U,
+				wasm.OpcodeEnd,
+			}},
+		},
 	}
 
 	for _, tt := range tests {
@@ -336,6 +422,16 @@ func TestFuncParser_Errors(t *testing.T) {
 			source:      "(func local.get 4294967296)",
 			expectedErr: "1:17: index outside range of uint32: 4294967296",
 		},
+		{
+			name:        "f32.const overflow",
+			source:      "(func f32.const 4294967296)",
+			expectedErr: "1:17: f32 outside range of uint32: 4294967296",
+		},
+		{
+			name:        "f64.const overflow",
+			source:      "(func f64.const 18446744073709551616)",
+			expectedErr: "1:17: f64 outside range of uint64: 18446744073709551616",
+		},
 		{
 			name:        "i32.const overflow",
 			source:      "(func i32.const 4294967296)",
@@ -348,13 +444,13 @@ func TestFuncParser_Errors(t *testing.T) {
 		},
 		{
 			name:        "instruction not yet supported",
-			source:      "(func f32.const 1.1)",
-			expectedErr: "1:7: unsupported instruction: f32.const",
+			source:      "(func loop)",
+			expectedErr: "1:7: unsupported instruction: loop",
 		},
 		{
 			name:        "s-expressions not yet supported",
-			source:      "(func (f32.const 1.1))",
-			expectedErr: "1:8: TODO: s-expressions are not yet supported: f32.const",
+			source:      "(func (f64.const 1.1))",
+			expectedErr: "1:8: TODO: s-expressions are not yet supported: f64.const",
 		},
 		{
 			name:        "param after result",
@@ -391,6 +487,46 @@ func TestFuncParser_Errors(t *testing.T) {
 			source:      "(func (param i64) local.get 0 i64.extend32_s)",
 			expectedErr: "1:31: i64.extend32_s invalid as feature \"sign-extension-ops\" is disabled",
 		},
+		{
+			name:        "i32.trunc_sat_f32_s disabled",
+			source:      "(func (param f32) (result i32) local.get 0 i32.trunc_sat_f32_s)",
+			expectedErr: "1:44: i32.trunc_sat_f32_s invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
+		},
+		{
+			name:        "i32.trunc_sat_f32_u disabled",
+			source:      "(func (param f32) (result i32) local.get 0 i32.trunc_sat_f32_u)",
+			expectedErr: "1:44: i32.trunc_sat_f32_u invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
+		},
+		{
+			name:        "i32.trunc_sat_f64_s disabled",
+			source:      "(func (param f64) (result i32) local.get 0 i32.trunc_sat_f64_s)",
+			expectedErr: "1:44: i32.trunc_sat_f64_s invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
+		},
+		{
+			name:        "i32.trunc_sat_f64_u disabled",
+			source:      "(func (param f64) (result i32) local.get 0 i32.trunc_sat_f64_u)",
+			expectedErr: "1:44: i32.trunc_sat_f64_u invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
+		},
+		{
+			name:        "i64.trunc_sat_f32_s disabled",
+			source:      "(func (param f32) (result i64) local.get 0 i64.trunc_sat_f32_s)",
+			expectedErr: "1:44: i64.trunc_sat_f32_s invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
+		},
+		{
+			name:        "i64.trunc_sat_f32_u disabled",
+			source:      "(func (param f32) (result i64) local.get 0 i64.trunc_sat_f32_u)",
+			expectedErr: "1:44: i64.trunc_sat_f32_u invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
+		},
+		{
+			name:        "i64.trunc_sat_f64_s disabled",
+			source:      "(func (param f64) (result i64) local.get 0 i64.trunc_sat_f64_s)",
+			expectedErr: "1:44: i64.trunc_sat_f64_s invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
+		},
+		{
+			name:        "i64.trunc_sat_f64_u disabled",
+			source:      "(func (param f64) (result i64) local.get 0 i64.trunc_sat_f64_u)",
+			expectedErr: "1:44: i64.trunc_sat_f64_u invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
+		},
 	}
 
 	for _, tt := range tests {
diff --git a/internal/wasm/text/type_parser_test.go b/internal/wasm/text/type_parser_test.go
index cd57f783..0bbfca26 100644
--- a/internal/wasm/text/type_parser_test.go
+++ b/internal/wasm/text/type_parser_test.go
@@ -12,6 +12,7 @@ var (
 	i32_v                           = &wasm.FunctionType{Params: []wasm.ValueType{i32}}
 	v_i32                           = &wasm.FunctionType{Results: []wasm.ValueType{i32}}
 	v_i32i64                        = &wasm.FunctionType{Results: []wasm.ValueType{i32, i64}}
+	f32_i32                         = &wasm.FunctionType{Params: []wasm.ValueType{f32}, Results: []wasm.ValueType{i32}}
 	i64_i64                         = &wasm.FunctionType{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i64}}
 	i32i64_v                        = &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}}
 	i32i32_i32                      = &wasm.FunctionType{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}}
diff --git a/internal/wazeroir/compiler_test.go b/internal/wazeroir/compiler_test.go
index 9e6031a0..eb32ce55 100644
--- a/internal/wazeroir/compiler_test.go
+++ b/internal/wazeroir/compiler_test.go
@@ -14,11 +14,12 @@ import (
 var ctx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
 
 var (
-	f64, i32   = wasm.ValueTypeF64, wasm.ValueTypeI32
-	i32_i32    = &wasm.FunctionType{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32}}
-	i32i32_i32 = &wasm.FunctionType{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}}
-	v_v        = &wasm.FunctionType{}
-	v_f64f64   = &wasm.FunctionType{Results: []wasm.ValueType{f64, f64}}
+	f32, f64, i32 = wasm.ValueTypeF32, wasm.ValueTypeF64, wasm.ValueTypeI32
+	f32_i32       = &wasm.FunctionType{Params: []wasm.ValueType{f32}, Results: []wasm.ValueType{i32}}
+	i32_i32       = &wasm.FunctionType{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32}}
+	i32i32_i32    = &wasm.FunctionType{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}}
+	v_v           = &wasm.FunctionType{}
+	v_f64f64      = &wasm.FunctionType{Results: []wasm.ValueType{f64, f64}}
 )
 
 func TestCompile(t *testing.T) {
@@ -149,6 +150,58 @@ func TestCompile_Block(t *testing.T) {
 	}
 }
 
+// TestCompile_SignExtensionOps picks an arbitrary operator from "sign-extension-ops".
+func TestCompile_SignExtensionOps(t *testing.T) {
+	module := requireModuleText(t, `(module
+  (func (param i32) (result i32) local.get 0 i32.extend8_s)
+)`)
+
+	expected := &CompilationResult{
+		Operations: []Operation{ // begin with params: [$0]
+			&OperationPick{Depth: 0},                                 // [$0, $0]
+			&OperationSignExtend32From8{},                            // [$0, i32.extend8_s($0)]
+			&OperationDrop{Depth: &InclusiveRange{Start: 1, End: 1}}, // [i32.extend8_s($0)]
+			&OperationBr{Target: &BranchTarget{}},                    // return!
+		},
+		LabelCallers: map[string]uint32{},
+		Signature:    i32_i32,
+		Functions:    []wasm.Index{0},
+		Types:        []*wasm.FunctionType{i32_i32},
+	}
+
+	res, err := CompileFunctions(ctx, wasm.FeatureSignExtensionOps, module)
+	require.NoError(t, err)
+	require.Equal(t, expected, res[0])
+}
+
+// TestCompile_NonTrappingFloatToIntConversion picks an arbitrary operator from "nontrapping-float-to-int-conversion".
+func TestCompile_NonTrappingFloatToIntConversion(t *testing.T) {
+	module := requireModuleText(t, `(module
+  (func (param f32) (result i32) local.get 0 i32.trunc_sat_f32_s)
+)`)
+
+	expected := &CompilationResult{
+		Operations: []Operation{ // begin with params: [$0]
+			&OperationPick{Depth: 0}, // [$0, $0]
+			&OperationITruncFromF{ // [$0, i32.trunc_sat_f32_s($0)]
+				InputType:   Float32,
+				OutputType:  SignedInt32,
+				NonTrapping: true,
+			},
+			&OperationDrop{Depth: &InclusiveRange{Start: 1, End: 1}}, // [i32.trunc_sat_f32_s($0)]
+			&OperationBr{Target: &BranchTarget{}},                    // return!
+		},
+		LabelCallers: map[string]uint32{},
+		Signature:    f32_i32,
+		Functions:    []wasm.Index{0},
+		Types:        []*wasm.FunctionType{f32_i32},
+	}
+
+	res, err := CompileFunctions(ctx, wasm.FeatureNonTrappingFloatToIntConversion, module)
+	require.NoError(t, err)
+	require.Equal(t, expected, res[0])
+}
+
 func TestCompile_MultiValue(t *testing.T) {
 	i32i32_i32i32 := &wasm.FunctionType{Params: []wasm.ValueType{
 		wasm.ValueTypeI32, wasm.ValueTypeI32},
diff --git a/internal/wazeroir/signature_test.go b/internal/wazeroir/signature_test.go
new file mode 100644
index 00000000..9a8214c7
--- /dev/null
+++ b/internal/wazeroir/signature_test.go
@@ -0,0 +1,65 @@
+package wazeroir
+
+import (
+	"testing"
+
+	"github.com/tetratelabs/wazero/internal/testing/require"
+	"github.com/tetratelabs/wazero/internal/wasm"
+)
+
+func TestCompiler_wasmOpcodeSignature(t *testing.T) {
+	for _, tc := range []struct {
+		name string
+		body []byte
+		exp  *signature
+	}{
+		{
+			name: "i32.trunc_sat_f32_s",
+			body: []byte{wasm.OpcodeMiscPrefix, wasm.OpcodeMiscI32TruncSatF32S},
+			exp:  signature_F32_I32,
+		},
+		{
+			name: "i32.trunc_sat_f32_u",
+			body: []byte{wasm.OpcodeMiscPrefix, wasm.OpcodeMiscI32TruncSatF32U},
+			exp:  signature_F32_I32,
+		},
+		{
+			name: "i32.trunc_sat_f64_s",
+			body: []byte{wasm.OpcodeMiscPrefix, wasm.OpcodeMiscI32TruncSatF64S},
+			exp:  signature_F64_I32,
+		},
+		{
+			name: "i32.trunc_sat_f64_u",
+			body: []byte{wasm.OpcodeMiscPrefix, wasm.OpcodeMiscI32TruncSatF64U},
+			exp:  signature_F64_I32,
+		},
+		{
+			name: "i64.trunc_sat_f32_s",
+			body: []byte{wasm.OpcodeMiscPrefix, wasm.OpcodeMiscI64TruncSatF32S},
+			exp:  signature_F32_I64,
+		},
+		{
+			name: "i64.trunc_sat_f32_u",
+			body: []byte{wasm.OpcodeMiscPrefix, wasm.OpcodeMiscI64TruncSatF32U},
+			exp:  signature_F32_I64,
+		},
+		{
+			name: "i64.trunc_sat_f64_s",
+			body: []byte{wasm.OpcodeMiscPrefix, wasm.OpcodeMiscI64TruncSatF64S},
+			exp:  signature_F64_I64,
+		},
+		{
+			name: "i64.trunc_sat_f64_u",
+			body: []byte{wasm.OpcodeMiscPrefix, wasm.OpcodeMiscI64TruncSatF64U},
+			exp:  signature_F64_I64,
+		},
+	} {
+
+		t.Run(tc.name, func(t *testing.T) {
+			c := &compiler{body: tc.body}
+			actual, err := c.wasmOpcodeSignature(tc.body[0], 0)
+			require.NoError(t, err)
+			require.Equal(t, tc.exp, actual)
+		})
+	}
+}