diff --git a/doc/changelog.rst b/doc/changelog.rst index 6d917055..72ece7b1 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -70,6 +70,10 @@ General - Sensors of type :ref:`user` no longer require :at:`objtype` and :at:`objname`. If unspecified, the objtype will be :ref:`mjOBJ_UNKNOWN`. ``user`` sensors :at:`datatype` default is now :at-val:`"real"`. - Add support for capsules in URDF import. +- On macOS, issue an informative error message when run under `Rosetta 2 `_ + translation on an Apple Silicon machine. Pre-built MuJoCo binaries make use of + `AVX `_ instructions on x86-64 machines, which is not + supported by Rosetta 2. (Before this version, users only get a cryptic "Illegal instruction" message.) Simulate ^^^^^^^^ diff --git a/python/mujoco/__init__.py b/python/mujoco/__init__.py index 5fb63ec4..191a4ff4 100644 --- a/python/mujoco/__init__.py +++ b/python/mujoco/__init__.py @@ -23,6 +23,18 @@ import subprocess _SYSTEM = platform.system() if _SYSTEM == 'Windows': ctypes.WinDLL(os.path.join(os.path.dirname(__file__), 'mujoco.dll')) +elif _SYSTEM == 'Darwin': + proc_translated = subprocess.run( + ['sysctl', '-n', 'sysctl.proc_translated'], capture_output=True).stdout + try: + is_rosetta = bool(int(proc_translated)) + except ValueError: + is_rosetta = False + if is_rosetta and platform.machine() == 'x86_64': + raise ImportError( + 'You are running an x86_64 build of Python on an Apple Silicon ' + 'machine. This is not supported by MuJoCo. Please install and run a ' + 'native, arm64 build of Python.') from mujoco._callbacks import * from mujoco._constants import * diff --git a/simulate/CMakeLists.txt b/simulate/CMakeLists.txt index dfbffcb2..6708fa35 100644 --- a/simulate/CMakeLists.txt +++ b/simulate/CMakeLists.txt @@ -122,7 +122,7 @@ target_include_directories(libsimulate PUBLIC $ -std::string getSavePath(const char* filename) { +std::string GetSavePath(const char* filename) { NSSavePanel* panel = [NSSavePanel savePanel]; NSURL* userDocumentsDir = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject; @@ -31,3 +31,14 @@ std::string getSavePath(const char* filename) { return ""; } } + +#ifdef __AVX__ +void DisplayErrorDialogBox(const char* title, const char* msg) { + NSAlert *alert = [[[NSAlert alloc] init] autorelease]; + [alert setMessageText:[NSString stringWithUTF8String:title]]; + [alert setInformativeText:[NSString stringWithUTF8String:msg]]; + [alert setAlertStyle:NSAlertStyleCritical]; + [alert addButtonWithTitle:@"Exit"]; + [alert runModal]; +} +#endif diff --git a/simulate/main.cc b/simulate/main.cc index c182e8aa..704b38f5 100644 --- a/simulate/main.cc +++ b/simulate/main.cc @@ -437,8 +437,25 @@ void PhysicsThread(mj::Simulate* sim, const char* filename) { //------------------------------------------ main -------------------------------------------------- +// machinery for replacing command line error by a macOS dialog box when running under Rosetta +#if defined(__APPLE__) && defined(__AVX__) +extern void DisplayErrorDialogBox(const char* title, const char* msg); +static const char* rosetta_error_msg = nullptr; +__attribute__((used, visibility("default"))) extern "C" void _mj_rosettaError(const char* msg) { + rosetta_error_msg = msg; +} +#endif + // run event loop int main(int argc, const char** argv) { + // display an error if running on macOS under Rosetta 2 +#if defined(__APPLE__) && defined(__AVX__) + if (rosetta_error_msg) { + DisplayErrorDialogBox("Rosetta 2 is not supported", rosetta_error_msg); + std::exit(1); + } +#endif + // print version, check compatibility std::printf("MuJoCo version %s\n", mj_versionString()); if (mjVERSION_HEADER!=mj_version()) { diff --git a/simulate/simulate.cc b/simulate/simulate.cc index 2f54878b..b20d2350 100644 --- a/simulate/simulate.cc +++ b/simulate/simulate.cc @@ -40,9 +40,9 @@ // Since the dialog box logic needs to be written in Objective-C, we separate it into a different // source file. #ifdef __APPLE__ -std::string getSavePath(const char* filename); +std::string GetSavePath(const char* filename); #else -static std::string getSavePath(const char* filename) { +static std::string GetSavePath(const char* filename) { return filename; } #endif @@ -1077,7 +1077,7 @@ void uiEvent(mjuiState* state) { switch (it->itemid) { case 0: // Save xml { - const std::string path = getSavePath("mjmodel.xml"); + const std::string path = GetSavePath("mjmodel.xml"); if (!path.empty() && !mj_saveLastXML(path.c_str(), m, err, 200)) { std::printf("Save XML error: %s", err); } @@ -1086,7 +1086,7 @@ void uiEvent(mjuiState* state) { case 1: // Save mjb { - const std::string path = getSavePath("mjmodel.mjb"); + const std::string path = GetSavePath("mjmodel.mjb"); if (!path.empty()) { mj_saveModel(m, path.c_str(), nullptr, 0); } @@ -1850,7 +1850,7 @@ void Simulate::render() { // Unfortunately, if we just yank ".xml"/".mjb" from the filename and append .PNG, the macOS // file dialog does not automatically open that location. Thus, we defer to a default // "screenshot.png" for now. - const std::string path = getSavePath("screenshot.png"); + const std::string path = GetSavePath("screenshot.png"); if (!path.empty()) { if (lodepng::encode(path, rgb.get(), w, h, LCT_RGB)) { mju_error("could not save screenshot"); diff --git a/src/engine/CMakeLists.txt b/src/engine/CMakeLists.txt index 6d6b1fce..fe09f920 100644 --- a/src/engine/CMakeLists.txt +++ b/src/engine/CMakeLists.txt @@ -27,6 +27,7 @@ set(MUJOCO_ENGINE_SRCS engine_core_constraint.h engine_core_smooth.c engine_core_smooth.h + engine_crossplatform.c engine_crossplatform.h engine_derivative.c engine_derivative.h diff --git a/src/engine/engine_crossplatform.c b/src/engine/engine_crossplatform.c new file mode 100644 index 00000000..c7f5f6b7 --- /dev/null +++ b/src/engine/engine_crossplatform.c @@ -0,0 +1,39 @@ +// Copyright 2022 DeepMind Technologies Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#if defined(__APPLE__) && defined(__AVX__) + +#include +#include +#include + +__attribute__((weak, visibility("default"))) void _mj_rosettaError(const char* msg) { + fprintf(stderr, "%s\n", msg); + __asm__ __volatile__ ("ud2"); // raises SIGILL but leave this function at the top of the stack +} + +__attribute__((constructor(10000), target("no-avx"))) static void _mj_checkRosetta() { + int is_translated = 0; + { + size_t len = sizeof(is_translated); + if (sysctlbyname("sysctl.proc_translated", &is_translated, &len, NULL, 0)) { + is_translated = 0; + } + } + if (is_translated) { + _mj_rosettaError("MuJoCo cannot be run under Rosetta 2 on an Apple Silicon machine."); + } +} + +#endif // defined(__APPLE__) && defined(__AVX__)