Repository navigation
Description
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
chharvey commentedon May 16, 2026
Got a PR started: #8717. It is by no means complete; edits/amendments are most likely needed.
stevenfontanella commentedon May 19, 2026
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.kripken commentedon May 19, 2026
PR sounds reasonable to me, will comment there.
chharvey commentedon May 19, 2026
@stevenfontanella "some magic" is correct. I'm not entiely sure how it works, but in the original post.js file,
seems to be reassigning the
std::coutin the C code.In the TS rewrite, i'm exporting
outviaEXPORTED_RUNTIME_METHODSin the CMakeLists file, which binds it to theBinaryenObjobject (previously called "Module"). Then innothing happens because all it does is reassign that binding, but doesn’t change
std::cout.[C/JS API] Add BinaryenExpressionAllocateAndWriteText (#8717)