summaryrefslogtreecommitdiffstats
path: root/js/src/jit/JitSpewer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit/JitSpewer.cpp')
-rw-r--r--js/src/jit/JitSpewer.cpp660
1 files changed, 660 insertions, 0 deletions
diff --git a/js/src/jit/JitSpewer.cpp b/js/src/jit/JitSpewer.cpp
new file mode 100644
index 0000000000..f00efe7b79
--- /dev/null
+++ b/js/src/jit/JitSpewer.cpp
@@ -0,0 +1,660 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifdef JS_JITSPEW
+
+# include "jit/JitSpewer.h"
+
+# include "mozilla/Atomics.h"
+# include "mozilla/Sprintf.h"
+
+# include "jit/MIR.h"
+# include "jit/MIRGenerator.h"
+# include "jit/MIRGraph.h"
+# include "threading/LockGuard.h"
+# include "util/GetPidProvider.h" // getpid()
+# include "vm/MutexIDs.h"
+
+# ifndef JIT_SPEW_DIR
+# if defined(_WIN32)
+# define JIT_SPEW_DIR "."
+# elif defined(__ANDROID__)
+# define JIT_SPEW_DIR "/data/local/tmp"
+# else
+# define JIT_SPEW_DIR "/tmp"
+# endif
+# endif
+
+using namespace js;
+using namespace js::jit;
+
+class IonSpewer {
+ private:
+ Mutex outputLock_ MOZ_UNANNOTATED;
+ Fprinter jsonOutput_;
+ bool firstFunction_;
+ bool asyncLogging_;
+ bool inited_;
+
+ void release();
+
+ public:
+ IonSpewer()
+ : outputLock_(mutexid::IonSpewer),
+ firstFunction_(false),
+ asyncLogging_(false),
+ inited_(false) {}
+
+ // File output is terminated safely upon destruction.
+ ~IonSpewer();
+
+ bool init();
+ bool isEnabled() { return inited_; }
+ void setAsyncLogging(bool incremental) { asyncLogging_ = incremental; }
+ bool getAsyncLogging() { return asyncLogging_; }
+
+ void beginFunction();
+ void spewPass(GraphSpewer* gs);
+ void endFunction(GraphSpewer* gs);
+};
+
+// IonSpewer singleton.
+static IonSpewer ionspewer;
+
+static bool LoggingChecked = false;
+static_assert(JitSpew_Terminator <= 64,
+ "Increase the size of the LoggingBits global.");
+static uint64_t LoggingBits = 0;
+static mozilla::Atomic<uint32_t, mozilla::Relaxed> filteredOutCompilations(0);
+
+static const char* const ChannelNames[] = {
+# define JITSPEW_CHANNEL(name) #name,
+ JITSPEW_CHANNEL_LIST(JITSPEW_CHANNEL)
+# undef JITSPEW_CHANNEL
+};
+
+static size_t ChannelIndentLevel[] = {
+# define JITSPEW_CHANNEL(name) 0,
+ JITSPEW_CHANNEL_LIST(JITSPEW_CHANNEL)
+# undef JITSPEW_CHANNEL
+};
+
+// The IONFILTER environment variable specifies an expression to select only
+// certain functions for spewing to reduce amount of log data generated.
+static const char* gSpewFilter = nullptr;
+
+static bool FilterContainsLocation(JSScript* function) {
+ // If there is no filter we accept all outputs.
+ if (!gSpewFilter || !gSpewFilter[0]) {
+ return true;
+ }
+
+ // Disable wasm output when filter is set.
+ if (!function) {
+ return false;
+ }
+
+ const char* filename = function->filename();
+ const size_t line = function->lineno();
+ const size_t filelen = strlen(filename);
+ const char* index = strstr(gSpewFilter, filename);
+ while (index) {
+ if (index == gSpewFilter || index[-1] == ',') {
+ if (index[filelen] == 0 || index[filelen] == ',') {
+ return true;
+ }
+ if (index[filelen] == ':' && line != size_t(-1)) {
+ size_t read_line = strtoul(&index[filelen + 1], nullptr, 10);
+ if (read_line == line) {
+ return true;
+ }
+ }
+ }
+ index = strstr(index + filelen, filename);
+ }
+ return false;
+}
+
+void jit::EnableIonDebugSyncLogging() {
+ ionspewer.init();
+ ionspewer.setAsyncLogging(false);
+ EnableChannel(JitSpew_IonSyncLogs);
+}
+
+void jit::EnableIonDebugAsyncLogging() {
+ ionspewer.init();
+ ionspewer.setAsyncLogging(true);
+}
+
+void IonSpewer::release() {
+ if (jsonOutput_.isInitialized()) {
+ jsonOutput_.finish();
+ }
+ inited_ = false;
+}
+
+bool IonSpewer::init() {
+ if (inited_) {
+ return true;
+ }
+
+ // Filter expression for spewing
+ gSpewFilter = getenv("IONFILTER");
+
+ const size_t bufferLength = 256;
+ char jsonBuffer[bufferLength];
+ const char* jsonFilename = JIT_SPEW_DIR "/ion.json";
+
+ const char* usePid = getenv("ION_SPEW_BY_PID");
+ if (usePid && *usePid != 0) {
+ uint32_t pid = getpid();
+ size_t len;
+ len = SprintfLiteral(jsonBuffer, JIT_SPEW_DIR "/ion%" PRIu32 ".json", pid);
+ if (bufferLength <= len) {
+ fprintf(stderr, "Warning: IonSpewer::init: Cannot serialize file name.");
+ return false;
+ }
+ jsonFilename = jsonBuffer;
+ }
+
+ if (!jsonOutput_.init(jsonFilename)) {
+ release();
+ return false;
+ }
+
+ jsonOutput_.printf("{\n \"functions\": [\n");
+ firstFunction_ = true;
+
+ inited_ = true;
+ return true;
+}
+
+void IonSpewer::beginFunction() {
+ // If we are doing a synchronous logging then we spew everything as we go,
+ // as this is useful in case of failure during the compilation. On the other
+ // hand, it is recommended to disable off thread compilation.
+ if (!getAsyncLogging() && !firstFunction_) {
+ LockGuard<Mutex> guard(outputLock_);
+ jsonOutput_.put(","); // separate functions
+ }
+}
+
+void IonSpewer::spewPass(GraphSpewer* gs) {
+ if (!getAsyncLogging()) {
+ LockGuard<Mutex> guard(outputLock_);
+ gs->dump(jsonOutput_);
+ }
+}
+
+void IonSpewer::endFunction(GraphSpewer* gs) {
+ LockGuard<Mutex> guard(outputLock_);
+ if (getAsyncLogging() && !firstFunction_) {
+ jsonOutput_.put(","); // separate functions
+ }
+
+ gs->dump(jsonOutput_);
+ firstFunction_ = false;
+}
+
+IonSpewer::~IonSpewer() {
+ if (!inited_) {
+ return;
+ }
+
+ jsonOutput_.printf("\n]}\n");
+ release();
+}
+
+GraphSpewer::GraphSpewer(TempAllocator* alloc)
+ : graph_(nullptr),
+ jsonPrinter_(alloc->lifoAlloc()),
+ jsonSpewer_(jsonPrinter_) {}
+
+void GraphSpewer::init(MIRGraph* graph, JSScript* function) {
+ MOZ_ASSERT(!isSpewing());
+ if (!ionspewer.isEnabled()) {
+ return;
+ }
+
+ if (!FilterContainsLocation(function)) {
+ // filter out logs during the compilation.
+ filteredOutCompilations++;
+ MOZ_ASSERT(!isSpewing());
+ return;
+ }
+
+ graph_ = graph;
+ MOZ_ASSERT(isSpewing());
+}
+
+void GraphSpewer::beginFunction(JSScript* function) {
+ if (!isSpewing()) {
+ return;
+ }
+ jsonSpewer_.beginFunction(function);
+ ionspewer.beginFunction();
+}
+
+void GraphSpewer::beginWasmFunction(unsigned funcIndex) {
+ if (!isSpewing()) {
+ return;
+ }
+ jsonSpewer_.beginWasmFunction(funcIndex);
+ ionspewer.beginFunction();
+}
+
+void GraphSpewer::spewPass(const char* pass) {
+ if (!isSpewing()) {
+ return;
+ }
+
+ jsonSpewer_.beginPass(pass);
+ jsonSpewer_.spewMIR(graph_);
+ jsonSpewer_.spewLIR(graph_);
+ jsonSpewer_.endPass();
+
+ ionspewer.spewPass(this);
+
+ // As this function is used for debugging, we ignore any of the previous
+ // failures and ensure there is enough ballast space, such that we do not
+ // exhaust the ballast space before running the next phase.
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ if (!graph_->alloc().ensureBallast()) {
+ oomUnsafe.crash(
+ "Could not ensure enough ballast space after spewing graph "
+ "information.");
+ }
+}
+
+void GraphSpewer::spewPass(const char* pass, BacktrackingAllocator* ra) {
+ if (!isSpewing()) {
+ return;
+ }
+
+ jsonSpewer_.beginPass(pass);
+ jsonSpewer_.spewMIR(graph_);
+ jsonSpewer_.spewLIR(graph_);
+ jsonSpewer_.spewRanges(ra);
+ jsonSpewer_.endPass();
+
+ ionspewer.spewPass(this);
+}
+
+void GraphSpewer::endFunction() {
+ if (!ionspewer.isEnabled()) {
+ return;
+ }
+
+ if (!isSpewing()) {
+ MOZ_ASSERT(filteredOutCompilations != 0);
+ filteredOutCompilations--;
+ return;
+ }
+
+ jsonSpewer_.endFunction();
+
+ ionspewer.endFunction(this);
+ graph_ = nullptr;
+}
+
+void GraphSpewer::dump(Fprinter& jsonOut) {
+ if (!jsonPrinter_.hadOutOfMemory()) {
+ jsonPrinter_.exportInto(jsonOut);
+ } else {
+ jsonOut.put("{}");
+ }
+ jsonOut.flush();
+ jsonPrinter_.clear();
+}
+
+void jit::SpewBeginFunction(MIRGenerator* mir, JSScript* function) {
+ MIRGraph* graph = &mir->graph();
+ mir->graphSpewer().init(graph, function);
+ mir->graphSpewer().beginFunction(function);
+}
+
+void jit::SpewBeginWasmFunction(MIRGenerator* mir, unsigned funcIndex) {
+ MIRGraph* graph = &mir->graph();
+ mir->graphSpewer().init(graph, nullptr);
+ mir->graphSpewer().beginWasmFunction(funcIndex);
+}
+
+AutoSpewEndFunction::~AutoSpewEndFunction() {
+ mir_->graphSpewer().endFunction();
+}
+
+Fprinter& jit::JitSpewPrinter() {
+ static Fprinter out;
+ return out;
+}
+
+static void PrintHelpAndExit(int status = 0) {
+ fflush(nullptr);
+ printf(
+ "\n"
+ "usage: IONFLAGS=option,option,option,... where options can be:\n"
+ "\n"
+ " aborts Compilation abort messages\n"
+ " scripts Compiled scripts\n"
+ " mir MIR information\n"
+ " prune Prune unused branches\n"
+ " escape Escape analysis\n"
+ " alias Alias analysis\n"
+ " alias-sum Alias analysis: shows summaries for every block\n"
+ " gvn Global Value Numbering\n"
+ " licm Loop invariant code motion\n"
+ " flac Fold linear arithmetic constants\n"
+ " eaa Effective address analysis\n"
+ " sink Sink transformation\n"
+ " regalloc Register allocation\n"
+ " inline Inlining\n"
+ " snapshots Snapshot information\n"
+ " codegen Native code generation\n"
+ " bailouts Bailouts\n"
+ " caches Inline caches\n"
+ " osi Invalidation\n"
+ " safepoints Safepoints\n"
+ " pools Literal Pools (ARM only for now)\n"
+ " cacheflush Instruction Cache flushes (ARM only for now)\n"
+ " range Range Analysis\n"
+ " wasmbce Wasm Bounds Check Elimination\n"
+ " shapeguards Redundant shape guard elimination\n"
+ " gcbarriers Redundant GC barrier elimination\n"
+ " logs JSON visualization logging\n"
+ " logs-sync Same as logs, but flushes between each pass (sync. "
+ "compiled functions only).\n"
+ " profiling Profiling-related information\n"
+ " dump-mir-expr Dump the MIR expressions\n"
+ " scriptstats Tracelogger summary stats\n"
+ " warp-snapshots WarpSnapshots created by WarpOracle\n"
+ " warp-transpiler Warp CacheIR transpiler\n"
+ " warp-trial-inlining Trial inlining for Warp\n"
+ " all Everything\n"
+ "\n"
+ " bl-aborts Baseline compiler abort messages\n"
+ " bl-scripts Baseline script-compilation\n"
+ " bl-op Baseline compiler detailed op-specific messages\n"
+ " bl-ic Baseline inline-cache messages\n"
+ " bl-ic-fb Baseline IC fallback stub messages\n"
+ " bl-osr Baseline IC OSR messages\n"
+ " bl-bails Baseline bailouts\n"
+ " bl-dbg-osr Baseline debug mode on stack recompile messages\n"
+ " bl-all All baseline spew\n"
+ "\n"
+ "See also SPEW=help for information on the Structured Spewer."
+ "\n");
+ exit(status);
+}
+
+static bool IsFlag(const char* found, const char* flag) {
+ return strlen(found) == strlen(flag) && strcmp(found, flag) == 0;
+}
+
+void jit::CheckLogging() {
+ if (LoggingChecked) {
+ return;
+ }
+
+ LoggingChecked = true;
+
+ char* env = getenv("IONFLAGS");
+ if (!env) {
+ return;
+ }
+
+ const char* found = strtok(env, ",");
+ while (found) {
+ fprintf(stderr, "found tag: %s\n", found);
+ // We're at the end of a flag; check if the previous substring was a
+ // known flag (i-1 is the last character of the flag we just read).
+ if (IsFlag(found, "help")) {
+ PrintHelpAndExit();
+ } else if (IsFlag(found, "aborts")) {
+ EnableChannel(JitSpew_IonAbort);
+ } else if (IsFlag(found, "prune")) {
+ EnableChannel(JitSpew_Prune);
+ } else if (IsFlag(found, "escape")) {
+ EnableChannel(JitSpew_Escape);
+ } else if (IsFlag(found, "alias")) {
+ EnableChannel(JitSpew_Alias);
+ } else if (IsFlag(found, "alias-sum")) {
+ EnableChannel(JitSpew_AliasSummaries);
+ } else if (IsFlag(found, "scripts")) {
+ EnableChannel(JitSpew_IonScripts);
+ } else if (IsFlag(found, "mir")) {
+ EnableChannel(JitSpew_IonMIR);
+ } else if (IsFlag(found, "gvn")) {
+ EnableChannel(JitSpew_GVN);
+ } else if (IsFlag(found, "range")) {
+ EnableChannel(JitSpew_Range);
+ } else if (IsFlag(found, "wasmbce")) {
+ EnableChannel(JitSpew_WasmBCE);
+ } else if (IsFlag(found, "licm")) {
+ EnableChannel(JitSpew_LICM);
+ } else if (IsFlag(found, "flac")) {
+ EnableChannel(JitSpew_FLAC);
+ } else if (IsFlag(found, "eaa")) {
+ EnableChannel(JitSpew_EAA);
+ } else if (IsFlag(found, "sink")) {
+ EnableChannel(JitSpew_Sink);
+ } else if (IsFlag(found, "regalloc")) {
+ EnableChannel(JitSpew_RegAlloc);
+ } else if (IsFlag(found, "inline")) {
+ EnableChannel(JitSpew_Inlining);
+ } else if (IsFlag(found, "snapshots")) {
+ EnableChannel(JitSpew_IonSnapshots);
+ } else if (IsFlag(found, "codegen")) {
+ EnableChannel(JitSpew_Codegen);
+ } else if (IsFlag(found, "bailouts")) {
+ EnableChannel(JitSpew_IonBailouts);
+ } else if (IsFlag(found, "osi")) {
+ EnableChannel(JitSpew_IonInvalidate);
+ } else if (IsFlag(found, "caches")) {
+ EnableChannel(JitSpew_IonIC);
+ } else if (IsFlag(found, "safepoints")) {
+ EnableChannel(JitSpew_Safepoints);
+ } else if (IsFlag(found, "pools")) {
+ EnableChannel(JitSpew_Pools);
+ } else if (IsFlag(found, "cacheflush")) {
+ EnableChannel(JitSpew_CacheFlush);
+ } else if (IsFlag(found, "shapeguards")) {
+ EnableChannel(JitSpew_RedundantShapeGuards);
+ } else if (IsFlag(found, "gcbarriers")) {
+ EnableChannel(JitSpew_RedundantGCBarriers);
+ } else if (IsFlag(found, "logs")) {
+ EnableIonDebugAsyncLogging();
+ } else if (IsFlag(found, "logs-sync")) {
+ EnableIonDebugSyncLogging();
+ } else if (IsFlag(found, "profiling")) {
+ EnableChannel(JitSpew_Profiling);
+ } else if (IsFlag(found, "dump-mir-expr")) {
+ EnableChannel(JitSpew_MIRExpressions);
+ } else if (IsFlag(found, "scriptstats")) {
+ EnableChannel(JitSpew_ScriptStats);
+ } else if (IsFlag(found, "warp-snapshots")) {
+ EnableChannel(JitSpew_WarpSnapshots);
+ } else if (IsFlag(found, "warp-transpiler")) {
+ EnableChannel(JitSpew_WarpTranspiler);
+ } else if (IsFlag(found, "warp-trial-inlining")) {
+ EnableChannel(JitSpew_WarpTrialInlining);
+ } else if (IsFlag(found, "all")) {
+ LoggingBits = uint64_t(-1);
+ } else if (IsFlag(found, "bl-aborts")) {
+ EnableChannel(JitSpew_BaselineAbort);
+ } else if (IsFlag(found, "bl-scripts")) {
+ EnableChannel(JitSpew_BaselineScripts);
+ } else if (IsFlag(found, "bl-op")) {
+ EnableChannel(JitSpew_BaselineOp);
+ } else if (IsFlag(found, "bl-ic")) {
+ EnableChannel(JitSpew_BaselineIC);
+ } else if (IsFlag(found, "bl-ic-fb")) {
+ EnableChannel(JitSpew_BaselineICFallback);
+ } else if (IsFlag(found, "bl-osr")) {
+ EnableChannel(JitSpew_BaselineOSR);
+ } else if (IsFlag(found, "bl-bails")) {
+ EnableChannel(JitSpew_BaselineBailouts);
+ } else if (IsFlag(found, "bl-dbg-osr")) {
+ EnableChannel(JitSpew_BaselineDebugModeOSR);
+ } else if (IsFlag(found, "bl-all")) {
+ EnableChannel(JitSpew_BaselineAbort);
+ EnableChannel(JitSpew_BaselineScripts);
+ EnableChannel(JitSpew_BaselineOp);
+ EnableChannel(JitSpew_BaselineIC);
+ EnableChannel(JitSpew_BaselineICFallback);
+ EnableChannel(JitSpew_BaselineOSR);
+ EnableChannel(JitSpew_BaselineBailouts);
+ EnableChannel(JitSpew_BaselineDebugModeOSR);
+ } else {
+ fprintf(stderr, "Unknown flag.\n");
+ PrintHelpAndExit(64);
+ }
+ found = strtok(nullptr, ",");
+ }
+
+ FILE* spewfh = stderr;
+ const char* filename = getenv("ION_SPEW_FILENAME");
+ if (filename && *filename) {
+ char actual_filename[2048] = {0};
+ SprintfLiteral(actual_filename, "%s.%d", filename, getpid());
+ spewfh = fopen(actual_filename, "w");
+ MOZ_RELEASE_ASSERT(spewfh);
+ setbuf(spewfh, nullptr); // Make unbuffered
+ }
+ JitSpewPrinter().init(spewfh);
+}
+
+JitSpewIndent::JitSpewIndent(JitSpewChannel channel) : channel_(channel) {
+ ChannelIndentLevel[channel]++;
+}
+
+JitSpewIndent::~JitSpewIndent() { ChannelIndentLevel[channel_]--; }
+
+void jit::JitSpewStartVA(JitSpewChannel channel, const char* fmt, va_list ap) {
+ if (!JitSpewEnabled(channel)) {
+ return;
+ }
+
+ JitSpewHeader(channel);
+ Fprinter& out = JitSpewPrinter();
+ out.vprintf(fmt, ap);
+}
+
+void jit::JitSpewContVA(JitSpewChannel channel, const char* fmt, va_list ap) {
+ if (!JitSpewEnabled(channel)) {
+ return;
+ }
+
+ Fprinter& out = JitSpewPrinter();
+ out.vprintf(fmt, ap);
+}
+
+void jit::JitSpewFin(JitSpewChannel channel) {
+ if (!JitSpewEnabled(channel)) {
+ return;
+ }
+
+ Fprinter& out = JitSpewPrinter();
+ out.put("\n");
+}
+
+void jit::JitSpewVA(JitSpewChannel channel, const char* fmt, va_list ap) {
+ JitSpewStartVA(channel, fmt, ap);
+ JitSpewFin(channel);
+}
+
+void jit::JitSpew(JitSpewChannel channel, const char* fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ JitSpewVA(channel, fmt, ap);
+ va_end(ap);
+}
+
+void jit::JitSpewDef(JitSpewChannel channel, const char* str,
+ MDefinition* def) {
+ if (!JitSpewEnabled(channel)) {
+ return;
+ }
+
+ JitSpewHeader(channel);
+ Fprinter& out = JitSpewPrinter();
+ out.put(str);
+ def->dump(out);
+ def->dumpLocation(out);
+}
+
+void jit::JitSpewStart(JitSpewChannel channel, const char* fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ JitSpewStartVA(channel, fmt, ap);
+ va_end(ap);
+}
+void jit::JitSpewCont(JitSpewChannel channel, const char* fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ JitSpewContVA(channel, fmt, ap);
+ va_end(ap);
+}
+
+void jit::JitSpewHeader(JitSpewChannel channel) {
+ if (!JitSpewEnabled(channel)) {
+ return;
+ }
+
+ Fprinter& out = JitSpewPrinter();
+ out.printf("[%s] ", ChannelNames[channel]);
+ for (size_t i = ChannelIndentLevel[channel]; i != 0; i--) {
+ out.put(" ");
+ }
+}
+
+bool jit::JitSpewEnabled(JitSpewChannel channel) {
+ MOZ_ASSERT(LoggingChecked);
+ return (LoggingBits & (uint64_t(1) << uint32_t(channel))) &&
+ !filteredOutCompilations;
+}
+
+void jit::EnableChannel(JitSpewChannel channel) {
+ MOZ_ASSERT(LoggingChecked);
+ LoggingBits |= uint64_t(1) << uint32_t(channel);
+}
+
+void jit::DisableChannel(JitSpewChannel channel) {
+ MOZ_ASSERT(LoggingChecked);
+ LoggingBits &= ~(uint64_t(1) << uint32_t(channel));
+}
+
+const char* js::jit::ValTypeToString(JSValueType type) {
+ switch (type) {
+ case JSVAL_TYPE_DOUBLE:
+ return "Double";
+ case JSVAL_TYPE_INT32:
+ return "Int32";
+ case JSVAL_TYPE_BOOLEAN:
+ return "Boolean";
+ case JSVAL_TYPE_UNDEFINED:
+ return "Undefined";
+ case JSVAL_TYPE_NULL:
+ return "Null";
+ case JSVAL_TYPE_MAGIC:
+ return "Magic";
+ case JSVAL_TYPE_STRING:
+ return "String";
+ case JSVAL_TYPE_SYMBOL:
+ return "Symbol";
+ case JSVAL_TYPE_PRIVATE_GCTHING:
+ return "PrivateGCThing";
+ case JSVAL_TYPE_BIGINT:
+ return "BigInt";
+ case JSVAL_TYPE_OBJECT:
+ return "Object";
+ case JSVAL_TYPE_UNKNOWN:
+ return "None";
+ default:
+ MOZ_CRASH("Unknown JSValueType");
+ }
+}
+
+#endif /* JS_JITSPEW */