diff options
Diffstat (limited to 'pkg/cpuid')
-rw-r--r-- | pkg/cpuid/BUILD | 35 | ||||
-rw-r--r-- | pkg/cpuid/cpuid_arm64_state_autogen.go | 51 | ||||
-rw-r--r-- | pkg/cpuid/cpuid_arm64_test.go | 55 | ||||
-rw-r--r-- | pkg/cpuid/cpuid_parse_x86_test.go | 144 | ||||
-rw-r--r-- | pkg/cpuid/cpuid_state_autogen.go | 3 | ||||
-rw-r--r-- | pkg/cpuid/cpuid_x86_state_autogen.go | 111 | ||||
-rw-r--r-- | pkg/cpuid/cpuid_x86_test.go | 243 |
7 files changed, 165 insertions, 477 deletions
diff --git a/pkg/cpuid/BUILD b/pkg/cpuid/BUILD deleted file mode 100644 index d6cb1a549..000000000 --- a/pkg/cpuid/BUILD +++ /dev/null @@ -1,35 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "cpuid", - srcs = [ - "cpu_amd64.s", - "cpuid.go", - "cpuid_arm64.go", - "cpuid_x86.go", - ], - visibility = ["//:sandbox"], - deps = ["//pkg/log"], -) - -go_test( - name = "cpuid_test", - size = "small", - srcs = [ - "cpuid_arm64_test.go", - "cpuid_x86_test.go", - ], - library = ":cpuid", -) - -go_test( - name = "cpuid_parse_test", - size = "small", - srcs = [ - "cpuid_parse_x86_test.go", - ], - library = ":cpuid", - tags = ["manual"], -) diff --git a/pkg/cpuid/cpuid_arm64_state_autogen.go b/pkg/cpuid/cpuid_arm64_state_autogen.go new file mode 100644 index 000000000..d5911799d --- /dev/null +++ b/pkg/cpuid/cpuid_arm64_state_autogen.go @@ -0,0 +1,51 @@ +// automatically generated by stateify. + +// +build arm64 + +package cpuid + +import ( + "gvisor.dev/gvisor/pkg/state" +) + +func (fs *FeatureSet) StateTypeName() string { + return "pkg/cpuid.FeatureSet" +} + +func (fs *FeatureSet) StateFields() []string { + return []string{ + "Set", + "CPUImplementer", + "CPUArchitecture", + "CPUVariant", + "CPUPartnum", + "CPURevision", + } +} + +func (fs *FeatureSet) beforeSave() {} + +func (fs *FeatureSet) StateSave(stateSinkObject state.Sink) { + fs.beforeSave() + stateSinkObject.Save(0, &fs.Set) + stateSinkObject.Save(1, &fs.CPUImplementer) + stateSinkObject.Save(2, &fs.CPUArchitecture) + stateSinkObject.Save(3, &fs.CPUVariant) + stateSinkObject.Save(4, &fs.CPUPartnum) + stateSinkObject.Save(5, &fs.CPURevision) +} + +func (fs *FeatureSet) afterLoad() {} + +func (fs *FeatureSet) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fs.Set) + stateSourceObject.Load(1, &fs.CPUImplementer) + stateSourceObject.Load(2, &fs.CPUArchitecture) + stateSourceObject.Load(3, &fs.CPUVariant) + stateSourceObject.Load(4, &fs.CPUPartnum) + stateSourceObject.Load(5, &fs.CPURevision) +} + +func init() { + state.Register((*FeatureSet)(nil)) +} diff --git a/pkg/cpuid/cpuid_arm64_test.go b/pkg/cpuid/cpuid_arm64_test.go deleted file mode 100644 index a34f67779..000000000 --- a/pkg/cpuid/cpuid_arm64_test.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// 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. - -// +build arm64 - -package cpuid - -import ( - "testing" -) - -var justFP = &FeatureSet{ - Set: map[Feature]bool{ - ARM64FeatureFP: true, - }} - -func TestHostFeatureSet(t *testing.T) { - hostFeatures := HostFeatureSet() - if len(hostFeatures.Set) == 0 { - t.Errorf("Got invalid feature set %v from HostFeatureSet()", hostFeatures) - } -} - -func TestHasFeature(t *testing.T) { - if !justFP.HasFeature(ARM64FeatureFP) { - t.Errorf("HasFeature failed, %v should contain %v", justFP, ARM64FeatureFP) - } - - if justFP.HasFeature(ARM64FeatureSM3) { - t.Errorf("HasFeature failed, %v should not contain %v", justFP, ARM64FeatureSM3) - } -} - -func TestFeatureFromString(t *testing.T) { - f, ok := FeatureFromString("asimd") - if f != ARM64FeatureASIMD || !ok { - t.Errorf("got %v want asimd", f) - } - - f, ok = FeatureFromString("bad") - if ok { - t.Errorf("got %v want nothing", f) - } -} diff --git a/pkg/cpuid/cpuid_parse_x86_test.go b/pkg/cpuid/cpuid_parse_x86_test.go deleted file mode 100644 index e4ae0d689..000000000 --- a/pkg/cpuid/cpuid_parse_x86_test.go +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// 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. - -// +build 386 amd64 - -package cpuid - -import ( - "fmt" - "io/ioutil" - "regexp" - "strconv" - "strings" - "syscall" - "testing" -) - -func kernelVersion() (int, int, error) { - var u syscall.Utsname - if err := syscall.Uname(&u); err != nil { - return 0, 0, err - } - - var sb strings.Builder - for _, b := range u.Release { - if b == 0 { - break - } - sb.WriteByte(byte(b)) - } - - s := strings.Split(sb.String(), ".") - if len(s) < 2 { - return 0, 0, fmt.Errorf("kernel release missing major and minor component: %s", sb.String()) - } - - major, err := strconv.Atoi(s[0]) - if err != nil { - return 0, 0, fmt.Errorf("error parsing major version %q in %q: %w", s[0], sb.String(), err) - } - - minor, err := strconv.Atoi(s[1]) - if err != nil { - return 0, 0, fmt.Errorf("error parsing minor version %q in %q: %w", s[1], sb.String(), err) - } - - return major, minor, nil -} - -// TestHostFeatureFlags tests that all features detected by HostFeatureSet are -// on the host. -// -// It does *not* verify that all features reported by the host are detected by -// HostFeatureSet. -// -// i.e., test that HostFeatureSet is a subset of the host features. -func TestHostFeatureFlags(t *testing.T) { - cpuinfoBytes, _ := ioutil.ReadFile("/proc/cpuinfo") - cpuinfo := string(cpuinfoBytes) - t.Logf("Host cpu info:\n%s", cpuinfo) - - major, minor, err := kernelVersion() - if err != nil { - t.Fatalf("Unable to parse kernel version: %v", err) - } - - re := regexp.MustCompile(`(?m)^flags\s+: (.*)$`) - m := re.FindStringSubmatch(cpuinfo) - if len(m) != 2 { - t.Fatalf("Unable to extract flags from %q", cpuinfo) - } - - cpuinfoFlags := make(map[string]struct{}) - for _, f := range strings.Split(m[1], " ") { - cpuinfoFlags[f] = struct{}{} - } - - fs := HostFeatureSet() - - // All features have a string and appear in host cpuinfo. - for f := range fs.Set { - name := f.flagString(false) - if name == "" { - t.Errorf("Non-parsable feature: %v", f) - } - - // Special cases not consistently visible. We don't mind if - // they are exposed in earlier versions. - switch { - // Block 0. - case f == X86FeatureSDBG && (major < 4 || major == 4 && minor < 3): - // SDBG only exposed in - // b1c599b8ff80ea79b9f8277a3f9f36a7b0cfedce (4.3). - continue - // Block 2. - case f == X86FeatureRDT && (major < 4 || major == 4 && minor < 10): - // RDT only exposed in - // 4ab1586488cb56ed8728e54c4157cc38646874d9 (4.10). - continue - // Block 3. - case f == X86FeatureAVX512VBMI && (major < 4 || major == 4 && minor < 10): - // AVX512VBMI only exposed in - // a8d9df5a509a232a959e4ef2e281f7ecd77810d6 (4.10). - continue - case f == X86FeatureUMIP && (major < 4 || major == 4 && minor < 15): - // UMIP only exposed in - // 3522c2a6a4f341058b8291326a945e2a2d2aaf55 (4.15). - continue - case f == X86FeaturePKU && (major < 4 || major == 4 && minor < 9): - // PKU only exposed in - // dfb4a70f20c5b3880da56ee4c9484bdb4e8f1e65 (4.9). - continue - // Block 4. - case f == X86FeatureXSAVES && (major < 4 || major == 4 && minor < 8): - // XSAVES only exposed in - // b8be15d588060a03569ac85dc4a0247460988f5b (4.8). - continue - // Block 5. - case f == X86FeaturePERFCTR_LLC && (major < 4 || major == 4 && minor < 14): - // PERFCTR_LLC renamed in - // 910448bbed066ab1082b510eef1ae61bb792d854 (4.14). - continue - } - - hidden := f.flagString(true) == "" - _, ok := cpuinfoFlags[name] - if hidden && ok { - t.Errorf("Unexpectedly hidden flag: %v", f) - } else if !hidden && !ok { - t.Errorf("Non-native flag: %v", f) - } - } -} diff --git a/pkg/cpuid/cpuid_state_autogen.go b/pkg/cpuid/cpuid_state_autogen.go new file mode 100644 index 000000000..86206a6bf --- /dev/null +++ b/pkg/cpuid/cpuid_state_autogen.go @@ -0,0 +1,3 @@ +// automatically generated by stateify. + +package cpuid diff --git a/pkg/cpuid/cpuid_x86_state_autogen.go b/pkg/cpuid/cpuid_x86_state_autogen.go new file mode 100644 index 000000000..023f8d742 --- /dev/null +++ b/pkg/cpuid/cpuid_x86_state_autogen.go @@ -0,0 +1,111 @@ +// automatically generated by stateify. + +// +build 386 amd64 + +package cpuid + +import ( + "gvisor.dev/gvisor/pkg/state" +) + +func (c *Cache) StateTypeName() string { + return "pkg/cpuid.Cache" +} + +func (c *Cache) StateFields() []string { + return []string{ + "Level", + "Type", + "FullyAssociative", + "Partitions", + "Ways", + "Sets", + "InvalidateHierarchical", + "Inclusive", + "DirectMapped", + } +} + +func (c *Cache) beforeSave() {} + +func (c *Cache) StateSave(stateSinkObject state.Sink) { + c.beforeSave() + stateSinkObject.Save(0, &c.Level) + stateSinkObject.Save(1, &c.Type) + stateSinkObject.Save(2, &c.FullyAssociative) + stateSinkObject.Save(3, &c.Partitions) + stateSinkObject.Save(4, &c.Ways) + stateSinkObject.Save(5, &c.Sets) + stateSinkObject.Save(6, &c.InvalidateHierarchical) + stateSinkObject.Save(7, &c.Inclusive) + stateSinkObject.Save(8, &c.DirectMapped) +} + +func (c *Cache) afterLoad() {} + +func (c *Cache) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &c.Level) + stateSourceObject.Load(1, &c.Type) + stateSourceObject.Load(2, &c.FullyAssociative) + stateSourceObject.Load(3, &c.Partitions) + stateSourceObject.Load(4, &c.Ways) + stateSourceObject.Load(5, &c.Sets) + stateSourceObject.Load(6, &c.InvalidateHierarchical) + stateSourceObject.Load(7, &c.Inclusive) + stateSourceObject.Load(8, &c.DirectMapped) +} + +func (fs *FeatureSet) StateTypeName() string { + return "pkg/cpuid.FeatureSet" +} + +func (fs *FeatureSet) StateFields() []string { + return []string{ + "Set", + "VendorID", + "ExtendedFamily", + "ExtendedModel", + "ProcessorType", + "Family", + "Model", + "SteppingID", + "Caches", + "CacheLine", + } +} + +func (fs *FeatureSet) beforeSave() {} + +func (fs *FeatureSet) StateSave(stateSinkObject state.Sink) { + fs.beforeSave() + stateSinkObject.Save(0, &fs.Set) + stateSinkObject.Save(1, &fs.VendorID) + stateSinkObject.Save(2, &fs.ExtendedFamily) + stateSinkObject.Save(3, &fs.ExtendedModel) + stateSinkObject.Save(4, &fs.ProcessorType) + stateSinkObject.Save(5, &fs.Family) + stateSinkObject.Save(6, &fs.Model) + stateSinkObject.Save(7, &fs.SteppingID) + stateSinkObject.Save(8, &fs.Caches) + stateSinkObject.Save(9, &fs.CacheLine) +} + +func (fs *FeatureSet) afterLoad() {} + +func (fs *FeatureSet) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fs.Set) + stateSourceObject.Load(1, &fs.VendorID) + stateSourceObject.Load(2, &fs.ExtendedFamily) + stateSourceObject.Load(3, &fs.ExtendedModel) + stateSourceObject.Load(4, &fs.ProcessorType) + stateSourceObject.Load(5, &fs.Family) + stateSourceObject.Load(6, &fs.Model) + stateSourceObject.Load(7, &fs.SteppingID) + stateSourceObject.Load(8, &fs.Caches) + stateSourceObject.Load(9, &fs.CacheLine) +} + +func init() { + state.Register((*Cache)(nil)) + state.Register((*FeatureSet)(nil)) +} diff --git a/pkg/cpuid/cpuid_x86_test.go b/pkg/cpuid/cpuid_x86_test.go deleted file mode 100644 index bacf345c8..000000000 --- a/pkg/cpuid/cpuid_x86_test.go +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// 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. - -// +build 386 amd64 - -package cpuid - -import ( - "testing" -) - -// These are the default values of various FeatureSet fields. -const ( - defaultVendorID = "GenuineIntel" - - // These processor signature defaults are derived from the values - // listed in Intel Application Note 485 for i7/Xeon processors. - defaultExtFamily uint8 = 0 - defaultExtModel uint8 = 1 - defaultType uint8 = 0 - defaultFamily uint8 = 0x06 - defaultModel uint8 = 0x0a - defaultSteppingID uint8 = 0 -) - -// newEmptyFeatureSet creates a new FeatureSet with a sensible default model and no features. -func newEmptyFeatureSet() *FeatureSet { - return &FeatureSet{ - Set: make(map[Feature]bool), - VendorID: defaultVendorID, - ExtendedFamily: defaultExtFamily, - ExtendedModel: defaultExtModel, - ProcessorType: defaultType, - Family: defaultFamily, - Model: defaultModel, - SteppingID: defaultSteppingID, - } -} - -var justFPU = &FeatureSet{ - Set: map[Feature]bool{ - X86FeatureFPU: true, - }} - -var justFPUandPAE = &FeatureSet{ - Set: map[Feature]bool{ - X86FeatureFPU: true, - X86FeaturePAE: true, - }} - -func TestSubtract(t *testing.T) { - if diff := justFPU.Subtract(justFPUandPAE); diff != nil { - t.Errorf("Got %v is not subset of %v, want diff (%v) to be nil", justFPU, justFPUandPAE, diff) - } - - if justFPUandPAE.Subtract(justFPU) == nil { - t.Errorf("Got %v is a subset of %v, want diff to be nil", justFPU, justFPUandPAE) - } -} - -// TODO(b/73346484): Run this test on a very old platform, and make sure more -// bits are enabled than just FPU and PAE. This test currently may not detect -// if HostFeatureSet gives back junk bits. -func TestHostFeatureSet(t *testing.T) { - hostFeatures := HostFeatureSet() - if justFPUandPAE.Subtract(hostFeatures) != nil { - t.Errorf("Got invalid feature set %v from HostFeatureSet()", hostFeatures) - } -} - -func TestHasFeature(t *testing.T) { - if !justFPU.HasFeature(X86FeatureFPU) { - t.Errorf("HasFeature failed, %v should contain %v", justFPU, X86FeatureFPU) - } - - if justFPU.HasFeature(X86FeatureAVX) { - t.Errorf("HasFeature failed, %v should not contain %v", justFPU, X86FeatureAVX) - } -} - -// Note: these tests are aware of and abuse internal details of FeatureSets. -// Users of FeatureSets should not depend on this. -func TestAdd(t *testing.T) { - // Test a basic insertion into the FeatureSet. - testFeatures := newEmptyFeatureSet() - testFeatures.Add(X86FeatureCLFSH) - if len(testFeatures.Set) != 1 { - t.Errorf("Got length %v want 1", len(testFeatures.Set)) - } - - if !testFeatures.HasFeature(X86FeatureCLFSH) { - t.Errorf("Add failed, got %v want set with %v", testFeatures, X86FeatureCLFSH) - } - - // Test that duplicates are ignored. - testFeatures.Add(X86FeatureCLFSH) - if len(testFeatures.Set) != 1 { - t.Errorf("Got length %v, want 1", len(testFeatures.Set)) - } -} - -func TestRemove(t *testing.T) { - // Try removing the last feature. - testFeatures := newEmptyFeatureSet() - testFeatures.Add(X86FeatureFPU) - testFeatures.Add(X86FeaturePAE) - testFeatures.Remove(X86FeaturePAE) - if !testFeatures.HasFeature(X86FeatureFPU) || len(testFeatures.Set) != 1 || testFeatures.HasFeature(X86FeaturePAE) { - t.Errorf("Remove failed, got %v want %v", testFeatures, justFPU) - } - - // Try removing a feature not in the set. - testFeatures.Remove(X86FeatureRDRAND) - if !testFeatures.HasFeature(X86FeatureFPU) || len(testFeatures.Set) != 1 { - t.Errorf("Remove failed, got %v want %v", testFeatures, justFPU) - } -} - -func TestFeatureFromString(t *testing.T) { - f, ok := FeatureFromString("avx") - if f != X86FeatureAVX || !ok { - t.Errorf("got %v want avx", f) - } - - f, ok = FeatureFromString("bad") - if ok { - t.Errorf("got %v want nothing", f) - } -} - -// This tests function 0 (eax=0), which returns the vendor ID and highest cpuid -// function reported to be available. -func TestEmulateIDVendorAndLength(t *testing.T) { - testFeatures := newEmptyFeatureSet() - - ax, bx, cx, dx := testFeatures.EmulateID(0, 0) - wantEax := uint32(0xd) // Highest supported cpuid function. - - // These magical constants are the characters of "GenuineIntel". - // See Intel AN485 for a reference on why they are laid out like this. - wantEbx := uint32(0x756e6547) - wantEcx := uint32(0x6c65746e) - wantEdx := uint32(0x49656e69) - if wantEax != ax { - t.Errorf("highest function failed, got %x want %x", ax, wantEax) - } - - if wantEbx != bx || wantEcx != cx || wantEdx != dx { - t.Errorf("vendor string emulation failed, bx:cx:dx, got %x:%x:%x want %x:%x:%x", bx, cx, dx, wantEbx, wantEcx, wantEdx) - } -} - -func TestEmulateIDBasicFeatures(t *testing.T) { - // Make a minimal test feature set. - testFeatures := newEmptyFeatureSet() - testFeatures.Add(X86FeatureCLFSH) - testFeatures.Add(X86FeatureAVX) - testFeatures.CacheLine = 64 - - ax, bx, cx, dx := testFeatures.EmulateID(1, 0) - ECXAVXBit := uint32(1 << uint(X86FeatureAVX)) - EDXCLFlushBit := uint32(1 << uint(X86FeatureCLFSH-32)) // We adjust by 32 since it's in block 1. - - if EDXCLFlushBit&dx == 0 || dx&^EDXCLFlushBit != 0 { - t.Errorf("EmulateID failed, got feature bits %x want %x", dx, testFeatures.blockMask(1)) - } - - if ECXAVXBit&cx == 0 || cx&^ECXAVXBit != 0 { - t.Errorf("EmulateID failed, got feature bits %x want %x", cx, testFeatures.blockMask(0)) - } - - // Default signature bits, based on values for i7/Xeon. - // See Intel AN485 for information on stepping/model bits. - defaultSignature := uint32(0x000106a0) - if defaultSignature != ax { - t.Errorf("EmulateID stepping emulation failed, got %x want %x", ax, defaultSignature) - } - - clflushSizeInfo := uint32(8 << 8) - if clflushSizeInfo != bx { - t.Errorf("EmulateID bx emulation failed, got %x want %x", bx, clflushSizeInfo) - } -} - -func TestEmulateIDExtendedFeatures(t *testing.T) { - // Make a minimal test feature set, one bit in each extended feature word. - testFeatures := newEmptyFeatureSet() - testFeatures.Add(X86FeatureSMEP) - testFeatures.Add(X86FeatureAVX512VBMI) - - ax, bx, cx, dx := testFeatures.EmulateID(7, 0) - EBXSMEPBit := uint32(1 << uint(X86FeatureSMEP-2*32)) // Adjust by 2*32 since SMEP is a block 2 feature. - ECXAVXBit := uint32(1 << uint(X86FeatureAVX512VBMI-3*32)) // We adjust by 3*32 since it's a block 3 feature. - - // Test that the desired bit is set and no other bits are set. - if EBXSMEPBit&bx == 0 || bx&^EBXSMEPBit != 0 { - t.Errorf("extended feature emulation failed, got feature bits %x want %x", bx, testFeatures.blockMask(2)) - } - - if ECXAVXBit&cx == 0 || cx&^ECXAVXBit != 0 { - t.Errorf("extended feature emulation failed, got feature bits %x want %x", cx, testFeatures.blockMask(3)) - } - - if ax != 0 || dx != 0 { - t.Errorf("extended feature emulation failed, ax:dx, got %x:%x want 0:0", ax, dx) - } - - // Check that no subleaves other than 0 do anything. - ax, bx, cx, dx = testFeatures.EmulateID(7, 1) - if ax != 0 || bx != 0 || cx != 0 || dx != 0 { - t.Errorf("extended feature emulation failed, got %x:%x:%x:%x want 0:0", ax, bx, cx, dx) - } - -} - -// Checks that the expected extended features are available via cpuid functions -// 0x80000000 and up. -func TestEmulateIDExtended(t *testing.T) { - testFeatures := newEmptyFeatureSet() - testFeatures.Add(X86FeatureSYSCALL) - EDXSYSCALLBit := uint32(1 << uint(X86FeatureSYSCALL-6*32)) // Adjust by 6*32 since SYSCALL is a block 6 feature. - - ax, bx, cx, dx := testFeatures.EmulateID(0x80000000, 0) - if ax != 0x80000001 || bx != 0 || cx != 0 || dx != 0 { - t.Errorf("EmulateID extended emulation failed, ax:bx:cx:dx, got %x:%x:%x:%x want 0x80000001:0:0:0", ax, bx, cx, dx) - } - - _, _, _, dx = testFeatures.EmulateID(0x80000001, 0) - if EDXSYSCALLBit&dx == 0 || dx&^EDXSYSCALLBit != 0 { - t.Errorf("extended feature emulation failed, got feature bits %x want %x", dx, testFeatures.blockMask(6)) - } -} |