Skip to content

Please provide a stringify-expression function in the C libarary #8716

Closed

Description

@chharvey

Greetings all, For those of you who don't know, a new effort to migrate JS to TS has been going on for a few weeks new. Please follow along the discussion here: #8656.

My specific question here is with the emitText() function, which hijacks out:

Module['emitText'] = function(expr) {
  if (typeof expr === 'object') {
    return expr.emitText();
  }
  const old = out;
  let ret = '';
  out = x => { ret += x + '\n' };
  Module['_BinaryenExpressionPrint'](expr);
  out = old;
  return ret;
};

Since this code is appended to the Emscripten preprocessed input, this mysterious out is able to be temporarily reassigned to a function that just updates the ret string.

(for reference:)

void BinaryenExpressionPrint(BinaryenExpressionRef expr) {
  std::cout << *(Expression*)expr << '\n';
}

But seeing as the TS effort will not have access to those internals (see #8656), my current rewrite looks like this:

(adding out & err to the EXPORTED_RUNTIME_METHODS)

  target_link_libraries(binaryen_js PRIVATE "-sEXPORTED_RUNTIME_METHODS=out,err,HEAP8,HEAPU8,HEAP32,HEAPU32,stackSave,stackRestore,stackAlloc,UTF8ToString,stringToAscii,stringToUTF8OnStack,getExceptionMessage")
  target_link_libraries(binaryen_js PRIVATE "-sEXPORTED_FUNCTIONS=_malloc,_free,__i32_load")

(calling it "BinaryenObj" instead of "Module" to avoid confusion)

const BinaryenObj = await Binaryen();

export function emitText(expr: ExpressionRef): string {
	let returned = "";
	const temp_out = BinaryenObj.out;
	BinaryenObj.out = (x: string): void => {
		returned += `${ x }\n`;
	};
	BinaryenObj["_BinaryenExpressionPrint"](expr);
	BinaryenObj.out = temp_out;
	return returned;
}

The problem is, it doesn't do anything. Calling this function still prints the text to the console, and then returns an empty string. I guess that's because temporarily reassigning BinaryenObj.out doesn't actually hijack the internals used in BinaryenExpressionPrint.

So my hope is we can offer a new function in the C++ library to return the expression stringified, which could then just be pulled into the TS library. We already do this in the Module class:

char* BinaryenModuleAllocateAndWriteText(BinaryenModuleRef module) {
  std::ostringstream os;
  bool colors = Colors::isEnabled();

  Colors::setEnabled(false); // do not use colors for writing
  os << *(Module*)module;
  Colors::setEnabled(colors); // restore colors state

  auto str = os.str();
  const size_t len = str.length() + 1;
  char* output = (char*)malloc(len);
  std::copy_n(str.c_str(), len, output);
  return output;
}
function wrapModule(module, self = {}) {
  // ...
  self['emitText'] = function() {
    let textPtr = Module['_BinaryenModuleAllocateAndWriteText'](module);
    let text = UTF8ToString(textPtr);
    if (textPtr) _free(textPtr);
    return text;
  };
}

(and my TS rewrite)

class Module {
	emitText(): string {
		const textPtr = BinaryenObj["_BinaryenModuleAllocateAndWriteText"](this[PTR]);
		const text = UTF8ToString(textPtr);
		if (textPtr) {
			_free(textPtr);
		}
		return text;
	}
}

Following this same pattern, we could have a new "BinaryenExpressionAllocateAndWriteText" function, which just spits out the string of the expression, and then I'd update the global emitText JS/TS function accordingly. Moreover, the original BinaryenExpressionPrint function could be updated to call that instead of duplicating code.

Please let me know if there'd be any problems with this approach. Thank you!

Activity

  1. chharvey commented on May 16, 2026

    @chharvey
    ContributorAuthor

    Got a PR started: #8717. It is by no means complete; edits/amendments are most likely needed.

  2. stevenfontanella commented on May 19, 2026

    @stevenfontanella
    Member

    Gave an initial review on the PR. I'm still a little out of the loop on the motivation here. It sounds like the problem is that BinaryenExpressionPrint always prints to stdout, and the JS API does some magic to overwrite stdout, but we can't do that in the TS api for some reason. cc @kripken, does this seem reasonable to you?

    One thing I wonder is if we can add a new version of BinaryenExpressionPrint that takes a FILE* param to write to. But I'm not sure how the interop with JS works and if we'd be able to call that from the JS api.

  3. kripken commented on May 19, 2026

    @kripken
    Member

    PR sounds reasonable to me, will comment there.

  4. chharvey commented on May 19, 2026

    @chharvey
    ContributorAuthor

    @stevenfontanella "some magic" is correct. I'm not entiely sure how it works, but in the original post.js file,

    out = x => { ret += x + '\n' };

    seems to be reassigning the std::cout in the C code.

    In the TS rewrite, i'm exporting out via EXPORTED_RUNTIME_METHODS in the CMakeLists file, which binds it to the BinaryenObj object (previously called "Module"). Then in

    BinaryenObj.out = (x: string): void => {
    	returned += `${ x }\n`;
    };

    nothing happens because all it does is reassign that binding, but doesn’t change std::cout.

  5. added a commit that references this issue on Jun 5, 2026
    32a8e1b
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions