Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions scripts/test/wasm2c.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from .shared import print_heading

spec_tests = [
'i32.wast',
]


Expand Down
200 changes: 191 additions & 9 deletions src/tools/wasm2c/assertion-emitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@
* limitations under the License.
*/

#include <cassert>
#include <iostream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include "parser/wat-parser.h"
#include "support/file.h"
Expand Down Expand Up @@ -50,6 +54,42 @@ inline std::string getBasename(const std::string& path) {
return path.substr(lastSlash + 1);
}

std::string mangleName(const std::string& name) {
if (name.empty()) {
return "";
}
std::string result;
bool isFirst = true;
for (char c : name) {
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9')) {
result += c;
isFirst = false;
} else if (c == '_') {
if (isFirst) {
result += "0x5F";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this create an identifier starting with a number? That's not valid, right? I see that this is only used to mangle some suffix of the final name. It would be good to add a comment about that.

isFirst = false;
} else {
result += '_';
}
} else {
char buf[8];
snprintf(buf, sizeof(buf), "0x%02X", (unsigned char)c);
result += buf;
isFirst = false;
}
}
return result;
}

std::string literalToCLiteral(const Literal& lit) {
if (lit.type == Type::i32) {
return std::to_string(static_cast<uint32_t>(lit.geti32())) + "u";
}
Fatal() << "Unsupported literal type for C emission: " << lit.type;
return "";
}

} // anonymous namespace

AssertionEmitter::AssertionEmitter(WATParser::WASTScript& script,
Expand All @@ -64,7 +104,13 @@ void AssertionEmitter::emit(std::ostream& cOut,
outputCPath.empty() ? "spec" : stripExtension(outputCPath);
std::string baseBasename = getBasename(basePath);

// Loop sequentially through WASTScript AST commands
std::vector<std::string> modulePrefixes;
std::unordered_map<Name, std::string> moduleNameToPrefix;
std::unordered_map<std::string, std::unordered_set<std::string>>
moduleExports;
std::string lastModulePrefix;
Comment on lines +107 to +111

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to have some comments on what these hold. In particular it would be good to point out that we may be translating multiple modules at once for spec tests that contain multiple modules.


// First pass: Process modules and generate files
for (size_t i = 0; i < script.size(); i++) {
auto& entry = script[i];
auto& cmd = entry.cmd;
Expand All @@ -79,6 +125,21 @@ void AssertionEmitter::emit(std::ostream& cOut,
auto wasm = *w;
size_t currentIdx = moduleCounter++;
std::string prefix = "spec_" + std::to_string(currentIdx);
modulePrefixes.push_back(prefix);
lastModulePrefix = prefix;

if (wasm->name.is()) {
moduleNameToPrefix[wasm->name] = prefix;
}

// Collect exports
std::unordered_set<std::string> exports;
for (auto& exp : wasm->exports) {
if (exp->kind == ExternalKind::Function) {
exports.insert(exp->name.toString());
}
}
moduleExports[prefix] = exports;
Comment thread
lexi-nadia marked this conversation as resolved.

// Generate separate files for this module
std::string modHFilename =
Expand All @@ -98,19 +159,140 @@ void AssertionEmitter::emit(std::ostream& cOut,
builder.processWasm(wasm.get(), modCOut.getStream(), modHOut.getStream());

c << "#include \"" << modHFilename << "\"" << endl;
c << SpecTop << endl << endl;

} else if (std::get_if<WATParser::Register>(&cmd)) {
Fatal() << "register is not yet supported";
} else if (std::get_if<WATParser::Assertion>(&cmd)) {
Fatal() << "assertions are not yet supported";
} else {
Fatal() << "unsupported command";
}
}

c << SpecTop << endl << endl;

// Declare static instances
for (const auto& prefix : modulePrefixes) {
c << "static w2c_" << prefix << " instance_" << prefix << ";" << endl;
}
c << endl;

// Write main execution entry point
c << "void run_spec_tests() {" << endl;
c.indent();

// Instantiate modules
for (const auto& prefix : modulePrefixes) {
c << "wasm2c_" << prefix << "_instantiate(&instance_" << prefix << ");"
<< endl;
}
c << endl;

// Process assertions
for (size_t i = 0; i < script.size(); i++) {
auto& entry = script[i];
auto& cmd = entry.cmd;

if (auto* assertCmd = std::get_if<WATParser::Assertion>(&cmd)) {
if (auto* assertReturn =
std::get_if<WATParser::AssertReturn>(assertCmd)) {
auto* invoke =
std::get_if<WATParser::InvokeAction>(&assertReturn->action);
if (!invoke) {
Fatal() << "Only InvokeAction is supported in AssertReturn";
}

std::string activePrefix;
if (invoke->base.has_value()) {
auto it = moduleNameToPrefix.find(invoke->base.value());
if (it != moduleNameToPrefix.end()) {
activePrefix = it->second;
} else {
Fatal() << "Unknown module reference: " << invoke->base.value();
}
} else {
activePrefix = lastModulePrefix;
}

// Verify export exists
auto expIt = moduleExports.find(activePrefix);
if (expIt == moduleExports.end() ||
Comment thread
lexi-nadia marked this conversation as resolved.
!expIt->second.count(invoke->name.toString())) {
Comment thread
lexi-nadia marked this conversation as resolved.
Fatal() << "Invoked function is not exported: " << invoke->name;
}

std::string callStr = "w2c_" + activePrefix + "_" +
mangleName(invoke->name.toString()) +
"(&instance_" + activePrefix;
for (const auto& arg : invoke->args) {
callStr += ", " + literalToCLiteral(arg);
}
callStr += ")";

if (assertReturn->expected.empty()) {
c << "ASSERT_RETURN(" << callStr << ");" << endl;
} else if (assertReturn->expected.size() == 1) {
auto& alts = assertReturn->expected[0];
if (alts.size() != 1) {
Fatal() << "Multiple alternatives in expected result not supported";
}
auto& expectedRes = alts[0];
if (auto* lit = std::get_if<Literal>(&expectedRes)) {
if (lit->type == Type::i32) {
c << "ASSERT_RETURN_I32(" << callStr << ", "
<< literalToCLiteral(*lit) << ");" << endl;
} else {
Fatal() << "Unsupported expected result type: " << lit->type;
}
} else {
Fatal() << "Unsupported expected result kind";
}
} else {
Fatal() << "Multi-value return assertions not supported";
}

} else if (auto* assertAction =
std::get_if<WATParser::AssertAction>(assertCmd)) {
if (assertAction->type != WATParser::ActionAssertionType::Trap) {
Fatal() << "Only Trap assertion is supported in AssertAction";
}
auto* invoke =
std::get_if<WATParser::InvokeAction>(&assertAction->action);
if (!invoke) {
Fatal() << "Only InvokeAction is supported in AssertAction";
}

std::string activePrefix;
if (invoke->base.has_value()) {
auto it = moduleNameToPrefix.find(invoke->base.value());
if (it != moduleNameToPrefix.end()) {
activePrefix = it->second;
} else {
Fatal() << "Unknown module reference: " << invoke->base.value();
}
} else {
activePrefix = lastModulePrefix;
}
Comment on lines +259 to +268

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be extracted out into a helper function or lambda. Same for getting the export below.


// Verify export exists
auto expIt = moduleExports.find(activePrefix);
if (expIt == moduleExports.end() ||
!expIt->second.count(invoke->name.toString())) {
Fatal() << "Invoked function is not exported: " << invoke->name;
}

std::string callStr = "w2c_" + activePrefix + "_" +
mangleName(invoke->name.toString()) +
"(&instance_" + activePrefix;
for (const auto& arg : invoke->args) {
callStr += ", " + literalToCLiteral(arg);
}
callStr += ")";

c << "ASSERT_TRAP(" << callStr << ");" << endl;
}
}
}

// Free modules
for (const auto& prefix : modulePrefixes) {
c << "wasm2c_" << prefix << "_free(&instance_" << prefix << ");" << endl;
}

c.outdent();
c << "}" << endl;
}

Expand Down
Loading
Loading