diff --git a/README.md b/README.md index 9be8188..d55a949 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Luna is a reeeaaally tiny, yet expanding, compiler for WebAssembly Text Format, luna -It is so tiny that can only make additions lol. +It is so tiny that can only make the four operations (addition, subtraction, multiplication and division) with `i32` type numbers. # Why ❓ diff --git a/compiler/compiler.go b/compiler/compiler.go index a598647..94189c5 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -268,6 +268,19 @@ func Compile(ast []types.AstNode) Module { functionBody = append(functionBody, sectionData{0x01}) functionBody = append(functionBody, encodeVector(functionBodyData)...) + // Internal instructions (e.g. i32.const) + case texts.InternalInstruction: + var localIndex uint + value, ok := node.Expression.Value.(string) + if !ok { + log.Fatal("Not string") + } + + v, _ := strconv.Atoi(value) + localIndex = uint(v) + + code = append(code, sectionData{node.MapTo}...) + code = append(code, omologateEncoded(localIndex)...) } } diff --git a/compiler/parser.go b/compiler/parser.go index f1f6cf7..89acc14 100644 --- a/compiler/parser.go +++ b/compiler/parser.go @@ -135,8 +135,6 @@ func parseStatement(currentToken *iteratorEmulatorStruct, eatToken func(val stri Expression: parseExpression(currentToken, eatToken, index), MapTo: defaults.Opcodes["get_local"], } - } - switch currentToken.token.Value { case "i32.add": eatToken("i32.add") return types.AstNode{ @@ -151,6 +149,27 @@ func parseStatement(currentToken *iteratorEmulatorStruct, eatToken func(val stri Expression: types.ExpressionNode{}, MapTo: defaults.Opcodes["i32_sub"], } + case "i32.mul": + eatToken("i32.mul") + return types.AstNode{ + Type: texts.FuncInstruction, + Expression: types.ExpressionNode{}, + MapTo: defaults.Opcodes["i32_mul"], + } + case "i32.div": + eatToken("i32.div") + return types.AstNode{ + Type: texts.FuncInstruction, + Expression: types.ExpressionNode{}, + MapTo: defaults.Opcodes["i32_div"], + } + case "i32.const": + eatToken("i32.const") + return types.AstNode{ + Type: texts.InternalInstruction, + Expression: parseExpression(currentToken, eatToken, index), + MapTo: defaults.Opcodes["i32_const"], + } } } diff --git a/compiler/tokenizer.go b/compiler/tokenizer.go index c6718f2..a12cef1 100644 --- a/compiler/tokenizer.go +++ b/compiler/tokenizer.go @@ -31,7 +31,7 @@ var tokens = []string{ // A better and more robust regex could be implemented var instructions = []string{ "local\\.get", - "i32\\.(add|sub)", + "i32\\.(add|sub|mul|div|const)", } var numTypes = []string{ diff --git a/defaults/defaults.go b/defaults/defaults.go index 9a00056..f3cd6b0 100644 --- a/defaults/defaults.go +++ b/defaults/defaults.go @@ -30,6 +30,8 @@ var ( i32_and = 0x71 i32_add = 0x6a i32_sub = 0x6b + i32_mul = 0x6c + i32_div = 0x6d f32_add = 0x92 f32_sub = 0x93 f32_mul = 0x94 @@ -51,6 +53,8 @@ var Opcodes = map[string]interface{}{ "i32_eqz": i32_eqz, "i32_add": i32_add, "i32_sub": i32_sub, + "i32_mul": i32_mul, + "i32_div": i32_div, "i32_eq": i32_eq, "f32_eq": f32_eq, "f32_lt": f32_lt, diff --git a/example/dist/bundle.js b/example/dist/bundle.js index f5cc15e..c213b0c 100644 --- a/example/dist/bundle.js +++ b/example/dist/bundle.js @@ -43,6 +43,8 @@ var Opcodes = { i32_and: 113, i32_add: 106, i32_sub: 107, + i32_mul: 108, + i32_div: 109, f32_add: 146, f32_sub: 147, f32_mul: 148, @@ -190,14 +192,18 @@ function parseCodeSection(wasm) { let numberOfLocals = wasm.readByte(); let instructions = []; let locals = []; + let internals = []; while (wasm.pos < wasm.data.length) { const instruction = wasm.readByte(); instructions.push(instruction); if (instruction == Opcodes.get_local) { locals.push(wasm.readByte()); } + if (instruction == Opcodes.i32_const) { + internals.push(wasm.readByte()); + } } - code.push([locals, instructions]); + code.push({ locals, internals, instructions }); } return code; } @@ -227,10 +233,11 @@ var Processor = class { this.stack = []; } executeFunc() { - for (const instruction of this.func[1]) { - if (instruction == Opcodes.get_local) { - this.stack.push(this.params[this.func[0].shift()]); - } + for (const instruction of this.func.instructions) { + if (instruction == Opcodes.get_local) + this.stack.push(this.params[this.func.locals.shift()]); + if (instruction == Opcodes.i32_const) + this.stack.push(this.func.internals.shift()); this.#parseInstruction(instruction); } } @@ -243,6 +250,12 @@ var Processor = class { case Opcodes.i32_sub: result = this.stack.reduce((prev, current) => prev - current); return this.stack.push(result); + case Opcodes.i32_mul: + result = this.stack.reduce((prev, current) => prev * current, 1); + return this.stack.push(result); + case Opcodes.i32_div: + result = this.stack.reduce((prev, current) => prev / current); + return this.stack.push(result); } } getResult() { diff --git a/example/editor/editor.js b/example/editor/editor.js deleted file mode 100644 index e69de29..0000000 diff --git a/example/index.html b/example/index.html index 4edc5bd..3b524cb 100644 --- a/example/index.html +++ b/example/index.html @@ -15,10 +15,17 @@

Luna

A sample compiler for WebAssembly Text Format.
Check the github repository

+ Try to change the name of the export (e.g. "addNumbers" to "lunaAddition")
- Try to change the name of the export (e.g. "addNumbers" to "lunaAddition")
+
@@ -29,11 +36,11 @@

Luna

Calculate (add or subtract):
- +
- +
@@ -46,7 +53,8 @@

Luna

as it defines some global objects --> - + + \ No newline at end of file diff --git a/example/js/index.js b/example/js/index.js index 1125e52..8e251d3 100644 --- a/example/js/index.js +++ b/example/js/index.js @@ -1,4 +1,6 @@ import startAeonRuntime from '../dist/bundle.js'; +import { defaultText } from './inputDefaultText.js'; +import { CONST_MODE } from './texts/texts.js'; const go = new Go(); // Defined in wasm_exec.js. Don't forget to add this in your index.html. @@ -43,8 +45,10 @@ const runLunaAddition = async () => { // Set the result onto the doc const input1 = document.getElementById('input-1'); const input2 = document.getElementById('input-2'); + const label2 = document.getElementById('label-2') const codeContainer = document.getElementById('code'); const moduleContainer = document.getElementById('module'); + const selectInstruction = document.getElementById('instruction'); const compile = document.getElementById('compile'); const btn = document.getElementById('btn') @@ -54,21 +58,23 @@ const runLunaAddition = async () => { btn.setAttribute('disabled', true) - const input = `(module - (func (export "addNumbers") (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.add) -) -` - - const editor = CodeMirror(codeContainer, { - value: input, + let editor = CodeMirror(codeContainer, { + value: defaultText(selectInstruction), mode: "wast", lineNumbers: true, }); + // Change editor content based on selected mode + selectInstruction.onchange = (e) => { + editor.setValue(defaultText(selectInstruction)); + // Hide second input on const + if (e.target.value === CONST_MODE) { + input2.style.display = 'none'; + label2.style.display = 'none'; + } + } + compile.addEventListener('click', async () => { moduleContainer.innerHTML = "" const textContent = editor.getValue() diff --git a/example/js/inputDefaultText.js b/example/js/inputDefaultText.js new file mode 100644 index 0000000..d41fd56 --- /dev/null +++ b/example/js/inputDefaultText.js @@ -0,0 +1,47 @@ +import { ADDITION_MODE, CONST_MODE, DIVISION_MODE, MULTIPLICATION_MODE, SUBTRACTION_MODE } from "./texts/texts.js" + +export const defaultText = (select) => { + const instruction = select.value + switch (instruction) { + case ADDITION_MODE: + return `(module + (func (export "addNumbers") (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.add) +) +` + case SUBTRACTION_MODE: + return `(module + (func (export "subtractNumbers") (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.sub) +) +` + case MULTIPLICATION_MODE: + return `(module + (func (export "multiplyNumbers") (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.mul) +) +` + case DIVISION_MODE: + return `(module + (func (export "divideNumbers") (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.div) +) +` + case CONST_MODE: + return `(module + (func (export "operationWithInternalVariable") (param i32 i32) (result i32) + local.get 0 + i32.const 10 + i32.add) +) +` + } +} \ No newline at end of file diff --git a/example/js/texts/texts.js b/example/js/texts/texts.js new file mode 100644 index 0000000..3ea8f0a --- /dev/null +++ b/example/js/texts/texts.js @@ -0,0 +1,5 @@ +export const CONST_MODE = 'const'; +export const ADDITION_MODE = 'addition'; +export const SUBTRACTION_MODE = 'subtraction'; +export const MULTIPLICATION_MODE = 'multiplication'; +export const DIVISION_MODE = 'division'; \ No newline at end of file diff --git a/example/main.wasm b/example/main.wasm index d0f5f31..763e8dd 100755 Binary files a/example/main.wasm and b/example/main.wasm differ diff --git a/runtime/README.md b/runtime/README.md index 61d87cf..cbf7e27 100644 --- a/runtime/README.md +++ b/runtime/README.md @@ -18,7 +18,7 @@ Aeon was built with Luna in mind and they travel together so whatever Luna can c (Check the `./example/example.js`) ```js -const startAeonRuntime = require("./runtime/start"); +import startAeonRuntime from "./runtime/start" // This binary // - takes 3 parameters of type `i32` (3, 127, 127, 127) // - outputs one `i32` result (1, 127) diff --git a/runtime/runtime/parser.js b/runtime/runtime/parser.js index 8cfa53d..11d8e28 100644 --- a/runtime/runtime/parser.js +++ b/runtime/runtime/parser.js @@ -221,6 +221,7 @@ export function parseCodeSection(wasm) { let instructions = []; let locals = []; + let internals = []; while (wasm.pos < wasm.data.length) { const instruction = wasm.readByte(); @@ -230,8 +231,12 @@ export function parseCodeSection(wasm) { if (instruction == Opcodes.get_local) { locals.push(wasm.readByte()) } + + if (instruction == Opcodes.i32_const) { + internals.push(wasm.readByte()) + } } - code.push([locals, instructions]); + code.push({locals, internals, instructions}); } return code diff --git a/runtime/runtime/processor.js b/runtime/runtime/processor.js index da4e97e..8a475de 100644 --- a/runtime/runtime/processor.js +++ b/runtime/runtime/processor.js @@ -9,10 +9,9 @@ export default class Processor { } executeFunc() { - for (const instruction of this.func[1]) { - if (instruction == Opcodes.get_local) { - this.stack.push(this.params[this.func[0].shift()]); - } + for (const instruction of this.func.instructions) { + if (instruction == Opcodes.get_local) this.stack.push(this.params[this.func.locals.shift()]); + if (instruction == Opcodes.i32_const) this.stack.push(this.func.internals.shift()); this.#parseInstruction(instruction) } @@ -31,6 +30,14 @@ export default class Processor { case Opcodes.i32_sub: result = this.stack.reduce((prev, current) => prev - current); return this.stack.push(result); + + case Opcodes.i32_mul: + result = this.stack.reduce((prev, current) => prev * current, 1); + return this.stack.push(result); + + case Opcodes.i32_div: + result = this.stack.reduce((prev, current) => prev / current); + return this.stack.push(result); } } diff --git a/runtime/utils/defaults.js b/runtime/utils/defaults.js index ea0a6a3..4f4c4ba 100644 --- a/runtime/utils/defaults.js +++ b/runtime/utils/defaults.js @@ -21,7 +21,9 @@ export const Opcodes = { f32_gt : 0x5e, i32_and : 0x71, i32_add : 0x6a, - i32_sub : 0x6b, + i32_sub : 0x6b, + i32_mul : 0x6c, + i32_div : 0x6d, f32_add : 0x92, f32_sub : 0x93, f32_mul : 0x94, diff --git a/texts/texts.go b/texts/texts.go index ea6cd12..013c9ae 100644 --- a/texts/texts.go +++ b/texts/texts.go @@ -19,6 +19,7 @@ const ( TypeInstruction = "instruction" FuncInstruction = "funcInstruction" + InternalInstruction = "internalInstruction" GetLocalInstruction = "getLocalInstruction" Number = "number"