[vm/ffi] Correctly sign- or zero-extend arguments and return values from native code.

Bugs fixed: dartbug.com/36122

Change-Id: Id64429b8e808356ab19b96ef6faf48577ae962db
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/96946
Commit-Queue: Samir Jindel <sjindel@google.com>
Reviewed-by: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Aart Bik <ajcbik@google.com>
diff --git a/runtime/bin/ffi_test_functions.cc b/runtime/bin/ffi_test_functions.cc
index 248020d..6dfa08d 100644
--- a/runtime/bin/ffi_test_functions.cc
+++ b/runtime/bin/ffi_test_functions.cc
@@ -33,9 +33,60 @@
   return retval;
 }
 
-// Test 32-bit (int32_t) -> 64-bit (Dart int) sign extension and truncation.
-DART_EXPORT int32_t TestExtension() {
-  return 1UL << 31;
+//// Tests for sign and zero extension of arguments and results.
+
+DART_EXPORT uint8_t ReturnMaxUint8() {
+  return 0xff;
+}
+
+DART_EXPORT uint16_t ReturnMaxUint16() {
+  return 0xffff;
+}
+
+DART_EXPORT uint32_t ReturnMaxUint32() {
+  return 0xffffffff;
+}
+
+DART_EXPORT int8_t ReturnMinInt8() {
+  return 0x80;
+}
+
+DART_EXPORT int16_t ReturnMinInt16() {
+  return 0x8000;
+}
+
+DART_EXPORT int32_t ReturnMinInt32() {
+  return 0x80000000;
+}
+
+DART_EXPORT intptr_t TakeMaxUint8(uint8_t x) {
+  return x == 0xff ? 1 : 0;
+}
+
+DART_EXPORT intptr_t TakeMaxUint16(uint16_t x) {
+  return x == 0xffff ? 1 : 0;
+}
+
+DART_EXPORT intptr_t TakeMaxUint32(uint32_t x) {
+  return x == 0xffffffff ? 1 : 0;
+}
+
+DART_EXPORT intptr_t TakeMinInt8(int8_t x) {
+  const int64_t expected = -0x80;
+  const int64_t received = x;
+  return expected == received ? 1 : 0;
+}
+
+DART_EXPORT intptr_t TakeMinInt16(int16_t x) {
+  const int64_t expected = -0x8000;
+  const int64_t received = x;
+  return expected == received ? 1 : 0;
+}
+
+DART_EXPORT intptr_t TakeMinInt32(int32_t x) {
+  const int64_t expected = -(int32_t)0x80000000;
+  const int64_t received = x;
+  return expected == received ? 1 : 0;
 }
 
 // Performs some computation on various sized signed ints.
diff --git a/runtime/vm/compiler/backend/constant_propagator.cc b/runtime/vm/compiler/backend/constant_propagator.cc
index 7eab5b9..855e00a 100644
--- a/runtime/vm/compiler/backend/constant_propagator.cc
+++ b/runtime/vm/compiler/backend/constant_propagator.cc
@@ -1320,6 +1320,11 @@
   SetValue(instr, non_constant_);
 }
 
+void ConstantPropagator::VisitUnboxedWidthExtender(
+    UnboxedWidthExtenderInstr* instr) {
+  SetValue(instr, non_constant_);
+}
+
 void ConstantPropagator::VisitUnaryUint32Op(UnaryUint32OpInstr* instr) {
   // TODO(kmillikin): Handle unary operations.
   SetValue(instr, non_constant_);
diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h
index a16b035..d7ae5c0 100644
--- a/runtime/vm/compiler/backend/il.h
+++ b/runtime/vm/compiler/backend/il.h
@@ -450,6 +450,7 @@
   M(BoxInt32, _)                                                               \
   M(UnboxInt32, kNoGC)                                                         \
   M(UnboxedIntConverter, _)                                                    \
+  M(UnboxedWidthExtender, _)                                                   \
   M(Deoptimize, kNoGC)                                                         \
   M(SimdOp, kNoGC)
 
@@ -7500,7 +7501,58 @@
   DISALLOW_COPY_AND_ASSIGN(UnboxedIntConverterInstr);
 };
 
+// Sign- or zero-extends an integer in unboxed 32-bit representation.
 //
+// The choice between sign- and zero- extension is made based on the whether the
+// chosen representation is signed or unsigned.
+//
+// It is only supported to extend 1- or 2-byte operands; however, since we don't
+// have a representation less than 32-bits, both the input and output
+// representations are 32-bit (and equal).
+class UnboxedWidthExtenderInstr : public TemplateDefinition<1, NoThrow, Pure> {
+ public:
+  UnboxedWidthExtenderInstr(Value* value,
+                            Representation rep,
+                            intptr_t from_width_bytes)
+      : TemplateDefinition(DeoptId::kNone),
+        representation_(rep),
+        from_width_bytes_(from_width_bytes) {
+    ASSERT(from_width_bytes == 1 || from_width_bytes == 2);
+    ASSERT(rep == kUnboxedInt32 || rep == kUnboxedUint32);
+    SetInputAt(0, value);
+  }
+
+  Value* value() const { return inputs_[0]; }
+
+  Representation representation() const { return representation_; }
+
+  bool ComputeCanDeoptimize() const { return false; }
+
+  virtual Representation RequiredInputRepresentation(intptr_t idx) const {
+    ASSERT(idx == 0);
+    return representation_;
+  }
+
+  virtual bool AttributesEqual(Instruction* other) const {
+    ASSERT(other->IsUnboxedWidthExtender());
+    const UnboxedWidthExtenderInstr* ext = other->AsUnboxedWidthExtender();
+    return ext->representation() == representation() &&
+           ext->from_width_bytes_ == from_width_bytes_;
+  }
+
+  virtual CompileType ComputeType() const { return CompileType::Int(); }
+
+  DECLARE_INSTRUCTION(UnboxedWidthExtender);
+
+  PRINT_OPERANDS_TO_SUPPORT
+
+ private:
+  const Representation representation_;
+  const intptr_t from_width_bytes_;
+
+  DISALLOW_COPY_AND_ASSIGN(UnboxedWidthExtenderInstr);
+};
+
 // SimdOpInstr
 //
 // All SIMD intrinsics and recognized methods are represented via instances
diff --git a/runtime/vm/compiler/backend/il_arm.cc b/runtime/vm/compiler/backend/il_arm.cc
index bcf71bb..34bad43 100644
--- a/runtime/vm/compiler/backend/il_arm.cc
+++ b/runtime/vm/compiler/backend/il_arm.cc
@@ -6585,6 +6585,36 @@
   }
 }
 
+LocationSummary* UnboxedWidthExtenderInstr::MakeLocationSummary(
+    Zone* zone,
+    bool is_optimizing) const {
+  const intptr_t kNumTemps = 0;
+  LocationSummary* summary = new (zone)
+      LocationSummary(zone, /*num_inputs=*/InputCount(),
+                      /*num_temps=*/kNumTemps, LocationSummary::kNoCall);
+  summary->set_in(0, Location::RequiresRegister());
+  summary->set_out(0, Location::SameAsFirstInput());
+  return summary;
+}
+
+void UnboxedWidthExtenderInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
+  Register reg = locs()->in(0).reg();
+  // There are no builtin sign- or zero-extension instructions, so we'll have to
+  // use shifts instead.
+  const intptr_t shift_length = (kWordSize - from_width_bytes_) * kBitsPerByte;
+  __ Lsl(reg, reg, Operand(shift_length));
+  switch (representation_) {
+    case kUnboxedInt32:  // Sign extend operand.
+      __ Asr(reg, reg, Operand(shift_length));
+      break;
+    case kUnboxedUint32:  // Zero extend operand.
+      __ Lsr(reg, reg, Operand(shift_length));
+      break;
+    default:
+      UNREACHABLE();
+  }
+}
+
 LocationSummary* ThrowInstr::MakeLocationSummary(Zone* zone, bool opt) const {
   return new (zone) LocationSummary(zone, 0, 0, LocationSummary::kCall);
 }
diff --git a/runtime/vm/compiler/backend/il_arm64.cc b/runtime/vm/compiler/backend/il_arm64.cc
index 7e1de69..6a4649d 100644
--- a/runtime/vm/compiler/backend/il_arm64.cc
+++ b/runtime/vm/compiler/backend/il_arm64.cc
@@ -5819,6 +5819,50 @@
   }
 }
 
+LocationSummary* UnboxedWidthExtenderInstr::MakeLocationSummary(
+    Zone* zone,
+    bool is_optimizing) const {
+  const intptr_t kNumTemps = 0;
+  LocationSummary* summary = new (zone)
+      LocationSummary(zone, /*num_inputs=*/InputCount(),
+                      /*num_temps=*/kNumTemps, LocationSummary::kNoCall);
+  summary->set_in(0, Location::RequiresRegister());
+  summary->set_out(0, Location::SameAsFirstInput());
+  return summary;
+}
+
+void UnboxedWidthExtenderInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
+  Register reg = locs()->in(0).reg();
+  switch (representation_) {
+    case kUnboxedInt32:  // Sign extend operand.
+      switch (from_width_bytes_) {
+        case 1:
+          __ sxtb(reg, reg);
+          break;
+        case 2:
+          __ sxth(reg, reg);
+          break;
+        default:
+          UNREACHABLE();
+      }
+      break;
+    case kUnboxedUint32:  // Zero extend operand.
+      switch (from_width_bytes_) {
+        case 1:
+          __ uxtb(reg, reg);
+          break;
+        case 2:
+          __ uxth(reg, reg);
+          break;
+        default:
+          UNREACHABLE();
+      }
+      break;
+    default:
+      UNREACHABLE();
+  }
+}
+
 LocationSummary* ThrowInstr::MakeLocationSummary(Zone* zone, bool opt) const {
   return new (zone) LocationSummary(zone, 0, 0, LocationSummary::kCall);
 }
diff --git a/runtime/vm/compiler/backend/il_dbc.cc b/runtime/vm/compiler/backend/il_dbc.cc
index 580ffee..27f7815 100644
--- a/runtime/vm/compiler/backend/il_dbc.cc
+++ b/runtime/vm/compiler/backend/il_dbc.cc
@@ -46,7 +46,8 @@
   M(SpeculativeShiftUint32Op)                                                  \
   M(TruncDivMod)                                                               \
   M(UnaryUint32Op)                                                             \
-  M(UnboxedIntConverter)
+  M(UnboxedIntConverter)                                                       \
+  M(UnboxedWidthExtender)
 
 // List of instructions that are not used by DBC.
 // Things we aren't planning to implement for DBC:
diff --git a/runtime/vm/compiler/backend/il_ia32.cc b/runtime/vm/compiler/backend/il_ia32.cc
index 1d0b013..a935736 100644
--- a/runtime/vm/compiler/backend/il_ia32.cc
+++ b/runtime/vm/compiler/backend/il_ia32.cc
@@ -5926,6 +5926,49 @@
   }
 }
 
+LocationSummary* UnboxedWidthExtenderInstr::MakeLocationSummary(
+    Zone* zone,
+    bool is_optimizing) const {
+  const intptr_t kNumTemps = 0;
+  LocationSummary* summary = new (zone)
+      LocationSummary(zone, /*num_inputs=*/InputCount(),
+                      /*num_temps=*/kNumTemps, LocationSummary::kNoCall);
+  summary->set_in(0, Location::RegisterLocation(EAX));
+  summary->set_out(0, Location::RegisterLocation(EAX));
+  return summary;
+}
+
+void UnboxedWidthExtenderInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
+  switch (representation_) {
+    case kUnboxedInt32:  // Sign-extend operand.
+      switch (from_width_bytes_) {
+        case 1:
+          __ movsxb(EAX, AL);
+          break;
+        case 2:
+          __ movsxw(EAX, EAX);
+          break;
+        default:
+          UNREACHABLE();
+      }
+      break;
+    case kUnboxedUint32:  // Zero-extend operand.
+      switch (from_width_bytes_) {
+        case 1:
+          __ movzxb(EAX, AL);
+          break;
+        case 2:
+          __ movzxw(EAX, EAX);
+          break;
+        default:
+          UNREACHABLE();
+      }
+      break;
+    default:
+      UNREACHABLE();
+  }
+}
+
 LocationSummary* ThrowInstr::MakeLocationSummary(Zone* zone, bool opt) const {
   return new (zone) LocationSummary(zone, 0, 0, LocationSummary::kCall);
 }
diff --git a/runtime/vm/compiler/backend/il_printer.cc b/runtime/vm/compiler/backend/il_printer.cc
index c6ab009..a329b13 100644
--- a/runtime/vm/compiler/backend/il_printer.cc
+++ b/runtime/vm/compiler/backend/il_printer.cc
@@ -991,6 +991,12 @@
   Definition::PrintOperandsTo(f);
 }
 
+void UnboxedWidthExtenderInstr::PrintOperandsTo(BufferFormatter* f) const {
+  f->Print("%" Pd " -> 4 (%s), ", from_width_bytes_,
+           RepresentationToCString(representation()));
+  Definition::PrintOperandsTo(f);
+}
+
 void ParameterInstr::PrintOperandsTo(BufferFormatter* f) const {
   f->Print("%" Pd, index());
 }
diff --git a/runtime/vm/compiler/backend/il_x64.cc b/runtime/vm/compiler/backend/il_x64.cc
index 53c0b02..89a8616 100644
--- a/runtime/vm/compiler/backend/il_x64.cc
+++ b/runtime/vm/compiler/backend/il_x64.cc
@@ -6207,6 +6207,49 @@
   }
 }
 
+LocationSummary* UnboxedWidthExtenderInstr::MakeLocationSummary(
+    Zone* zone,
+    bool is_optimizing) const {
+  const intptr_t kNumTemps = 0;
+  LocationSummary* summary = new (zone)
+      LocationSummary(zone, /*num_inputs=*/InputCount(),
+                      /*num_temps=*/kNumTemps, LocationSummary::kNoCall);
+  summary->set_in(0, Location::RegisterLocation(RAX));
+  summary->set_out(0, Location::RegisterLocation(RAX));
+  return summary;
+}
+
+void UnboxedWidthExtenderInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
+  switch (representation_) {
+    case kUnboxedInt32:  // Sign extend operand.
+      switch (from_width_bytes_) {
+        case 1:
+          __ movsxb(RAX, RAX);
+          break;
+        case 2:
+          __ movsxw(RAX, RAX);
+          break;
+        default:
+          UNREACHABLE();
+      }
+      break;
+    case kUnboxedUint32:  // Zero extend operand.
+      switch (from_width_bytes_) {
+        case 1:
+          __ movzxb(RAX, RAX);
+          break;
+        case 2:
+          __ movzxw(RAX, RAX);
+          break;
+        default:
+          UNREACHABLE();
+      }
+      break;
+    default:
+      UNREACHABLE();
+  }
+}
+
 LocationSummary* ThrowInstr::MakeLocationSummary(Zone* zone, bool opt) const {
   return new (zone) LocationSummary(zone, 0, 0, LocationSummary::kCall);
 }
diff --git a/runtime/vm/compiler/ffi.h b/runtime/vm/compiler/ffi.h
index e3eca6f..2cfaf78 100644
--- a/runtime/vm/compiler/ffi.h
+++ b/runtime/vm/compiler/ffi.h
@@ -18,6 +18,10 @@
 
 namespace ffi {
 
+// On all supported platforms, the minimum width an argument must be sign- or
+// zero-extended to is 4 bytes.
+constexpr intptr_t kMinimumArgumentWidth = 4;
+
 // Storage size for an FFI type (extends 'ffi.NativeType').
 size_t ElementSizeInBytes(intptr_t class_id);
 
diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc
index 15b989b..931dc0f 100644
--- a/runtime/vm/compiler/frontend/kernel_to_il.cc
+++ b/runtime/vm/compiler/frontend/kernel_to_il.cc
@@ -2419,6 +2419,18 @@
   return Fragment(box);
 }
 
+Fragment FlowGraphBuilder::FfiUnboxedExtend(Representation representation,
+                                            const AbstractType& ffi_type) {
+  const intptr_t width =
+      compiler::ffi::ElementSizeInBytes(ffi_type.type_class_id());
+  if (width >= compiler::ffi::kMinimumArgumentWidth) return {};
+
+  auto* extend =
+      new (Z) UnboxedWidthExtenderInstr(Pop(), representation, width);
+  Push(extend);
+  return Fragment(extend);
+}
+
 Fragment FlowGraphBuilder::FfiPointerFromAddress(const Type& result_type) {
   Fragment test;
   TargetEntryInstr* null_entry;
@@ -2515,7 +2527,9 @@
       body += LoadAddressFromFfiPointer();
       body += UnboxTruncate(kUnboxedIntPtr);
     } else {
-      body += UnboxTruncate(arg_reps[pos - 1]);
+      Representation rep = arg_reps[pos - 1];
+      body += UnboxTruncate(rep);
+      body += FfiUnboxedExtend(rep, ffi_type);
     }
   }
 
@@ -2538,7 +2552,9 @@
     body += Drop();
     body += NullConstant();
   } else {
-    body += Box(compiler::ffi::ResultRepresentation(signature));
+    Representation rep = compiler::ffi::ResultRepresentation(signature);
+    body += FfiUnboxedExtend(rep, ffi_type);
+    body += Box(rep);
   }
 
   body += Return(TokenPosition::kNoSource);
diff --git a/runtime/vm/compiler/frontend/kernel_to_il.h b/runtime/vm/compiler/frontend/kernel_to_il.h
index 1e76f9a..4355383 100644
--- a/runtime/vm/compiler/frontend/kernel_to_il.h
+++ b/runtime/vm/compiler/frontend/kernel_to_il.h
@@ -217,6 +217,11 @@
   // Sign-extends kUnboxedInt32 and zero-extends kUnboxedUint32.
   Fragment Box(Representation from);
 
+  // Sign- or zero-extends an integer parameter or return value for an FFI call
+  // as necessary.
+  Fragment FfiUnboxedExtend(Representation representation,
+                            const AbstractType& ffi_type);
+
   // Pops an 'ffi.Pointer' off the stack.
   // If it's null, pushes 0.
   // Otherwise pushes the address (in boxed representation).
diff --git a/tests/standalone_2/ffi/function_test.dart b/tests/standalone_2/ffi/function_test.dart
index d22f771..2baf086 100644
--- a/tests/standalone_2/ffi/function_test.dart
+++ b/tests/standalone_2/ffi/function_test.dart
@@ -55,10 +55,10 @@
 }
 
 typedef NativeQuadOpSigned = ffi.Int64 Function(
-    ffi.Int64, ffi.Int32, ffi.Int16, ffi.Int8);
+    ffi.Int8, ffi.Int16, ffi.Int32, ffi.Int64);
 typedef QuadOp = int Function(int, int, int, int);
 typedef NativeQuadOpUnsigned = ffi.Uint64 Function(
-    ffi.Uint64, ffi.Uint32, ffi.Uint16, ffi.Uint8);
+    ffi.Uint8, ffi.Uint16, ffi.Uint32, ffi.Uint64);
 
 BinaryOp sumPlus42 =
     ffiTestFunctions.lookupFunction<NativeBinaryOp, BinaryOp>("SumPlus42");
@@ -77,23 +77,92 @@
       -0x8000000000000000, intComputation(0, 0, 0, -0x8000000000000000));
 }
 
-typedef NativeNullaryOpSigned = ffi.Int32 Function();
-typedef NativeNullaryOpUnsigned = ffi.Uint32 Function();
-
-int Function() unsignedOp = ffiTestFunctions
-    .lookup("TestExtension")
-    .cast<ffi.Pointer<ffi.NativeFunction<NativeNullaryOpUnsigned>>>()
+typedef NativeReturnMaxUint8 = ffi.Uint8 Function();
+int Function() returnMaxUint8 = ffiTestFunctions
+    .lookup("ReturnMaxUint8")
+    .cast<ffi.Pointer<ffi.NativeFunction<NativeReturnMaxUint8>>>()
     .asFunction();
 
-int Function() signedOp = ffiTestFunctions
-    .lookup("TestExtension")
-    .cast<ffi.Pointer<ffi.NativeFunction<NativeNullaryOpSigned>>>()
+typedef NativeReturnMaxUint16 = ffi.Uint16 Function();
+int Function() returnMaxUint16 = ffiTestFunctions
+    .lookup("ReturnMaxUint16")
+    .cast<ffi.Pointer<ffi.NativeFunction<NativeReturnMaxUint16>>>()
     .asFunction();
 
-// Test 32-bit (int32_t) -> 64-bit (Dart int) sign extension and truncation.
+typedef NativeReturnMaxUint32 = ffi.Uint32 Function();
+int Function() returnMaxUint32 = ffiTestFunctions
+    .lookup("ReturnMaxUint32")
+    .cast<ffi.Pointer<ffi.NativeFunction<NativeReturnMaxUint32>>>()
+    .asFunction();
+
+typedef NativeReturnMinInt8 = ffi.Int8 Function();
+int Function() returnMinInt8 = ffiTestFunctions
+    .lookup("ReturnMinInt8")
+    .cast<ffi.Pointer<ffi.NativeFunction<NativeReturnMinInt8>>>()
+    .asFunction();
+
+typedef NativeReturnMinInt16 = ffi.Int16 Function();
+int Function() returnMinInt16 = ffiTestFunctions
+    .lookup("ReturnMinInt16")
+    .cast<ffi.Pointer<ffi.NativeFunction<NativeReturnMinInt16>>>()
+    .asFunction();
+
+typedef NativeReturnMinInt32 = ffi.Int32 Function();
+int Function() returnMinInt32 = ffiTestFunctions
+    .lookup("ReturnMinInt32")
+    .cast<ffi.Pointer<ffi.NativeFunction<NativeReturnMinInt32>>>()
+    .asFunction();
+
+typedef NativeTakeMaxUint8 = ffi.IntPtr Function(ffi.Uint8);
+int Function(int) takeMaxUint8 = ffiTestFunctions
+    .lookup("TakeMaxUint8")
+    .cast<ffi.Pointer<ffi.NativeFunction<NativeTakeMaxUint8>>>()
+    .asFunction();
+
+typedef NativeTakeMaxUint16 = ffi.IntPtr Function(ffi.Uint16);
+int Function(int) takeMaxUint16 = ffiTestFunctions
+    .lookup("TakeMaxUint16")
+    .cast<ffi.Pointer<ffi.NativeFunction<NativeTakeMaxUint16>>>()
+    .asFunction();
+
+typedef NativeTakeMaxUint32 = ffi.IntPtr Function(ffi.Uint32);
+int Function(int) takeMaxUint32 = ffiTestFunctions
+    .lookup("TakeMaxUint32")
+    .cast<ffi.Pointer<ffi.NativeFunction<NativeTakeMaxUint32>>>()
+    .asFunction();
+
+typedef NativeTakeMinInt8 = ffi.IntPtr Function(ffi.Int8);
+int Function(int) takeMinInt8 = ffiTestFunctions
+    .lookup("TakeMinInt8")
+    .cast<ffi.Pointer<ffi.NativeFunction<NativeTakeMinInt8>>>()
+    .asFunction();
+
+typedef NativeTakeMinInt16 = ffi.IntPtr Function(ffi.Int16);
+int Function(int) takeMinInt16 = ffiTestFunctions
+    .lookup("TakeMinInt16")
+    .cast<ffi.Pointer<ffi.NativeFunction<NativeTakeMinInt16>>>()
+    .asFunction();
+
+typedef NativeTakeMinInt32 = ffi.IntPtr Function(ffi.Int32);
+int Function(int) takeMinInt32 = ffiTestFunctions
+    .lookup("TakeMinInt32")
+    .cast<ffi.Pointer<ffi.NativeFunction<NativeTakeMinInt32>>>()
+    .asFunction();
+
 void testExtension() {
-  Expect.equals(unsignedOp(), 0x80000000);
-  Expect.equals(signedOp(), 0xffffffff80000000);
+  Expect.equals(returnMaxUint8(), 0xff);
+  Expect.equals(returnMaxUint16(), 0xffff);
+  Expect.equals(returnMaxUint32(), 0xffffffff);
+  Expect.equals(returnMinInt8(), -0x80);
+  Expect.equals(returnMinInt16(), -0x8000);
+  Expect.equals(returnMinInt32(), -0x80000000);
+
+  Expect.equals(takeMaxUint8(0xff), 1);
+  Expect.equals(takeMaxUint16(0xffff), 1);
+  Expect.equals(takeMaxUint32(0xffffffff), 1);
+  Expect.equals(takeMinInt8(0x80), 1);
+  Expect.equals(takeMinInt16(0x8000), 1);
+  Expect.equals(takeMinInt32(0x80000000), 1);
 }
 
 QuadOp uintComputation = ffiTestFunctions
@@ -118,8 +187,6 @@
     .lookupFunction<NativeSenaryOp, SenaryOp>("SumSmallNumbers");
 
 void testTruncation() {
-  // TODO(dacoharkes): implement truncation and sign extension in trampolines
-  // for values smaller than 32 bits.
   sumSmallNumbers(128, 0, 0, 0, 0, 0);
   sumSmallNumbers(-129, 0, 0, 0, 0, 0);
   sumSmallNumbers(0, 0, 0, 256, 0, 0);