From c5349ea8ee1858cf58d1d70af88406f7448be95e Mon Sep 17 00:00:00 2001
From: Lukas Stadler <lukas.stadler@oracle.com>
Date: Thu, 12 Oct 2017 18:20:37 +0200
Subject: [PATCH] handle collation and timezones based on env variables

---
 .../builtin/base/DatePOSIXFunctions.java      |   8 +-
 .../truffle/r/nodes/builtin/base/IConv.java   |  12 +-
 .../r/nodes/builtin/base/LocaleFunctions.java |  96 +++-------
 .../truffle/r/nodes/builtin/base/Order.java   | 179 +++++++++---------
 .../truffle/r/nodes/builtin/base/Rank.java    |   6 +-
 .../oracle/truffle/r/runtime/REnvVars.java    |  35 ++--
 .../com/oracle/truffle/r/runtime/RError.java  |   1 +
 .../com/oracle/truffle/r/runtime/RLocale.java | 179 ++++++++++++++++++
 .../r/runtime/context/ChildContextInfo.java   |  30 +--
 .../truffle/r/runtime/context/RContext.java   |  33 ++--
 .../r/test/builtins/TestBuiltin_order.java    |   2 +
 .../truffle/r/test/generate/FastRSession.java |   7 +-
 .../r/test/generate/GnuROneShotRSession.java  |   2 +
 13 files changed, 358 insertions(+), 232 deletions(-)
 create mode 100644 com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RLocale.java

diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/DatePOSIXFunctions.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/DatePOSIXFunctions.java
index 56bb231456..769b288779 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/DatePOSIXFunctions.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/DatePOSIXFunctions.java
@@ -189,7 +189,7 @@ public class DatePOSIXFunctions {
         protected RList asPOSIXlt(RAbstractDoubleVector x, String tz) {
             TimeZone zone;
             if (tz.isEmpty()) {
-                zone = RContext.getInstance().getSystemTimeZone();
+                zone = RContext.getInstance().stateREnvVars.getSystemTimeZone();
             } else {
                 zone = TimeZone.getTimeZone(tz);
             }
@@ -235,7 +235,7 @@ public class DatePOSIXFunctions {
             RAbstractVector yearVector = (RAbstractVector) RRuntime.convertScalarVectors(x.getDataAt(5));
             TimeZone zone;
             if (tz.isEmpty()) {
-                zone = RContext.getInstance().getSystemTimeZone();
+                zone = RContext.getInstance().stateREnvVars.getSystemTimeZone();
             } else {
                 zone = TimeZone.getTimeZone(tz);
             }
@@ -360,7 +360,7 @@ public class DatePOSIXFunctions {
                     builder.appendLiteral(' ').appendZoneText(TextStyle.SHORT);
                 }
             } else {
-                zone = RContext.getInstance().getSystemTimeZone().toZoneId();
+                zone = RContext.getInstance().stateREnvVars.getSystemTimeZone().toZoneId();
             }
 
             DateTimeFormatter[] formatters = new DateTimeFormatter[builders.length];
@@ -406,7 +406,7 @@ public class DatePOSIXFunctions {
             TimeZone zone;
             String zoneString = RRuntime.asString(tz);
             if (zoneString.isEmpty()) {
-                zone = RContext.getInstance().getSystemTimeZone();
+                zone = RContext.getInstance().stateREnvVars.getSystemTimeZone();
             } else {
                 zone = TimeZone.getTimeZone(zoneString);
             }
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/IConv.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/IConv.java
index 4c2723335e..7a80372f4e 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/IConv.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/IConv.java
@@ -46,8 +46,10 @@ import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
 import com.oracle.truffle.r.runtime.RError;
 import com.oracle.truffle.r.runtime.RError.Message;
 import com.oracle.truffle.r.runtime.RInternalError;
+import com.oracle.truffle.r.runtime.RLocale;
 import com.oracle.truffle.r.runtime.RRuntime;
 import com.oracle.truffle.r.runtime.builtins.RBuiltin;
+import com.oracle.truffle.r.runtime.context.RContext;
 import com.oracle.truffle.r.runtime.data.RDataFactory;
 import com.oracle.truffle.r.runtime.data.RStringVector;
 import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector;
@@ -79,7 +81,7 @@ public abstract class IConv extends RBuiltinNode.Arg6 {
         Charset fromCharset = getCharset(from, from, to);
         Charset toCharset = getCharset(to, from, to);
         boolean complete = x.isComplete();
-        if (fromCharset == StandardCharsets.UTF_8 && toCharset == StandardCharsets.UTF_8) {
+        if ((fromCharset == StandardCharsets.UTF_8 || fromCharset == StandardCharsets.UTF_16) && (toCharset == StandardCharsets.UTF_8 || toCharset == StandardCharsets.UTF_16)) {
             // this conversion cannot change anything
             return x;
         } else {
@@ -117,13 +119,15 @@ public abstract class IConv extends RBuiltinNode.Arg6 {
     }
 
     private Charset getCharset(String name, String from, String to) {
-        String toCharsetName = "".equals(name) ? LocaleFunctions.LC.CTYPE.getValue() : name;
+        if (name.isEmpty()) {
+            return RContext.getInstance().stateRLocale.getCharset(RLocale.CTYPE);
+        }
         Charset toCharset;
-        if ("C".equals(toCharsetName)) {
+        if ("C".equals(name)) {
             toCharset = StandardCharsets.US_ASCII;
         } else {
             try {
-                toCharset = Charset.forName(toCharsetName);
+                toCharset = Charset.forName(name);
             } catch (IllegalCharsetNameException | UnsupportedCharsetException e) {
                 throw error(Message.UNSUPPORTED_ENCODING_CONVERSION, from, to);
             }
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/LocaleFunctions.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/LocaleFunctions.java
index 49df208d7e..bdc7684361 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/LocaleFunctions.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/LocaleFunctions.java
@@ -33,7 +33,6 @@ import static com.oracle.truffle.r.runtime.builtins.RBehavior.READS_STATE;
 import static com.oracle.truffle.r.runtime.builtins.RBuiltinKind.INTERNAL;
 import static com.oracle.truffle.r.runtime.builtins.RBuiltinKind.PRIMITIVE;
 
-import java.nio.charset.Charset;
 import java.util.Locale;
 
 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
@@ -41,6 +40,7 @@ import com.oracle.truffle.api.dsl.Specialization;
 import com.oracle.truffle.r.nodes.builtin.NodeWithArgumentCasts.Casts;
 import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
 import com.oracle.truffle.r.runtime.RError;
+import com.oracle.truffle.r.runtime.RLocale;
 import com.oracle.truffle.r.runtime.RRuntime;
 import com.oracle.truffle.r.runtime.builtins.RBuiltin;
 import com.oracle.truffle.r.runtime.context.RContext;
@@ -52,49 +52,9 @@ import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector;
 
 public class LocaleFunctions {
 
-    enum LC {
-        COLLATE(stdDefault()),
-        CTYPE(stdDefault()),
-        MONETARY(stdDefault()),
-        NUMERIC("C"),
-        TIME(stdDefault()),
-        MESSAGES(stdDefault()),
-        PAPER(""),
-        MEASUREMENT("");
-
-        private String value;
-        private String defaultValue;
-
-        LC(String defaultValue) {
-            this.defaultValue = defaultValue;
-        }
-
-        static String stdDefault() {
-            String defLocale = Locale.getDefault().toString() + "_";
-            String defCharSet = Charset.defaultCharset().name();
-            return defLocale + defCharSet;
-        }
-
-        private String getLCEnvVar() {
-            if (value != null) {
-                return value;
-            }
-            String val = RContext.getInstance().stateREnvVars.get("LC_" + name());
-            if (val == null) {
-                return defaultValue;
-            } else {
-                return val;
-            }
-        }
-
-        private static boolean isAll(int category) {
-            return category == 1;
-        }
-
-        private static LC getLCCategory(int category) {
-            return LC.values()[category - 2];
-        }
-    }
+    private static final int LC_ALL = 1;
+    private static final int MAPPING_START = 2;
+    private static final RLocale[] MAPPING = new RLocale[]{RLocale.COLLATE, RLocale.CTYPE, RLocale.MONETARY, RLocale.NUMERIC, RLocale.TIME, RLocale.MESSAGES, RLocale.PAPER, RLocale.MEASUREMENT};
 
     @RBuiltin(name = "Sys.getlocale", kind = INTERNAL, parameterNames = {"category"}, behavior = READS_STATE)
     public abstract static class GetLocale extends RBuiltinNode.Arg1 {
@@ -104,30 +64,33 @@ public class LocaleFunctions {
             CastsHelper.category(casts);
         }
 
-        private static final int[] ALL_CATEGORIES = new int[]{3, 2, 4, 5, 6, 7, 8, 9};
-
         @Specialization
         @TruffleBoundary
-        protected Object getLocale(int category) {
-            return RDataFactory.createStringVector(getLocaleData(category));
-        }
-
-        protected String getLocaleData(int category) {
-            if (LC.isAll(category)) {
-                // "LC_ALL"
+        protected static Object getLocale(int category) {
+            RContext context = RContext.getInstance();
+            if (category == LC_ALL) {
+                String singleRep = RLocale.COLLATE.getRepresentation(context);
+                for (RLocale locale : RLocale.values()) {
+                    if (locale.isListed() && !locale.getRepresentation(context).equals(singleRep)) {
+                        singleRep = null;
+                        break;
+                    }
+                }
+                if (singleRep != null) {
+                    return singleRep;
+                }
                 StringBuilder sb = new StringBuilder();
-                for (int i = 0; i < ALL_CATEGORIES.length; i++) {
-                    String d = getLocaleData(ALL_CATEGORIES[i]);
-                    if (d.length() > 0) {
-                        sb.append(d);
-                        if (i != ALL_CATEGORIES.length - 1) {
+                for (RLocale locale : RLocale.values()) {
+                    if (locale.isListed()) {
+                        if (sb.length() > 0) {
                             sb.append('/');
                         }
+                        sb.append(locale.getRepresentation(context));
                     }
                 }
                 return sb.toString();
             } else {
-                return LC.getLCCategory(category).getLCEnvVar();
+                return MAPPING[category - MAPPING_START].getRepresentation(context);
             }
         }
     }
@@ -141,17 +104,20 @@ public class LocaleFunctions {
             casts.arg("locale").mustBe(stringValue()).asStringVector().mustBe(singleElement()).findFirst();
         }
 
+        private static final RLocale[] SET_ALL = new RLocale[]{RLocale.COLLATE, RLocale.CTYPE, RLocale.MONETARY, RLocale.TIME};
+
         @Specialization
         @TruffleBoundary
-        protected Object setLocale(int category, String locale) {
-            if (LC.isAll(category)) {
-                for (LC lc : LC.values()) {
-                    lc.value = locale;
+        protected Object setLocale(int category, String value) {
+            RContext context = RContext.getInstance();
+            if (category == LC_ALL) {
+                for (RLocale locale : SET_ALL) {
+                    context.stateRLocale.setLocale(locale, value);
                 }
             } else {
-                LC.getLCCategory(category).value = locale;
+                context.stateRLocale.setLocale(MAPPING[category - MAPPING_START], value);
             }
-            return locale;
+            return GetLocale.getLocale(category);
         }
     }
 
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Order.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Order.java
index a82f8b5c89..ac41e8e535 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Order.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Order.java
@@ -18,9 +18,11 @@ import static com.oracle.truffle.r.runtime.RError.Message.INVALID_LOGICAL;
 import static com.oracle.truffle.r.runtime.builtins.RBehavior.PURE;
 import static com.oracle.truffle.r.runtime.builtins.RBuiltinKind.INTERNAL;
 
+import java.text.CollationKey;
 import java.text.Collator;
 import java.text.ParseException;
 import java.text.RuleBasedCollator;
+import java.util.Locale;
 
 import com.oracle.truffle.api.CompilerDirectives;
 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
@@ -39,9 +41,11 @@ import com.oracle.truffle.r.nodes.unary.CastToVectorNode;
 import com.oracle.truffle.r.nodes.unary.CastToVectorNodeGen;
 import com.oracle.truffle.r.runtime.RError;
 import com.oracle.truffle.r.runtime.RInternalError;
+import com.oracle.truffle.r.runtime.RLocale;
 import com.oracle.truffle.r.runtime.RRuntime;
 import com.oracle.truffle.r.runtime.RType;
 import com.oracle.truffle.r.runtime.builtins.RBuiltin;
+import com.oracle.truffle.r.runtime.context.RContext;
 import com.oracle.truffle.r.runtime.data.RArgsValuesAndNames;
 import com.oracle.truffle.r.runtime.data.RComplex;
 import com.oracle.truffle.r.runtime.data.RDataFactory;
@@ -76,17 +80,13 @@ public abstract class Order extends RPrecedenceBuiltinNode {
 
     private static final int[] SINCS = {1073790977, 268460033, 67121153, 16783361, 4197377, 1050113, 262913, 65921, 16577, 4193, 1073, 281, 77, 23, 8, 1, 0};
 
-    private RIntVector executeOrderVector1(RAbstractVector v, byte naLast, boolean dec) {
-        return executeOrderVector1(v, naLast, dec, false);
-    }
-
-    private RIntVector executeOrderVector1(RAbstractVector vIn, byte naLast, boolean dec, boolean needsStringCollation) {
+    private RIntVector executeOrderVector1(RAbstractVector vIn, byte naLast, boolean dec) {
         RAbstractVector v = vectorProfile.profile(vIn);
         int n = v.getLength();
         reportWork(n);
 
         int[] indx = createIndexes(v, n, naLast);
-        initOrderVector1(needsStringCollation).execute(indx, v, naLast, dec, null);
+        initOrderVector1().execute(indx, v, naLast, dec, true);
         for (int i = 0; i < indx.length; i++) {
             indx[i] = indx[i] + 1;
         }
@@ -134,10 +134,10 @@ public abstract class Order extends RPrecedenceBuiltinNode {
         return result;
     }
 
-    private OrderVector1Node initOrderVector1(boolean needsStringCollation) {
-        if (orderVector1Node == null || needsStringCollation && !orderVector1Node.needsStringCollation) {
+    private OrderVector1Node initOrderVector1() {
+        if (orderVector1Node == null) {
             CompilerDirectives.transferToInterpreterAndInvalidate();
-            orderVector1Node = insert(OrderVector1NodeGen.create(needsStringCollation));
+            orderVector1Node = insert(OrderVector1NodeGen.create());
         }
         return orderVector1Node;
     }
@@ -198,35 +198,19 @@ public abstract class Order extends RPrecedenceBuiltinNode {
     }
 
     @Specialization(guards = {"oneVec(args)", "isFirstStringPrecedence(args)"})
-    Object orderString(byte naLast, boolean decreasing, RArgsValuesAndNames args,
-                    @Cached("create()") BranchProfile collationProfile) {
+    Object orderString(byte naLast, boolean decreasing, RArgsValuesAndNames args) {
         RAbstractStringVector v = (RAbstractStringVector) castVector(args.getArgument(0));
-        int n = v.getLength();
-        boolean needsCollation = false;
-        outer: for (int i = 0; i < n; i++) {
-            String str = v.getDataAt(i);
-            for (int i2 = 0; i2 < str.length(); i2++) {
-                char c = str.charAt(i2);
-                if (c > 127) {
-                    collationProfile.enter();
-                    needsCollation = true;
-                    break outer;
-                }
-            }
-        }
-
-        return executeOrderVector1(v, naLast, decreasing, needsCollation);
+        return executeOrderVector1(v, naLast, decreasing);
     }
 
-    @Specialization(guards = {"oneVec(args)", "isFirstComplexPrecedence( args)"})
+    @Specialization(guards = {"oneVec(args)", "isFirstComplexPrecedence(args)"})
     Object orderComplex(byte naLast, boolean decreasing, RArgsValuesAndNames args) {
         RAbstractComplexVector v = (RAbstractComplexVector) castVector(args.getArgument(0));
         return executeOrderVector1(v, naLast, decreasing);
     }
 
-    @SuppressWarnings("unused")
-    @Specialization(guards = {"oneVec(args)", "isFirstListPrecedence( args)"})
-    Object orderList(byte naLast, boolean decreasing, RArgsValuesAndNames args) {
+    @Specialization(guards = {"oneVec(args)", "isFirstListPrecedence(args)"})
+    Object orderList(@SuppressWarnings("unused") byte naLast, @SuppressWarnings("unused") boolean decreasing, RArgsValuesAndNames args) {
         /*
          * Lists are not actually supported by GnuR but there is a corner case of a length < 2 list
          * that produces a result in GnuR and there is a unit test for that (when called via
@@ -261,6 +245,9 @@ public abstract class Order extends RPrecedenceBuiltinNode {
         return n;
     }
 
+    /*
+     * TODO: multi-element order does not honor string collation.
+     */
     @Specialization(guards = {"!oneVec(args)", "!noVec(args)"})
     Object orderMulti(byte naLast, boolean decreasing, RArgsValuesAndNames args,
                     @Cached("createEqualityProfile()") ValueProfile lengthProfile) {
@@ -320,25 +307,20 @@ public abstract class Order extends RPrecedenceBuiltinNode {
      * Also used by {@link Rank}, where the "rho" parameter is not null. TODO handle S4 objects
      * (which involves rho)
      */
+
     abstract static class OrderVector1Node extends RBaseNode {
         private final ConditionProfile decProfile = ConditionProfile.createBinaryProfile();
 
-        private final boolean needsStringCollation;
-
-        protected OrderVector1Node(boolean needsStringCollation) {
-            this.needsStringCollation = needsStringCollation;
-        }
-
-        public abstract Object execute(int[] v, Object dv, byte naLast, boolean dec, Object rho);
+        public abstract Object execute(int[] v, Object dv, byte naLast, boolean dec, boolean sortNA);
 
         @Specialization
-        protected Object orderVector1(int[] indx, RAbstractIntVector dv, byte naLast, boolean decreasing, Object rho) {
+        protected Object orderVector1(int[] indx, RAbstractIntVector dv, byte naLast, boolean decreasing, boolean sortNA) {
             if (indx.length < 2) {
                 return indx;
             }
             int lo = 0;
             int hi = indx.length - 1;
-            if (rho == null) {
+            if (sortNA) {
                 int numNa = 0;
                 if (!dv.isComplete() && !RRuntime.isNA(naLast)) {
                     boolean[] isNa = new boolean[indx.length];
@@ -370,13 +352,13 @@ public abstract class Order extends RPrecedenceBuiltinNode {
         }
 
         @Specialization
-        protected Object orderVector1(int[] indx, RAbstractDoubleVector dv, byte naLast, boolean decreasing, Object rho) {
+        protected Object orderVector1(int[] indx, RAbstractDoubleVector dv, byte naLast, boolean decreasing, boolean sortNA) {
             if (indx.length < 2) {
                 return indx;
             }
             int lo = 0;
             int hi = indx.length - 1;
-            if (rho == null && !RRuntime.isNA(naLast)) {
+            if (sortNA && !RRuntime.isNA(naLast)) {
                 int numNa = 0;
                 boolean[] isNa = new boolean[indx.length];
                 for (int i = 0; i < isNa.length; i++) {
@@ -406,13 +388,13 @@ public abstract class Order extends RPrecedenceBuiltinNode {
         }
 
         @Specialization
-        protected Object orderVector1(int[] indx, RAbstractStringVector dv, byte naLast, boolean decreasing, Object rho) {
+        protected Object orderVector1(int[] indx, RAbstractStringVector dv, byte naLast, boolean decreasing, boolean sortNA) {
             if (indx.length < 2) {
                 return indx;
             }
             int lo = 0;
             int hi = indx.length - 1;
-            if (rho == null) {
+            if (sortNA) {
                 int numNa = 0;
                 if (!dv.isComplete() && !RRuntime.isNA(naLast)) {
                     boolean[] isNa = new boolean[indx.length];
@@ -444,13 +426,13 @@ public abstract class Order extends RPrecedenceBuiltinNode {
         }
 
         @Specialization
-        protected Object orderVector1(int[] indx, RAbstractComplexVector dv, byte naLast, boolean decreasing, Object rho) {
+        protected Object orderVector1(int[] indx, RAbstractComplexVector dv, byte naLast, boolean decreasing, boolean sortNA) {
             if (indx.length < 2) {
                 return indx;
             }
             int lo = 0;
             int hi = indx.length - 1;
-            if (rho == null) {
+            if (sortNA) {
                 int numNa = 0;
                 if (!dv.isComplete() && !RRuntime.isNA(naLast)) {
                     boolean[] isNa = new boolean[indx.length];
@@ -483,7 +465,7 @@ public abstract class Order extends RPrecedenceBuiltinNode {
 
         @SuppressWarnings("unused")
         @Specialization
-        protected Object orderVector1(int[] indx, RList dv, byte naLast, boolean decreasing, Object rho) {
+        protected Object orderVector1(int[] indx, RList dv, byte naLast, boolean decreasing, boolean sortNA) {
             /* Only needed to satisfy .Internal(rank) in unit test */
             return indx;
         }
@@ -544,61 +526,76 @@ public abstract class Order extends RPrecedenceBuiltinNode {
             }
         }
 
+        @TruffleBoundary
         private void sort(int[] indx, RAbstractStringVector dv, int lo, int hi, boolean dec) {
-            Collator collator = createCollator();
             int t = 0;
             for (; SINCS[t] > hi - lo + 1; t++) {
             }
-            for (int h = SINCS[t]; t < 16; h = SINCS[++t]) {
-                for (int i = lo + h; i <= hi; i++) {
-                    int itmp = indx[i];
-                    int j = i;
-                    while (j >= lo + h) {
-                        int a = indx[j - h];
-                        int b = itmp;
-                        int c = compareString(collator, dv.getDataAt(a), dv.getDataAt(b));
-                        if (decProfile.profile(dec)) {
-                            if (!(c < 0 || (c == 0 && a > b))) {
-                                break;
-                            }
-                        } else {
-                            if (!(c > 0 || (c == 0 && a > b))) {
-                                break;
+
+            Locale locale = RContext.getInstance().stateRLocale.getLocale(RLocale.COLLATE);
+            if (locale == Locale.ROOT) {
+                // simple comparison based on numeric value of characters
+                for (int h = SINCS[t]; t < 16; h = SINCS[++t]) {
+                    for (int i = lo + h; i <= hi; i++) {
+                        int itmp = indx[i];
+                        int j = i;
+                        while (j >= lo + h) {
+                            int a = indx[j - h];
+                            int b = itmp;
+                            int c = dv.getDataAt(a).compareTo(dv.getDataAt(b));
+                            if (decProfile.profile(dec)) {
+                                if (!(c < 0 || (c == 0 && a > b))) {
+                                    break;
+                                }
+                            } else {
+                                if (!(c > 0 || (c == 0 && a > b))) {
+                                    break;
+                                }
                             }
+                            indx[j] = indx[j - h];
+                            j -= h;
                         }
-                        indx[j] = indx[j - h];
-                        j -= h;
+                        indx[j] = itmp;
                     }
-                    indx[j] = itmp;
                 }
-            }
-        }
-
-        private int compareString(Collator collator, String dataAt, String dataAt2) {
-            if (needsStringCollation) {
-                return compare(collator, dataAt, dataAt2);
             } else {
-                return dataAt.compareToIgnoreCase(dataAt2);
-            }
-        }
-
-        @TruffleBoundary
-        private static int compare(Collator collator, String dataAt, String dataAt2) {
-            return collator.compare(dataAt, dataAt2);
-        }
+                int length = dv.getLength();
+                Collator baseCollator = Collator.getInstance(locale);
+                String rules = ((RuleBasedCollator) baseCollator).getRules();
+                Collator collator;
+                try {
+                    collator = new RuleBasedCollator(rules.replaceAll("<'\u005f'", "<' '<'\u005f'"));
+                } catch (ParseException e) {
+                    throw RInternalError.shouldNotReachHere(e);
+                }
+                CollationKey[] entries = new CollationKey[length];
+                for (int i = 0; i < length; i++) {
+                    entries[i] = collator.getCollationKey(dv.getDataAt(i));
+                }
 
-        @TruffleBoundary
-        private Collator createCollator() {
-            if (!needsStringCollation) {
-                return null;
-            }
-            // add rule for space before '_'
-            Collator collator = Collator.getInstance();
-            String rules = ((RuleBasedCollator) collator).getRules();
-            try {
-                return new RuleBasedCollator(rules.replaceAll("<'\u005f'", "<' '<'\u005f'"));
-            } catch (ParseException e) {
-                throw RInternalError.shouldNotReachHere(e);
+                for (int h = SINCS[t]; t < 16; h = SINCS[++t]) {
+                    for (int i = lo + h; i <= hi; i++) {
+                        int itmp = indx[i];
+                        int j = i;
+                        while (j >= lo + h) {
+                            int a = indx[j - h];
+                            int b = itmp;
+                            int c = entries[a].compareTo(entries[b]);
+                            if (decProfile.profile(dec)) {
+                                if (!(c < 0 || (c == 0 && a > b))) {
+                                    break;
+                                }
+                            } else {
+                                if (!(c > 0 || (c == 0 && a > b))) {
+                                    break;
+                                }
+                            }
+                            indx[j] = indx[j - h];
+                            j -= h;
+                        }
+                        indx[j] = itmp;
+                    }
+                }
             }
         }
 
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Rank.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Rank.java
index d9c48f7c88..15e8e71aa7 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Rank.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Rank.java
@@ -52,8 +52,6 @@ public abstract class Rank extends RBuiltinNode.Arg3 {
     @Child private Order.CmpNode orderCmpNode;
     private final BranchProfile errorProfile = BranchProfile.create();
 
-    private static final Object rho = new Object();
-
     private enum TiesKind {
         AVERAGE,
         MAX,
@@ -76,7 +74,7 @@ public abstract class Rank extends RBuiltinNode.Arg3 {
     private Order.OrderVector1Node initOrderVector1() {
         if (orderVector1Node == null) {
             CompilerDirectives.transferToInterpreterAndInvalidate();
-            orderVector1Node = insert(OrderVector1NodeGen.create(false));
+            orderVector1Node = insert(OrderVector1NodeGen.create());
         }
         return orderVector1Node;
     }
@@ -112,7 +110,7 @@ public abstract class Rank extends RBuiltinNode.Arg3 {
             indx[i] = i;
         }
         RAbstractVector x = xa instanceof RAbstractLogicalVector ? xa.castSafe(RType.Integer, isNAProfile) : xa;
-        initOrderVector1().execute(indx, x, RRuntime.LOGICAL_TRUE, false, rho);
+        initOrderVector1().execute(indx, x, RRuntime.LOGICAL_TRUE, false, false);
         initOrderCmp();
         int j;
         for (int i = 0; i < n; i = j + 1) {
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/REnvVars.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/REnvVars.java
index c5c06f0b1b..1300b87938 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/REnvVars.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/REnvVars.java
@@ -35,6 +35,7 @@ import java.security.CodeSource;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.TimeZone;
 
 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
 import com.oracle.truffle.api.vm.PolyglotEngine;
@@ -53,15 +54,14 @@ import com.oracle.truffle.r.runtime.ffi.BaseRFFI;
  */
 public final class REnvVars implements RContext.ContextState {
 
-    private final Map<String, String> envVars = new HashMap<>(System.getenv());
+    private final HashMap<String, String> envVars;
 
-    private REnvVars() {
+    private REnvVars(Map<String, String> initialEnvVars) {
+        envVars = new HashMap<>(initialEnvVars);
     }
 
     @Override
     public RContext.ContextState initialize(RContext context) {
-        // explicit environment settings in nested contexts must be installed first
-        checkExplicitEnvSettings(context);
         RCmdOptions cmdOptions = context.getCmdOptions();
         // If running Rscript, R_DEFAULT_PACKAGES may need to be set
         String defaultPackages = cmdOptions.getString(RCmdOption.DEFAULT_PACKAGES);
@@ -125,23 +125,8 @@ public final class REnvVars implements RContext.ContextState {
         return this;
     }
 
-    public static REnvVars newContextState() {
-        return new REnvVars();
-    }
-
-    private void checkExplicitEnvSettings(RContext context) {
-        String[] envs = context.getEnvSettings();
-        if (envs == null || envs.length == 0) {
-            return;
-        }
-        for (String envdef : envs) {
-            String[] parts = envdef.split("=");
-            if (parts.length == 2) {
-                envVars.put(parts[0], parts[1]);
-            } else {
-                // for now just ignore
-            }
-        }
+    public static REnvVars newContextState(Map<String, String> initialEnvVars) {
+        return new REnvVars(initialEnvVars);
     }
 
     private String getEitherCase(String var) {
@@ -290,6 +275,14 @@ public final class REnvVars implements RContext.ContextState {
         }
     }
 
+    public TimeZone getSystemTimeZone() {
+        String tzName = envVars.get("TZ");
+        if (tzName != null) {
+            return TimeZone.getTimeZone(tzName);
+        }
+        return TimeZone.getDefault();
+    }
+
     private String expandParameters(String value) {
         StringBuilder result = new StringBuilder();
         int x = 0;
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java
index 4180cd6a52..e8d02437ed 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java
@@ -903,6 +903,7 @@ public final class RError extends RuntimeException implements TruffleException {
         FILE_NOT_FOUND_IN_ZIP("requested file not found in the zip file"),
         LIST_NO_VALID_NAMES("list argument has no valid names"),
         VALUES_MUST_BE_LENGTH("values must be length %s,\n but FUN(X[[%d]]) result is length %s"),
+        OS_REQUEST_LOCALE("OS reports request to set locale to \"%s\" cannot be honored"),
         INVALID_TYPE("invalid type (%s) for '%s' (must be a %s)");
 
         public final String message;
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RLocale.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RLocale.java
new file mode 100644
index 0000000000..ca3c2f2891
--- /dev/null
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RLocale.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.oracle.truffle.r.runtime;
+
+import java.nio.charset.Charset;
+import java.nio.charset.IllegalCharsetNameException;
+import java.nio.charset.StandardCharsets;
+import java.nio.charset.UnsupportedCharsetException;
+import java.util.EnumMap;
+import java.util.Locale;
+
+import com.oracle.truffle.r.runtime.RError.Message;
+import com.oracle.truffle.r.runtime.context.RContext;
+import com.oracle.truffle.r.runtime.context.RContext.ContextState;
+
+public enum RLocale {
+    COLLATE(true, true, true),
+    CTYPE(false, true, true),
+    MONETARY(true, true, true),
+    NUMERIC(true, false, true),
+    TIME(true, true, true),
+    MESSAGES(true, true, true),
+    PAPER(false, false, false),
+    MEASUREMENT(false, false, false);
+
+    private final String name;
+    private final boolean needsLocale;
+    private final boolean initializedAtStartup;
+    private final boolean listed;
+
+    RLocale(boolean needsLocale, boolean initializedAtStartup, boolean listed) {
+        this.needsLocale = needsLocale;
+        this.initializedAtStartup = initializedAtStartup;
+        this.listed = listed;
+        this.name = "LC_" + name();
+    }
+
+    public static final class ContextStateImpl implements RContext.ContextState {
+        private final EnumMap<RLocale, Locale> locales = new EnumMap<>(RLocale.class);
+        private final EnumMap<RLocale, Charset> charsets = new EnumMap<>(RLocale.class);
+
+        private ContextStateImpl() {
+            // private constructor
+        }
+
+        private static String getDefinition(String name, REnvVars envVars) {
+            // lookup order: LC_ALL, LC_<name>, LANG
+            for (String identifier : new String[]{"LC_ALL", name, "LANG"}) {
+                String value = envVars.get(identifier);
+                if (value != null && !value.isEmpty()) {
+                    return value;
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public ContextState initialize(RContext context) {
+            REnvVars envVars = context.stateREnvVars;
+            for (RLocale locale : RLocale.values()) {
+                if (locale.initializedAtStartup) {
+                    setLocale(locale, getDefinition(locale.name, envVars), true);
+                } else {
+                    setLocale(locale, "C", true);
+                }
+            }
+            return this;
+        }
+
+        private static Charset getCharset(String value) {
+            try {
+                int dot = value.indexOf('.');
+                return dot == -1 ? Charset.forName(value) : Charset.forName(value.substring(dot + 1));
+            } catch (UnsupportedCharsetException | IllegalCharsetNameException e) {
+                return null;
+            }
+        }
+
+        private static Locale getLocale(String value) {
+            int dot = value.indexOf('.');
+            String code = dot == -1 ? value : value.substring(0, dot);
+            Locale.Builder builder = new Locale.Builder();
+            switch (code.length()) {
+                case 2:
+                    return builder.setLanguage(code).build();
+                case 5:
+                    if (code.charAt(2) == '_') {
+                        return builder.setLanguage(code.substring(0, 2)).setRegion(code.substring(3)).build();
+                    }
+                    break;
+                default:
+                    if (code.length() >= 7 && code.charAt(2) == '_' && code.charAt(5) == '_') {
+                        return builder.setLanguage(code.substring(0, 2)).setRegion(code.substring(3)).setVariant(code.substring(6)).build();
+                    }
+                    break;
+            }
+            return null;
+        }
+
+        public void setLocale(RLocale locale, String value) {
+            setLocale(locale, value, false);
+        }
+
+        public void setLocale(RLocale locale, String value, boolean startup) {
+            Charset c = null;
+            Locale l = null;
+            if ("C".equals(value) || "POSIX".equals(value)) {
+                c = StandardCharsets.US_ASCII;
+            } else if (value != null) {
+                c = getCharset(value);
+                l = getLocale(value);
+                if ((c == null && l == null) || (l == null && locale.needsLocale)) {
+                    if (startup) {
+                        RContext.getInstance().getConsole().printErrorln("Setting " + locale.name + " failed, using default");
+                    } else {
+                        RError.warning(RError.SHOW_CALLER, Message.OS_REQUEST_LOCALE, value);
+                    }
+                }
+            }
+            charsets.put(locale, c == null ? StandardCharsets.UTF_8 : c);
+            locales.put(locale, l == null ? Locale.ROOT : l);
+        }
+
+        public Charset getCharset(RLocale locale) {
+            return charsets.get(locale);
+        }
+
+        public Locale getLocale(RLocale locale) {
+            return locales.get(locale);
+        }
+
+        public static ContextStateImpl newContextState() {
+            return new ContextStateImpl();
+        }
+    }
+
+    public String getRepresentation(RContext context) {
+        ContextStateImpl state = context.stateRLocale;
+        Locale l = state.locales.get(this);
+        Charset c = state.charsets.get(this);
+        if (c == StandardCharsets.US_ASCII) {
+            if (l == Locale.ROOT) {
+                return "C";
+            } else {
+                return l.toString();
+            }
+        } else {
+            if (l == Locale.ROOT) {
+                return c.name();
+            } else {
+                return l.toString() + "." + c.name();
+            }
+        }
+    }
+
+    public boolean isListed() {
+        return listed;
+    }
+}
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/context/ChildContextInfo.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/context/ChildContextInfo.java
index bdcd6c61d3..a40ce96e9b 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/context/ChildContextInfo.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/context/ChildContextInfo.java
@@ -25,7 +25,7 @@ package com.oracle.truffle.r.runtime.context;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.TimeZone;
+import java.util.Map;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -34,8 +34,8 @@ import com.oracle.truffle.api.TruffleContext;
 import com.oracle.truffle.api.vm.PolyglotEngine;
 import com.oracle.truffle.api.vm.PolyglotEngine.Builder;
 import com.oracle.truffle.r.launcher.RCmdOptions;
-import com.oracle.truffle.r.launcher.RStartParams;
 import com.oracle.truffle.r.launcher.RCmdOptions.Client;
+import com.oracle.truffle.r.launcher.RStartParams;
 import com.oracle.truffle.r.runtime.RInternalError;
 import com.oracle.truffle.r.runtime.context.RContext.ContextKind;
 
@@ -71,9 +71,8 @@ public final class ChildContextInfo {
     static final AtomicInteger multiSlotInds = new AtomicInteger(-1);
 
     private final RStartParams startParams;
-    private final String[] env;
+    private final Map<String, String> env;
     private final RContext.ContextKind kind;
-    private final TimeZone systemTimeZone;
 
     /**
      * Any context created by another has a parent. When such a context is destroyed we must reset
@@ -89,7 +88,8 @@ public final class ChildContextInfo {
     private PolyglotEngine vm;
     public Executor executor;
 
-    private ChildContextInfo(RStartParams startParams, String[] env, ContextKind kind, RContext parent, InputStream stdin, OutputStream stdout, OutputStream stderr, TimeZone systemTimeZone, int id,
+    private ChildContextInfo(RStartParams startParams, Map<String, String> env, ContextKind kind, RContext parent, InputStream stdin, OutputStream stdout, OutputStream stderr,
+                    int id,
                     int multiSlotInd) {
         this.startParams = startParams;
         this.env = env;
@@ -98,7 +98,6 @@ public final class ChildContextInfo {
         this.stdin = stdin;
         this.stdout = stdout;
         this.stderr = stderr;
-        this.systemTimeZone = systemTimeZone;
         this.multiSlotInd = multiSlotInd;
         this.id = id;
     }
@@ -149,10 +148,8 @@ public final class ChildContextInfo {
      * @param kind defines the degree to which this context shares base and package environments
      *            with its parent
      * @param parent if non-null {@code null}, the parent creating the context
-     * @param systemTimeZone the system's time zone
      */
-    public static ChildContextInfo create(RStartParams startParams, String[] env, ContextKind kind, RContext parent, InputStream stdin, OutputStream stdout, OutputStream stderr,
-                    TimeZone systemTimeZone) {
+    public static ChildContextInfo create(RStartParams startParams, Map<String, String> env, ContextKind kind, RContext parent, InputStream stdin, OutputStream stdout, OutputStream stderr) {
         int id = contextInfoIds.incrementAndGet();
         int multiSlotInd = multiSlotInds.get();
         if (kind == ContextKind.SHARE_ALL || kind == ContextKind.SHARE_NOTHING) {
@@ -164,23 +161,18 @@ public final class ChildContextInfo {
             throw RInternalError.shouldNotReachHere();
         }
         assert kind != ContextKind.SHARE_PARENT_RW || (kind == ContextKind.SHARE_PARENT_RW && parent.getKind() == ContextKind.SHARE_NOTHING && parent.getMultiSlotInd() == 0);
-        return new ChildContextInfo(startParams, env, kind, parent, stdin, stdout, stderr, systemTimeZone, id, kind == ContextKind.SHARE_PARENT_RW ? 0 : multiSlotInd);
-    }
-
-    public static ChildContextInfo create(RStartParams startParams, String[] env, ContextKind kind, RContext parent, InputStream stdin, OutputStream stdout, OutputStream stderr) {
-        return create(startParams, env, kind, parent, stdin, stdout, stderr, TimeZone.getDefault());
+        return new ChildContextInfo(startParams, env, kind, parent, stdin, stdout, stderr, id, kind == ContextKind.SHARE_PARENT_RW ? 0 : multiSlotInd);
     }
 
     /**
      * Create a context configuration object such that FastR does not restore previously stored
      * sessions on startup.
      *
-     * @param env TODO
      * @param kind defines the degree to which this context shares base and package environments
      *            with its parent
      * @param parent if non-null {@code null}, the parent creating the context
      */
-    public static ChildContextInfo createNoRestore(Client client, String[] env, ContextKind kind, RContext parent, InputStream stdin, OutputStream stdout, OutputStream stderr) {
+    public static ChildContextInfo createNoRestore(Client client, Map<String, String> env, ContextKind kind, RContext parent, InputStream stdin, OutputStream stdout, OutputStream stderr) {
         RStartParams params = new RStartParams(RCmdOptions.parseArguments(client, new String[]{"R", "--vanilla", "--slave", "--silent", "--no-restore"}, false), false);
         return create(params, env, kind, parent, stdin, stdout, stderr);
     }
@@ -189,7 +181,7 @@ public final class ChildContextInfo {
         return startParams;
     }
 
-    public String[] getEnv() {
+    public Map<String, String> getEnv() {
         return env;
     }
 
@@ -201,10 +193,6 @@ public final class ChildContextInfo {
         return parent;
     }
 
-    public TimeZone getSystemTimeZone() {
-        return systemTimeZone;
-    }
-
     public int getId() {
         return id;
     }
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/context/RContext.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/context/RContext.java
index 41383f5865..c20435b25a 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/context/RContext.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/context/RContext.java
@@ -40,11 +40,11 @@ import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.TimeZone;
 import java.util.WeakHashMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
@@ -80,6 +80,7 @@ import com.oracle.truffle.r.runtime.RError;
 import com.oracle.truffle.r.runtime.RErrorHandling;
 import com.oracle.truffle.r.runtime.RInternalCode.ContextStateImpl;
 import com.oracle.truffle.r.runtime.RInternalError;
+import com.oracle.truffle.r.runtime.RLocale;
 import com.oracle.truffle.r.runtime.ROptions;
 import com.oracle.truffle.r.runtime.RProfile;
 import com.oracle.truffle.r.runtime.RRuntime;
@@ -226,9 +227,7 @@ public final class RContext implements RTruffleObject {
 
     private final RStartParams startParameters;
     private final RCmdOptions cmdOptions;
-    private final String[] environment;
     private final RContext.ContextKind contextKind;
-    private final TimeZone systemTimeZone;
     public final Map<Class<?>, RootCallTarget> nativeCallTargets = new HashMap<>();
 
     public RootCallTarget getOrCreateNativeCallTarget(Class<?> clazz, Supplier<RootCallTarget> creatFunction) {
@@ -332,6 +331,7 @@ public final class RContext implements RTruffleObject {
      * processor, but the set is relatively small, so we just enumerate them here.
      */
     public final REnvVars stateREnvVars;
+    public final RLocale.ContextStateImpl stateRLocale;
     public final TempPathName stateTempPath;
     public final RProfile stateRProfile;
     public final StdConnections.ContextStateImpl stateStdConnections;
@@ -357,7 +357,8 @@ public final class RContext implements RTruffleObject {
     private final AllocationReporter allocationReporter;
 
     private ContextState[] contextStates() {
-        return new ContextState[]{stateREnvVars, stateRProfile, stateTempPath, stateROptions, stateREnvironment, stateRErrorHandling, stateRConnection, stateStdConnections, stateRNG, stateRFFI,
+        return new ContextState[]{stateREnvVars, stateRLocale, stateRProfile, stateTempPath, stateROptions, stateREnvironment, stateRErrorHandling, stateRConnection, stateStdConnections, stateRNG,
+                        stateRFFI,
                         stateRSerialize, stateLazyDBCache, stateInstrumentation, stateDLL};
     }
 
@@ -386,35 +387,33 @@ public final class RContext implements RTruffleObject {
         }
 
         Object initialInfo = env.getConfig().get(ChildContextInfo.CONFIG_KEY);
+        Map<String, String> initialEnvVars;
         if (initialInfo == null) {
             /*
              * This implies that FastR is being invoked initially from another Truffle language or
-             * via RCommand/RscriptCommand. TODO How to deciden if session state is to be restored
-             * stored session should be restored.
+             * via RCommand/RscriptCommand. TODO How to decide if session state is to be restored
              */
             this.cmdOptions = RCmdOptions.parseArguments(Client.R, args, true);
             this.startParameters = new RStartParams(cmdOptions, false);
-            this.environment = null;
             this.contextKind = ContextKind.SHARE_NOTHING;
-            this.systemTimeZone = TimeZone.getDefault();
             this.parentContext = null;
             this.id = ChildContextInfo.contextInfoIds.incrementAndGet();
             this.multiSlotIndex = 0;
-            this.truffleContext = null; // TODO
+            this.truffleContext = null;
             this.executor = null;
+            initialEnvVars = System.getenv();
         } else {
             // child spawned explicitly by R
             ChildContextInfo info = (ChildContextInfo) initialInfo;
             this.cmdOptions = RCmdOptions.parseArguments(Client.R, args, true);
             this.startParameters = info.getStartParams();
-            this.environment = info.getEnv();
             this.contextKind = info.getKind();
-            this.systemTimeZone = info.getSystemTimeZone();
             this.parentContext = info.getParent();
             this.id = info.getId();
             this.multiSlotIndex = info.getMultiSlotInd();
             this.truffleContext = info.getTruffleContext();
             this.executor = info.executor;
+            initialEnvVars = info.getEnv() == null ? Collections.emptyMap() : info.getEnv();
         }
 
         outputWelcomeMessage(startParameters);
@@ -425,7 +424,8 @@ public final class RContext implements RTruffleObject {
 
         this.initial = isInitial;
         this.env = env;
-        this.stateREnvVars = REnvVars.newContextState();
+        this.stateREnvVars = REnvVars.newContextState(initialEnvVars);
+        this.stateRLocale = RLocale.ContextStateImpl.newContextState();
         this.stateTempPath = TempPathName.newContextState();
         this.stateROptions = ROptions.ContextStateImpl.newContextState(stateREnvVars);
         this.stateRProfile = RProfile.newContextState(stateREnvVars);
@@ -502,6 +502,7 @@ public final class RContext implements RTruffleObject {
             doEnvOptionsProfileInitialization();
         }
 
+        stateRLocale.initialize(this);
         stateREnvironment.initialize(this);
         stateRErrorHandling.initialize(this);
         stateRConnection.initialize(this);
@@ -730,10 +731,6 @@ public final class RContext implements RTruffleObject {
         return startParameters;
     }
 
-    public String[] getEnvSettings() {
-        return environment;
-    }
-
     public boolean hasExecutor() {
         return executor != null;
     }
@@ -790,10 +787,6 @@ public final class RContext implements RTruffleObject {
         return loadingBase;
     }
 
-    public TimeZone getSystemTimeZone() {
-        return systemTimeZone;
-    }
-
     public String getNamespaceName() {
         return nameSpaceName;
     }
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_order.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_order.java
index c9fd1e66c5..5763cc90d5 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_order.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_order.java
@@ -164,5 +164,7 @@ public class TestBuiltin_order extends TestBase {
         assertEval("order(c('40 50', '405', '40 51', '4028', '40 20', '40 30', '404'))");
 
         assertEval("order(c(1,2,0), decreasing=NA)");
+
+        assertEval("invisible(Sys.setlocale('LC_COLLATE', 'EN_us')); str(as.data.frame(list(a=c('A wo','Far ','abc ')))); invisible(Sys.setlocale('LC_COLLATE', 'C')); str(as.data.frame(list(a=c('A wo','Far ','abc '))));");
     }
 }
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/generate/FastRSession.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/generate/FastRSession.java
index e3311fb648..b03e4a7ac0 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/generate/FastRSession.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/generate/FastRSession.java
@@ -30,7 +30,8 @@ import java.nio.CharBuffer;
 import java.nio.charset.CharsetDecoder;
 import java.nio.charset.CodingErrorAction;
 import java.nio.charset.StandardCharsets;
-import java.util.TimeZone;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Timer;
 import java.util.TimerTask;
 
@@ -113,7 +114,9 @@ public final class FastRSession implements RSession {
 
     public ChildContextInfo createContextInfo(ContextKind contextKind) {
         RStartParams params = new RStartParams(RCmdOptions.parseArguments(Client.R, new String[]{"R", "--vanilla", "--slave", "--silent", "--no-restore"}, false), false);
-        return ChildContextInfo.create(params, null, contextKind, mainContext, input, output, output, TimeZone.getTimeZone("GMT"));
+        Map<String, String> env = new HashMap<>();
+        env.put("TZ", "GMT");
+        return ChildContextInfo.create(params, env, contextKind, mainContext, input, output, output);
     }
 
     private FastRSession() {
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/generate/GnuROneShotRSession.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/generate/GnuROneShotRSession.java
index 8c6ba569e2..bc65c7c631 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/generate/GnuROneShotRSession.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/generate/GnuROneShotRSession.java
@@ -86,6 +86,8 @@ public class GnuROneShotRSession implements RSession {
         // fix time zone to "GMT" (to create consistent expected output)
         pb.environment().put("TZ", "GMT");
         pb.environment().remove("R_HOME"); // don't confuse GnuR with FastR!
+        pb.environment().remove("LC_ALL");
+        pb.environment().remove("LC_COLLATE");
         pb.redirectErrorStream(true);
         Process p = pb.start();
         p.getOutputStream().write(GNUR_OPTIONS.getBytes());
-- 
GitLab