virtual_machine.odin 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  1. package regex_vm
  2. /*
  3. (c) Copyright 2024 Feoramund <[email protected]>.
  4. Made available under Odin's BSD-3 license.
  5. List of contributors:
  6. Feoramund: Initial implementation.
  7. */
  8. import "base:intrinsics"
  9. @require import "core:io"
  10. import "core:slice"
  11. import "core:text/regex/common"
  12. import "core:text/regex/parser"
  13. import "core:unicode/utf8"
  14. Rune_Class_Range :: parser.Rune_Class_Range
  15. // NOTE: This structure differs intentionally from the one in `regex/parser`,
  16. // as this data doesn't need to be a dynamic array once it hits the VM.
  17. Rune_Class_Data :: struct {
  18. runes: []rune,
  19. ranges: []Rune_Class_Range,
  20. }
  21. Opcode :: enum u8 {
  22. // | [ operands ]
  23. Match = 0x00, // |
  24. Match_And_Exit = 0x01, // |
  25. Byte = 0x02, // | u8
  26. Rune = 0x03, // | i32
  27. Rune_Class = 0x04, // | u8
  28. Rune_Class_Negated = 0x05, // | u8
  29. Wildcard = 0x06, // |
  30. Jump = 0x07, // | u16
  31. Split = 0x08, // | u16, u16
  32. Save = 0x09, // | u8
  33. Assert_Start = 0x0A, // |
  34. Assert_Start_Multiline = 0x0B, // |
  35. Assert_End = 0x0C, // |
  36. Assert_Word_Boundary = 0x0D, // |
  37. Assert_Non_Word_Boundary = 0x0E, // |
  38. Multiline_Open = 0x0F, // |
  39. Multiline_Close = 0x10, // |
  40. Wait_For_Byte = 0x11, // | u8
  41. Wait_For_Rune = 0x12, // | i32
  42. Wait_For_Rune_Class = 0x13, // | u8
  43. Wait_For_Rune_Class_Negated = 0x14, // | u8
  44. Match_All_And_Escape = 0x15, // |
  45. }
  46. Thread :: struct {
  47. pc: int,
  48. saved: ^[2 * common.MAX_CAPTURE_GROUPS]int,
  49. }
  50. Program :: []Opcode
  51. Machine :: struct {
  52. // Program state
  53. memory: string,
  54. class_data: []Rune_Class_Data,
  55. code: Program,
  56. // Thread state
  57. top_thread: int,
  58. threads: [^]Thread,
  59. next_threads: [^]Thread,
  60. // The busy map is used to merge threads based on their program counters.
  61. busy_map: []u64,
  62. // Global state
  63. string_pointer: int,
  64. current_rune: rune,
  65. current_rune_size: int,
  66. next_rune: rune,
  67. next_rune_size: int,
  68. last_rune: rune,
  69. }
  70. // @MetaCharacter
  71. // NOTE: This must be kept in sync with the compiler & tokenizer.
  72. is_word_class :: #force_inline proc "contextless" (r: rune) -> bool {
  73. switch r {
  74. case '0'..='9', 'A'..='Z', '_', 'a'..='z':
  75. return true
  76. case:
  77. return false
  78. }
  79. }
  80. set_busy_map :: #force_inline proc "contextless" (vm: ^Machine, pc: int) -> bool #no_bounds_check {
  81. slot := cast(u64)pc >> 6
  82. bit: u64 = 1 << (cast(u64)pc & 0x3F)
  83. if vm.busy_map[slot] & bit > 0 {
  84. return false
  85. }
  86. vm.busy_map[slot] |= bit
  87. return true
  88. }
  89. check_busy_map :: #force_inline proc "contextless" (vm: ^Machine, pc: int) -> bool #no_bounds_check {
  90. slot := cast(u64)pc >> 6
  91. bit: u64 = 1 << (cast(u64)pc & 0x3F)
  92. return vm.busy_map[slot] & bit > 0
  93. }
  94. add_thread :: proc(vm: ^Machine, saved: ^[2 * common.MAX_CAPTURE_GROUPS]int, pc: int) #no_bounds_check {
  95. if check_busy_map(vm, pc) {
  96. return
  97. }
  98. saved := saved
  99. pc := pc
  100. resolution_loop: for {
  101. if !set_busy_map(vm, pc) {
  102. return
  103. }
  104. when common.ODIN_DEBUG_REGEX {
  105. io.write_string(common.debug_stream, "Thread [PC:")
  106. common.write_padded_hex(common.debug_stream, pc, 4)
  107. io.write_string(common.debug_stream, "] thinking about ")
  108. io.write_string(common.debug_stream, opcode_to_name(vm.code[pc]))
  109. io.write_rune(common.debug_stream, '\n')
  110. }
  111. #partial switch vm.code[pc] {
  112. case .Jump:
  113. pc = cast(int)intrinsics.unaligned_load(cast(^u16)&vm.code[pc + size_of(Opcode)])
  114. continue
  115. case .Split:
  116. jmp_x := cast(int)intrinsics.unaligned_load(cast(^u16)&vm.code[pc + size_of(Opcode)])
  117. jmp_y := cast(int)intrinsics.unaligned_load(cast(^u16)&vm.code[pc + size_of(Opcode) + size_of(u16)])
  118. add_thread(vm, saved, jmp_x)
  119. pc = jmp_y
  120. continue
  121. case .Save:
  122. new_saved := new([2 * common.MAX_CAPTURE_GROUPS]int)
  123. new_saved ^= saved^
  124. saved = new_saved
  125. index := vm.code[pc + size_of(Opcode)]
  126. sp := vm.string_pointer+vm.current_rune_size
  127. saved[index] = sp
  128. when common.ODIN_DEBUG_REGEX {
  129. io.write_string(common.debug_stream, "Thread [PC:")
  130. common.write_padded_hex(common.debug_stream, pc, 4)
  131. io.write_string(common.debug_stream, "] saving state: (slot ")
  132. io.write_int(common.debug_stream, cast(int)index)
  133. io.write_string(common.debug_stream, " = ")
  134. io.write_int(common.debug_stream, sp)
  135. io.write_string(common.debug_stream, ")\n")
  136. }
  137. pc += size_of(Opcode) + size_of(u8)
  138. continue
  139. case .Assert_Start:
  140. sp := vm.string_pointer+vm.current_rune_size
  141. if sp == 0 {
  142. pc += size_of(Opcode)
  143. continue
  144. }
  145. case .Assert_Start_Multiline:
  146. sp := vm.string_pointer+vm.current_rune_size
  147. if sp == 0 || vm.last_rune == '\n' || vm.last_rune == '\r' {
  148. pc += size_of(Opcode)
  149. continue
  150. }
  151. case .Assert_End:
  152. sp := vm.string_pointer+vm.current_rune_size
  153. if sp == len(vm.memory) {
  154. pc += size_of(Opcode)
  155. continue
  156. }
  157. case .Multiline_Open:
  158. sp := vm.string_pointer+vm.current_rune_size
  159. if sp == len(vm.memory) {
  160. // Skip the `Multiline_Close` opcode.
  161. pc += 2 * size_of(Opcode)
  162. continue
  163. } else {
  164. // Not at the end of the string.
  165. // Try to consume a newline next frame in the other opcode loop.
  166. when common.ODIN_DEBUG_REGEX {
  167. io.write_string(common.debug_stream, "*** New thread added [PC:")
  168. common.write_padded_hex(common.debug_stream, pc, 4)
  169. io.write_string(common.debug_stream, "]\n")
  170. }
  171. vm.next_threads[vm.top_thread] = Thread{ pc = pc, saved = saved }
  172. vm.top_thread += 1
  173. }
  174. case .Assert_Word_Boundary:
  175. sp := vm.string_pointer+vm.current_rune_size
  176. if sp == 0 || sp == len(vm.memory) {
  177. pc += size_of(Opcode)
  178. continue
  179. } else {
  180. last_rune_is_wc := is_word_class(vm.current_rune)
  181. this_rune_is_wc := is_word_class(vm.next_rune)
  182. if last_rune_is_wc && !this_rune_is_wc || !last_rune_is_wc && this_rune_is_wc {
  183. pc += size_of(Opcode)
  184. continue
  185. }
  186. }
  187. case .Assert_Non_Word_Boundary:
  188. sp := vm.string_pointer+vm.current_rune_size
  189. if sp != 0 && sp != len(vm.memory) {
  190. last_rune_is_wc := is_word_class(vm.current_rune)
  191. this_rune_is_wc := is_word_class(vm.next_rune)
  192. if last_rune_is_wc && this_rune_is_wc || !last_rune_is_wc && !this_rune_is_wc {
  193. pc += size_of(Opcode)
  194. continue
  195. }
  196. }
  197. case .Wait_For_Byte:
  198. operand := cast(rune)vm.code[pc + size_of(Opcode)]
  199. if vm.next_rune == operand {
  200. add_thread(vm, saved, pc + size_of(Opcode) + size_of(u8))
  201. }
  202. when common.ODIN_DEBUG_REGEX {
  203. io.write_string(common.debug_stream, "*** New thread added [PC:")
  204. common.write_padded_hex(common.debug_stream, pc, 4)
  205. io.write_string(common.debug_stream, "]\n")
  206. }
  207. vm.next_threads[vm.top_thread] = Thread{ pc = pc, saved = saved }
  208. vm.top_thread += 1
  209. case .Wait_For_Rune:
  210. operand := intrinsics.unaligned_load(cast(^rune)&vm.code[pc + size_of(Opcode)])
  211. if vm.next_rune == operand {
  212. add_thread(vm, saved, pc + size_of(Opcode) + size_of(rune))
  213. }
  214. when common.ODIN_DEBUG_REGEX {
  215. io.write_string(common.debug_stream, "*** New thread added [PC:")
  216. common.write_padded_hex(common.debug_stream, pc, 4)
  217. io.write_string(common.debug_stream, "]\n")
  218. }
  219. vm.next_threads[vm.top_thread] = Thread{ pc = pc, saved = saved }
  220. vm.top_thread += 1
  221. case .Wait_For_Rune_Class:
  222. operand := cast(u8)vm.code[pc + size_of(Opcode)]
  223. class_data := vm.class_data[operand]
  224. next_rune := vm.next_rune
  225. check: {
  226. for r in class_data.runes {
  227. if next_rune == r {
  228. add_thread(vm, saved, pc + size_of(Opcode) + size_of(u8))
  229. break check
  230. }
  231. }
  232. for range in class_data.ranges {
  233. if range.lower <= next_rune && next_rune <= range.upper {
  234. add_thread(vm, saved, pc + size_of(Opcode) + size_of(u8))
  235. break check
  236. }
  237. }
  238. }
  239. when common.ODIN_DEBUG_REGEX {
  240. io.write_string(common.debug_stream, "*** New thread added [PC:")
  241. common.write_padded_hex(common.debug_stream, pc, 4)
  242. io.write_string(common.debug_stream, "]\n")
  243. }
  244. vm.next_threads[vm.top_thread] = Thread{ pc = pc, saved = saved }
  245. vm.top_thread += 1
  246. case .Wait_For_Rune_Class_Negated:
  247. operand := cast(u8)vm.code[pc + size_of(Opcode)]
  248. class_data := vm.class_data[operand]
  249. next_rune := vm.next_rune
  250. check_negated: {
  251. for r in class_data.runes {
  252. if next_rune == r {
  253. break check_negated
  254. }
  255. }
  256. for range in class_data.ranges {
  257. if range.lower <= next_rune && next_rune <= range.upper {
  258. break check_negated
  259. }
  260. }
  261. add_thread(vm, saved, pc + size_of(Opcode) + size_of(u8))
  262. }
  263. when common.ODIN_DEBUG_REGEX {
  264. io.write_string(common.debug_stream, "*** New thread added [PC:")
  265. common.write_padded_hex(common.debug_stream, pc, 4)
  266. io.write_string(common.debug_stream, "]\n")
  267. }
  268. vm.next_threads[vm.top_thread] = Thread{ pc = pc, saved = saved }
  269. vm.top_thread += 1
  270. case:
  271. when common.ODIN_DEBUG_REGEX {
  272. io.write_string(common.debug_stream, "*** New thread added [PC:")
  273. common.write_padded_hex(common.debug_stream, pc, 4)
  274. io.write_string(common.debug_stream, "]\n")
  275. }
  276. vm.next_threads[vm.top_thread] = Thread{ pc = pc, saved = saved }
  277. vm.top_thread += 1
  278. }
  279. break resolution_loop
  280. }
  281. return
  282. }
  283. run :: proc(vm: ^Machine, $UNICODE_MODE: bool) -> (saved: ^[2 * common.MAX_CAPTURE_GROUPS]int, ok: bool) #no_bounds_check {
  284. when UNICODE_MODE {
  285. vm.next_rune, vm.next_rune_size = utf8.decode_rune_in_string(vm.memory[vm.string_pointer:])
  286. } else {
  287. if len(vm.memory) > 0 {
  288. vm.next_rune = cast(rune)vm.memory[vm.string_pointer]
  289. vm.next_rune_size = 1
  290. }
  291. }
  292. when common.ODIN_DEBUG_REGEX {
  293. io.write_string(common.debug_stream, "### Adding initial thread.\n")
  294. }
  295. {
  296. starter_saved := new([2 * common.MAX_CAPTURE_GROUPS]int)
  297. starter_saved ^= -1
  298. add_thread(vm, starter_saved, 0)
  299. }
  300. // `add_thread` adds to `next_threads` by default, but we need to put this
  301. // thread in the current thread buffer.
  302. vm.threads, vm.next_threads = vm.next_threads, vm.threads
  303. when common.ODIN_DEBUG_REGEX {
  304. io.write_string(common.debug_stream, "### VM starting.\n")
  305. defer io.write_string(common.debug_stream, "### VM finished.\n")
  306. }
  307. for {
  308. slice.zero(vm.busy_map[:])
  309. assert(vm.string_pointer <= len(vm.memory), "VM string pointer went out of bounds.")
  310. current_rune := vm.next_rune
  311. vm.current_rune = current_rune
  312. vm.current_rune_size = vm.next_rune_size
  313. when UNICODE_MODE {
  314. vm.next_rune, vm.next_rune_size = utf8.decode_rune_in_string(vm.memory[vm.string_pointer+vm.current_rune_size:])
  315. } else {
  316. if vm.string_pointer+size_of(u8) < len(vm.memory) {
  317. vm.next_rune = cast(rune)vm.memory[vm.string_pointer+size_of(u8)]
  318. vm.next_rune_size = size_of(u8)
  319. } else {
  320. vm.next_rune = 0
  321. vm.next_rune_size = 0
  322. }
  323. }
  324. when common.ODIN_DEBUG_REGEX {
  325. io.write_string(common.debug_stream, ">>> Dispatching rune: ")
  326. io.write_encoded_rune(common.debug_stream, current_rune)
  327. io.write_byte(common.debug_stream, '\n')
  328. }
  329. thread_count := vm.top_thread
  330. vm.top_thread = 0
  331. thread_loop: for i := 0; i < thread_count; i += 1 {
  332. t := vm.threads[i]
  333. when common.ODIN_DEBUG_REGEX {
  334. io.write_string(common.debug_stream, "Thread [PC:")
  335. common.write_padded_hex(common.debug_stream, t.pc, 4)
  336. io.write_string(common.debug_stream, "] stepping on ")
  337. io.write_string(common.debug_stream, opcode_to_name(vm.code[t.pc]))
  338. io.write_byte(common.debug_stream, '\n')
  339. }
  340. #partial opcode: switch vm.code[t.pc] {
  341. case .Match:
  342. when common.ODIN_DEBUG_REGEX {
  343. io.write_string(common.debug_stream, "Thread matched!\n")
  344. }
  345. saved = t.saved
  346. ok = true
  347. break thread_loop
  348. case .Match_And_Exit:
  349. when common.ODIN_DEBUG_REGEX {
  350. io.write_string(common.debug_stream, "Thread matched! (Exiting)\n")
  351. }
  352. return nil, true
  353. case .Byte:
  354. operand := cast(rune)vm.code[t.pc + size_of(Opcode)]
  355. if current_rune == operand {
  356. add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(u8))
  357. }
  358. case .Rune:
  359. operand := intrinsics.unaligned_load(cast(^rune)&vm.code[t.pc + size_of(Opcode)])
  360. if current_rune == operand {
  361. add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(rune))
  362. }
  363. case .Rune_Class:
  364. operand := cast(u8)vm.code[t.pc + size_of(Opcode)]
  365. class_data := vm.class_data[operand]
  366. for r in class_data.runes {
  367. if current_rune == r {
  368. add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(u8))
  369. break opcode
  370. }
  371. }
  372. for range in class_data.ranges {
  373. if range.lower <= current_rune && current_rune <= range.upper {
  374. add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(u8))
  375. break opcode
  376. }
  377. }
  378. case .Rune_Class_Negated:
  379. operand := cast(u8)vm.code[t.pc + size_of(Opcode)]
  380. class_data := vm.class_data[operand]
  381. for r in class_data.runes {
  382. if current_rune == r {
  383. break opcode
  384. }
  385. }
  386. for range in class_data.ranges {
  387. if range.lower <= current_rune && current_rune <= range.upper {
  388. break opcode
  389. }
  390. }
  391. add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(u8))
  392. case .Wildcard:
  393. add_thread(vm, t.saved, t.pc + size_of(Opcode))
  394. case .Multiline_Open:
  395. if current_rune == '\n' {
  396. // UNIX newline.
  397. add_thread(vm, t.saved, t.pc + 2 * size_of(Opcode))
  398. } else if current_rune == '\r' {
  399. if vm.next_rune == '\n' {
  400. // Windows newline. (1/2)
  401. add_thread(vm, t.saved, t.pc + size_of(Opcode))
  402. } else {
  403. // Mac newline.
  404. add_thread(vm, t.saved, t.pc + 2 * size_of(Opcode))
  405. }
  406. }
  407. case .Multiline_Close:
  408. if current_rune == '\n' {
  409. // Windows newline. (2/2)
  410. add_thread(vm, t.saved, t.pc + size_of(Opcode))
  411. }
  412. case .Wait_For_Byte:
  413. operand := cast(rune)vm.code[t.pc + size_of(Opcode)]
  414. if vm.next_rune == operand {
  415. add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(u8))
  416. }
  417. when common.ODIN_DEBUG_REGEX {
  418. io.write_string(common.debug_stream, "*** New thread added [PC:")
  419. common.write_padded_hex(common.debug_stream, t.pc, 4)
  420. io.write_string(common.debug_stream, "]\n")
  421. }
  422. vm.next_threads[vm.top_thread] = Thread{ pc = t.pc, saved = t.saved }
  423. vm.top_thread += 1
  424. case .Wait_For_Rune:
  425. operand := intrinsics.unaligned_load(cast(^rune)&vm.code[t.pc + size_of(Opcode)])
  426. if vm.next_rune == operand {
  427. add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(rune))
  428. }
  429. when common.ODIN_DEBUG_REGEX {
  430. io.write_string(common.debug_stream, "*** New thread added [PC:")
  431. common.write_padded_hex(common.debug_stream, t.pc, 4)
  432. io.write_string(common.debug_stream, "]\n")
  433. }
  434. vm.next_threads[vm.top_thread] = Thread{ pc = t.pc, saved = t.saved }
  435. vm.top_thread += 1
  436. case .Wait_For_Rune_Class:
  437. operand := cast(u8)vm.code[t.pc + size_of(Opcode)]
  438. class_data := vm.class_data[operand]
  439. next_rune := vm.next_rune
  440. check: {
  441. for r in class_data.runes {
  442. if next_rune == r {
  443. add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(u8))
  444. break check
  445. }
  446. }
  447. for range in class_data.ranges {
  448. if range.lower <= next_rune && next_rune <= range.upper {
  449. add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(u8))
  450. break check
  451. }
  452. }
  453. }
  454. when common.ODIN_DEBUG_REGEX {
  455. io.write_string(common.debug_stream, "*** New thread added [PC:")
  456. common.write_padded_hex(common.debug_stream, t.pc, 4)
  457. io.write_string(common.debug_stream, "]\n")
  458. }
  459. vm.next_threads[vm.top_thread] = Thread{ pc = t.pc, saved = t.saved }
  460. vm.top_thread += 1
  461. case .Wait_For_Rune_Class_Negated:
  462. operand := cast(u8)vm.code[t.pc + size_of(Opcode)]
  463. class_data := vm.class_data[operand]
  464. next_rune := vm.next_rune
  465. check_negated: {
  466. for r in class_data.runes {
  467. if next_rune == r {
  468. break check_negated
  469. }
  470. }
  471. for range in class_data.ranges {
  472. if range.lower <= next_rune && next_rune <= range.upper {
  473. break check_negated
  474. }
  475. }
  476. add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(u8))
  477. }
  478. when common.ODIN_DEBUG_REGEX {
  479. io.write_string(common.debug_stream, "*** New thread added [PC:")
  480. common.write_padded_hex(common.debug_stream, t.pc, 4)
  481. io.write_string(common.debug_stream, "]\n")
  482. }
  483. vm.next_threads[vm.top_thread] = Thread{ pc = t.pc, saved = t.saved }
  484. vm.top_thread += 1
  485. case .Match_All_And_Escape:
  486. t.pc += size_of(Opcode)
  487. // The point of this loop is to walk out of wherever this
  488. // opcode lives to the end of the program, while saving the
  489. // index to the length of the string at each pass on the way.
  490. escape_loop: for {
  491. #partial switch vm.code[t.pc] {
  492. case .Match, .Match_And_Exit:
  493. break escape_loop
  494. case .Jump:
  495. t.pc = cast(int)intrinsics.unaligned_load(cast(^u16)&vm.code[t.pc + size_of(Opcode)])
  496. case .Save:
  497. index := vm.code[t.pc + size_of(Opcode)]
  498. t.saved[index] = len(vm.memory)
  499. t.pc += size_of(Opcode) + size_of(u8)
  500. case .Match_All_And_Escape:
  501. // Layering these is fine.
  502. t.pc += size_of(Opcode)
  503. // If the loop has to process any opcode not listed above,
  504. // it means someone did something odd like `a(.*$)b`, in
  505. // which case, just fail. Technically, the expression makes
  506. // no sense.
  507. case:
  508. break opcode
  509. }
  510. }
  511. saved = t.saved
  512. ok = true
  513. return
  514. case:
  515. when common.ODIN_DEBUG_REGEX {
  516. io.write_string(common.debug_stream, "Opcode: ")
  517. io.write_int(common.debug_stream, cast(int)vm.code[t.pc])
  518. io.write_string(common.debug_stream, "\n")
  519. }
  520. panic("Invalid opcode in RegEx thread loop.")
  521. }
  522. }
  523. vm.threads, vm.next_threads = vm.next_threads, vm.threads
  524. when common.ODIN_DEBUG_REGEX {
  525. io.write_string(common.debug_stream, "<<< Frame ended. (Threads: ")
  526. io.write_int(common.debug_stream, vm.top_thread)
  527. io.write_string(common.debug_stream, ")\n")
  528. }
  529. if vm.string_pointer == len(vm.memory) || vm.top_thread == 0 {
  530. break
  531. }
  532. vm.last_rune = vm.current_rune
  533. vm.string_pointer += vm.current_rune_size
  534. }
  535. return
  536. }
  537. opcode_count :: proc(code: Program) -> (opcodes: int) {
  538. iter := Opcode_Iterator{ code, 0 }
  539. for _ in iterate_opcodes(&iter) {
  540. opcodes += 1
  541. }
  542. return
  543. }
  544. create :: proc(code: Program, str: string, allocator := context.allocator) -> (vm: Machine) {
  545. assert(len(code) > 0, "RegEx VM has no instructions.")
  546. context.allocator = allocator
  547. vm.memory = str
  548. vm.code = code
  549. sizing := len(code) >> 6 + (1 if len(code) & 0x3F > 0 else 0)
  550. assert(sizing > 0)
  551. vm.busy_map = make([]u64, sizing)
  552. max_possible_threads := max(1, opcode_count(vm.code) - 1)
  553. vm.threads = make([^]Thread, max_possible_threads)
  554. vm.next_threads = make([^]Thread, max_possible_threads)
  555. return
  556. }
  557. destroy :: proc(vm: Machine, allocator := context.allocator) {
  558. context.allocator = allocator
  559. delete(vm.busy_map)
  560. free(vm.threads)
  561. free(vm.next_threads)
  562. }