Skip to content

Instantly share code, notes, and snippets.

@bellbind
Last active October 17, 2019 18:29
Show Gist options
  • Save bellbind/64786335e8760e9c9756 to your computer and use it in GitHub Desktop.
Save bellbind/64786335e8760e9c9756 to your computer and use it in GitHub Desktop.
[iojs][emscripten] Call sudoku solver function implemented in C on iojs
// [sudoku solver in C11 for emscripten]
// emcc -Wall -Wextra -std=c11 libsudoku.c -o libsudoku.js \
// -s EXPORTED_FUNCTIONS="['_sudoku','_output']" \
// -s RESERVED_FUNCTION_POINTERS=20
//
// [Options for optimization and debug level]
// -O0: no optimization (default)
// -O1: remove debug info
// -O2: minify JS code (.mem file generated. it is required for run)
// -O3: more JS optimization
// -g1: preserve whitespaces
// -g2: preserve function names in JS code
// -g3: preserve variable names in JS code
// -g4: generate source maps (.map file generated)
#include <stdio.h>
// helpers for board data
static inline unsigned masks(unsigned i) {return i ? 1 << (i - 1) : 0;}
static inline unsigned row(unsigned i) {return i / 9;}
static inline unsigned col(unsigned i) {return i % 9;}
static inline unsigned blk(unsigned i) {return i / 27 * 3 + i % 9 / 3;}
extern void output(unsigned board[])
{
char buffer[(9 + 3) * (9 + 3)];
char* cur = buffer;
for (unsigned y = 0; y < 9; y++) {
for (unsigned x = 0; x < 9; x++) {
*cur++ = board[y * 9 + x] > 0 ? board[y * 9 + x] + '0' : '.';
if (x % 3 == 2) *cur++ = x == 8 ? '\n' : '|';
}
if (y == 8) {
*cur++ = '\0';
} else if (y % 3 == 2) {
for (unsigned i = 0; i < 11; i++) *cur++ = i % 4 == 3 ? '+' : '-';
*cur++ = '\n';
}
}
puts(buffer);
}
// sudoku solver
typedef void (*sudoku_cb)(unsigned board[]);
typedef struct {
unsigned board[81];
unsigned rows[9], cols[9], blks[9];
sudoku_cb callback;
} sudoku_t;
static void sudoku_init(sudoku_t* s, unsigned board[])
{
for (unsigned i = 0; i < 81; i++) {
const unsigned mask = masks(board[i]);
s->rows[row(i)] |= mask, s->cols[col(i)] |= mask, s->blks[blk(i)] |= mask;
s->board[i] = board[i];
}
}
static void sudoku_solve(sudoku_t* s, unsigned i)
{
if (i == 81) {
s->callback(s->board);
} else if (s->board[i] != 0) {
sudoku_solve(s, i + 1);
} else {
const unsigned r = row(i), c = col(i), b = blk(i);
const unsigned used = s->rows[r] | s->cols[c] | s->blks[b];
for (unsigned v = 1; v <= 9; v++) {
const unsigned mask = masks(v);
if (used & mask) continue;
s->board[i] = v;
s->rows[r] |= mask, s->cols[c] |= mask, s->blks[b] |= mask;
sudoku_solve(s, i + 1);
s->rows[r] &= ~mask, s->cols[c] &= ~mask, s->blks[b] &= ~mask;
s->board[i] = 0;
}
}
}
extern void sudoku(unsigned board[], sudoku_cb callback)
{
sudoku_t s = {
.board = {0}, .rows = {0}, .cols = {0}, .blks = {0}, .callback = callback};
sudoku_init(&s, board);
sudoku_solve(&s, 0);
}
all: libsudoku.js sudoku.js sudoku
clean:
rm -f libsudoku.js sudoku.js sudoku
rm -f libsudoku.js.map sudoku.js.map
rm -f libsudoku.js.mem sudoku.js.mem
# library for using functions in hand written JavaScript code
libsudoku.js: libsudoku.c
emcc -g4 -Wall -Wextra -std=c11 $^ -o $@ \
-s EXPORTED_FUNCTIONS="['_output', '_sudoku']" \
-s RESERVED_FUNCTION_POINTERS=20
# whole console program as js code
sudoku.js: libsudoku.c sudoku-main.c
emcc -O1 -Wall -Wextra -std=c11 $^ -o $@
# executable by c compiler
sudoku: libsudoku.c sudoku-main.c
$(CC) -O3 -Wall -Wextra -std=c11 $^ -o $@
// use emscripten generated "libsudoku.js" as a library
var libsudoku = require("./libsudoku.js");
// use ccall to call emscripten C function
var output = function (board) {
// unsigned[] as Uint8Array view of Uint32Array
var unsignedBoard = new Uint8Array(new Uint32Array(board).buffer);
// ccall(cfuncname, return_type, argument_types, arguments)
// type: "string", "number", "array" or undefined as return type
return libsudoku.ccall("output", undefined, ["array"], [unsignedBoard]);
};
// use cwrap to call emscripten C function with callback
var sudoku = function sudoku(board, callback) {
// NOTE: For using addFunction(),
// emcc option "-s REQUIRED_FUNCTION_POINTERS=10" (more than 1) required
var callbackPtr = libsudoku.Runtime.addFunction(function (resultPtr) {
var r = new Uint32Array(libsudoku.HEAPU8.buffer, resultPtr, 81);
// NOTE: copy memory value as array for async use in callback(like IO)
callback([].slice.call(r));
});
var unsignedBoard = new Uint8Array(new Uint32Array(board).buffer);
sudoku._cfunc(unsignedBoard, callbackPtr);
libsudoku.Runtime.removeFunction(callbackPtr);
};
// pointer as a "number"
sudoku._cfunc = libsudoku.cwrap("sudoku", undefined, ["array", "number"]);
// main
if (process.argv.length <= 2) {
// example problem from http://rosettacode.org/wiki/Sudoku
var problem = [
8, 5, 0, 0, 0, 2, 4, 0, 0,
7, 2, 0, 0, 0, 0, 0, 0, 9,
0, 0, 4, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 7, 0, 0, 2,
3, 0, 5, 0, 0, 0, 9, 0, 0,
0, 4, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 8, 0, 0, 7, 0,
0, 1, 7, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 3, 6, 0, 4, 0
];
console.log("[problem]");
output(problem);
console.log("[solution]");
//sudoku(problem, output);
/**/
sudoku(problem, function (board) {
var buf = "";
for (var y = 0; y < 9; y++) {
for (var x = 0; x < 9; x++) {
buf += board[y*9 + x] > 0 ? board[y*9 + x].toString() : ".";
if (x % 3 === 2) buf += x === 8 ? "\n" : "|";
}
if (y === 2 || y === 5) buf += "---+---+---\n";
}
console.log(buf);
});
/**/
} else {
for (var i = 2; i < process.argv.length; i++) {
var problem = Array(81);
for (var j = 0; j < 81; j++) {
var ch = process.argv[i].charCodeAt(j);
problem[j] = 0 <= ch && ch <= 9 ? v : 0;
}
console.log("[problem]");
output(problem);
console.log("[solution]");
sudoku(problem, output);
}
}
#include <stdio.h>
extern void output(unsigned board[]);
typedef void (*sudoku_cb)(unsigned board[]);
extern void sudoku(unsigned board[], sudoku_cb callback);
// example problem from http://rosettacode.org/wiki/Sudoku
static unsigned problem[] = {
8, 5, 0, 0, 0, 2, 4, 0, 0,
7, 2, 0, 0, 0, 0, 0, 0, 9,
0, 0, 4, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 7, 0, 0, 2,
3, 0, 5, 0, 0, 0, 9, 0, 0,
0, 4, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 8, 0, 0, 7, 0,
0, 1, 7, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 3, 6, 0, 4, 0
};
extern int main(int argc, char* argv[])
{
if (argc <= 1) {
puts("[problem]");
output(problem);
puts("[solutions]");
sudoku(problem, output);
} else {
for (int i = 1; i < argc; i++) {
char ch = -1;
for (int c = 0; c < 81; c++) {
if (ch == '\0') {
problem[c] = 0;
} else {
ch = argv[i][c];
problem[c] = '0' <= ch && ch <= '9' ? ch - '0' : 0;
}
}
puts("[problem]");
output(problem);
puts("[solution]");
sudoku(problem, output);
}
}
return 0;
}
@bellbind
Copy link
Author

Profile

$ time ./sudoku
real    0m0.020s
user    0m0.015s
sys 0m0.002s
$ time iojs sudoku-call-libsudoku.js
real    0m0.355s
user    0m0.313s
sys 0m0.037s
$ time iojs sudoku.js
real    0m0.888s
user    0m0.312s
sys 0m0.045s

Profile from https://gist.github.com/bellbind/2415a53ff67b28233d94

$ time iojs sudoku-backtrack.js
real    0m0.723s
user    0m0.684s
sys 0m0.030s
$ time iojs sudoku-function.js
real    0m1.984s
user    0m1.771s
sys 0m0.060s
$ time iojs sudoku-list.js
real    0m2.210s
user    0m2.134s
sys 0m0.049s
$ time iojs sudoku.js
real    0m2.682s
user    0m2.584s
sys 0m0.056s
$ time ./sudoku
real    0m0.018s
user    0m0.012s
sys 0m0.002s

@bellbind
Copy link
Author

bellbind commented Jun 5, 2015

Used emscripten 1.33.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment